Render markdown in OpenAPI descriptions on the API pages - #22
Conversation
Operation, tag, parameter and response descriptions were rendered as plain text, so backticks, links, bold, lists and tables in a spec description showed up as raw markdown syntax. Add a small `mdToHtml` helper (remark + rehype) and use it on the server-rendered API pages: - operation description and per-response descriptions (operation page) - parameter / schema attribute descriptions (schema table) - tag descriptions (endpoint list) - `mdToText` for the page `<meta>`/OG description, so markdown syntax doesn't leak into snippets The output is injected via dangerouslySetInnerHTML, so it is sanitized: remark-rehype drops raw HTML in the source (no rehype-raw), and rehype-sanitize's default (GitHub) schema strips unsafe URL schemes such as `javascript:` while keeping the elements GFM emits (links, code, tables, lists). Covered by a unit test. Scope is intentionally limited to the server-rendered operation/tag pages. The interactive apiref view (`markline-apiref`) also renders descriptions, but adding markdown there is an HTML-injection/sanitization design call I'd rather leave to you — happy to follow up on it. Adds direct deps: unified, remark-parse, remark-rehype, rehype-sanitize, rehype-stringify (remark-gfm was already present).
Now that descriptions render markdown, give tables, lists and inline code minimal styling (border-collapse, code chips, list indentation) so they read correctly in the operation / tag / parameter description slots without depending on the full docs-prose styles.
There was a problem hiding this comment.
The sanitization work here is solid. I checked the output myself: [x](javascript:alert(1)) comes out as an inert <a>, raw <img onerror> and <script> get dropped, and mdToText gives clean meta descriptions. Tests and typecheck pass, static export builds fine.
On the apiref view, the approach is right and there's no design call left to make. mdToHtml with rehype-sanitize and no rehype-raw is exactly what I'd want there too, so go ahead and run the same pipeline over r.lead and the operation leads in markline-apiref.tsx.
The reason I'd rather have that in this PR than a follow-up: /api-reference/<tag> is the page people actually land on, and it renders MarklineApiRef, not ApiOperationPage. The operation page only comes up on per-operation deep links. So as it stands a tag description with **bold** and a [link](…) still renders literally on the resource page, and most readers see no change at all.
Two things worth fixing while you're in there:
.api-desc ahas no colour set, so links fall through to the browser default#0000EE. Against the dark theme background that's about 1.3:1.- Response descriptions go into a
<span>(operation-page.tsx:180), so a description containing a list emits<p>/<ul>inside a span in a flex row. Invalid nesting, and the status pill drifts out of alignment. Adivsorts it, andmdToHtmlalready unwraps single paragraphs so inline descriptions stay inline.
Problem
Operation, tag, parameter and response descriptions are rendered as
plain text. So a spec description like:
shows up in the docs with the literal
**,[…](…)and backticksinstead of formatting.
Fix
A small
mdToHtmlhelper (lib/md-desc.ts, remark + rehype) used on theserver-rendered API pages:
operation-page.tsx)schema-table.tsx)endpoint-list.tsx)mdToTextfor the page<meta>/ OG description, so markdown syntaxdoesn't leak into search snippets (
page.tsx)Safety
The rendered HTML is injected via
dangerouslySetInnerHTML, so it issanitized:
rehype-raw, so a<script>/<img onerror>in a description never reaches the output.rehype-sanitize(default GitHubschema) turns
[x](javascript:alert(1))into inert<a>x</a>whilekeeping normal
https:links, code, tables and lists.Verified end-to-end: injected a description with bold, a
javascript:link and a GFM table into a spec, built, and confirmed the page renders
<strong>/<code>/<table>and that thejavascript:href is gone.Scope
Intentionally limited to the server-rendered operation/tag pages. The
interactive apiref view (
markline-apiref) also renders descriptions,but rendering markdown there is an HTML-injection / sanitization design
call I'd rather leave to you — glad to send a follow-up once you're happy
with the approach here.
Dependencies
Adds direct deps:
unified,remark-parse,remark-rehype,rehype-sanitize,rehype-stringify.remark-gfmwas already present.Verification
npm run typecheck— cleannpm test— 25/25 pass (addstest/md-desc.test.ts)npm run build— static export succeeds