@wdprlib/render 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/dist/index.cjs +2344 -1668
  2. package/dist/index.d.cts +15 -13
  3. package/dist/index.d.ts +15 -13
  4. package/dist/index.js +2375 -1699
  5. package/package.json +1 -1
  6. package/src/context/attributes.ts +14 -0
  7. package/src/context/bibliography.ts +109 -0
  8. package/src/context/counters.ts +51 -0
  9. package/src/context/image-urls.ts +31 -0
  10. package/src/context/index.ts +285 -0
  11. package/src/context/output.ts +17 -0
  12. package/src/context/page-urls.ts +81 -0
  13. package/src/context/style-slots.ts +29 -0
  14. package/src/context/urls.ts +2 -0
  15. package/src/elements/bibliography/block.ts +27 -0
  16. package/src/elements/bibliography/cite.ts +23 -0
  17. package/src/elements/bibliography/ids.ts +9 -0
  18. package/src/elements/bibliography/index.ts +9 -0
  19. package/src/elements/code/contents.ts +18 -0
  20. package/src/elements/code/index.ts +29 -0
  21. package/src/elements/collapsible/index.ts +31 -0
  22. package/src/elements/collapsible/labels.ts +35 -0
  23. package/src/elements/collapsible/link.ts +11 -0
  24. package/src/elements/collapsible/sections.ts +39 -0
  25. package/src/elements/container/attributes.ts +28 -0
  26. package/src/elements/container/header.ts +27 -0
  27. package/src/elements/container/index.ts +35 -0
  28. package/src/elements/container/string-container.ts +40 -0
  29. package/src/elements/container/string-types.ts +63 -0
  30. package/src/elements/container/wrappers.ts +32 -0
  31. package/src/elements/date/format.ts +20 -0
  32. package/src/elements/{date.ts → date/index.ts} +4 -29
  33. package/src/elements/date/output.ts +6 -0
  34. package/src/elements/embed/iframe.ts +8 -0
  35. package/src/elements/embed/index.ts +28 -0
  36. package/src/elements/embed/providers.ts +43 -0
  37. package/src/elements/embed/validation.ts +15 -0
  38. package/src/elements/embed-block/allowlist.ts +60 -0
  39. package/src/elements/embed-block/boolean-attributes.ts +38 -0
  40. package/src/elements/embed-block/iframe.ts +33 -0
  41. package/src/elements/embed-block/index.ts +31 -0
  42. package/src/elements/embed-block/sanitize-config.ts +22 -0
  43. package/src/elements/embed-block/sanitize.ts +44 -0
  44. package/src/elements/expr/branch.ts +29 -0
  45. package/src/elements/expr/index.ts +63 -0
  46. package/src/elements/expr/result.ts +19 -0
  47. package/src/elements/footnote/body.ts +11 -0
  48. package/src/elements/footnote/index.ts +35 -0
  49. package/src/elements/footnote/ref.ts +16 -0
  50. package/src/elements/html/attributes.ts +24 -0
  51. package/src/elements/html/index.ts +39 -0
  52. package/src/elements/html/url.ts +19 -0
  53. package/src/elements/iframe/attributes.ts +28 -0
  54. package/src/elements/iframe/index.ts +22 -0
  55. package/src/elements/iftags/condition.ts +42 -0
  56. package/src/elements/iftags/index.ts +39 -0
  57. package/src/elements/iftags/style-slot.ts +23 -0
  58. package/src/elements/iftags/tokens.ts +36 -0
  59. package/src/elements/image/alignment.ts +44 -0
  60. package/src/elements/image/attributes.ts +10 -0
  61. package/src/elements/image/img-attributes.ts +26 -0
  62. package/src/elements/image/index.ts +36 -0
  63. package/src/elements/image/link-href.ts +24 -0
  64. package/src/elements/image/link.ts +13 -0
  65. package/src/elements/image/source.ts +16 -0
  66. package/src/elements/{include.ts → include/index.ts} +5 -13
  67. package/src/elements/include/missing.ts +15 -0
  68. package/src/elements/link/anchor-name.ts +6 -0
  69. package/src/elements/link/anchor.ts +27 -0
  70. package/src/elements/link/attributes.ts +47 -0
  71. package/src/elements/link/index.ts +26 -0
  72. package/src/elements/link/label.ts +23 -0
  73. package/src/elements/link/target.ts +20 -0
  74. package/src/elements/list/attributes.ts +19 -0
  75. package/src/elements/list/definition-list.ts +16 -0
  76. package/src/elements/list/index.ts +48 -0
  77. package/src/elements/list/item-rendering.ts +38 -0
  78. package/src/elements/list/items.ts +61 -0
  79. package/src/elements/list/no-marker.ts +53 -0
  80. package/src/elements/list/paragraphs.ts +34 -0
  81. package/src/elements/list/trim.ts +38 -0
  82. package/src/elements/math/block.ts +29 -0
  83. package/src/elements/math/equation-ref.ts +12 -0
  84. package/src/elements/math/index.ts +14 -0
  85. package/src/elements/math/inline.ts +19 -0
  86. package/src/elements/math/latex.ts +27 -0
  87. package/src/elements/math/source.ts +18 -0
  88. package/src/elements/module/backlinks.ts +2 -1
  89. package/src/elements/module/categories.ts +2 -2
  90. package/src/elements/module/empty-container.ts +10 -0
  91. package/src/elements/module/index.ts +2 -4
  92. package/src/elements/module/join-markup.ts +10 -0
  93. package/src/elements/module/join.ts +2 -7
  94. package/src/elements/module/listpages.ts +2 -2
  95. package/src/elements/module/listusers.ts +2 -2
  96. package/src/elements/module/page-tree.ts +2 -2
  97. package/src/elements/module/rate-markup.ts +10 -0
  98. package/src/elements/module/rate.ts +4 -13
  99. package/src/elements/module/unknown.ts +11 -0
  100. package/src/elements/tab-view/ids.ts +16 -0
  101. package/src/elements/tab-view/index.ts +31 -0
  102. package/src/elements/tab-view/navigation.ts +15 -0
  103. package/src/elements/tab-view/panels.ts +16 -0
  104. package/src/elements/table/attributes.ts +23 -0
  105. package/src/elements/table/cell-attributes.ts +62 -0
  106. package/src/elements/table/cell.ts +13 -0
  107. package/src/elements/table/index.ts +27 -0
  108. package/src/elements/text/email.ts +20 -0
  109. package/src/elements/text/index.ts +11 -0
  110. package/src/elements/text/plain.ts +11 -0
  111. package/src/elements/text/raw.ts +20 -0
  112. package/src/elements/toc/body.ts +12 -0
  113. package/src/elements/toc/entries.ts +34 -0
  114. package/src/elements/toc/frame.ts +27 -0
  115. package/src/elements/toc/index.ts +17 -0
  116. package/src/elements/toc/link.ts +26 -0
  117. package/src/elements/user/index.ts +40 -0
  118. package/src/elements/user/markup.ts +34 -0
  119. package/src/elements/user/resolve.ts +6 -0
  120. package/src/escape/attribute-allowlists.ts +101 -0
  121. package/src/escape/attributes.ts +62 -0
  122. package/src/escape/css-color-functions.ts +18 -0
  123. package/src/escape/css-colors.ts +183 -0
  124. package/src/escape/css-danger.ts +22 -0
  125. package/src/escape/css-normalize.ts +54 -0
  126. package/src/escape/css-style.ts +78 -0
  127. package/src/escape/css-urls.ts +76 -0
  128. package/src/escape/css.ts +4 -0
  129. package/src/escape/email.ts +22 -0
  130. package/src/escape/html.ts +68 -0
  131. package/src/escape/index.ts +15 -0
  132. package/src/escape/url.ts +18 -0
  133. package/src/libs/highlighter/engine/end-pattern.ts +26 -0
  134. package/src/libs/highlighter/engine/html.ts +19 -0
  135. package/src/libs/highlighter/engine/index.ts +3 -0
  136. package/src/libs/highlighter/engine/keywords.ts +22 -0
  137. package/src/libs/highlighter/engine/parts.ts +36 -0
  138. package/src/libs/highlighter/engine/preprocess.ts +10 -0
  139. package/src/libs/highlighter/engine/render.ts +31 -0
  140. package/src/libs/highlighter/engine/token.ts +7 -0
  141. package/src/libs/highlighter/engine/tokenizer.ts +266 -0
  142. package/src/libs/highlighter/engine/utils.ts +38 -0
  143. package/src/render/collected-styles.ts +22 -0
  144. package/src/render/dispatch.ts +181 -0
  145. package/src/render/index.ts +28 -0
  146. package/src/render/primitives.ts +17 -0
  147. package/src/render/style-tag.ts +6 -0
  148. package/src/render/style.ts +15 -0
  149. package/src/types.ts +6 -2
  150. package/src/context.ts +0 -422
  151. package/src/elements/bibliography.ts +0 -123
  152. package/src/elements/code.ts +0 -49
  153. package/src/elements/collapsible.ts +0 -105
  154. package/src/elements/container.ts +0 -302
  155. package/src/elements/embed-block.ts +0 -327
  156. package/src/elements/embed.ts +0 -166
  157. package/src/elements/expr.ts +0 -102
  158. package/src/elements/footnote.ts +0 -76
  159. package/src/elements/html.ts +0 -79
  160. package/src/elements/iframe.ts +0 -44
  161. package/src/elements/iftags.ts +0 -118
  162. package/src/elements/image.ts +0 -154
  163. package/src/elements/link.ts +0 -201
  164. package/src/elements/list.ts +0 -241
  165. package/src/elements/math.ts +0 -177
  166. package/src/elements/tab-view.ts +0 -75
  167. package/src/elements/table.ts +0 -101
  168. package/src/elements/text.ts +0 -57
  169. package/src/elements/toc.ts +0 -147
  170. package/src/elements/user.ts +0 -79
  171. package/src/escape.ts +0 -829
  172. package/src/libs/highlighter/engine.ts +0 -352
  173. package/src/render.ts +0 -231
@@ -0,0 +1,266 @@
1
+ import type { LanguageDefinition } from "../types";
2
+ import { buildEndPattern } from "./end-pattern";
3
+ import { resolveKeywordClass } from "./keywords";
4
+ import { buildPartTokens } from "./parts";
5
+ import { preprocessHighlightInput } from "./preprocess";
6
+ import type { HighlightToken } from "./token";
7
+ import { findGroupPosition } from "./utils";
8
+
9
+ interface HighlighterState {
10
+ state: number;
11
+ lastdelim: string;
12
+ lastinner: string;
13
+ endpattern: RegExp | null;
14
+ }
15
+
16
+ /**
17
+ * Tokenize source code using a language definition's state machine.
18
+ */
19
+ export function tokenize(def: LanguageDefinition, input: string): HighlightToken[] {
20
+ const str = preprocessHighlightInput(input);
21
+ const len = str.length;
22
+ if (len === 0) return [];
23
+
24
+ let state = -1;
25
+ let pos = 0;
26
+ let lastinner = def.defClass;
27
+ let lastdelim = def.defClass;
28
+ let endpattern: RegExp | null = null;
29
+ const stateStack: HighlighterState[] = [];
30
+ const tokenStack: HighlightToken[] = [];
31
+ const result: HighlightToken[] = [];
32
+
33
+ function getToken(): HighlightToken | null {
34
+ if (tokenStack.length > 0) {
35
+ return tokenStack.pop()!;
36
+ }
37
+ if (pos >= len) {
38
+ return null;
39
+ }
40
+
41
+ const endStateMatch = findEndStateMatch(str, pos, state, endpattern);
42
+ const token = matchStateToken({
43
+ def,
44
+ str,
45
+ pos,
46
+ state,
47
+ lastinner,
48
+ lastdelim,
49
+ endpattern,
50
+ stateStack,
51
+ tokenStack,
52
+ endStateMatch,
53
+ setPosition: (nextPos) => {
54
+ pos = nextPos;
55
+ },
56
+ setState: (next) => {
57
+ state = next.state;
58
+ lastinner = next.lastinner;
59
+ lastdelim = next.lastdelim;
60
+ endpattern = next.endpattern;
61
+ },
62
+ });
63
+ if (token) {
64
+ return token;
65
+ }
66
+
67
+ if (endStateMatch.endpos > -1) {
68
+ tokenStack.push({ class: lastdelim, content: endStateMatch.endmatch });
69
+ if (endStateMatch.endpos > pos) {
70
+ tokenStack.push({ class: lastinner, content: str.substring(pos, endStateMatch.endpos) });
71
+ }
72
+ const prev = stateStack.pop()!;
73
+ state = prev.state;
74
+ lastdelim = prev.lastdelim;
75
+ lastinner = prev.lastinner;
76
+ endpattern = prev.endpattern;
77
+ pos = endStateMatch.endpos + endStateMatch.endmatch.length;
78
+ if (tokenStack.length > 0) {
79
+ return tokenStack.pop()!;
80
+ }
81
+ return getToken();
82
+ }
83
+
84
+ const p = pos;
85
+ pos = len;
86
+ return { class: lastinner, content: str.substring(p) };
87
+ }
88
+
89
+ let token: HighlightToken | null;
90
+ while ((token = getToken()) !== null) {
91
+ result.push(token);
92
+ }
93
+
94
+ return result;
95
+ }
96
+
97
+ interface EndStateMatch {
98
+ endpos: number;
99
+ endmatch: string;
100
+ }
101
+
102
+ function findEndStateMatch(
103
+ str: string,
104
+ pos: number,
105
+ state: number,
106
+ endpattern: RegExp | null,
107
+ ): EndStateMatch {
108
+ if (state === -1 || !endpattern) {
109
+ return { endpos: -1, endmatch: "" };
110
+ }
111
+
112
+ endpattern.lastIndex = pos;
113
+ const match = endpattern.exec(str);
114
+ return match ? { endpos: match.index, endmatch: match[0] } : { endpos: -1, endmatch: "" };
115
+ }
116
+
117
+ interface MatchStateTokenArgs {
118
+ def: LanguageDefinition;
119
+ str: string;
120
+ pos: number;
121
+ state: number;
122
+ lastinner: string;
123
+ lastdelim: string;
124
+ endpattern: RegExp | null;
125
+ stateStack: HighlighterState[];
126
+ tokenStack: HighlightToken[];
127
+ endStateMatch: EndStateMatch;
128
+ setPosition(pos: number): void;
129
+ setState(state: HighlighterState): void;
130
+ }
131
+
132
+ function matchStateToken(args: MatchStateTokenArgs): HighlightToken | null {
133
+ const reg = args.def.regs[args.state];
134
+ if (!reg) return null;
135
+
136
+ reg.lastIndex = args.pos;
137
+ const match = reg.exec(args.str);
138
+ if (!match) return null;
139
+
140
+ const countsArr = args.def.counts[args.state]!;
141
+ let captureIndex = 1;
142
+
143
+ for (let patternIndex = 0; patternIndex < countsArr.length; patternIndex++) {
144
+ const count = countsArr[patternIndex]!;
145
+ if (captureIndex >= match.length) break;
146
+
147
+ if (
148
+ match[captureIndex] != null &&
149
+ (args.endStateMatch.endpos === -1 || match.index < args.endStateMatch.endpos)
150
+ ) {
151
+ return emitMatchedPattern(args, match, patternIndex, captureIndex, count);
152
+ }
153
+
154
+ captureIndex += count + 1;
155
+ }
156
+
157
+ return null;
158
+ }
159
+
160
+ function emitMatchedPattern(
161
+ args: MatchStateTokenArgs,
162
+ match: RegExpExecArray,
163
+ patternIndex: number,
164
+ captureIndex: number,
165
+ count: number,
166
+ ): HighlightToken {
167
+ const statesArr = args.def.states[args.state]!;
168
+ const delimArr = args.def.delim[args.state]!;
169
+ const matchStart = match.index;
170
+ const matchStr = match[captureIndex]!;
171
+ const groupStart = findGroupPosition(args.str, match, captureIndex, matchStart);
172
+
173
+ if (statesArr[patternIndex] !== -1) {
174
+ args.tokenStack.push({ class: delimArr[patternIndex]!, content: matchStr });
175
+ } else {
176
+ pushNonTransitionTokens(args, match, patternIndex, captureIndex, count, groupStart, matchStr);
177
+ }
178
+
179
+ if (groupStart > args.pos) {
180
+ args.tokenStack.push({
181
+ class: args.lastinner,
182
+ content: args.str.substring(args.pos, groupStart),
183
+ });
184
+ }
185
+
186
+ args.setPosition(groupStart + matchStr.length);
187
+
188
+ if (statesArr[patternIndex] !== -1) {
189
+ enterState(args, match, patternIndex, captureIndex, count);
190
+ }
191
+
192
+ return args.tokenStack.pop()!;
193
+ }
194
+
195
+ function pushNonTransitionTokens(
196
+ args: MatchStateTokenArgs,
197
+ match: RegExpExecArray,
198
+ patternIndex: number,
199
+ captureIndex: number,
200
+ count: number,
201
+ groupStart: number,
202
+ matchStr: string,
203
+ ): void {
204
+ let inner = args.def.inner[args.state]![patternIndex]!;
205
+ const partDef = args.def.parts[args.state]?.[patternIndex];
206
+ if (partDef) {
207
+ pushPartTokens(args, match, partDef, captureIndex, count, groupStart, matchStr, inner);
208
+ return;
209
+ }
210
+
211
+ inner = resolveKeywordClass(args.def, args.state, patternIndex, matchStr, inner);
212
+ args.tokenStack.push({ class: inner, content: matchStr });
213
+ }
214
+
215
+ function pushPartTokens(
216
+ args: MatchStateTokenArgs,
217
+ match: RegExpExecArray,
218
+ partDef: Record<number, string>,
219
+ captureIndex: number,
220
+ count: number,
221
+ groupStart: number,
222
+ matchStr: string,
223
+ inner: string,
224
+ ): void {
225
+ const parts: HighlightToken[] = [];
226
+ parts.push(
227
+ ...buildPartTokens(args.str, match, partDef, captureIndex, count, groupStart, matchStr, inner),
228
+ );
229
+ args.tokenStack.push(...parts);
230
+ }
231
+
232
+ function enterState(
233
+ args: MatchStateTokenArgs,
234
+ match: RegExpExecArray,
235
+ patternIndex: number,
236
+ captureIndex: number,
237
+ count: number,
238
+ ): void {
239
+ const statesArr = args.def.states[args.state]!;
240
+ const delimArr = args.def.delim[args.state]!;
241
+ const innerArr = args.def.inner[args.state]!;
242
+ args.stateStack.push({
243
+ state: args.state,
244
+ lastdelim: args.lastdelim,
245
+ lastinner: args.lastinner,
246
+ endpattern: args.endpattern,
247
+ });
248
+
249
+ const prevState = args.state;
250
+ const nextState = statesArr[patternIndex]!;
251
+ const endRe = args.def.end[nextState];
252
+ args.setState({
253
+ state: nextState,
254
+ lastinner: innerArr[patternIndex]!,
255
+ lastdelim: delimArr[patternIndex]!,
256
+ endpattern: buildEndPattern(
257
+ args.def,
258
+ prevState,
259
+ patternIndex,
260
+ count,
261
+ captureIndex,
262
+ match,
263
+ endRe ?? undefined,
264
+ ),
265
+ });
266
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Find the actual position of capture group `n` within the source string.
3
+ */
4
+ export function findGroupPosition(
5
+ str: string,
6
+ match: RegExpExecArray,
7
+ groupIndex: number,
8
+ matchStart: number,
9
+ ): number {
10
+ const groupStr = match[groupIndex]!;
11
+ const idx = str.indexOf(groupStr, matchStart);
12
+ return idx >= 0 ? idx : matchStart;
13
+ }
14
+
15
+ /**
16
+ * Escape regex special characters in a string for safe use in `new RegExp()`.
17
+ */
18
+ export function escapeRegex(str: string): string {
19
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20
+ }
21
+
22
+ /**
23
+ * Swap bracket characters to their matching counterparts.
24
+ */
25
+ export function matchingBrackets(str: string): string {
26
+ return str.replace(/[()<>[\]{}]/g, (char) => MATCHING_BRACKETS[char] ?? char);
27
+ }
28
+
29
+ const MATCHING_BRACKETS: Record<string, string> = {
30
+ "(": ")",
31
+ ")": "(",
32
+ "<": ">",
33
+ ">": "<",
34
+ "[": "]",
35
+ "]": "[",
36
+ "{": "}",
37
+ "}": "{",
38
+ };
@@ -0,0 +1,22 @@
1
+ import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
2
+ import type { RenderContext } from "../context";
3
+ import { pushStyleTag } from "./style-tag";
4
+
5
+ export function renderCollectedStyles(ctx: RenderContext, styles: string[] | undefined): void {
6
+ if (!ctx.settings.allowStyleElements || !styles?.length) return;
7
+
8
+ for (const style of styles) {
9
+ if (style.startsWith(STYLE_SLOT_PREFIX)) {
10
+ renderStyleSlot(ctx, style);
11
+ } else {
12
+ pushStyleTag(ctx, style);
13
+ }
14
+ }
15
+ }
16
+
17
+ function renderStyleSlot(ctx: RenderContext, marker: string): void {
18
+ const slotId = parseInt(marker.slice(STYLE_SLOT_PREFIX.length), 10);
19
+ for (const css of ctx.getStyleSlotContents(slotId)) {
20
+ pushStyleTag(ctx, css);
21
+ }
22
+ }
@@ -0,0 +1,181 @@
1
+ import type { Element } from "@wdprlib/ast";
2
+ import { RenderContext } from "../context";
3
+ import { renderBibliographyBlock, renderBibliographyCite } from "../elements/bibliography";
4
+ import { renderClearFloat } from "../elements/clear-float";
5
+ import { renderCode } from "../elements/code";
6
+ import { renderCollapsible } from "../elements/collapsible";
7
+ import { renderContainer } from "../elements/container";
8
+ import { renderColor } from "../elements/color";
9
+ import { renderDate } from "../elements/date";
10
+ import { renderEmbed } from "../elements/embed";
11
+ import { renderEmbedBlock } from "../elements/embed-block";
12
+ import { renderExpr, renderIf, renderIfExpr } from "../elements/expr";
13
+ import { renderFootnoteBlock, renderFootnoteRef } from "../elements/footnote";
14
+ import { renderHtmlBlock } from "../elements/html";
15
+ import { renderIframe } from "../elements/iframe";
16
+ import { renderIfTags } from "../elements/iftags";
17
+ import { renderImage } from "../elements/image";
18
+ import { renderInclude } from "../elements/include";
19
+ import { renderLineBreaks } from "../elements/line-break";
20
+ import { renderAnchor, renderAnchorName, renderLink } from "../elements/link";
21
+ import { renderDefinitionList, renderList } from "../elements/list";
22
+ import { renderMath, renderMathInline, renderEquationRef } from "../elements/math";
23
+ import { renderModule } from "../elements/module/index";
24
+ import { renderTable } from "../elements/table";
25
+ import { renderTabView } from "../elements/tab-view";
26
+ import { renderRaw, renderEmail, renderText } from "../elements/text";
27
+ import { renderTableOfContents } from "../elements/toc";
28
+ import { renderUser } from "../elements/user";
29
+ import {
30
+ renderContentSeparator,
31
+ renderHorizontalRule,
32
+ renderLineBreak,
33
+ renderTextNode,
34
+ } from "./primitives";
35
+ import { renderStyleElement } from "./style";
36
+
37
+ /**
38
+ * Render a list of sibling AST elements in document order.
39
+ */
40
+ export function renderElements(ctx: RenderContext, elements: Element[]): void {
41
+ for (const element of elements) {
42
+ renderElement(ctx, element);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Dispatch a single AST element to its type-specific renderer.
48
+ */
49
+ export function renderElement(ctx: RenderContext, element: Element): void {
50
+ switch (element.element) {
51
+ case "text":
52
+ renderTextNode(ctx, element.data);
53
+ break;
54
+ case "raw":
55
+ renderRaw(ctx, element.data);
56
+ break;
57
+ case "variable":
58
+ renderText(ctx, element.data);
59
+ break;
60
+ case "email":
61
+ renderEmail(ctx, element.data);
62
+ break;
63
+ case "container":
64
+ renderContainer(ctx, element.data);
65
+ break;
66
+ case "link":
67
+ renderLink(ctx, element.data);
68
+ break;
69
+ case "anchor":
70
+ renderAnchor(ctx, element.data);
71
+ break;
72
+ case "anchor-name":
73
+ renderAnchorName(ctx, element.data);
74
+ break;
75
+ case "image":
76
+ renderImage(ctx, element.data);
77
+ break;
78
+ case "list":
79
+ renderList(ctx, element.data);
80
+ break;
81
+ case "definition-list":
82
+ renderDefinitionList(ctx, element.data);
83
+ break;
84
+ case "table":
85
+ renderTable(ctx, element.data);
86
+ break;
87
+ case "collapsible":
88
+ renderCollapsible(ctx, element.data);
89
+ break;
90
+ case "code":
91
+ renderCode(ctx, element.data);
92
+ break;
93
+ case "tab-view":
94
+ renderTabView(ctx, element.data);
95
+ break;
96
+ case "footnote":
97
+ renderFootnoteRef(ctx, ctx.nextFootnoteIndex() + 1);
98
+ break;
99
+ case "footnote-ref":
100
+ renderFootnoteRef(ctx, element.data);
101
+ break;
102
+ case "footnote-block":
103
+ renderFootnoteBlock(ctx, element.data);
104
+ break;
105
+ case "bibliography-cite":
106
+ renderBibliographyCite(ctx, element.data);
107
+ break;
108
+ case "bibliography-block":
109
+ renderBibliographyBlock(ctx, element.data, renderElements);
110
+ break;
111
+ case "table-of-contents":
112
+ renderTableOfContents(ctx, element.data);
113
+ break;
114
+ case "math":
115
+ renderMath(ctx, element.data);
116
+ break;
117
+ case "math-inline":
118
+ renderMathInline(ctx, element.data);
119
+ break;
120
+ case "module":
121
+ renderModule(ctx, element.data);
122
+ break;
123
+ case "embed":
124
+ renderEmbed(ctx, element.data);
125
+ break;
126
+ case "embed-block":
127
+ renderEmbedBlock(ctx, element.data);
128
+ break;
129
+ case "user":
130
+ renderUser(ctx, element.data);
131
+ break;
132
+ case "date":
133
+ renderDate(ctx, element.data);
134
+ break;
135
+ case "color":
136
+ renderColor(ctx, element.data);
137
+ break;
138
+ case "html":
139
+ renderHtmlBlock(ctx, element.data);
140
+ break;
141
+ case "iframe":
142
+ renderIframe(ctx, element.data);
143
+ break;
144
+ case "include":
145
+ renderInclude(ctx, element.data);
146
+ break;
147
+ case "if-tags":
148
+ renderIfTags(ctx, element.data);
149
+ break;
150
+ case "style":
151
+ renderStyleElement(ctx, element.data);
152
+ break;
153
+ case "line-break":
154
+ renderLineBreak(ctx);
155
+ break;
156
+ case "line-breaks":
157
+ renderLineBreaks(ctx, element.data);
158
+ break;
159
+ case "clear-float":
160
+ renderClearFloat(ctx, element.data);
161
+ break;
162
+ case "horizontal-rule":
163
+ renderHorizontalRule(ctx);
164
+ break;
165
+ case "content-separator":
166
+ renderContentSeparator(ctx);
167
+ break;
168
+ case "expr":
169
+ renderExpr(ctx, element.data);
170
+ break;
171
+ case "if":
172
+ renderIf(ctx, element.data);
173
+ break;
174
+ case "ifexpr":
175
+ renderIfExpr(ctx, element.data);
176
+ break;
177
+ case "equation-reference":
178
+ renderEquationRef(ctx, element.data);
179
+ break;
180
+ }
181
+ }
@@ -0,0 +1,28 @@
1
+ import type { SyntaxTree } from "@wdprlib/ast";
2
+ import { RenderContext } from "../context";
3
+ import type { RenderOptions } from "../types";
4
+ import { renderCollectedStyles } from "./collected-styles";
5
+ import { renderElements } from "./dispatch";
6
+
7
+ export { renderElement, renderElements } from "./dispatch";
8
+
9
+ /**
10
+ * Render a {@link SyntaxTree} to an HTML string.
11
+ *
12
+ * This is the main entry point of `@wdprlib/render`. It walks the AST
13
+ * produced by `@wdprlib/parser`, serialises each element to HTML, and
14
+ * appends any collected `[[module CSS]]` styles at the end (when
15
+ * `WikitextSettings.allowStyleElements` is `true`).
16
+ *
17
+ * @param tree - Parsed AST (from `parse()` or `resolveModules()`)
18
+ * @param options - Rendering configuration
19
+ * @returns Complete HTML string
20
+ *
21
+ * @group Render
22
+ */
23
+ export function renderToHtml(tree: SyntaxTree, options: RenderOptions = {}): string {
24
+ const ctx = new RenderContext(tree, options);
25
+ renderElements(ctx, tree.elements);
26
+ renderCollectedStyles(ctx, tree.styles);
27
+ return ctx.getOutput();
28
+ }
@@ -0,0 +1,17 @@
1
+ import { RenderContext } from "../context";
2
+
3
+ export function renderTextNode(ctx: RenderContext, text: string): void {
4
+ ctx.pushEscaped(text);
5
+ }
6
+
7
+ export function renderLineBreak(ctx: RenderContext): void {
8
+ ctx.push("<br />");
9
+ }
10
+
11
+ export function renderHorizontalRule(ctx: RenderContext): void {
12
+ ctx.push("<hr />");
13
+ }
14
+
15
+ export function renderContentSeparator(ctx: RenderContext): void {
16
+ ctx.push(`<div class="content-separator" style="display: none:"></div>`);
17
+ }
@@ -0,0 +1,6 @@
1
+ import type { RenderContext } from "../context";
2
+ import { escapeStyleContent } from "../escape";
3
+
4
+ export function pushStyleTag(ctx: RenderContext, css: string): void {
5
+ ctx.push(`<style>${escapeStyleContent(css)}</style>`);
6
+ }
@@ -0,0 +1,15 @@
1
+ import { RenderContext } from "../context";
2
+ import { pushStyleTag } from "./style-tag";
3
+
4
+ export function renderStyleElement(ctx: RenderContext, css: string): void {
5
+ if (!ctx.renderInlineStyles || !ctx.settings.allowStyleElements) {
6
+ return;
7
+ }
8
+
9
+ if (ctx.hasActiveStyleSlot()) {
10
+ ctx.pushToStyleSlot(css);
11
+ return;
12
+ }
13
+
14
+ pushStyleTag(ctx, css);
15
+ }
package/src/types.ts CHANGED
@@ -13,10 +13,14 @@ import type { EmbedAllowlistEntry } from "./elements/embed-block";
13
13
  export interface PageContext {
14
14
  /** Full page name including category prefix (e.g. `"secret:test2"`) */
15
15
  pageName: string;
16
- /** Site slug used to build inter-site URLs (e.g. `"scp-wiki"`) */
16
+ /** Site slug for the current page (e.g. `"scp-wiki"`) */
17
17
  site?: string;
18
- /** Site domain used for absolute URL generation (e.g. `"scp-wiki.wikidot.com"`) */
18
+ /** Current site domain used for absolute URL generation (e.g. `"scp-wiki.example.org"`) */
19
19
  domain?: string;
20
+ /** Known domains for cross-site page references keyed by site slug. */
21
+ siteDomains?: Record<string, string>;
22
+ /** Resolve a cross-site page reference site slug to a domain. */
23
+ resolveSiteDomain?: (site: string) => string | null | undefined;
20
24
  /**
21
25
  * Returns whether a page exists on the site.
22
26
  * When a target page does not exist, the renderer adds `class="newpage"`