Skip to content

Render pages as Markdown when the client asks for it - #578

Open
dreamldx wants to merge 8 commits into
redlib-org:mainfrom
dreamldx:wip-markdown-format
Open

dreamldx wants to merge 8 commits into
redlib-org:mainfrom
dreamldx:wip-markdown-format

Conversation

@dreamldx

Copy link
Copy Markdown

What

Redlib pages can now be returned as Markdown. When a request has Content-Type: text/markdown (or Accept: text/markdown), the page is rendered from a Markdown template instead of the HTML one. Without that header nothing changes; a browser's Accept: text/html,... never triggers it.

curl -H "Content-Type: text/markdown" "http://127.0.0.1:8080/r/rust/hot?limit=5"

This makes it easy to feed Reddit threads to an LLM or a script without HTML noise.

Covered pages

Front page, subreddits (incl. a+b, all, popular), posts with nested comments, user pages, search (global and per subreddit), duplicates, wiki/sidebar, and the error/info pages. Comments are nested Markdown lists, one indent level per reply.

Not covered: /settings, the media proxies and RSS keep their formats.

How

  • Post and Comment keep Reddit's Markdown source in a new body_md field (selftext / body), so the templates print Markdown rather than embedding the rendered HTML.
  • wants_markdown(), markdown() and page() in utils.rs: markdown() builds the response like template() does for HTML, page() picks one for a request.
  • Each page struct gets a small XxxMarkdown<'a> { t: &'a XxxTemplate } wrapper, so a handler builds its data once and only the last step differs (page(&req, &html, &md)). Fetching, settings and filtering are shared with the HTML page, including the NSFW display setting.
  • New templates live in templates/md/. Askama forbids recursive macros, so Comment::markdown() renders replies through comment.md.

Commits are split so each one builds on its own (I ran cargo check on every commit).

Notes / open questions

  • Links and images in the output are relative to the instance (/r/..., /img/...), like the HTML pages.
  • Post and comment bodies are Reddit's raw Markdown, so they can contain Reddit's escapes (Allocator\_api) and absolute reddit.com links that are not rewritten.
  • Post-level NSFW gating (should_be_nsfw_gated) and subscription filters apply as they do for HTML because the handlers are shared. The NSFW landing and quarantine/gated wall pages themselves are still HTML.
  • No automated tests yet. I checked the output by hand against a local instance (listings, threads, users, search, wiki, errors, and br/gzip compression).

Opening as a draft to get feedback on the approach first (header-based negotiation rather than a URL suffix, and the wrapper-struct pattern).

🤖 Generated with Claude Code

dreamldx and others added 8 commits September 24, 2026 19:31
Post and Comment only carried the rendered HTML body. Store the Markdown
source Reddit already returns (selftext for posts, body for comments) in a
new body_md field, so a Markdown view can print it instead of embedding
HTML. Removed content gets a plain "[removed]" placeholder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wants_markdown() checks Content-Type/Accept for text/markdown, markdown()
builds the response like template() does for HTML, and page() picks one of
the two for a request. error() and info() use a small message.md template
when Markdown is requested.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Post listing entries, media, poll, badges and user comments are used by
several pages, so they live in md/utils.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comments are nested Markdown lists, one level of indentation per reply.
Askama does not allow recursive macros, so Comment::markdown() renders a
comment's replies through comment.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wikis keep Reddit's Markdown source (content_md) next to the HTML.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Community descriptions are collapsed to one line so they stay in a list
item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dreamldx
dreamldx marked this pull request as ready for review September 25, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant