A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1/**
2 * The render-article module: one deep interface for turning a stored document
3 * into safe HTML + plain text (Decision 0018).
4 *
5 * Turning a block tree into injectable HTML requires three calls in one
6 * load-bearing order — `resolveBlobImageUrls` → `renderBlocks` →
7 * `sanitizeArticleHtml` — and the text fallback (`textContent || blocksToText`)
8 * alongside it. That ordering is the AGENTS.md #6b invariant (sanitise is the
9 * LAST step before any injection); it lives here, once, so every read surface
10 * (article page, RSS, future AMP/email/embeds) gets it by construction instead
11 * of by convention. Like `render.ts` underneath, this module is dependency-free
12 * and must never import `@wordpress/*` (Decision 0003).
13 */
14import { renderBlocks, blocksToText } from '../blocks/render';
15import { resolveBlobImageUrls } from '../media/blob';
16import { sanitizeArticleHtml } from './sanitize';
17import { highlightCodeBlocks } from './highlight';
18import type { BlockNode } from '../blocks/render';
19
20/** The slice of a `site.standard.document` value the renderer consumes. */
21export interface RenderableDocument {
22 textContent?: string;
23 content?: { blocks?: BlockNode[] };
24}
25
26export interface RenderedArticle {
27 /** Sanitised article HTML, safe to inject (`set:html`, RSS CDATA, …). */
28 html: string;
29 /** Plain text: the stored `textContent`, else derived from the blocks. */
30 text: string;
31}
32
33export interface RenderOptions {
34 /** Syntax-highlight code blocks (web page only — RSS stays plain). */
35 highlight?: boolean;
36}
37
38/**
39 * Render a document's blocks to sanitised HTML and plain text. `author` is the
40 * document's writer (current PDS + DID) — blob-backed image URLs are rebuilt
41 * against it before rendering, so images survive a PDS migration. Pass
42 * `{ highlight: true }` to syntax-highlight code blocks (web page only; the
43 * highlight step runs before sanitise, so its token markup is sanitiser-checked).
44 */
45export function renderArticle(
46 doc: RenderableDocument,
47 author: { pdsUrl: string; did: string },
48 options: RenderOptions = {}
49): RenderedArticle {
50 const blocks = resolveBlobImageUrls( doc.content?.blocks ?? [], author );
51 const rendered = renderBlocks( blocks );
52 const highlighted = options.highlight ? highlightCodeBlocks( rendered ) : rendered;
53 return {
54 html: sanitizeArticleHtml( highlighted ),
55 text: doc.textContent || blocksToText( blocks ),
56 };
57}