From 3445a88d2098c849a94746eec64dd7fd205dc0a9 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Sun, 13 Oct 2024 11:51:47 +0200 Subject: [PATCH 1/4] Add Mastodon/Fediverse attribution meta tags Add `` and `fediverse:creator` meta. See https://joinmastodon.org/verification and https://github.com/mastodon/mastodon/pull/30398 --- lib/template.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/template.html b/lib/template.html index 7b6a1c1..051809a 100755 --- a/lib/template.html +++ b/lib/template.html @@ -69,6 +69,11 @@ {% endif %} +{% if site.metadata.mastodon %} + + +{% endif %} + {% if site.metadata.twitter %} From f054400eecab94cdeb16701e8f688ba9d94248fe Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Sun, 22 Dec 2024 08:30:47 +0100 Subject: [PATCH 2/4] Document Mastodon attribution and verification --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a8f1317..393ab85 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Bridgetown SEO Tag adds the following meta tags to your site: * Canonical URL * Next and previous URLs on paginated pages * [Open Graph](https://ogp.me/) title, description, site title, and URL (for Facebook, LinkedIn, etc.) +* Mastodon [verification](https://docs.joinmastodon.org/user/profile/#verification) and [attribution](https://blog.joinmastodon.org/2024/07/highlighting-journalism-on-mastodon/) * [Twitter Summary Card](https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started) metadata While you could theoretically add the necessary metadata tags yourself, Bridgetown SEO Tag provides a battle-tested template of crowdsourced best-practices. @@ -75,6 +76,10 @@ The SEO tag will respect any of the following if included in your site's `site_m * `description` - A longer description used for the description meta tag. Also used as fallback for documents that don't provide their own `description` and as part of the home page title tag if `tagline` is not defined. * `author` - global author information (see [Advanced usage](https://github.com/bridgetownrb/bridgetown-seo-tag/wiki/Advanced-Usage#author-information)) +* `mastodon` - You can add a single Mastodon handle to both verify your Mastodon profile, and link to your Mastodon profile when someone shares your content on the network. + * `mastodon:server` - The server for your account (mastodon.social, for instance) + * `mastodon:username` - Your username (with or without `@`) + * `twitter` - You can add a single Twitter handle to be used in Twitter card tags, like "bridgetownrb". Or you use a YAML mapping with additional details: * `twitter:card` - The site's default card type * `twitter:username` - The site's Twitter handle From 671bed04d9acda98797a0e4e28550d856f0266d8 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Sun, 22 Dec 2024 23:16:05 +0100 Subject: [PATCH 3/4] Fix typos in code comments --- lib/bridgetown-seo-tag/author_drop.rb | 2 +- lib/bridgetown-seo-tag/drop.rb | 2 +- lib/bridgetown-seo-tag/image_drop.rb | 2 +- lib/bridgetown-seo-tag/url_helper.rb | 2 +- spec/bridgetown_seo_tag/author_drop_spec.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bridgetown-seo-tag/author_drop.rb b/lib/bridgetown-seo-tag/author_drop.rb index 710b63b..686bd32 100644 --- a/lib/bridgetown-seo-tag/author_drop.rb +++ b/lib/bridgetown-seo-tag/author_drop.rb @@ -55,7 +55,7 @@ def resolved_author @resolved_author = sources.find { |s| !s.to_s.empty? } end - # If resolved_author is a string, attempts to find coresponding author + # If resolved_author is a string, attempts to find corresponding author # metadata in `site.data.authors` # # Returns a hash representing additional metadata or an empty hash diff --git a/lib/bridgetown-seo-tag/drop.rb b/lib/bridgetown-seo-tag/drop.rb index 62c5f26..3c30c5f 100644 --- a/lib/bridgetown-seo-tag/drop.rb +++ b/lib/bridgetown-seo-tag/drop.rb @@ -100,7 +100,7 @@ def author end # Returns a Drop representing the page's image - # Returns nil if the image has no path, to preserve backwards compatability + # Returns nil if the image has no path, to preserve backwards compatibility def image @image ||= ImageDrop.new(page: page, context: @context) @image if @image.path diff --git a/lib/bridgetown-seo-tag/image_drop.rb b/lib/bridgetown-seo-tag/image_drop.rb index 8a207e9..7351914 100644 --- a/lib/bridgetown-seo-tag/image_drop.rb +++ b/lib/bridgetown-seo-tag/image_drop.rb @@ -24,7 +24,7 @@ def initialize(page: nil, context: nil) @context = context end - # Called path for backwards compatability, this is really + # Called path for backwards compatibility, this is really # the escaped, absolute URL representing the page's image # Returns nil if no image path can be determined def path diff --git a/lib/bridgetown-seo-tag/url_helper.rb b/lib/bridgetown-seo-tag/url_helper.rb index c675ca2..56b8f63 100644 --- a/lib/bridgetown-seo-tag/url_helper.rb +++ b/lib/bridgetown-seo-tag/url_helper.rb @@ -9,7 +9,7 @@ module UrlHelper # Determines if the given string is an absolute URL # # Returns true if an absolute URL. - # Retruns false if it's a relative URL + # Returns false if it's a relative URL # Returns nil if it is not a string or can't be parsed as a URL def absolute_url?(string) return false unless string diff --git a/spec/bridgetown_seo_tag/author_drop_spec.rb b/spec/bridgetown_seo_tag/author_drop_spec.rb index 65905af..1b7f802 100644 --- a/spec/bridgetown_seo_tag/author_drop_spec.rb +++ b/spec/bridgetown_seo_tag/author_drop_spec.rb @@ -143,7 +143,7 @@ context "without an author name or handle" do let(:page_meta) { { "author" => { "foo" => "bar" } } } - it "dosen't blow up" do + it "doesn't blow up" do expect(subject["twitter"]).to be_nil end end From 872002f232b5d8b9c9623b864cbb890286c42f54 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Sun, 22 Dec 2024 23:44:10 +0100 Subject: [PATCH 4/4] Move Mastodon logic to a dedicated drop --- README.md | 4 +- lib/bridgetown-seo-tag.rb | 1 + lib/bridgetown-seo-tag/mastodon_drop.rb | 84 +++++++++++++++++++ lib/template.html | 6 +- spec/bridgetown_seo_tag/mastodon_drop_spec.rb | 83 ++++++++++++++++++ 5 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 lib/bridgetown-seo-tag/mastodon_drop.rb create mode 100644 spec/bridgetown_seo_tag/mastodon_drop_spec.rb diff --git a/README.md b/README.md index 393ab85..4820b86 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,7 @@ The SEO tag will respect any of the following if included in your site's `site_m * `description` - A longer description used for the description meta tag. Also used as fallback for documents that don't provide their own `description` and as part of the home page title tag if `tagline` is not defined. * `author` - global author information (see [Advanced usage](https://github.com/bridgetownrb/bridgetown-seo-tag/wiki/Advanced-Usage#author-information)) -* `mastodon` - You can add a single Mastodon handle to both verify your Mastodon profile, and link to your Mastodon profile when someone shares your content on the network. - * `mastodon:server` - The server for your account (mastodon.social, for instance) - * `mastodon:username` - Your username (with or without `@`) +* `mastodon` - Your Mastodon handle, to both verify your Mastodon profile, and link to your profile when someone shares your content on the network. If the page metadata contains a `mastodon` entry, it will take precedence over `site_metadata.yml`. * `twitter` - You can add a single Twitter handle to be used in Twitter card tags, like "bridgetownrb". Or you use a YAML mapping with additional details: * `twitter:card` - The site's default card type diff --git a/lib/bridgetown-seo-tag.rb b/lib/bridgetown-seo-tag.rb index c89da41..3ce0c4e 100644 --- a/lib/bridgetown-seo-tag.rb +++ b/lib/bridgetown-seo-tag.rb @@ -6,6 +6,7 @@ module Bridgetown class SeoTag < Liquid::Tag autoload :AuthorDrop, "bridgetown-seo-tag/author_drop" + autoload :MastodonDrop, "bridgetown-seo-tag/mastodon_drop" autoload :ImageDrop, "bridgetown-seo-tag/image_drop" autoload :UrlHelper, "bridgetown-seo-tag/url_helper" autoload :Drop, "bridgetown-seo-tag/drop" diff --git a/lib/bridgetown-seo-tag/mastodon_drop.rb b/lib/bridgetown-seo-tag/mastodon_drop.rb new file mode 100644 index 0000000..e69cdd3 --- /dev/null +++ b/lib/bridgetown-seo-tag/mastodon_drop.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Bridgetown + class SeoTag + # A drop representing the current page's mastodon handle + # + # Mastodon handle will be pulled from: + # + # 1. The page's `mastodon` key + # 2. The `mastodon` key in the site config + class MastodonDrop < Bridgetown::Drops::Drop + HANDLE_REGEX = /\A@?(?[^@]+)@(?[^@]+)\z/ + + # Initialize a new MastodonDrop + # + # page - The page hash (e.g., Page#to_liquid) + # site - The Bridgetown::Drops::SiteDrop + def initialize(page: nil, site: nil) + raise ArgumentError unless page && site + + @mutations = {} + @page = page + @site = site + end + + def mastodon_handle + "@#{username}@#{server}" if handle? + end + alias_method :to_s, :mastodon_handle + + def mastodon_url + "https://#{server}/@#{username}" if handle? + end + + # Make the drop behave like a hash + def [](key) + return mastodon_handle if key.to_sym == :mastodon + end + + private + + attr_reader :page, :site + + # Finds the mastodon handle in page.metadata, or site.metadata + # + # Returns a string + def resolved_handle + return @resolved_handle if defined? @resolved_handle + + sources = [page["mastodon"]] + sources << site.data.dig("site_metadata", "mastodon") + @resolved_handle = sources.find { |s| !s.to_s.empty? } + end + + # Returns the username parsed from the resolved handle + def username + handle_hash["username"] + end + + # Returns the server parsed from the resolved handle + def server + handle_hash["server"] + end + + # Returns a hash containing username and server + # or an empty hash, if the handle cannot be parsed + def handle_hash + @handle_hash ||= case resolved_handle + when String + HANDLE_REGEX.match(resolved_handle)&.named_captures || {} + else + {} + end + end + # Since author_hash is aliased to fallback_data, any values in the hash + # will be exposed via the drop, allowing support for arbitrary metadata + alias_method :fallback_data, :handle_hash + + def handle? + handle_hash != {} + end + end + end +end diff --git a/lib/template.html b/lib/template.html index 051809a..35efe73 100755 --- a/lib/template.html +++ b/lib/template.html @@ -69,9 +69,9 @@ {% endif %} -{% if site.metadata.mastodon %} - - +{% if seo_tag.mastodon_handle %} + + {% endif %} {% if site.metadata.twitter %} diff --git a/spec/bridgetown_seo_tag/mastodon_drop_spec.rb b/spec/bridgetown_seo_tag/mastodon_drop_spec.rb new file mode 100644 index 0000000..2bb30af --- /dev/null +++ b/spec/bridgetown_seo_tag/mastodon_drop_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +RSpec.describe Bridgetown::SeoTag::MastodonDrop do + let(:data) { {} } + let(:site_config) { {} } + let(:metadata_config) { { "mastodon" => "@handle@metadata.config" } } + let(:site) do + site = make_site(metadata_config, site_config) + site.data = site.data.merge(data) + site + end + let(:site_payload) { site.site_payload["site"] } + + let(:page_meta) { { "title" => "page title" } } + let(:page) { make_page(page_meta) } + subject { described_class.new(page: page.to_liquid, site: site_payload.to_liquid) } + + before do + Bridgetown.logger.log_level = :error + end + + it "returns the mastodon handle for #to_s" do + expect(subject.to_s).to eql("@handle@metadata.config") + end + + context "with mastodon handle in site metadata" do + it "returns the site metadata handle" do + expect(subject.mastodon_handle).to eql("@handle@metadata.config") + end + end + + context "with mastodon handle in front matter default" do + let(:site_config) do + { + "defaults" => [ + { + "scope" => { "path" => "" }, + "values" => { "mastodon" => "@handle@frontmatter.defaults" }, + }, + ], + } + end + + it "uses the handle from the front matter default" do + site # init new config + defaults_page = Bridgetown::SeoTag::MastodonDrop.new(page: make_resource_page.to_liquid, site: site_payload.to_liquid) + expect(defaults_page["mastodon"]).to eql("@handle@frontmatter.defaults") + end + end + + context "with mastodon override in page meta" do + let(:page_meta) { { "mastodon" => "@handle@page.meta" } } + + it "uses the value defined in page metadata" do + expect(subject["mastodon"]).to eql("@handle@page.meta") + end + + context "with an empty value" do + let(:metadata_config) { {} } + let(:page_meta) { { "mastodon" => "" } } + + it "doesn't blow up" do + expect(subject["mastodon"]).to be_nil + end + end + + context "with a hash value" do + let(:page_meta) { { "mastodon" => { "some" => "thing" } } } + + it "doesn't blow up" do + expect(subject["mastodon"]).to be_nil + end + end + + context "with an array value" do + let(:page_meta) { { "mastodon" => [] } } + + it "doesn't blow up" do + expect(subject["mastodon"]).to be_nil + end + end + end +end