Skip to content

[FEATURE] Render every page to Markdown alongside HTML - #1410

Draft
linawolf wants to merge 5 commits into
mainfrom
task/markdown-output-format
Draft

[FEATURE] Render every page to Markdown alongside HTML#1410
linawolf wants to merge 5 commits into
mainfrom
task/markdown-output-format

Conversation

@linawolf

@linawolf linawolf commented Sep 12, 2026

Copy link
Copy Markdown
Member

Part of #1400.

Tools that read our documentation programmatically want the content without the
surrounding HTML. The reStructuredText source is not good enough: includes,
substitutions and interlinks are unresolved there, and directives only become
content during rendering.

A new package typo3-docs-theme-md registers an md output format on the same
AST and pipeline as HTML, so RenderContext writes Feature.md beside
Feature.html. The theme ships it; render-markdown="false" switches it off.

Markdown is GitHub Flavored — admonitions and version changes become alerts,
tables become pipe tables. Links are permalinks built from
interlink_shortcode, because a downloaded file has to keep working.

Four commits, each reviewable on its own:

  • 703c478 the render target: renderer, 40 templates, always-on via the
    compiler pass
  • 5792203 the setting, the download button and the rel="alternate" link
  • 5c9851c escaping, confval, rubric and text roles
  • 953541b tabs, cards, accordions, definition lists and images

Verified against the TYPO3 core changelog, 3912 entries: no unrendered
directives, no HTML entities, and all 12622 permalinks resolve to a target in
the generated inventory. Rendering Markdown alongside HTML costs 8.7%
(127s → 138s). The changelog uses no images and no tabs, cards or accordions, so
the last commit only adds content there, in the two files using rst-class.

That count was worth less than it looked: three nodes were exempted from the
marker as structural, and silently dropped their content instead. 953541b gives
each one a template, so the marker count can now be trusted.

Still open, which is why this is a draft: console:command and the PHP API
directives have no template yet — together 139 of the 159 markers left in the
rendertest, down from 243. A node without one leaves an HTML comment naming it,
so the gaps are greppable rather than silent.

This supersedes #1401, which added the same rel="alternate" link pointing at
the copied reStructuredText source.

Signed-off-by: lina.wolf
Assisted-by: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_019EuSAzF5qw81NzPusWy2hC

Tools that read our documentation programmatically want the content
without the surrounding HTML. The reStructuredText source is not good
enough: includes, substitutions and interlinks are unresolved there, and
directives only become content during rendering.

This is a render target on the same footing as HTML - same AST, same
pipeline, its own templates. A new package typo3-docs-theme-md registers
an "md" output format with 40 templates and its own node renderers, so
RenderContext writes Feature.md beside Feature.html. The theme ships it,
so no second extension has to be configured.

Markdown is GitHub Flavored: admonitions and version changes become
alerts, tables become pipe tables. CommonMark has no tables at all.

It renders always. That cannot be done by prepending "output_format":
an explicit value replaces the default ["html", "interlink"] rather than
extending it, so a project configuring nothing would get Markdown and no
HTML. Appending in the compiler pass leaves every other format
untouched - a project rendering only "singlepage" or only "rst" keeps
doing that. Naming "md" explicitly still wins, so a Markdown-only render
stays possible.

Links are permalinks built from interlink_shortcode, because a
downloaded file has to keep working. A page linked without an anchor is
in this same manual, so its document entry supplies one; "#" is the page
itself. Anchors are normalised the way getPermalink() does it: a URL
fragment keeps the casing of the element id, while the inventory
registers the target reduced. Where no permalink can be built the link
stays relative and that is logged.

A toctree renders as the list of links it is, unless it is :hidden: -
the same rule the HTML output applies. A node with no Markdown template
leaves an HTML comment naming it, so the remaining gaps are greppable
rather than silent.

The HTML head points at the Markdown with rel="alternate", which
supersedes #1401.

Verified against the TYPO3 core changelog, 3912 entries: four confval
directives left unrendered, no HTML entities, and all 12622 permalinks
resolve to a target in the generated inventory. Rendering Markdown
alongside HTML costs 8.7% (127s to 138s).

Part of: #1400

Signed-off-by: lina.wolf
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EuSAzF5qw81NzPusWy2hC
render-markdown="false" drops the Markdown files, the head link and the
menu entries together.

Adds a "Markdown" download button beside "Give feedback" and "Edit on
GitHub", and "View as Markdown" in the options menu.

The download is named from the manual and the page instead of the URL,
where every overview page arrives as "index.md". The name comes from the
document's label, not its title: titles repeat, so an entry backported
to three versions shares one title anchor. Using it collided 29 of 3912
names and pointed a :doc: link at the wrong version.

Signed-off-by: lina.wolf
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EuSAzF5qw81NzPusWy2hC
Source prose is not Markdown. Unescaped, "__dunder__" turned bold,
"array[0](x)" turned into a link and a leading "#" into a heading. Only
what can start a construct is escaped: an underscore inside a word is
not emphasis in CommonMark, so escaping every one added 2287 backslashes
to the core changelog for nothing.

confval renders as a list item: indenting under a bare paragraph is an
indented code block, not containment, so a definition had no visible end
and nested confvals did not nest. The name is bold and the property
labels italic; neither wraps a code span, which not every renderer
parses.

rubric becomes bold via a GeneralDirectiveNodeRenderer that looks up a
template per directive name, the way HTML does. Text roles dispatch by
name too, so ":code:" and the default role keep their markup.

Signed-off-by: lina.wolf
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EuSAzF5qw81NzPusWy2hC
composer normalize sorts "T3Docs\Typo3DocsThemeMd\" before
"T3Docs\Typo3DocsTheme\", and the Quality job checks it.

Signed-off-by: lina.wolf
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EuSAzF5qw81NzPusWy2hC
…down

Tabs, cards and accordions are boxes: panels behind a click, a bordered
card, a collapsed stack. Markdown has none of that, so each title becomes
bold and the contents follow one below the other.

A card writes out its image, header, title, body and footer in that order.
A footer given a block can hold a whole list, so it is rendered as blocks
rather than collapsed onto one line.

Three nodes were exempted from the fallback marker as structural, on the
assumption that they pass their content through. They do not: rst-class
dropped its whole content, and field and definition lists lost their
terms. Each one now has a template.

An image read its alt text and its path from properties that do not
exist, so every image rendered as "![]()" pointing at an unnormalised
path. It now reads the same options the HTML template reads.

Signed-off-by: lina.wolf
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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