@wdprlib/render 2.0.0 → 2.1.0

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 (58) hide show
  1. package/dist/index.cjs +11 -387
  2. package/dist/index.js +2 -378
  3. package/package.json +5 -3
  4. package/src/context.ts +422 -0
  5. package/src/elements/bibliography.ts +123 -0
  6. package/src/elements/clear-float.ts +27 -0
  7. package/src/elements/code.ts +49 -0
  8. package/src/elements/collapsible.ts +105 -0
  9. package/src/elements/color.ts +32 -0
  10. package/src/elements/container.ts +302 -0
  11. package/src/elements/date.ts +59 -0
  12. package/src/elements/embed-block.ts +327 -0
  13. package/src/elements/embed.ts +166 -0
  14. package/src/elements/expr.ts +102 -0
  15. package/src/elements/footnote.ts +76 -0
  16. package/src/elements/html.ts +79 -0
  17. package/src/elements/iframe.ts +44 -0
  18. package/src/elements/iftags.ts +118 -0
  19. package/src/elements/image.ts +154 -0
  20. package/src/elements/include.ts +43 -0
  21. package/src/elements/index.ts +35 -0
  22. package/src/elements/line-break.ts +22 -0
  23. package/src/elements/link.ts +201 -0
  24. package/src/elements/list.ts +241 -0
  25. package/src/elements/math.ts +177 -0
  26. package/src/elements/module/backlinks.ts +28 -0
  27. package/src/elements/module/categories.ts +27 -0
  28. package/src/elements/module/index.ts +67 -0
  29. package/src/elements/module/join.ts +33 -0
  30. package/src/elements/module/listpages.ts +27 -0
  31. package/src/elements/module/listusers.ts +27 -0
  32. package/src/elements/module/page-tree.ts +27 -0
  33. package/src/elements/module/rate.ts +44 -0
  34. package/src/elements/tab-view.ts +75 -0
  35. package/src/elements/table.ts +101 -0
  36. package/src/elements/text.ts +57 -0
  37. package/src/elements/toc.ts +147 -0
  38. package/src/elements/user.ts +79 -0
  39. package/src/escape.ts +829 -0
  40. package/src/hash.ts +62 -0
  41. package/src/index.ts +26 -0
  42. package/src/libs/highlighter/engine.ts +352 -0
  43. package/src/libs/highlighter/index.ts +70 -0
  44. package/src/libs/highlighter/languages/cpp.ts +345 -0
  45. package/src/libs/highlighter/languages/css.ts +104 -0
  46. package/src/libs/highlighter/languages/diff.ts +154 -0
  47. package/src/libs/highlighter/languages/dtd.ts +99 -0
  48. package/src/libs/highlighter/languages/html.ts +59 -0
  49. package/src/libs/highlighter/languages/java.ts +251 -0
  50. package/src/libs/highlighter/languages/javascript.ts +213 -0
  51. package/src/libs/highlighter/languages/php.ts +433 -0
  52. package/src/libs/highlighter/languages/python.ts +308 -0
  53. package/src/libs/highlighter/languages/ruby.ts +360 -0
  54. package/src/libs/highlighter/languages/sql.ts +125 -0
  55. package/src/libs/highlighter/languages/xml.ts +68 -0
  56. package/src/libs/highlighter/types.ts +44 -0
  57. package/src/render.ts +231 -0
  58. package/src/types.ts +140 -0
@@ -0,0 +1,125 @@
1
+ import type { LanguageDefinition } from "../types";
2
+
3
+ export const sqlLang: LanguageDefinition = {
4
+ language: "sql",
5
+ defClass: "code",
6
+ regs: {
7
+ [-1]: /(`)|(\/\*)|((#|--\s).*)|([a-z_]\w*)|(")|(\()|(')|(((\d+|((\d*\.\d+)|(\d+\.\d*)))[eE][+-]?\d+))|((\d*\.\d+)|(\d+\.\d*))|(\d+l?|\b0l?\b)|(0[xX][\da-f]+l?)/gi,
8
+ 0: null,
9
+ 1: null,
10
+ 2: /(\\.)/gi,
11
+ 3: /(`)|(\/\*)|((#|--\s).*)|([a-z_]\w*)|(")|(\()|(')|(((\d+|((\d*\.\d+)|(\d+\.\d*)))[eE][+-]?\d+))|((\d*\.\d+)|(\d+\.\d*))|(\d+l?|\b0l?\b)|(0[xX][\da-f]+l?)/gi,
12
+ 4: /(\\.)/gi,
13
+ },
14
+ counts: {
15
+ [-1]: [0, 0, 1, 0, 0, 0, 0, 5, 2, 0, 0],
16
+ 0: [],
17
+ 1: [],
18
+ 2: [0],
19
+ 3: [0, 0, 1, 0, 0, 0, 0, 5, 2, 0, 0],
20
+ 4: [0],
21
+ },
22
+ delim: {
23
+ [-1]: ["quotes", "comment", "", "", "quotes", "brackets", "quotes", "", "", "", ""],
24
+ 0: [],
25
+ 1: [],
26
+ 2: [""],
27
+ 3: ["quotes", "comment", "", "", "quotes", "brackets", "quotes", "", "", "", ""],
28
+ 4: [""],
29
+ },
30
+ inner: {
31
+ [-1]: [
32
+ "identifier",
33
+ "comment",
34
+ "comment",
35
+ "identifier",
36
+ "string",
37
+ "code",
38
+ "string",
39
+ "number",
40
+ "number",
41
+ "number",
42
+ "number",
43
+ ],
44
+ 0: [],
45
+ 1: [],
46
+ 2: ["special"],
47
+ 3: [
48
+ "identifier",
49
+ "comment",
50
+ "comment",
51
+ "identifier",
52
+ "string",
53
+ "code",
54
+ "string",
55
+ "number",
56
+ "number",
57
+ "number",
58
+ "number",
59
+ ],
60
+ 4: ["special"],
61
+ },
62
+ end: {
63
+ 0: /`/gi,
64
+ 1: /\*\//gi,
65
+ 2: /"/gi,
66
+ 3: /\)/gi,
67
+ 4: /'/gi,
68
+ },
69
+ states: {
70
+ [-1]: [0, 1, -1, -1, 2, 3, 4, -1, -1, -1, -1],
71
+ 0: [],
72
+ 1: [],
73
+ 2: [-1],
74
+ 3: [0, 1, -1, -1, 2, 3, 4, -1, -1, -1, -1],
75
+ 4: [-1],
76
+ },
77
+ keywords: {
78
+ [-1]: [
79
+ -1,
80
+ -1,
81
+ {},
82
+ {
83
+ reserved:
84
+ /^(absolute|action|add|admin|after|aggregate|alias|all|allocate|alter|and|any|are|array|as|asc|assertion|at|authorization|before|begin|binary|bit|blob|boolean|both|breadth|by|call|cascade|cascaded|case|cast|catalog|char|character|check|class|clob|close|collate|collation|column|commit|completion|connect|connection|constraint|constraints|constructor|continue|corresponding|create|cross|cube|current|current_date|current_path|current_role|current_time|current_timestamp|current_user|cursor|cycle|data|date|day|deallocate|dec|decimal|declare|default|deferrable|deferred|delete|depth|deref|desc|describe|descriptor|destroy|destructor|deterministic|diagnostics|dictionary|disconnect|distinct|domain|double|drop|dynamic|each|else|end|end-exec|equals|escape|every|except|exception|exec|execute|external|false|fetch|first|float|for|foreign|found|free|from|full|function|general|get|global|go|goto|grant|group|grouping|having|host|hour|identity|ignore|immediate|in|indicator|initialize|initially|inner|inout|input|insert|int|integer|intersect|interval|into|is|isolation|iterate|join|key|language|large|last|lateral|leading|left|less|level|like|limit|local|localtime|localtimestamp|locator|map|match|minute|modifies|modify|module|month|names|national|natural|nchar|nclob|new|next|no|none|not|null|numeric|object|of|off|old|on|only|open|operation|option|or|order|ordinality|out|outer|output|pad|parameter|parameters|partial|path|postfix|precision|prefix|preorder|prepare|preserve|primary|prior|privileges|procedure|public|read|reads|real|recursive|ref|references|referencing|relative|restrict|result|return|returns|revoke|right|role|rollback|rollup|routine|row|rows|savepoint|schema|scope|scroll|search|second|section|select|sequence|session|session_user|set|sets|size|smallint|some|space|specific|specifictype|sql|sqlexception|sqlstate|sqlwarning|start|state|statement|static|structure|system_user|table|temporary|terminate|than|then|time|timestamp|timezone_hour|timezone_minute|to|trailing|transaction|translation|treat|trigger|true|under|union|unique|unknown|unnest|update|usage|user|using|value|values|varchar|variable|varying|view|when|whenever|where|with|without|work|write|year|zone)$/i,
85
+ keyword:
86
+ /^(abs|ada|asensitive|assignment|asymmetric|atomic|avg|between|bitvar|bit_length|c|called|cardinality|catalog_name|chain|character_length|character_set_catalog|character_set_name|character_set_schema|char_length|checked|class_origin|coalesce|cobol|collation_catalog|collation_name|collation_schema|column_name|command_function|command_function_code|committed|condition_number|connection_name|constraint_catalog|constraint_name|constraint_schema|contains|convert|count|cursor_name|datetime_interval_code|datetime_interval_precision|defined|definer|dispatch|dynamic_function|dynamic_function_code|existing|exists|extract|final|fortran|g|generated|granted|hierarchy|hold|implementation|infix|insensitive|instance|instantiable|invoker|k|key_member|key_type|length|lower|m|max|message_length|message_octet_length|message_text|method|min|mod|more|mumps|name|nullable|nullif|number|octet_length|options|overlaps|overlay|overriding|parameter_mode|parameter_name|parameter_ordinal_position|parameter_specific_catalog|parameter_specific_name|parameter_specific_schema|pascal|pli|position|repeatable|returned_length|returned_octet_length|returned_sqlstate|routine_catalog|routine_name|routine_schema|row_count|scale|schema_name|security|self|sensitive|serializable|server_name|similar|simple|source|specific_name|style|subclass_origin|sublist|substring|sum|symmetric|system|table_name|transactions_committed|transactions_rolled_back|transaction_active|transform|transforms|translate|trigger_catalog|trigger_name|trigger_schema|trim|type|uncommitted|unnamed|upper|user_defined_type_catalog|user_defined_type_name|user_defined_type_schema)$/i,
87
+ },
88
+ -1,
89
+ -1,
90
+ -1,
91
+ {},
92
+ {},
93
+ {},
94
+ {},
95
+ ],
96
+ 0: [],
97
+ 1: [],
98
+ 2: [],
99
+ 3: [],
100
+ 4: [{}],
101
+ 7: [],
102
+ 8: [],
103
+ 9: [],
104
+ 10: [],
105
+ },
106
+ kwmap: {
107
+ reserved: "reserved",
108
+ keyword: "var",
109
+ },
110
+ parts: {
111
+ 0: [],
112
+ 1: [],
113
+ 2: [null],
114
+ 3: [null, null, null, null, null, null, null, null, null, null, null],
115
+ 4: [null],
116
+ },
117
+ subst: {
118
+ [-1]: [false, false, false, false, false, false, false, false, false, false, false],
119
+ 0: [],
120
+ 1: [],
121
+ 2: [false],
122
+ 3: [false, false, false, false, false, false, false, false, false, false, false],
123
+ 4: [false],
124
+ },
125
+ };
@@ -0,0 +1,68 @@
1
+ import type { LanguageDefinition } from "../types";
2
+
3
+ export const xmlLang: LanguageDefinition = {
4
+ language: "xml",
5
+ defClass: "code",
6
+ regs: {
7
+ [-1]: /(<!\[CDATA\[)|(<!--)|(<[?/]?)|((&|%)[\w\-.]+;)/gi,
8
+ 0: null,
9
+ 1: null,
10
+ 2: /((?<=[</?])[\w\-:]+)|([\w\-:]+)|(")/gi,
11
+ 3: /((&|%)[\w\-.]+;)/gi,
12
+ },
13
+ counts: {
14
+ [-1]: [0, 0, 0, 1],
15
+ 0: [],
16
+ 1: [],
17
+ 2: [0, 0, 0],
18
+ 3: [1],
19
+ },
20
+ delim: {
21
+ [-1]: ["comment", "comment", "brackets", ""],
22
+ 0: [],
23
+ 1: [],
24
+ 2: ["", "", "quotes"],
25
+ 3: [""],
26
+ },
27
+ inner: {
28
+ [-1]: ["comment", "comment", "code", "special"],
29
+ 0: [],
30
+ 1: [],
31
+ 2: ["reserved", "var", "string"],
32
+ 3: ["special"],
33
+ },
34
+ end: {
35
+ 0: /\]\]>/gi,
36
+ 1: /-->/gi,
37
+ 2: /[/?]?>/gi,
38
+ 3: /"/gi,
39
+ },
40
+ states: {
41
+ [-1]: [0, 1, 2, -1],
42
+ 0: [],
43
+ 1: [],
44
+ 2: [-1, -1, 3],
45
+ 3: [-1],
46
+ },
47
+ keywords: {
48
+ [-1]: [-1, -1, -1, {}],
49
+ 0: [],
50
+ 1: [],
51
+ 2: [{}, {}, -1],
52
+ 3: [{}],
53
+ },
54
+ kwmap: {},
55
+ parts: {
56
+ 0: [],
57
+ 1: [],
58
+ 2: [null, null, null],
59
+ 3: [null],
60
+ },
61
+ subst: {
62
+ [-1]: [false, false, false, false],
63
+ 0: [],
64
+ 1: [],
65
+ 2: [false, false, false],
66
+ 3: [false],
67
+ },
68
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ *
3
+ * Type definitions for the Text_Highlighter-compatible syntax highlighting
4
+ * engine. These interfaces mirror the data structures used by the original
5
+ * PEAR Text_Highlighter PHP library, adapted for TypeScript.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ /**
11
+ * Complete language definition for the Text_Highlighter-compatible engine.
12
+ *
13
+ * Each language definition describes a state machine where:
14
+ * - States are identified by numeric IDs (-1 for the root state).
15
+ * - Each state has a combined regex pattern that matches tokens.
16
+ * - Matched tokens may trigger state transitions (e.g., entering a string literal).
17
+ * - Keywords within certain states are highlighted with special CSS classes.
18
+ */
19
+ export interface LanguageDefinition {
20
+ /** Language name */
21
+ language: string;
22
+ /** Default CSS class for unmatched text */
23
+ defClass: string;
24
+ /** Regex patterns per state: state → combined regex (null = no pattern for this state) */
25
+ regs: Record<number, RegExp | null>;
26
+ /** Number of capture groups per pattern per state */
27
+ counts: Record<number, number[]>;
28
+ /** Delimiter classes per state per pattern */
29
+ delim: Record<number, string[]>;
30
+ /** Inner (token) classes per state per pattern */
31
+ inner: Record<number, string[]>;
32
+ /** End patterns per state (regex that ends the state, null = no end pattern) */
33
+ end: Record<number, RegExp | null>;
34
+ /** State transitions: state → pattern_index → next_state (-1 = no transition) */
35
+ states: Record<number, number[]>;
36
+ /** Keywords: state → pattern_index → {group_name: regex} */
37
+ keywords: Record<number, (Record<string, RegExp> | -1)[]>;
38
+ /** Keyword group → CSS class mapping */
39
+ kwmap: Record<string, string>;
40
+ /** Parts: state → pattern_index → {subgroup: class} | null */
41
+ parts: Record<number, (Record<number, string> | null)[]>;
42
+ /** Substitution flags */
43
+ subst: Record<number, boolean[]>;
44
+ }
package/src/render.ts ADDED
@@ -0,0 +1,231 @@
1
+ import type { Element, SyntaxTree } from "@wdprlib/ast";
2
+ import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
3
+ import { RenderContext } from "./context";
4
+ import { escapeStyleContent } from "./escape";
5
+ import type { RenderOptions } from "./types";
6
+ import { renderContainer } from "./elements/container";
7
+ import { renderText, renderRaw, renderEmail } from "./elements/text";
8
+ import { renderLink, renderAnchor, renderAnchorName } from "./elements/link";
9
+ import { renderImage } from "./elements/image";
10
+ import { renderList, renderDefinitionList } from "./elements/list";
11
+ import { renderTable } from "./elements/table";
12
+ import { renderCollapsible } from "./elements/collapsible";
13
+ import { renderCode } from "./elements/code";
14
+ import { renderTabView } from "./elements/tab-view";
15
+ import { renderFootnoteRef, renderFootnoteBlock } from "./elements/footnote";
16
+ import { renderMath, renderMathInline, renderEquationRef } from "./elements/math";
17
+ import { renderModule } from "./elements/module/index";
18
+ import { renderEmbed } from "./elements/embed";
19
+ import { renderEmbedBlock } from "./elements/embed-block";
20
+ import { renderUser } from "./elements/user";
21
+ import { renderBibliographyCite, renderBibliographyBlock } from "./elements/bibliography";
22
+ import { renderTableOfContents } from "./elements/toc";
23
+ import { renderLineBreaks } from "./elements/line-break";
24
+ import { renderClearFloat } from "./elements/clear-float";
25
+ import { renderIframe } from "./elements/iframe";
26
+ import { renderHtmlBlock } from "./elements/html";
27
+ import { renderInclude } from "./elements/include";
28
+ import { renderIfTags } from "./elements/iftags";
29
+ import { renderColor } from "./elements/color";
30
+ import { renderDate } from "./elements/date";
31
+ import { renderExpr, renderIf, renderIfExpr } from "./elements/expr";
32
+
33
+ /**
34
+ * Render a {@link SyntaxTree} to an HTML string.
35
+ *
36
+ * This is the main entry point of `@wdprlib/render`. It walks the AST
37
+ * produced by `@wdprlib/parser`, serialises each element to HTML, and
38
+ * appends any collected `[[module CSS]]` styles at the end (when
39
+ * `WikitextSettings.allowStyleElements` is `true`).
40
+ *
41
+ * @param tree - Parsed AST (from `parse()` or `resolveModules()`)
42
+ * @param options - Rendering configuration
43
+ * @returns Complete HTML string
44
+ *
45
+ * @group Render
46
+ */
47
+ export function renderToHtml(tree: SyntaxTree, options: RenderOptions = {}): string {
48
+ const ctx = new RenderContext(tree, options);
49
+ renderElements(ctx, tree.elements);
50
+
51
+ // Append styles (with tag breakout prevention).
52
+ // Sentinel entries (STYLE_SLOT_PREFIX) mark positions where unresolved
53
+ // iftags styles should be spliced in, preserving source order.
54
+ if (ctx.settings.allowStyleElements && tree.styles?.length) {
55
+ for (const style of tree.styles) {
56
+ if (style.startsWith(STYLE_SLOT_PREFIX)) {
57
+ const slotId = parseInt(style.slice(STYLE_SLOT_PREFIX.length), 10);
58
+ for (const css of ctx.getStyleSlotContents(slotId)) {
59
+ ctx.push(`<style>${escapeStyleContent(css)}</style>`);
60
+ }
61
+ } else {
62
+ ctx.push(`<style>${escapeStyleContent(style)}</style>`);
63
+ }
64
+ }
65
+ }
66
+
67
+ return ctx.getOutput();
68
+ }
69
+
70
+ /**
71
+ * Render a list of sibling AST elements in document order.
72
+ *
73
+ * Used internally by container renderers that need to emit their
74
+ * children. Not exported from the package barrel — call
75
+ * {@link renderToHtml} instead for top-level rendering.
76
+ */
77
+ export function renderElements(ctx: RenderContext, elements: Element[]): void {
78
+ for (const element of elements) {
79
+ renderElement(ctx, element);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Dispatch a single AST element to its type-specific renderer.
85
+ *
86
+ * The switch covers every `ElementName` value defined by
87
+ * `@wdprlib/ast`. Unknown element types are silently ignored.
88
+ */
89
+ export function renderElement(ctx: RenderContext, element: Element): void {
90
+ switch (element.element) {
91
+ case "text":
92
+ ctx.pushEscaped(element.data);
93
+ break;
94
+ case "raw":
95
+ renderRaw(ctx, element.data);
96
+ break;
97
+ case "variable":
98
+ renderText(ctx, element.data);
99
+ break;
100
+ case "email":
101
+ renderEmail(ctx, element.data);
102
+ break;
103
+ case "container":
104
+ renderContainer(ctx, element.data);
105
+ break;
106
+ case "link":
107
+ renderLink(ctx, element.data);
108
+ break;
109
+ case "anchor":
110
+ renderAnchor(ctx, element.data);
111
+ break;
112
+ case "anchor-name":
113
+ renderAnchorName(ctx, element.data);
114
+ break;
115
+ case "image":
116
+ renderImage(ctx, element.data);
117
+ break;
118
+ case "list":
119
+ renderList(ctx, element.data);
120
+ break;
121
+ case "definition-list":
122
+ renderDefinitionList(ctx, element.data);
123
+ break;
124
+ case "table":
125
+ renderTable(ctx, element.data);
126
+ break;
127
+ case "collapsible":
128
+ renderCollapsible(ctx, element.data);
129
+ break;
130
+ case "code":
131
+ renderCode(ctx, element.data);
132
+ break;
133
+ case "tab-view":
134
+ renderTabView(ctx, element.data);
135
+ break;
136
+ case "footnote":
137
+ renderFootnoteRef(ctx, ctx.nextFootnoteIndex() + 1);
138
+ break;
139
+ case "footnote-ref":
140
+ renderFootnoteRef(ctx, element.data);
141
+ break;
142
+ case "footnote-block":
143
+ renderFootnoteBlock(ctx, element.data);
144
+ break;
145
+ case "bibliography-cite":
146
+ renderBibliographyCite(ctx, element.data);
147
+ break;
148
+ case "bibliography-block":
149
+ renderBibliographyBlock(ctx, element.data, renderElements);
150
+ break;
151
+ case "table-of-contents":
152
+ renderTableOfContents(ctx, element.data);
153
+ break;
154
+ case "math":
155
+ renderMath(ctx, element.data);
156
+ break;
157
+ case "math-inline":
158
+ renderMathInline(ctx, element.data);
159
+ break;
160
+ case "module":
161
+ renderModule(ctx, element.data);
162
+ break;
163
+ case "embed":
164
+ renderEmbed(ctx, element.data);
165
+ break;
166
+ case "embed-block":
167
+ renderEmbedBlock(ctx, element.data);
168
+ break;
169
+ case "user":
170
+ renderUser(ctx, element.data);
171
+ break;
172
+ case "date":
173
+ renderDate(ctx, element.data);
174
+ break;
175
+ case "color":
176
+ renderColor(ctx, element.data);
177
+ break;
178
+ case "html":
179
+ renderHtmlBlock(ctx, element.data);
180
+ break;
181
+ case "iframe":
182
+ renderIframe(ctx, element.data);
183
+ break;
184
+ case "include":
185
+ renderInclude(ctx, element.data);
186
+ break;
187
+ case "if-tags":
188
+ renderIfTags(ctx, element.data);
189
+ break;
190
+ case "style":
191
+ // Styles are collected into tree.styles during resolve and rendered
192
+ // at the end of renderToHtml. Style elements remaining in the AST
193
+ // (inside unresolved iftags) are either collected into a style slot
194
+ // (preserving source order) or rendered inline as a fallback.
195
+ if (ctx.renderInlineStyles && ctx.settings.allowStyleElements) {
196
+ if (ctx.hasActiveStyleSlot()) {
197
+ ctx.pushToStyleSlot(element.data);
198
+ } else {
199
+ ctx.push(`<style>${escapeStyleContent(element.data)}</style>`);
200
+ }
201
+ }
202
+ break;
203
+ case "line-break":
204
+ ctx.push("<br />");
205
+ break;
206
+ case "line-breaks":
207
+ renderLineBreaks(ctx, element.data);
208
+ break;
209
+ case "clear-float":
210
+ renderClearFloat(ctx, element.data);
211
+ break;
212
+ case "horizontal-rule":
213
+ ctx.push("<hr />");
214
+ break;
215
+ case "content-separator":
216
+ ctx.push(`<div class="content-separator" style="display: none:"></div>`);
217
+ break;
218
+ case "expr":
219
+ renderExpr(ctx, element.data);
220
+ break;
221
+ case "if":
222
+ renderIf(ctx, element.data);
223
+ break;
224
+ case "ifexpr":
225
+ renderIfExpr(ctx, element.data);
226
+ break;
227
+ case "equation-reference":
228
+ renderEquationRef(ctx, element.data);
229
+ break;
230
+ }
231
+ }
package/src/types.ts ADDED
@@ -0,0 +1,140 @@
1
+ import type { Element, WikitextSettings } from "@wdprlib/ast";
2
+ import type { EmbedAllowlistEntry } from "./elements/embed-block";
3
+
4
+ /**
5
+ * Contextual information about the wiki page being rendered.
6
+ *
7
+ * The renderer uses this to resolve relative links, local file paths,
8
+ * page-existence checks (adding a `"newpage"` CSS class to links
9
+ * targeting non-existent pages), and `[[iftags]]` evaluation.
10
+ *
11
+ * @group Render Options
12
+ */
13
+ export interface PageContext {
14
+ /** Full page name including category prefix (e.g. `"secret:test2"`) */
15
+ pageName: string;
16
+ /** Site slug used to build inter-site URLs (e.g. `"scp-wiki"`) */
17
+ site?: string;
18
+ /** Site domain used for absolute URL generation (e.g. `"scp-wiki.wikidot.com"`) */
19
+ domain?: string;
20
+ /**
21
+ * Returns whether a page exists on the site.
22
+ * When a target page does not exist, the renderer adds `class="newpage"`
23
+ * to the link element — the standard Wikidot convention for red-links.
24
+ */
25
+ pageExists?: (page: string) => boolean;
26
+ /** Page tags used for client-side `[[iftags]]` evaluation during rendering */
27
+ tags?: string[];
28
+ }
29
+
30
+ /**
31
+ * User profile data returned by a user-resolver callback.
32
+ *
33
+ * Passed to the renderer to produce the Wikidot user-info markup
34
+ * (`[[user username]]`). When a field is omitted the corresponding
35
+ * UI element is simply not emitted.
36
+ *
37
+ * @group Render Options
38
+ */
39
+ export interface ResolvedUser {
40
+ /** Display name shown in the rendered output (falls back to the raw username) */
41
+ name?: string;
42
+ /** Profile URL. When omitted the username is rendered as a non-navigable `<span>` */
43
+ url?: string;
44
+ /** Avatar image URL. When omitted no avatar `<img>` is rendered */
45
+ avatarUrl?: string;
46
+ /** Karma-badge image URL shown behind the avatar (Wikidot-specific feature) */
47
+ karmaUrl?: string;
48
+ }
49
+
50
+ /**
51
+ * Async/sync resolver callbacks for content that depends on external data.
52
+ *
53
+ * Unlike the parser's `DataProvider` (which fetches bulk data for
54
+ * module expansion), these resolvers are called per-element during the
55
+ * rendering pass.
56
+ *
57
+ * @group Render Options
58
+ */
59
+ export interface RenderResolvers {
60
+ /**
61
+ * Look up a user profile by username.
62
+ *
63
+ * @param username - The raw username from `[[user username]]`
64
+ * @returns Profile data for rendering, or `null` if the user is unknown.
65
+ * When `null` or when the resolver is omitted, the username is
66
+ * rendered as plain text.
67
+ */
68
+ user?: (username: string) => ResolvedUser | null;
69
+
70
+ /**
71
+ * Build an iframe `src` URL for an `[[html]]` block.
72
+ *
73
+ * @param index - Zero-based index matching `SyntaxTree["html-blocks"]`
74
+ * @returns The URL string. When empty or when the resolver is omitted,
75
+ * the default pattern `/{pageName}/html/{hash}-{nonce}` is used.
76
+ *
77
+ * @security The returned URL is injected directly into the iframe `src`
78
+ * attribute. The caller must validate the scheme to reject `javascript:`,
79
+ * `data:`, and other dangerous protocols.
80
+ */
81
+ htmlBlockUrl?: (index: number) => string;
82
+ }
83
+
84
+ /**
85
+ * Full configuration for `renderToHtml()`.
86
+ *
87
+ * Every field is optional; defaults produce safe, standalone HTML output
88
+ * suitable for a full wiki page.
89
+ *
90
+ * @group Render Options
91
+ */
92
+ export interface RenderOptions {
93
+ /**
94
+ * Base URL for resolving protocol-relative URLs (e.g. `"//example.com/path"`).
95
+ *
96
+ * The scheme of this URL (`http:` or `https:`) is prepended to
97
+ * protocol-relative references. When omitted, HTTPS is assumed.
98
+ *
99
+ * @example "https://scp-wiki.wikidot.com"
100
+ */
101
+ baseUrl?: string;
102
+
103
+ /**
104
+ * Context-dependent feature flags.
105
+ * Defaults to page-mode settings when omitted.
106
+ */
107
+ settings?: WikitextSettings;
108
+
109
+ /** Page context for resolving relative links, local file paths, etc. */
110
+ page?: PageContext;
111
+
112
+ /**
113
+ * Pre-collected footnote element arrays from `SyntaxTree.footnotes`.
114
+ * Passed through so the renderer can emit footnote bodies in the
115
+ * `[[footnoteblock]]` section.
116
+ */
117
+ footnotes?: Element[][];
118
+
119
+ /** Callbacks for resolving users, HTML-block URLs, etc. */
120
+ resolvers?: RenderResolvers;
121
+ /**
122
+ * Sandbox attribute value for htmlBlock iframes.
123
+ * - undefined/null: No sandbox attribute (Wikidot compatible, scripts can run)
124
+ * - string: Use as sandbox attribute value (e.g., "allow-scripts allow-same-origin")
125
+ *
126
+ * Examples:
127
+ * - No sandbox (Wikidot compatible): htmlBlockSandbox: null
128
+ * - Block scripts: htmlBlockSandbox: "allow-same-origin"
129
+ * - Allow scripts: htmlBlockSandbox: "allow-scripts allow-same-origin"
130
+ */
131
+ htmlBlockSandbox?: string | null;
132
+ /**
133
+ * Allowlist for [[embed]] content with host and optional path validation.
134
+ * - undefined: Uses default allowlist (YouTube, Vimeo, etc. with path restrictions)
135
+ * - EmbedAllowlistEntry[]: Custom allowlist with host patterns and optional path prefixes
136
+ * - []: Block all embeds
137
+ * - null: Allow any HTTPS iframe (Wikidot's 'anyiframe' behavior)
138
+ */
139
+ embedAllowlist?: EmbedAllowlistEntry[] | null;
140
+ }