Repository navigation
Markdown Feeds: Serve comment feeds and add comments to singular documents - #1121
Conversation
add_feed() registers the markdown format for every feed context, so /comments/feed/markdown/ and /<post>/feed/markdown/ resolve, but do_feed_markdown() always used the post renderer. Those URLs returned the post list instead of comments, unlike the core comment feeds at the same URLs. Comment feed contexts now use Markdown_Comment_Feed_Renderer: a heading for the site or the post, then one block per approved comment with the author (and the post, in the site-wide feed), the comment link and date, and the comment text converted to Markdown. Comments on password-protected posts are listed without their text, as in core. A new filter, wpai_markdown_comment_feed_item_sections, mirrors the post item filter.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1121 +/- ##
=============================================
+ Coverage 81.95% 82.11% +0.16%
- Complexity 3350 3394 +44
=============================================
Files 135 136 +1
Lines 13026 13178 +152
=============================================
+ Hits 10675 10821 +146
- Misses 2351 2357 +6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ents The comment feed renderer iterated the comments array directly, so the comment_text filters ran without the global comment and with the main query's post as the current post. Use the comment loop and set up each comment's post before filtering, like the core comment feeds do, and reset the post data after the loop.
…omment-feeds # Conflicts: # tests/Integration/Includes/Experiments/Markdown_Feeds/Markdown_FeedsTest.php
| $this->send_header( 'Content-Type: text/markdown; charset=' . get_option( 'blog_charset' ) ); | ||
|
|
||
| $renderer = new Markdown_Feed_Renderer(); | ||
| $renderer = is_comment_feed() ? new Markdown_Comment_Feed_Renderer() : new Markdown_Feed_Renderer(); |
There was a problem hiding this comment.
Apologies but testing again, this does not seem to work the way I'd expect. With these changes in place, the main post markdown feed only renders comments, not the actual post itself. That seems broken, shouldn't there be a specific comment feed that is used instead?
I also think header negotiation is broken for these feeds so that needs looked at
There was a problem hiding this comment.
Thanks, agreed on all of these.
For the per-post feed shape: I read core's /post/feed/ as the post's comment feed, which is why I switched /post/feed/markdown/ to comments. But I agree this changes the Markdown feature's existing behaviour, where that URL rendered the post itself.
The header negotiation problem is real and pre-existing: is_singular() is true on a post's comment feed, so /post/feed/ got Vary: Accept and was served the post's Markdown on Accept: text/markdown or ?output_format=markdown. Fixed here so feed requests are never negotiated, plus the smaller renderer fixes.
For the route shape, my suggested direction is to keep /post/feed/markdown/ rendering the post as before, keep /comments/feed/markdown/ for the site-wide comment feed, and add a separate explicit path or query for per-post comments only if you want that supported. Happy to go whichever way you prefer.
There was a problem hiding this comment.
I read core's /post/feed/ as the post's comment feed, which is why I switched /post/feed/markdown/ to comments. But I agree this changes the Markdown feature's existing behaviour, where that URL rendered the post itself.
Hmm.. I didn't realize this but in testing, does seem the post singular feed shows comments, if any, not the content of the post. That seems not great to me but I'm sure there's a reason. But this particular feature was built to show a markdown representation of the post and so I don't think we should change our feed to only be comments, even if that means we aren't matching Core (though open to a discussion on that if people feel that's the wrong approach).
For the route shape, my suggested direction is to keep /post/feed/markdown/ rendering the post as before, keep /comments/feed/markdown/ for the site-wide comment feed, and add a separate explicit path or query for per-post comments only if you want that supported. Happy to go whichever way you prefer.
For the first two questions, I think that's all correct.
For the last (separate path for a per-post-comment feed), I'd say we either don't support that for now or any thoughts on just rendering comments in the per-post feed, alongside the post content?
There was a problem hiding this comment.
That works for me. I will keep /post/feed/markdown/ as a post document first, matching the feature's existing purpose, and append approved comments under a ## Comments section when comments exist. Posts without comments will keep the same output as before.
/comments/feed/markdown/ and ?withcomments=1 will stay as the site-wide comment feed.
I will push that update today.
There was a problem hiding this comment.
In thinking about this a bit more and testing on some default WordPress sites, I think that supporting /<post>/feed/markdown/ was unintentional coming out of #855. Any thoughts on removing that singular post markdown feed here?
The main entry points from that PR were for global feeds to support /markdown but for singular content, they only serve markdown if you add the query param (like https://example.com/this-is-an-article?output_format=markdown). So I think it was unintentional that /<post>/feed/markdown/ exists/works.
So my thoughts are:
- Remove the singular markdown feed URL support
- For the singular markdown query param, update the output there to render comments
- Ensure global feeds (like
/comments/feed/markdown/) do all work
This also matches how WP.org is currently working. For example, if you go to https://make.wordpress.org/core/2026/10/07/proposal-minor-updates-to-the-wordpress-core-css-coding-standards/?output_format=markdown, you'll see comments rendered there. If you go to https://make.wordpress.org/core/2026/10/07/proposal-minor-updates-to-the-wordpress-core-css-coding-standards/feed/markdown, that will 404.
There was a problem hiding this comment.
Agreed on all three.
The singular feed URL exists because add_feed() registers the format for every feed rewrite rule, the per-post comment feed included, so the clean way to remove it is a 404 for is_singular() plus feed=markdown. That covers ?p=N&feed=markdown too.
For the comments in the singular document: a ## Comments section after the content, one block per approved comment (### By: author, link, date, text), ordered by the Discussion settings' comment order and capped at its comments-per-page value, so a post with 2,000 comments does not become a 2,000-comment document. Pingbacks and trackbacks are left out, and the section can be removed through the existing sections filter. Posts without comments keep today's document unchanged.
Global feeds stay as in this PR: /comments/feed/markdown/ and ?withcomments=1 list comments, the others list posts.
Pushing the update shortly.
There was a problem hiding this comment.
Pushed as f2b8aff: the singular feed URL answers 404, the ?output_format=markdown document ends with the approved comments (Discussion settings for order and count, pingbacks excluded, removable through the sections filter), global feeds unchanged. PR title and description updated.
A post's comment feed is a singular request too, so "/post/feed/" was getting a "Vary: Accept" header and was served the post's Markdown when the client sent "Accept: text/markdown" or "?output_format=markdown". Both gates now also require the request not to be a feed. In the comment feed renderer, escape the comment author in the item heading, rewind the comment loop after rendering, and drop a redundant assignment.
A post's own Markdown feed renders the post document as before and, when the post has approved comments, ends with a "## Comments" section holding one block per comment under a "### By:" heading. Posts without comments produce the same document as before. The site-wide comment feed is unchanged.
A post's Markdown document (?output_format=markdown) now ends with a "## Comments" section listing its approved comments, following the Discussion settings for order and count, pingbacks excluded. Posts without comments keep the same document as before. The Markdown feed of a single post was never a supported URL: its rewrite rule only exists because add_feed() registers the format for every feed context. It now answers 404 with the theme's not-found page. The site-wide comment feed is unchanged. The comment renderer is renamed Markdown_Comment_Renderer and its sections filter wpai_markdown_comment_sections; a new wpai_markdown_singular_comments_args filter controls the comment query.
…ent can't manipulate the markdown output. Ensure we handle threaded comments and ensure the number of comments we output matches core settings. Slightly better approach for marking the singular markdown feed as a 404. Minor performance improvements on setting global post
|
@ekamran I've made some last changes here, let me know if you see any issues with those though. Thanks! |
|
Thanks @dkotter, the escaping, notes exclusion and threading changes look good to me, and CI is green. One thing I found while testing: with Discussion set to show newer comments first, replies inside a thread also come out newest first. Core's comment list keeps replies oldest first and only reverses the top level: Tested on 7.2-alpha with two levels of replies:
Reversing child comments level by level when the query order is |
Ah, good find. And yeah, might as well fix that up in this PR so feel free to push that change when you have time. Thanks! |
With Discussion set to show newer comments first, the threaded query returns every level newest first, so a reply could appear above the comment it answers. Core's comment list keeps replies oldest first and only reverses the top level: comments_template() queries ASC and wp_list_comments() sets reverse_top_level from comment_order. When the query order is DESC, reverse the replies at each level. The order is read the way WP_Comment_Query reads it, anything but ASC is DESC, so a filter that drops the order gets the same result.
|
Pushed in fbbe201. When the query order is DESC, replies are now reversed at each level, so they stay oldest first under their parent, as in core's comment list. The order is read the way The new tests cover both comment orders and the dropped-order case, and they fail on the previous head. On a test site the document now matches One small thing in your commit: the comment above the early return says "posts with not comments". Happy to fix that here too if you want. |
What?
Closes #1120
Renders comments in the site-wide comment feed instead of the post list, adds a post's approved comments to its Markdown document (
?output_format=markdown), and answers 404 for the never-intended/<post>/feed/markdown/URL.Why?
add_feed( 'markdown', ... )registers the format for every feed context, so/comments/feed/markdown/and/<post>/feed/markdown/resolve, butdo_feed_markdown()always used the post renderer: those URLs returned the latest posts with full content, while the core comment feeds at the same URLs list comments. Details and sample output are in #1120. Review settled the per-post case differently from core: the singular Markdown entry point is?output_format=markdown(#855), so that document carries the comments, and the singular feed URL, a side effect ofadd_feed(), is not served.How?
Markdown_Comment_Renderer.do_feed_markdown()uses it for the site-wide comment feed (/comments/feed/markdown/,?feed=markdown&withcomments=1and other non-singular comment feed contexts); the post feed is unchanged otherwise.Markdown_Singular_Rendereradds acommentssection after the content when the post has approved comments:## Comments, then one block per comment. The query follows the Discussion settings: comment order, and with paging on only the first page in that order. With threading on, a page holds comments-per-page top-level comments, each followed by its replies, oldest first as in the core comment list (not nested). It skips pingbacks, trackbacks and notes, skips the query for posts without a comment count unless that filter is in use, and is filterable throughwpai_markdown_singular_comments_args; the whole section is removable throughwpai_markdown_singular_sections. Posts without comments keep today's document, byte for byte./<post>/feed/markdown/(and?p=N&feed=markdown) answers 404 with the theme's not-found page. The URL only exists becauseadd_feed()registers the format for every feed rewrite rule, the per-post comment feed included. Other feed formats of the post (/<post>/feed/), the main Markdown feed and the singular document are untouched.is_singular()is true on a post's comment feed, so/post/feed/previously gotVary: Acceptand could be served the post's Markdown onAccept: text/markdownor?output_format=markdown. Both gates now also require! is_feed().# Comments for <site name>and the site URL.## Comment on <post> by <author>site-wide,### By: <author>in a post's document, the same wording core uses for its comment feed item titles), a metadata list with the comment link and date, and the comment text run through thecomment_textfilters and converted to Markdown, so<strong>and auto-linked URLs come out as Markdown. Markdown syntax in the comment text is escaped, so a comment cannot add headings, lists or other structure to the document. The site-wide comment feed renders through WordPress' comment loop, while singular Markdown documents query approved comments for the post and set the comment and post globals before applyingcomment_text, so those filters see the same context as in the core comment feeds.comment_feed_*filters,posts_per_rss), so moderation, order and the limit match the core feeds.Last-Modified,ETagand 304 answers come from core as before.wpai_markdown_comment_sectionsmirrorswpai_markdown_feed_item_sectionsfor comment blocks in both places. Documented indocs/experiments/markdown-feeds.mdtogether with the comment feed URLs.Sample,
/<post>/?output_format=markdownwith this PR (the document as before, then its comments):Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code and Codex
Used for: Investigation, implementation, tests, verification, and PR wording. I reviewed the reasoning and test results, and I take responsibility for the contribution.
Testing Instructions
/comments/feed/markdown/. Ondevelopit lists posts. With this PR it lists the comments, newest first, each with a link and date./<post-slug>/?output_format=markdown. The document as before, then a## Commentssection with one block per approved comment, in the Discussion settings' order. With threading on, replies follow their parent, oldest first, also when newer comments are shown first. A post without comments gives exactly the same output as ondevelop./<post-slug>/feed/markdown/. Ondevelopit renders the post as a one-item feed; with this PR it is a 404 page./<post-slug>/feed/(RSS) still works./feed/markdown/. It still lists posts./comments/feed/markdown/without the comment text.curl -sI -H 'Accept: text/markdown' <post URL>feed/returnsapplication/rss+xmlwithout aVary: Acceptheader (ondevelopit returns the post's Markdown).Automated:
npm run test:php -- --filter 'Markdown_'runs the 95 Markdown Feeds tests; all pass here.composer lintand PHPStan pass.Changelog Entry