feat: add fediverse_creator handle for Mastodon attribution
Expose a per-post fediverse_creator frontmatter field plus a site-wide default in config.toml ([site].fediverse_creator). The handle is returned in the post and site payloads, rendered as <dc:creator> in the RSS feed and as authors[].name in the JSON feed, so a front-end can drop it into <meta name="fediverse:creator"> on rendered pages. Per-post value overrides the site default. Validated against a simple @user@host regex. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -18,7 +18,8 @@ module Volumen
|
||||
"description" => "A blog powered by volumen.",
|
||||
"base_url" => "https://example.com",
|
||||
"language" => "en",
|
||||
"author" => "Anonymous"
|
||||
"author" => "Anonymous",
|
||||
"fediverse_creator" => nil
|
||||
}.freeze,
|
||||
"admin" => {
|
||||
"password_hash" => "",
|
||||
@@ -46,6 +47,9 @@ module Volumen
|
||||
base_url = "https://example.com"
|
||||
language = "en"
|
||||
author = "Anonymous"
|
||||
# Default fediverse handle surfaced in <meta name="fediverse:creator">
|
||||
# by frontends consuming the API. Leave empty to disable.
|
||||
fediverse_creator = ""
|
||||
|
||||
[admin]
|
||||
# Bootstrap admin login: paste a hash from `volumen hash-password` to sign
|
||||
|
||||
@@ -7,6 +7,8 @@ require_relative "markdown"
|
||||
module Volumen
|
||||
# A single blog post: TOML frontmatter metadata plus a Markdown body.
|
||||
class Post
|
||||
FEDIVERSE_CREATOR_REGEX = /\A@[^@\s]+@[^@\s]+\z/
|
||||
|
||||
attr_reader :metadata, :body
|
||||
attr_accessor :path
|
||||
|
||||
@@ -56,6 +58,18 @@ module Volumen
|
||||
metadata["cover"]
|
||||
end
|
||||
|
||||
def fediverse_creator
|
||||
value = metadata["fediverse_creator"]
|
||||
return nil if value.nil?
|
||||
|
||||
text = value.to_s.strip
|
||||
text.empty? ? nil : text
|
||||
end
|
||||
|
||||
def valid_fediverse_creator?
|
||||
fediverse_creator&.match?(FEDIVERSE_CREATOR_REGEX)
|
||||
end
|
||||
|
||||
def publish_at
|
||||
metadata["publish_at"]
|
||||
end
|
||||
@@ -112,6 +126,7 @@ module Volumen
|
||||
"lang" => lang,
|
||||
"tags" => tags,
|
||||
"author" => author,
|
||||
"fediverse_creator" => fediverse_creator,
|
||||
"cover" => cover,
|
||||
"reading_time" => reading_time,
|
||||
"translations" => translations,
|
||||
|
||||
+34
-5
@@ -84,7 +84,12 @@ module Volumen
|
||||
end
|
||||
|
||||
def site_payload
|
||||
config.site.slice("title", "description", "base_url", "language", "author")
|
||||
config.site.slice("title", "description", "base_url", "language", "author",
|
||||
"fediverse_creator")
|
||||
end
|
||||
|
||||
def site_fediverse_creator
|
||||
config.site["fediverse_creator"]
|
||||
end
|
||||
|
||||
def posts_payload
|
||||
@@ -146,7 +151,7 @@ module Volumen
|
||||
items = published_posts.first(20).map { |post| feed_item(post) }.join("\n")
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<channel>
|
||||
<title>#{escape(site["title"])}</title>
|
||||
<link>#{escape(base_url)}</link>
|
||||
@@ -165,12 +170,14 @@ module Volumen
|
||||
else
|
||||
""
|
||||
end
|
||||
creator = post.fediverse_creator
|
||||
creator_tag = creator ? "\n <dc:creator>#{escape(creator)}</dc:creator>" : ""
|
||||
<<~XML.chomp
|
||||
<item>
|
||||
<title>#{escape(post.title)}</title>
|
||||
<link>#{escape(link)}</link>
|
||||
<guid>#{escape(link)}</guid>
|
||||
<description>#{escape(post.excerpt)}</description>#{pub}
|
||||
<description>#{escape(post.excerpt)}</description>#{pub}#{creator_tag}
|
||||
</item>
|
||||
XML
|
||||
end
|
||||
@@ -208,6 +215,8 @@ module Volumen
|
||||
"date_published" => iso_date(post.metadata["date"]),
|
||||
"tags" => post.tags
|
||||
}
|
||||
author = post.fediverse_creator || site_fediverse_creator
|
||||
item["authors"] = [{ "name" => author }] if author
|
||||
item.reject { |_k, v| v.nil? || v == "" || v == [] }
|
||||
end
|
||||
|
||||
@@ -401,7 +410,15 @@ module Volumen
|
||||
existing = store.find(params["slug"])
|
||||
halt(404, "Not found") if existing.nil?
|
||||
|
||||
store.save(post_from_params(params, existing: existing))
|
||||
post = post_from_params(params, existing: existing)
|
||||
error = fediverse_creator_error(post)
|
||||
if error
|
||||
@mode = :edit
|
||||
@post = post
|
||||
@error = error
|
||||
return erb(:form)
|
||||
end
|
||||
store.save(post)
|
||||
redirect to("/admin/")
|
||||
end
|
||||
|
||||
@@ -410,7 +427,7 @@ module Volumen
|
||||
return "Invalid slug." unless post.slug.match?(SLUG_REGEX)
|
||||
return "A post with that slug already exists." if store.find(post.slug)
|
||||
|
||||
nil
|
||||
fediverse_creator_error(post)
|
||||
end
|
||||
|
||||
def post_from_params(http_params, existing: nil)
|
||||
@@ -419,6 +436,7 @@ module Volumen
|
||||
"slug" => presence(http_params["slug"]) || existing&.slug,
|
||||
"lang" => presence(http_params["lang"]),
|
||||
"author" => presence(http_params["author"]),
|
||||
"fediverse_creator" => presence(http_params["fediverse_creator"]),
|
||||
"date" => parse_date(http_params["date"]),
|
||||
"publish_at" => parse_date(http_params["publish_at"]),
|
||||
"tags" => parse_tags(http_params["tags"]),
|
||||
@@ -432,6 +450,17 @@ module Volumen
|
||||
post
|
||||
end
|
||||
|
||||
def fediverse_creator_error(post)
|
||||
value = post.metadata["fediverse_creator"]
|
||||
return nil if value.nil?
|
||||
|
||||
if Post::FEDIVERSE_CREATOR_REGEX.match?(value.to_s)
|
||||
nil
|
||||
else
|
||||
"Fediverse creator must look like @user@host."
|
||||
end
|
||||
end
|
||||
|
||||
def clean_metadata(metadata)
|
||||
metadata.reject { |_key, value| value.nil? || value == "" || value == [] || value == false }
|
||||
end
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
</label>
|
||||
<label>Language <input type="text" name="lang" value="<%= h(@post.lang) %>"></label>
|
||||
<label>Author <input type="text" name="author" value="<%= h(@post.author) %>"></label>
|
||||
<label>Fediverse creator
|
||||
<input type="text" name="fediverse_creator" value="<%= h(@post.fediverse_creator) %>"
|
||||
placeholder="@user@instance.tld">
|
||||
</label>
|
||||
<label>Date <input type="date" name="date" value="<%= h(@post.date_string) %>"></label>
|
||||
<label>Publish at
|
||||
<input type="date" name="publish_at"
|
||||
|
||||
Reference in New Issue
Block a user