Support for author replies #23
Labels
No labels
activitypub
auth
bug
build
duplicate
enhancement
feeds
help wanted
invalid
markdown
new feature
question
style
webmentions
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
blacklight/madblog#23
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Author Replies — Design Document
Goal
Allow the blog author to reply to:
Replies are authored as Markdown files (same workflow as posts), rendered via the existing Markdown pipeline, and each reply gets its own permalink URL. However, replies must not appear in the home page post listing or in the blog RSS/Atom feeds.
Current State
Posts
*.mdfiles under<pages_dir>(typically<content_dir>/markdown).[//]: # (key: value)./article/<slug>(slug = relative path minus.md).*.mdfiles underpages_dirare walked recursively by_get_pages_from_files()and appear on the home page.Incoming Webmentions
FileWebmentionsStorageas Markdown files under<state_dir>/mentions/incoming/<post-slug>/webmention-*.md.sourceURL (the remote page) that serves as its external identity.Incoming ActivityPub Interactions
FileActivityPubStorageas JSON files under<state_dir>/activitypub/state/interactions/<sanitized-target>/.activity_idandobject_idfields — both are remote URLs (e.g.https://mastodon.social/users/alice/statuses/123).Rendering of Reactions
webmentionslibrary's Jinja2 template (webmention.html). The outer<div class="wm-mention">currently has noidattribute — there is no anchor for linking to a specific mention.interaction.html. The outer<div class="ap-interaction ...">also has noidattribute.Templateobject), so Madblog can inject its own templates that add anchor IDs without forking the libraries.Proposed Storage Format
Directory Layout
Replies live in a dedicated directory, separate from
pages_dir, so they are never picked up by_get_pages_from_files():All replies for a given article are grouped under the article's slug. This keeps the filesystem browsable and makes it easy to list all author replies for a given post.
Identifying the Reply Target (Metadata)
Each reply Markdown file uses the standard
[//]: #metadata header convention to declare what it is replying to:The
reply-tofield contains:reply-tovaluehttps://blog.example.com/article/my-postsourceURLobject_id(the remote post URL)This approach:
replies/<article-slug>/.reply-to) to express the target, regardless of whether it is the article itself, a webmention, or an AP interaction.Reply Slug
The reply filename (minus
.md) becomes the slug. The author is free to choose it. Examples:replies/my-post/thanks-alice.md→ slugthanks-alicereplies/my-post/re-thread.md→ slugre-threadURL Scheme
Replies are served at:
Examples:
/reply/my-post/thanks-alice/reply/my-post/re-threadThe
/reply/prefix keeps the namespace clean and distinct from/article/. The raw Markdown is available at/reply/<article-slug>/<reply-slug>.md, mirroring the article convention.Rendering
Reply Page
Browsing
/reply/<article-slug>/<reply-slug>renders the reply through the same Markdown → HTML pipeline used for articles. The existing_parse_page_metadata()/_render_page_html()flow can be reused with minimal adaptation (resolve the file from the replies directory instead ofpages_dir).The rendered page should include:
reply-to).A dedicated template (e.g.
reply.html) or the existingarticle.htmlwith a "reply" mode flag can be used.Replies Displayed on Article Pages
When rendering an article page, the system should collect any author replies from
replies/<article-slug>/and display them inline among (or after) the reactions section. This requires:reply-tometadata to determine placement: - Ifreply-tomatches the article URL → top-level author reply. Ifreply-tomatches a reaction's identity → threaded under that reaction./reply/<article-slug>/<reply-slug>.Ordering and nesting
Replies should be placed below the item they are replying to.
A group consisting of a root message + its replies is conventionally named a "thread".
Threads can be recursive/nested.
Ordering of items within a thread is the opposite of the ordering of root mentions/interactions. While interactions on the root level are sorted by creation date descending, replies should be sorted by creation date ascending.
Consider how to visually render recursive threads. Some proposals:
Domain of the changes
Reaction Permalinks and Anchor IDs
The Problem
Currently, neither the webmentions nor pubby templates emit
idattributes on individual reaction containers. This means:#reaction-abc123).Proposed Solution
Add
idattributes to each rendered reaction<div>, using a deterministic, URL-safe hash derived from the reaction's identity:sourceURLwm-<md5(source)[:12]>object_idoractivity_idap-<md5(object_id)[:12]>This produces stable, short anchors like
#wm-a1b2c3d4e5f6or#ap-f6e5d4c3b2a1.Where to Implement
The anchor IDs should be added in the rendering templates, not in the libraries' core logic. Two approaches:
Custom templates in Madblog — override the default
webmention.htmlandinteraction.htmltemplates by passing custom template strings/paths torender_webmentions()/render_interactions(). This avoids any changes to the webmentions or pubby libraries.Upstream in webmentions/pubby — add optional
idattributes to the default templates. This is cleaner long-term and benefits all consumers. Theidcould be computed via a new Jinja2 helper exposed inTemplateUtils.Recommendation: implement upstream in both libraries (option 2). The
idattribute is semantically correct HTML and broadly useful. If upstream changes are not immediately feasible, option 1 provides a quick Madblog-only fallback.Permalink Button
Each rendered reaction should include a small permalink/anchor button (e.g. 🔗 or a chain-link icon) that:
window.location.hashto the reaction's anchor.<article-url>#<anchor-id>) to the clipboard.This can be implemented as a small inline
<a>or<button>next to the reaction's date/footer. Like anchor IDs, this is best implemented upstream in the webmentions/pubby templates so all consumers benefit.Federation
ActivityPub
When an author reply is created or updated, it should be published as an ActivityPub
Note(or the configured object type) with:inReplyToset to thereply-toURL (the target article or remote post).idset to the reply's canonical URL (https://blog.example.com/reply/<article-slug>/<reply-slug>).attributedToset to the blog's AP actor.to/ccincluding the public collection and, when replying to a remote interaction, the original author's actor URL.This makes the reply appear as a threaded response on Mastodon and other fediverse platforms.
The existing
ActivityPubIntegration.on_content_change()andContentMonitorcan be extended to watch the replies directory.build_object()would need a small adaptation to populateinReplyTofrom thereply-tometadata.Webmentions
When a reply targets a URL that supports Webmentions (including the blog's own articles), an outgoing Webmention should be sent. The existing
FileWebmentionsStorage.on_content_change()mechanism can be reused — it just needs to know about the replies directory.Implementation Outline
Phase 1 — Core Reply Storage and Rendering
replies_dirproperty toBlogApppointing at<content_dir>/replies./reply/<path:article_slug>/<reply_slug>route (and.mdvariant) that resolves files fromreplies_dirand renders them via the existing Markdown pipeline.reply-tometadata and render a back-link in the reply page._get_pages_from_files()explicitly excludesreplies_dir.Phase 2 — Inline Display on Article Pages
replies/<article-slug>/for reply files.reply-to.Phase 3 — Reaction Anchors and Permalinks
idattributes to rendered reactions (upstream in webmentions/pubby, or via custom Madblog templates as fallback).Phase 4 — Federation
ContentMonitorto watchreplies_dir.ActivityPubIntegrationto publish replies withinReplyTo.FileWebmentionsStorageto send outgoing webmentions for replies.