Repository navigation
Expand file tree
/
Copy pathrenderer.ts
More file actions
123 lines (115 loc) · 4.65 KB
/
Copy pathrenderer.ts
File metadata and controls
123 lines (115 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* renderer.ts :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: dlesieur <dlesieur@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/04/28 22:27:50 by dlesieur #+# #+# */
/* Updated: 2026/04/28 22:27:51 by dlesieur ### ########.fr */
/* */
/* ************************************************************************** */
import type {
BlockNode,
DocumentNode,
InlineNode,
ListItemNode,
ListNode,
} from "./types";
import {
resolveIndexedMarkdownMode,
resolveMarkdownMode,
type MarkdownModeResolver,
type MarkdownModeState,
type MarkdownViewMode,
} from "./render-mode";
import { escapeHtml } from "./utils";
import { sanitizeUrl } from "../renderCore";
export interface RenderHtmlOptions {
mode?: MarkdownViewMode;
blockModes?: MarkdownViewMode[];
resolveBlockMode?: MarkdownModeResolver<BlockNode>;
}
function renderInlines(nodes: InlineNode[]): string {
return nodes
.map((node) => {
switch (node.kind) {
case "text":
return escapeHtml(node.value);
case "emphasis":
return `<em data-node-id="${node.id}">${renderInlines(node.children)}</em>`;
case "strong":
return `<strong data-node-id="${node.id}">${renderInlines(node.children)}</strong>`;
case "code_span":
return `<code data-node-id="${node.id}" class="md-inline-code">${escapeHtml(node.value)}</code>`;
case "link":
return `<a data-node-id="${node.id}" href="${escapeHtml(sanitizeUrl(node.href) || "#")}">${renderInlines(node.children)}</a>`;
default:
return "";
}
})
.join("");
}
function renderListItem(item: ListItemNode, state: MarkdownModeState): string {
const blockStateAttr = ` data-block-state="${state.getBlockState()}"`;
const checkedAttr =
typeof item.checked === "boolean"
? ` data-checked="${String(item.checked)}"`
: "";
const content = item.children
.map((node) => renderBlock(node, state))
.join("");
return `<li data-node-id="${item.id}"${blockStateAttr}${checkedAttr}>${content}</li>`;
}
function renderList(node: ListNode, state: MarkdownModeState): string {
const tag = node.ordered ? "ol" : "ul";
const start = node.ordered && node.start > 1 ? ` start="${node.start}"` : "";
const blockStateAttr = ` data-block-state="${state.getBlockState()}"`;
return `<${tag} data-node-id="${node.id}"${blockStateAttr}${start}>${node.items.map((item) => renderListItem(item, state)).join("")}</${tag}>`;
}
function renderBlock(node: BlockNode, state: MarkdownModeState): string {
const blockStateAttr = ` data-block-state="${state.getBlockState()}"`;
switch (node.kind) {
case "paragraph":
return `<p data-node-id="${node.id}"${blockStateAttr}>${renderInlines(node.children)}</p>`;
case "heading":
return `<h${node.depth} data-node-id="${node.id}"${blockStateAttr}>${renderInlines(node.children)}</h${node.depth}>`;
case "code_block": {
const languageClass = node.language
? ` language-${escapeHtml(node.language)}`
: "";
return `<pre data-node-id="${node.id}"${blockStateAttr} class="md-code-block${languageClass}"><code>${escapeHtml(node.value)}</code></pre>`;
}
case "list":
return renderList(node, state);
case "blockquote":
return `<blockquote data-node-id="${node.id}"${blockStateAttr}>${node.children.map((child) => renderBlock(child, state)).join("")}</blockquote>`;
case "thematic_break":
return `<hr data-node-id="${node.id}"${blockStateAttr} />`;
case "list_item":
return renderListItem(node, state);
default:
return "";
}
}
export function renderHtml(
ast: DocumentNode,
options: RenderHtmlOptions = {},
): string {
if (!options.blockModes && !options.resolveBlockMode) {
const state = resolveMarkdownMode(options.mode);
return ast.children.map((node) => renderBlock(node, state)).join("\n");
}
return ast.children
.map((node, index) => {
const state = resolveIndexedMarkdownMode(
options.mode,
index,
node,
options.blockModes,
options.resolveBlockMode,
);
return renderBlock(node, state);
})
.join("\n");
}