@wdprlib/render 2.1.0 → 3.0.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 (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
package/dist/index.js CHANGED
@@ -1,116 +1,77 @@
1
- // packages/render/src/render.ts
2
- import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
3
-
4
- // packages/render/src/context.ts
1
+ // packages/render/src/context/index.ts
5
2
  import { DEFAULT_SETTINGS } from "@wdprlib/ast";
6
3
 
7
- // packages/render/src/escape.ts
4
+ // packages/render/src/escape/html.ts
8
5
  function escapeHtml(text) {
9
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6
+ let start = 0;
7
+ let escaped = "";
8
+ for (let i = 0;i < text.length; i++) {
9
+ const char = text[i];
10
+ let replacement = null;
11
+ if (char === "&") {
12
+ replacement = "&amp;";
13
+ } else if (char === "<") {
14
+ replacement = "&lt;";
15
+ } else if (char === ">") {
16
+ replacement = "&gt;";
17
+ }
18
+ if (replacement) {
19
+ if (start < i) {
20
+ escaped += text.slice(start, i);
21
+ }
22
+ escaped += replacement;
23
+ start = i + 1;
24
+ }
25
+ }
26
+ if (start === 0) {
27
+ return text;
28
+ }
29
+ return start < text.length ? escaped + text.slice(start) : escaped;
10
30
  }
11
31
  function escapeAttr(value) {
32
+ if (value.indexOf("&") === -1 && value.indexOf("<") === -1 && value.indexOf(">") === -1 && value.indexOf('"') === -1 && value.indexOf("'") === -1) {
33
+ return value;
34
+ }
12
35
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
13
36
  }
14
37
  function escapeStyleContent(css) {
38
+ if (css.indexOf("</") === -1) {
39
+ return css;
40
+ }
15
41
  return css.replace(/<\/style/gi, "<\\/style");
16
42
  }
17
- var SAFE_ATTRIBUTES = new Set([
18
- "accept",
19
- "align",
20
- "alt",
21
- "autocapitalize",
22
- "autoplay",
23
- "background",
24
- "bgcolor",
25
- "border",
26
- "buffered",
27
- "checked",
28
- "cite",
29
- "class",
30
- "cols",
31
- "colspan",
32
- "contenteditable",
33
- "controls",
34
- "coords",
35
- "datetime",
36
- "decoding",
37
- "default",
38
- "dir",
39
- "dirname",
40
- "disabled",
41
- "download",
42
- "draggable",
43
- "for",
44
- "form",
45
- "headers",
46
- "height",
47
- "hidden",
48
- "high",
49
- "href",
50
- "hreflang",
51
- "id",
52
- "inputmode",
53
- "ismap",
54
- "itemprop",
55
- "kind",
56
- "label",
57
- "lang",
58
- "list",
59
- "loop",
60
- "low",
61
- "max",
62
- "maxlength",
63
- "min",
64
- "minlength",
65
- "multiple",
66
- "muted",
67
- "name",
68
- "optimum",
69
- "pattern",
70
- "placeholder",
71
- "poster",
72
- "preload",
73
- "readonly",
74
- "required",
75
- "reversed",
76
- "role",
77
- "rows",
78
- "rowspan",
79
- "scope",
80
- "selected",
81
- "shape",
82
- "size",
83
- "sizes",
84
- "span",
85
- "spellcheck",
86
- "src",
87
- "srclang",
88
- "srcset",
89
- "start",
90
- "step",
91
- "style",
92
- "tabindex",
93
- "target",
94
- "title",
95
- "translate",
96
- "type",
97
- "usemap",
98
- "value",
99
- "width",
100
- "wrap"
101
- ]);
102
- function isSafeAttribute(name) {
103
- const lower = name.toLowerCase();
104
- if (lower.startsWith("on"))
105
- return false;
106
- if (lower.startsWith("aria-") || lower.startsWith("data-"))
107
- return true;
108
- return SAFE_ATTRIBUTES.has(lower);
109
- }
43
+ // packages/render/src/escape/url.ts
110
44
  function isDangerousUrl(value) {
111
- const normalized = value.replace(/[\s\u0000-\u001f\u007f-\u009f]/g, "");
45
+ const normalized = stripControlAndWhitespace(value);
112
46
  return /^(javascript|data|vbscript):/i.test(normalized);
113
47
  }
48
+ var WHITESPACE = /\s/;
49
+ function stripControlAndWhitespace(value) {
50
+ let result = "";
51
+ for (const char of value) {
52
+ const code = char.charCodeAt(0);
53
+ if (WHITESPACE.test(char) || code <= 31 || code >= 127 && code <= 159) {
54
+ continue;
55
+ }
56
+ result += char;
57
+ }
58
+ return result;
59
+ }
60
+ // packages/render/src/escape/css-color-functions.ts
61
+ function isValidCssColorFunction(color) {
62
+ const fnMatch = color.match(/^(rgba?|hsla?)\(([^)]*)\)$/);
63
+ if (!fnMatch) {
64
+ return false;
65
+ }
66
+ const fn = fnMatch[1];
67
+ const args = fnMatch[2].split(",").map((s) => s.trim()).join(",");
68
+ if (fn.startsWith("rgb")) {
69
+ return /^\d{1,3},\d{1,3},\d{1,3}(,(0|1|0?\.\d+))?$/.test(args);
70
+ }
71
+ return /^\d{1,3},\d{1,3}%,\d{1,3}%(,(0|1|0?\.\d+))?$/.test(args);
72
+ }
73
+
74
+ // packages/render/src/escape/css-colors.ts
114
75
  var CSS_NAMED_COLORS = new Set([
115
76
  "aliceblue",
116
77
  "antiquewhite",
@@ -275,36 +236,58 @@ function isValidCssColor(color) {
275
236
  if (/^#[0-9a-f]{3}([0-9a-f])?$/.test(trimmed) || /^#[0-9a-f]{6}([0-9a-f]{2})?$/.test(trimmed)) {
276
237
  return true;
277
238
  }
278
- const fnMatch = trimmed.match(/^(rgba?|hsla?)\(([^)]*)\)$/);
279
- if (fnMatch) {
280
- const fn = fnMatch[1];
281
- const args = fnMatch[2].split(",").map((s) => s.trim()).join(",");
282
- if (fn.startsWith("rgb")) {
283
- if (/^\d{1,3},\d{1,3},\d{1,3}(,(0|1|0?\.\d+))?$/.test(args))
284
- return true;
285
- } else {
286
- if (/^\d{1,3},\d{1,3}%,\d{1,3}%(,(0|1|0?\.\d+))?$/.test(args))
287
- return true;
288
- }
289
- }
239
+ if (isValidCssColorFunction(trimmed))
240
+ return true;
290
241
  return false;
291
242
  }
292
243
  function sanitizeCssColor(color, fallback = "inherit") {
293
244
  return isValidCssColor(color) ? color : fallback;
294
245
  }
246
+ // packages/render/src/escape/css-normalize.ts
295
247
  function normalizeCssValue(value) {
296
248
  let result = value;
297
- result = result.replace(/\/\*[\s\S]*?\*\//g, "");
249
+ result = stripCssComments(result);
298
250
  result = result.replace(/\\(?:\r\n|[\n\r\f])/g, "");
299
251
  result = result.replace(/\\([0-9a-f]{1,6})\s?/gi, (_, hex) => {
300
252
  const code = Number.parseInt(hex, 16);
301
253
  return code > 0 && code <= 1114111 ? String.fromCodePoint(code) : "";
302
254
  });
303
255
  result = result.replace(/\\(.)/g, "$1");
304
- result = result.replace(/[\s\u0000-\u001f\u007f-\u009f]/g, "");
256
+ result = stripControlAndWhitespace2(result);
305
257
  return result.toLowerCase();
306
258
  }
307
- function isUrlAllowed(rawUrl) {
259
+ var WHITESPACE2 = /\s/;
260
+ function stripCssComments(value) {
261
+ let result = "";
262
+ let cursor = 0;
263
+ while (cursor < value.length) {
264
+ const start = value.indexOf("/*", cursor);
265
+ if (start === -1) {
266
+ result += value.slice(cursor);
267
+ break;
268
+ }
269
+ result += value.slice(cursor, start);
270
+ const end = value.indexOf("*/", start + 2);
271
+ if (end === -1) {
272
+ break;
273
+ }
274
+ cursor = end + 2;
275
+ }
276
+ return result;
277
+ }
278
+ function stripControlAndWhitespace2(value) {
279
+ let result = "";
280
+ for (const char of value) {
281
+ const code = char.charCodeAt(0);
282
+ if (WHITESPACE2.test(char) || code <= 31 || code >= 127 && code <= 159) {
283
+ continue;
284
+ }
285
+ result += char;
286
+ }
287
+ return result;
288
+ }
289
+ // packages/render/src/escape/css-urls.ts
290
+ function isCssUrlAllowed(rawUrl) {
308
291
  let url = rawUrl;
309
292
  if (url.length >= 2) {
310
293
  const first = url[0];
@@ -335,7 +318,7 @@ function isUrlAllowed(rawUrl) {
335
318
  }
336
319
  return false;
337
320
  }
338
- function* iterateUrls(normalized) {
321
+ function* iterateCssUrls(normalized) {
339
322
  let searchPos = 0;
340
323
  while (searchPos < normalized.length) {
341
324
  const idx = normalized.indexOf("url(", searchPos);
@@ -366,12 +349,14 @@ function* iterateUrls(normalized) {
366
349
  searchPos = i;
367
350
  }
368
351
  }
352
+
353
+ // packages/render/src/escape/css-danger.ts
369
354
  function isDangerousCssValue(value) {
370
355
  const normalized = normalizeCssValue(value);
371
- for (const { inner, malformed } of iterateUrls(normalized)) {
356
+ for (const { inner, malformed } of iterateCssUrls(normalized)) {
372
357
  if (malformed)
373
358
  return true;
374
- if (!isUrlAllowed(inner))
359
+ if (!isCssUrlAllowed(inner))
375
360
  return true;
376
361
  }
377
362
  if (normalized.includes("expression("))
@@ -384,71 +369,164 @@ function isDangerousCssValue(value) {
384
369
  return true;
385
370
  return false;
386
371
  }
372
+ // packages/render/src/escape/css-style.ts
373
+ function sanitizeStyleValue(style) {
374
+ const endsWithSemicolon = style.trimEnd().endsWith(";");
375
+ if (style.indexOf(";") === -1) {
376
+ return sanitizeSingleDeclaration(style.trim());
377
+ }
378
+ const safe = [];
379
+ for (const rawDecl of splitDeclarations(style)) {
380
+ const decl = sanitizeSingleDeclaration(rawDecl.trim());
381
+ if (decl)
382
+ safe.push(decl);
383
+ }
384
+ if (safe.length === 0)
385
+ return "";
386
+ return endsWithSemicolon ? safe.join(";") + ";" : safe.join(";");
387
+ }
388
+ function sanitizeSingleDeclaration(decl) {
389
+ if (decl === "")
390
+ return "";
391
+ const colonIdx = decl.indexOf(":");
392
+ if (colonIdx === -1)
393
+ return "";
394
+ const property = decl.slice(0, colonIdx).trim();
395
+ const value = decl.slice(colonIdx + 1).trim();
396
+ if (isDangerousCssValue(value))
397
+ return "";
398
+ const normalisedProperty = normalizeCssValue(property);
399
+ if (normalisedProperty.startsWith("-moz-binding"))
400
+ return "";
401
+ if (normalisedProperty === "behavior")
402
+ return "";
403
+ return decl;
404
+ }
387
405
  function splitDeclarations(style) {
388
406
  const out = [];
389
- let buf = "";
407
+ let start = 0;
390
408
  let parenDepth = 0;
391
409
  let quoteChar = null;
392
- for (const ch of style) {
410
+ for (let i = 0;i < style.length; i++) {
411
+ const ch = style[i];
393
412
  if (quoteChar !== null) {
394
- buf += ch;
395
413
  if (ch === quoteChar)
396
414
  quoteChar = null;
397
415
  continue;
398
416
  }
399
417
  if (ch === '"' || ch === "'") {
400
418
  quoteChar = ch;
401
- buf += ch;
402
419
  continue;
403
420
  }
404
421
  if (ch === "(") {
405
422
  parenDepth++;
406
- buf += ch;
407
423
  continue;
408
424
  }
409
425
  if (ch === ")") {
410
426
  if (parenDepth > 0)
411
427
  parenDepth--;
412
- buf += ch;
413
428
  continue;
414
429
  }
415
430
  if (ch === ";" && parenDepth === 0) {
416
- out.push(buf);
417
- buf = "";
431
+ out.push(style.slice(start, i));
432
+ start = i + 1;
418
433
  continue;
419
434
  }
420
- buf += ch;
421
435
  }
422
- if (buf.length > 0)
423
- out.push(buf);
436
+ if (start < style.length)
437
+ out.push(style.slice(start));
424
438
  return out;
425
439
  }
426
- function sanitizeStyleValue(style) {
427
- const endsWithSemicolon = style.trimEnd().endsWith(";");
428
- const declarations = splitDeclarations(style).map((d) => d.trim()).filter(Boolean);
429
- const safe = [];
430
- for (const decl of declarations) {
431
- const colonIdx = decl.indexOf(":");
432
- if (colonIdx === -1)
433
- continue;
434
- const property = decl.slice(0, colonIdx).trim();
435
- const value = decl.slice(colonIdx + 1).trim();
436
- if (isDangerousCssValue(value))
437
- continue;
438
- const normalisedProperty = normalizeCssValue(property);
439
- if (normalisedProperty.startsWith("-moz-binding"))
440
- continue;
441
- if (normalisedProperty === "behavior")
442
- continue;
443
- safe.push(decl);
444
- }
445
- if (safe.length === 0)
446
- return "";
447
- return endsWithSemicolon ? safe.join(";") + ";" : safe.join(";");
448
- }
440
+ // packages/render/src/escape/email.ts
449
441
  function isValidEmail(email) {
450
442
  return /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
451
443
  }
444
+ // packages/render/src/escape/attribute-allowlists.ts
445
+ var SAFE_ATTRIBUTES = new Set([
446
+ "accept",
447
+ "align",
448
+ "alt",
449
+ "autocapitalize",
450
+ "autoplay",
451
+ "background",
452
+ "bgcolor",
453
+ "border",
454
+ "buffered",
455
+ "checked",
456
+ "cite",
457
+ "class",
458
+ "cols",
459
+ "colspan",
460
+ "contenteditable",
461
+ "controls",
462
+ "coords",
463
+ "datetime",
464
+ "decoding",
465
+ "default",
466
+ "dir",
467
+ "dirname",
468
+ "disabled",
469
+ "download",
470
+ "draggable",
471
+ "for",
472
+ "form",
473
+ "headers",
474
+ "height",
475
+ "hidden",
476
+ "high",
477
+ "href",
478
+ "hreflang",
479
+ "id",
480
+ "inputmode",
481
+ "ismap",
482
+ "itemprop",
483
+ "kind",
484
+ "label",
485
+ "lang",
486
+ "list",
487
+ "loop",
488
+ "low",
489
+ "max",
490
+ "maxlength",
491
+ "min",
492
+ "minlength",
493
+ "multiple",
494
+ "muted",
495
+ "name",
496
+ "optimum",
497
+ "pattern",
498
+ "placeholder",
499
+ "poster",
500
+ "preload",
501
+ "readonly",
502
+ "required",
503
+ "reversed",
504
+ "role",
505
+ "rows",
506
+ "rowspan",
507
+ "scope",
508
+ "selected",
509
+ "shape",
510
+ "size",
511
+ "sizes",
512
+ "span",
513
+ "spellcheck",
514
+ "src",
515
+ "srclang",
516
+ "srcset",
517
+ "start",
518
+ "step",
519
+ "style",
520
+ "tabindex",
521
+ "target",
522
+ "title",
523
+ "translate",
524
+ "type",
525
+ "usemap",
526
+ "value",
527
+ "width",
528
+ "wrap"
529
+ ]);
452
530
  var URL_ATTRIBUTES = new Set([
453
531
  "href",
454
532
  "src",
@@ -458,12 +536,22 @@ var URL_ATTRIBUTES = new Set([
458
536
  "poster",
459
537
  "background"
460
538
  ]);
539
+
540
+ // packages/render/src/escape/attributes.ts
541
+ function isSafeAttributeLower(lower) {
542
+ if (lower.startsWith("on"))
543
+ return false;
544
+ if (lower.startsWith("aria-") || lower.startsWith("data-"))
545
+ return true;
546
+ return SAFE_ATTRIBUTES.has(lower);
547
+ }
461
548
  function sanitizeAttributes(attributes) {
462
549
  const result = {};
463
- for (const [key, value] of Object.entries(attributes)) {
464
- if (!isSafeAttribute(key))
465
- continue;
550
+ for (const key in attributes) {
551
+ const value = attributes[key];
466
552
  const lower = key.toLowerCase();
553
+ if (!isSafeAttributeLower(lower))
554
+ continue;
467
555
  if (URL_ATTRIBUTES.has(lower) && isDangerousUrl(value))
468
556
  continue;
469
557
  if (lower === "style") {
@@ -477,1023 +565,734 @@ function sanitizeAttributes(attributes) {
477
565
  }
478
566
  return result;
479
567
  }
480
-
481
- // packages/render/src/context.ts
482
- class RenderContext {
483
- chunks = [];
568
+ // packages/render/src/context/attributes.ts
569
+ function renderAttributeString(attributes) {
570
+ const safe = sanitizeAttributes(attributes);
571
+ let result = "";
572
+ for (const [key, value] of Object.entries(safe)) {
573
+ if (value !== "") {
574
+ result += ` ${key}="${escapeAttr(value)}"`;
575
+ } else {
576
+ result += ` ${key}=""`;
577
+ }
578
+ }
579
+ return result;
580
+ }
581
+
582
+ // packages/render/src/context/bibliography.ts
583
+ class BibliographyIndex {
584
+ elements;
585
+ state = null;
586
+ constructor(elements) {
587
+ this.elements = elements;
588
+ }
589
+ getCitationNumber(label) {
590
+ return this.getState().map.get(label);
591
+ }
592
+ getState() {
593
+ if (this.state === null) {
594
+ this.state = collectBibliographyState(this.elements);
595
+ }
596
+ return this.state;
597
+ }
598
+ }
599
+ function collectBibliographyState(elements) {
600
+ const state = {
601
+ map: new Map
602
+ };
603
+ collectFromElements(elements, state);
604
+ return state;
605
+ }
606
+ function collectFromElements(elements, state) {
607
+ for (const element of elements) {
608
+ if (element.element === "bibliography-block") {
609
+ addBlockEntries(element.data, state);
610
+ }
611
+ collectFromChildren(element, state);
612
+ }
613
+ }
614
+ function addBlockEntries(data, state) {
615
+ for (const entry of data.entries) {
616
+ if (state.map.has(entry.key_string))
617
+ continue;
618
+ state.map.set(entry.key_string, state.map.size + 1);
619
+ }
620
+ }
621
+ function collectFromChildren(element, state) {
622
+ switch (element.element) {
623
+ case "list":
624
+ collectFromList(element.data, state);
625
+ return;
626
+ case "table":
627
+ collectFromTable(element.data, state);
628
+ return;
629
+ case "definition-list":
630
+ for (const item of element.data) {
631
+ collectFromElements(item.key, state);
632
+ collectFromElements(item.value, state);
633
+ }
634
+ return;
635
+ case "tab-view":
636
+ collectFromTabs(element.data, state);
637
+ return;
638
+ default:
639
+ break;
640
+ }
641
+ if (hasElementChildren(element)) {
642
+ collectFromElements(element.data.elements, state);
643
+ }
644
+ }
645
+ function collectFromList(data, state) {
646
+ for (const item of data.items) {
647
+ if (item["item-type"] === "elements") {
648
+ collectFromElements(item.elements, state);
649
+ } else {
650
+ collectFromList(item.data, state);
651
+ }
652
+ }
653
+ }
654
+ function collectFromTable(data, state) {
655
+ for (const row of data.rows) {
656
+ for (const cell of row.cells) {
657
+ collectFromElements(cell.elements, state);
658
+ }
659
+ }
660
+ }
661
+ function collectFromTabs(tabs, state) {
662
+ for (const tab of tabs) {
663
+ collectFromElements(tab.elements, state);
664
+ }
665
+ }
666
+ function hasElementChildren(element) {
667
+ if (!("data" in element))
668
+ return false;
669
+ const data = element.data;
670
+ return data !== null && typeof data === "object" && "elements" in data && Array.isArray(data.elements);
671
+ }
672
+
673
+ // packages/render/src/context/counters.ts
674
+ class RenderCounters {
675
+ tocIndex = 0;
676
+ footnoteIndex = 0;
677
+ equationIndex = 0;
678
+ htmlBlockIndex = 0;
679
+ bibciteCounter = 0;
680
+ tabViewIndex = 0;
681
+ idSuffix;
682
+ constructor(useTrueIds) {
683
+ this.idSuffix = useTrueIds ? null : Math.random().toString(16).slice(2, 8);
684
+ }
685
+ nextTocIndex() {
686
+ return this.tocIndex++;
687
+ }
688
+ nextFootnoteIndex() {
689
+ return this.footnoteIndex++;
690
+ }
691
+ nextEquationIndex() {
692
+ return this.equationIndex++;
693
+ }
694
+ nextHtmlBlockIndex() {
695
+ return this.htmlBlockIndex++;
696
+ }
697
+ nextBibciteCounter() {
698
+ return ++this.bibciteCounter;
699
+ }
700
+ nextTabViewIndex() {
701
+ return this.tabViewIndex++;
702
+ }
703
+ generateId(prefix, index) {
704
+ if (this.idSuffix === null) {
705
+ return `${prefix}${index}`;
706
+ }
707
+ return `${prefix}${index}-${this.idSuffix}`;
708
+ }
709
+ generateFixedId(name) {
710
+ if (this.idSuffix === null) {
711
+ return name;
712
+ }
713
+ return `${name}-${this.idSuffix}`;
714
+ }
715
+ }
716
+
717
+ // packages/render/src/context/output.ts
718
+ class RenderOutputBuffer {
719
+ chunks = [];
720
+ push(html) {
721
+ this.chunks.push(html);
722
+ }
723
+ pushEscaped(text) {
724
+ this.chunks.push(escapeHtml(text));
725
+ }
726
+ getOutput() {
727
+ return this.chunks.join("");
728
+ }
729
+ }
730
+
731
+ // packages/render/src/context/style-slots.ts
732
+ class StyleSlotState {
733
+ activeSlotId = null;
734
+ contents = new Map;
735
+ enter(slotId) {
736
+ this.activeSlotId = slotId;
737
+ if (!this.contents.has(slotId)) {
738
+ this.contents.set(slotId, []);
739
+ }
740
+ }
741
+ exit() {
742
+ this.activeSlotId = null;
743
+ }
744
+ hasActiveSlot() {
745
+ return this.activeSlotId !== null;
746
+ }
747
+ push(css) {
748
+ if (this.activeSlotId !== null) {
749
+ this.contents.get(this.activeSlotId).push(css);
750
+ }
751
+ }
752
+ getContents(slotId) {
753
+ return this.contents.get(slotId) ?? [];
754
+ }
755
+ }
756
+
757
+ // packages/render/src/context/image-urls.ts
758
+ function resolveImageSource(source, settings, page) {
759
+ const pageName = page?.pageName;
760
+ switch (source.type) {
761
+ case "url": {
762
+ const url = source.data;
763
+ if (url.startsWith("/") && !url.startsWith("//")) {
764
+ if (!settings.allowLocalPaths)
765
+ return null;
766
+ return `/local--files${url}`;
767
+ }
768
+ return url;
769
+ }
770
+ case "file1":
771
+ if (!settings.allowLocalPaths)
772
+ return null;
773
+ return pageName ? `/local--files/${pageName}/${source.data.file}` : `/local--files/${source.data.file}`;
774
+ case "file2":
775
+ if (!settings.allowLocalPaths)
776
+ return null;
777
+ return `/local--files/${source.data.page}/${source.data.file}`;
778
+ case "file3":
779
+ if (!settings.allowLocalPaths)
780
+ return null;
781
+ return `/local--files/${source.data.site}/${source.data.page}/${source.data.file}`;
782
+ }
783
+ }
784
+ // packages/render/src/context/page-urls.ts
785
+ function resolvePageLink(location, pageContext) {
786
+ if (typeof location === "string") {
787
+ return location;
788
+ }
789
+ const page = location.page;
790
+ if (page.startsWith("//")) {
791
+ return page.toLowerCase();
792
+ }
793
+ const hashIdx = page.indexOf("#");
794
+ if (hashIdx !== -1) {
795
+ let pagePart = page.slice(0, hashIdx);
796
+ const anchor = page.slice(hashIdx);
797
+ if (pagePart.endsWith("/")) {
798
+ pagePart = pagePart.slice(0, -1);
799
+ }
800
+ return `/${pagePart.toLowerCase()}${anchor.toLowerCase()}`;
801
+ }
802
+ const normalizedPage = normalizePageName(page);
803
+ const safePage = normalizedPage.startsWith("/") ? normalizedPage.slice(1) : normalizedPage;
804
+ if (location.site) {
805
+ const domain = resolveSiteDomain(location.site, pageContext);
806
+ if (domain !== null) {
807
+ return `https://${domain}/${safePage}`;
808
+ }
809
+ return `/${normalizePageName(location.site)}/${safePage}`;
810
+ }
811
+ return `/${safePage}`;
812
+ }
813
+ function resolveSiteDomain(site, pageContext) {
814
+ const configuredDomain = pageContext?.resolveSiteDomain?.(site) ?? pageContext?.siteDomains?.[site] ?? (pageContext?.site === site ? pageContext.domain : undefined);
815
+ if (configuredDomain)
816
+ return normalizeDomain(configuredDomain);
817
+ if (site.includes(".")) {
818
+ return normalizeDomain(site);
819
+ }
820
+ return null;
821
+ }
822
+ function normalizeDomain(domain) {
823
+ let normalized = domain;
824
+ const lower = normalized.toLowerCase();
825
+ if (lower.startsWith("https://")) {
826
+ normalized = normalized.slice("https://".length);
827
+ } else if (lower.startsWith("http://")) {
828
+ normalized = normalized.slice("http://".length);
829
+ }
830
+ let end = normalized.length;
831
+ while (end > 0 && normalized[end - 1] === "/") {
832
+ end--;
833
+ }
834
+ return end === normalized.length ? normalized : normalized.slice(0, end);
835
+ }
836
+ function normalizePageName(page) {
837
+ let normalized = page.toLowerCase();
838
+ if (normalized.indexOf(":") !== -1) {
839
+ normalized = normalized.replace(/:\s+/g, ":");
840
+ }
841
+ if (/\s/.test(normalized)) {
842
+ normalized = normalized.replace(/\s+/g, "-").trim();
843
+ }
844
+ if (!normalized.startsWith("/") && normalized.indexOf("/") !== -1) {
845
+ normalized = normalized.replace(/\//g, "-");
846
+ }
847
+ return normalized;
848
+ }
849
+ // packages/render/src/context/index.ts
850
+ class RenderContext {
851
+ output = new RenderOutputBuffer;
484
852
  renderInlineStyles = false;
485
- _styleSlotId = null;
486
- _styleSlotContents = new Map;
487
- _tocIndex = 0;
488
- _footnoteIndex = 0;
489
- _equationIndex = 0;
490
- _htmlBlockIndex = 0;
491
- _bibciteCounter = 0;
492
- _idSuffix;
853
+ _styleSlots = new StyleSlotState;
854
+ counters;
493
855
  settings;
494
856
  options;
495
857
  footnotes;
496
858
  styles;
497
859
  htmlBlocks;
498
860
  tocElements;
499
- bibliographyMap;
500
- bibliographyEntries;
861
+ bibliography;
501
862
  constructor(tree, options = {}) {
502
863
  this.settings = options.settings ?? DEFAULT_SETTINGS;
503
- this._idSuffix = this.settings.useTrueIds ? null : Math.random().toString(16).slice(2, 8);
864
+ this.counters = new RenderCounters(this.settings.useTrueIds);
504
865
  this.options = options;
505
866
  this.footnotes = options.footnotes ?? tree.footnotes ?? [];
506
867
  this.styles = tree.styles ?? [];
507
868
  this.htmlBlocks = tree["html-blocks"] ?? [];
508
869
  this.tocElements = tree["table-of-contents"] ?? [];
509
- this.bibliographyMap = new Map;
510
- this.bibliographyEntries = [];
511
- this.buildBibliographyMap(tree.elements);
512
- }
513
- buildBibliographyMap(elements) {
514
- for (const el of elements) {
515
- if (el.element === "bibliography-block") {
516
- const data = el.data;
517
- for (const entry of data.entries) {
518
- if (!this.bibliographyMap.has(entry.key_string)) {
519
- const index = this.bibliographyMap.size + 1;
520
- this.bibliographyMap.set(entry.key_string, index);
521
- this.bibliographyEntries.push(entry);
522
- }
523
- }
524
- }
525
- if ("data" in el && el.data && typeof el.data === "object") {
526
- const data = el.data;
527
- if ("elements" in data && Array.isArray(data.elements)) {
528
- this.buildBibliographyMap(data.elements);
529
- }
530
- }
531
- }
870
+ this.bibliography = new BibliographyIndex(tree.elements);
871
+ }
872
+ getBibliographyCitationNumber(label) {
873
+ return this.bibliography.getCitationNumber(label);
532
874
  }
533
875
  push(html) {
534
- this.chunks.push(html);
876
+ this.output.push(html);
535
877
  }
536
878
  pushEscaped(text) {
537
- this.chunks.push(escapeHtml(text));
879
+ this.output.pushEscaped(text);
538
880
  }
539
881
  getOutput() {
540
- return this.chunks.join("");
882
+ return this.output.getOutput();
541
883
  }
542
884
  enterStyleSlot(slotId) {
543
- this._styleSlotId = slotId;
544
- if (!this._styleSlotContents.has(slotId)) {
545
- this._styleSlotContents.set(slotId, []);
546
- }
885
+ this._styleSlots.enter(slotId);
547
886
  }
548
887
  exitStyleSlot() {
549
- this._styleSlotId = null;
888
+ this._styleSlots.exit();
550
889
  }
551
890
  hasActiveStyleSlot() {
552
- return this._styleSlotId !== null;
891
+ return this._styleSlots.hasActiveSlot();
553
892
  }
554
893
  pushToStyleSlot(css) {
555
- if (this._styleSlotId !== null) {
556
- this._styleSlotContents.get(this._styleSlotId).push(css);
557
- }
894
+ this._styleSlots.push(css);
558
895
  }
559
896
  getStyleSlotContents(slotId) {
560
- return this._styleSlotContents.get(slotId) ?? [];
897
+ return this._styleSlots.getContents(slotId);
561
898
  }
562
899
  nextTocIndex() {
563
- return this._tocIndex++;
900
+ return this.counters.nextTocIndex();
564
901
  }
565
902
  nextFootnoteIndex() {
566
- return this._footnoteIndex++;
903
+ return this.counters.nextFootnoteIndex();
567
904
  }
568
905
  nextEquationIndex() {
569
- return this._equationIndex++;
906
+ return this.counters.nextEquationIndex();
570
907
  }
571
908
  nextHtmlBlockIndex() {
572
- return this._htmlBlockIndex++;
909
+ return this.counters.nextHtmlBlockIndex();
573
910
  }
574
911
  nextBibciteCounter() {
575
- return ++this._bibciteCounter;
912
+ return this.counters.nextBibciteCounter();
913
+ }
914
+ nextTabViewIndex() {
915
+ return this.counters.nextTabViewIndex();
576
916
  }
577
917
  generateId(prefix, index) {
578
- if (this._idSuffix === null) {
579
- return `${prefix}${index}`;
580
- }
581
- return `${prefix}${index}-${this._idSuffix}`;
918
+ return this.counters.generateId(prefix, index);
582
919
  }
583
920
  generateFixedId(name) {
584
- if (this._idSuffix === null) {
585
- return name;
586
- }
587
- return `${name}-${this._idSuffix}`;
921
+ return this.counters.generateFixedId(name);
588
922
  }
589
923
  get page() {
590
924
  return this.options.page;
591
925
  }
592
926
  resolveImageSource(source) {
593
- const pageName = this.page?.pageName;
594
- switch (source.type) {
595
- case "url": {
596
- const url = source.data;
597
- if (url.startsWith("/") && !url.startsWith("//")) {
598
- if (!this.settings.allowLocalPaths)
599
- return null;
600
- return `/local--files${url}`;
601
- }
602
- return url;
603
- }
604
- case "file1":
605
- if (!this.settings.allowLocalPaths)
606
- return null;
607
- return pageName ? `/local--files/${pageName}/${source.data.file}` : `/local--files/${source.data.file}`;
608
- case "file2":
609
- if (!this.settings.allowLocalPaths)
610
- return null;
611
- return `/local--files/${source.data.page}/${source.data.file}`;
612
- case "file3":
613
- if (!this.settings.allowLocalPaths)
614
- return null;
615
- return `/local--files/${source.data.site}/${source.data.page}/${source.data.file}`;
616
- }
927
+ return resolveImageSource(source, this.settings, this.page);
617
928
  }
618
929
  resolvePageLink(location) {
619
- if (typeof location === "string") {
620
- return location;
621
- }
622
- const page = location.page;
623
- if (page.startsWith("//")) {
624
- return page.toLowerCase();
625
- }
626
- const hashIdx = page.indexOf("#");
627
- if (hashIdx !== -1) {
628
- let pagePart = page.slice(0, hashIdx);
629
- const anchor = page.slice(hashIdx);
630
- if (pagePart.endsWith("/")) {
631
- pagePart = pagePart.slice(0, -1);
632
- }
633
- return `/${pagePart.toLowerCase()}${anchor.toLowerCase()}`;
634
- }
635
- const normalizedPage = this.normalizePageName(page);
636
- const safePage = normalizedPage.startsWith("/") ? normalizedPage.slice(1) : normalizedPage;
637
- if (location.site) {
638
- return `https://${location.site}.wikidot.com/${safePage}`;
639
- }
640
- return `/${safePage}`;
641
- }
642
- normalizePageName(page) {
643
- let normalized = page.toLowerCase();
644
- normalized = normalized.replace(/:\s+/g, ":");
645
- normalized = normalized.replace(/\s+/g, "-").trim();
646
- if (!normalized.startsWith("/")) {
647
- normalized = normalized.replace(/\//g, "-");
648
- }
649
- return normalized;
930
+ return resolvePageLink(location, this.page);
650
931
  }
651
932
  renderAttributes(attributes) {
652
- const safe = sanitizeAttributes(attributes);
653
- let result = "";
654
- for (const [key, value] of Object.entries(safe)) {
655
- if (value !== "") {
656
- result += ` ${key}="${escapeAttr(value)}"`;
657
- } else {
658
- result += ` ${key}=""`;
659
- }
933
+ return renderAttributeString(attributes);
934
+ }
935
+ }
936
+
937
+ // packages/render/src/render/collected-styles.ts
938
+ import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
939
+
940
+ // packages/render/src/render/style-tag.ts
941
+ function pushStyleTag(ctx, css) {
942
+ ctx.push(`<style>${escapeStyleContent(css)}</style>`);
943
+ }
944
+
945
+ // packages/render/src/render/collected-styles.ts
946
+ function renderCollectedStyles(ctx, styles) {
947
+ if (!ctx.settings.allowStyleElements || !styles?.length)
948
+ return;
949
+ for (const style of styles) {
950
+ if (style.startsWith(STYLE_SLOT_PREFIX)) {
951
+ renderStyleSlot(ctx, style);
952
+ } else {
953
+ pushStyleTag(ctx, style);
660
954
  }
661
- return result;
955
+ }
956
+ }
957
+ function renderStyleSlot(ctx, marker) {
958
+ const slotId = parseInt(marker.slice(STYLE_SLOT_PREFIX.length), 10);
959
+ for (const css of ctx.getStyleSlotContents(slotId)) {
960
+ pushStyleTag(ctx, css);
662
961
  }
663
962
  }
664
963
 
665
- // packages/render/src/elements/container.ts
666
- import { isStringContainerType, isHeaderType, isAlignType } from "@wdprlib/ast";
667
- function renderContainer(ctx, data) {
668
- const { type, attributes, elements } = data;
669
- if (isHeaderType(type)) {
670
- renderHeader(ctx, type.header.level, type.header["has-toc"], attributes, elements);
964
+ // packages/render/src/elements/bibliography/ids.ts
965
+ function generateBibliographyIdSuffix(label, counter) {
966
+ let h = 2166136261;
967
+ const input = label + counter;
968
+ for (let i = 0;i < input.length; i++) {
969
+ h ^= input.charCodeAt(i);
970
+ h = Math.imul(h, 16777619);
971
+ }
972
+ return (h >>> 0).toString(16).slice(0, 6);
973
+ }
974
+
975
+ // packages/render/src/elements/bibliography/cite.ts
976
+ function renderBibliographyCite(ctx, data) {
977
+ const number = ctx.getBibliographyCitationNumber(data.label);
978
+ const counter = ctx.nextBibciteCounter();
979
+ if (number === undefined) {
980
+ ctx.push(escapeHtml(data.label));
671
981
  return;
672
982
  }
673
- if (isAlignType(type)) {
674
- ctx.push(`<div style="text-align: ${type.align};">`);
675
- renderElements(ctx, elements);
676
- ctx.push("</div>");
983
+ const idSuffix = generateBibliographyIdSuffix(data.label, counter);
984
+ const id = ctx.generateId(`bibcite-${number}-`, idSuffix);
985
+ const bibitemId = ctx.generateId("bibitem-", number);
986
+ const onclick = `WIKIDOT.page.utils.scrollToReference('${bibitemId}')`;
987
+ ctx.push(`<a href="javascript:;" class="bibcite" id="${id}" onclick="${escapeAttr(onclick)}">`);
988
+ ctx.push(String(number));
989
+ ctx.push("</a>");
990
+ }
991
+ // packages/render/src/elements/bibliography/block.ts
992
+ function renderBibliographyBlock(ctx, data, renderElements) {
993
+ if (data.hide)
677
994
  return;
995
+ const title = data.title ?? "Bibliography";
996
+ ctx.push(`<div class="bibitems">`);
997
+ ctx.push(`<div class="title">${escapeHtml(title)}</div>`);
998
+ let index = 1;
999
+ for (const entry of data.entries) {
1000
+ const itemId = ctx.generateId("bibitem-", index);
1001
+ ctx.push(`<div class="bibitem" id="${itemId}">`);
1002
+ ctx.push(`${index}. `);
1003
+ renderElements(ctx, entry.value);
1004
+ ctx.push("</div>");
1005
+ index++;
678
1006
  }
679
- if (isStringContainerType(type)) {
680
- renderStringContainer(ctx, type, attributes, elements);
1007
+ ctx.push("</div>");
1008
+ }
1009
+ // packages/render/src/elements/clear-float.ts
1010
+ function renderClearFloat(ctx, direction) {
1011
+ ctx.push(`<div style="clear:${direction}; height: 0px; font-size: 1px"></div>`);
1012
+ }
1013
+
1014
+ // packages/render/src/libs/highlighter/engine/html.ts
1015
+ function escapeHighlightHtml(str) {
1016
+ if (str.indexOf("&") === -1 && str.indexOf("<") === -1 && str.indexOf(">") === -1 && str.indexOf('"') === -1) {
1017
+ return str;
681
1018
  }
1019
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
682
1020
  }
683
- function renderHeader(ctx, level, hasToc, attributes, elements) {
684
- const tag = `h${level}`;
685
- if (hasToc) {
686
- const tocId = ctx.generateId("toc", ctx.nextTocIndex());
687
- ctx.push(`<${tag} id="${tocId}"${renderAttrs(attributes)}>`);
688
- } else {
689
- ctx.push(`<${tag}${renderAttrs(attributes)}>`);
1021
+
1022
+ // packages/render/src/libs/highlighter/engine/render.ts
1023
+ function renderTokens(tokens) {
1024
+ if (tokens.length === 0)
1025
+ return "";
1026
+ let html = "";
1027
+ let lastClass = "";
1028
+ for (const token of tokens) {
1029
+ if (token.content.length === 0)
1030
+ continue;
1031
+ const escaped = escapeHighlightHtml(token.content);
1032
+ if (token.class !== lastClass) {
1033
+ if (lastClass) {
1034
+ html += "</span>";
1035
+ }
1036
+ html += `<span class="hl-${token.class}">`;
1037
+ lastClass = token.class;
1038
+ }
1039
+ html += escaped;
690
1040
  }
691
- ctx.push("<span>");
692
- renderElements(ctx, elements);
693
- ctx.push("</span>");
694
- ctx.push(`</${tag}>`);
1041
+ if (lastClass) {
1042
+ html += "</span>";
1043
+ }
1044
+ return `<div class="hl-main"><pre>${html}</pre></div>`;
695
1045
  }
696
- function renderStringContainer(ctx, type, attributes, elements) {
697
- switch (type) {
698
- case "paragraph":
699
- ctx.push(`<p${renderAttrs(attributes)}>`);
700
- renderElements(ctx, elements);
701
- ctx.push("</p>");
702
- break;
703
- case "bold":
704
- ctx.push(`<strong${renderAttrs(attributes)}>`);
705
- renderElements(ctx, elements);
706
- ctx.push("</strong>");
707
- break;
708
- case "italics":
709
- ctx.push(`<em${renderAttrs(attributes)}>`);
710
- renderElements(ctx, elements);
711
- ctx.push("</em>");
1046
+ // packages/render/src/libs/highlighter/engine/utils.ts
1047
+ function findGroupPosition(str, match, groupIndex, matchStart) {
1048
+ const groupStr = match[groupIndex];
1049
+ const idx = str.indexOf(groupStr, matchStart);
1050
+ return idx >= 0 ? idx : matchStart;
1051
+ }
1052
+ function escapeRegex(str) {
1053
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1054
+ }
1055
+ function matchingBrackets(str) {
1056
+ return str.replace(/[()<>[\]{}]/g, (char) => MATCHING_BRACKETS[char] ?? char);
1057
+ }
1058
+ var MATCHING_BRACKETS = {
1059
+ "(": ")",
1060
+ ")": "(",
1061
+ "<": ">",
1062
+ ">": "<",
1063
+ "[": "]",
1064
+ "]": "[",
1065
+ "{": "}",
1066
+ "}": "{"
1067
+ };
1068
+
1069
+ // packages/render/src/libs/highlighter/engine/end-pattern.ts
1070
+ function buildEndPattern(def, prevState, patternIndex, count, captureIndex, match, endRe) {
1071
+ if (!def.subst[prevState]?.[patternIndex] || !endRe) {
1072
+ return endRe ?? null;
1073
+ }
1074
+ let epSource = endRe.source;
1075
+ for (let k = 0;k <= count; k++) {
1076
+ const subIdx = captureIndex + k;
1077
+ if (subIdx >= match.length || match[subIdx] == null)
712
1078
  break;
713
- case "underline":
714
- ctx.push(`<span style="text-decoration: underline;"${renderAttrs(attributes)}>`);
715
- renderElements(ctx, elements);
716
- ctx.push("</span>");
717
- break;
718
- case "strikethrough":
719
- ctx.push(`<span style="text-decoration: line-through;"${renderAttrs(attributes)}>`);
720
- renderElements(ctx, elements);
721
- ctx.push("</span>");
722
- break;
723
- case "superscript":
724
- ctx.push(`<sup${renderAttrs(attributes)}>`);
725
- renderElements(ctx, elements);
726
- ctx.push("</sup>");
727
- break;
728
- case "subscript":
729
- ctx.push(`<sub${renderAttrs(attributes)}>`);
730
- renderElements(ctx, elements);
731
- ctx.push("</sub>");
732
- break;
733
- case "monospace":
734
- ctx.push(`<tt${renderAttrs(attributes)}>`);
735
- renderElements(ctx, elements);
736
- ctx.push("</tt>");
737
- break;
738
- case "span":
739
- ctx.push(`<span${renderAttrs(attributes)}>`);
740
- renderElements(ctx, elements);
741
- ctx.push("</span>");
742
- break;
743
- case "div":
744
- if (elements.length === 0 && Object.keys(attributes).length === 0) {
745
- break;
746
- }
747
- ctx.push(`<div${renderAttrs(attributes)}>`);
748
- renderElements(ctx, elements);
749
- ctx.push("</div>");
750
- break;
751
- case "blockquote":
752
- ctx.push(`<blockquote${renderAttrs(attributes)}>`);
753
- renderElements(ctx, elements);
754
- ctx.push("</blockquote>");
755
- break;
756
- case "mark":
757
- ctx.push(`<mark${renderAttrs(attributes)}>`);
758
- renderElements(ctx, elements);
759
- ctx.push("</mark>");
760
- break;
761
- case "insertion":
762
- ctx.push(`<ins${renderAttrs(attributes)}>`);
763
- renderElements(ctx, elements);
764
- ctx.push("</ins>");
765
- break;
766
- case "deletion":
767
- ctx.push(`<del${renderAttrs(attributes)}>`);
768
- renderElements(ctx, elements);
769
- ctx.push("</del>");
770
- break;
771
- case "size":
772
- renderSizeContainer(ctx, attributes, elements);
773
- break;
774
- case "hidden":
775
- ctx.push(`<span style="display: none"${renderAttrs(attributes)}>`);
776
- renderElements(ctx, elements);
777
- ctx.push("</span>");
778
- break;
779
- case "invisible":
780
- ctx.push(`<span style="visibility: hidden"${renderAttrs(attributes)}>`);
781
- renderElements(ctx, elements);
782
- ctx.push("</span>");
783
- break;
784
- case "ruby":
785
- ctx.push(`<ruby${renderAttrs(attributes)}>`);
786
- renderElements(ctx, elements);
787
- ctx.push("</ruby>");
788
- break;
789
- case "ruby-text":
790
- ctx.push(`<rt${renderAttrs(attributes)}>`);
791
- renderElements(ctx, elements);
792
- ctx.push("</rt>");
793
- break;
794
- case "heading":
795
- renderElements(ctx, elements);
796
- break;
797
- case "collapsible":
798
- renderElements(ctx, elements);
799
- break;
800
- case "definition-list":
801
- ctx.push("<dl>");
802
- renderElements(ctx, elements);
803
- ctx.push("</dl>");
804
- break;
805
- case "definition-list-item":
806
- renderElements(ctx, elements);
807
- break;
808
- case "definition-list-key":
809
- ctx.push("<dt>");
810
- renderElements(ctx, elements);
811
- ctx.push("</dt>");
812
- break;
813
- case "definition-list-value":
814
- ctx.push("<dd>");
815
- renderElements(ctx, elements);
816
- ctx.push("</dd>");
817
- break;
818
- case "table-row":
819
- ctx.push(`<tr${renderAttrs(attributes)}>`);
820
- renderElements(ctx, elements);
821
- ctx.push("</tr>");
822
- break;
823
- case "table-cell":
824
- ctx.push(`<td${renderAttrs(attributes)}>`);
825
- renderElements(ctx, elements);
826
- ctx.push("</td>");
827
- break;
828
- default:
829
- renderElements(ctx, elements);
1079
+ const quoted = escapeRegex(match[subIdx]);
1080
+ epSource = epSource.replace(`%${k}%`, quoted);
1081
+ epSource = epSource.replace(`%b${k}%`, matchingBrackets(quoted));
830
1082
  }
1083
+ return new RegExp(epSource, endRe.flags);
831
1084
  }
832
- function renderSizeContainer(ctx, attributes, elements) {
833
- const style = attributes.style ?? "";
834
- const existingAttrs = { ...attributes };
835
- if (!style.includes("font-size")) {
836
- ctx.push(`<span${renderAttrs(existingAttrs)}>`);
837
- } else {
838
- ctx.push(`<span${renderAttrs(existingAttrs)}>`);
1085
+
1086
+ // packages/render/src/libs/highlighter/engine/keywords.ts
1087
+ function resolveKeywordClass(def, state, patternIndex, matchStr, fallback) {
1088
+ let kwDef = def.keywords[state]?.[patternIndex];
1089
+ if (!kwDef || kwDef === -1 || typeof kwDef !== "object" || Object.keys(kwDef).length === 0) {
1090
+ kwDef = def.keywords[-1]?.[patternIndex];
1091
+ }
1092
+ if (kwDef && kwDef !== -1 && typeof kwDef === "object") {
1093
+ for (const [group, re] of Object.entries(kwDef)) {
1094
+ if (re.test(matchStr)) {
1095
+ return def.kwmap[group] ?? fallback;
1096
+ }
1097
+ }
839
1098
  }
840
- renderElements(ctx, elements);
841
- ctx.push("</span>");
1099
+ return fallback;
842
1100
  }
843
- function renderAttrs(attributes) {
844
- const safe = sanitizeAttributes(attributes);
845
- let result = "";
846
- for (const [key, value] of Object.entries(safe)) {
847
- if (key.startsWith("_"))
1101
+
1102
+ // packages/render/src/libs/highlighter/engine/parts.ts
1103
+ function buildPartTokens(str, match, partDef, captureIndex, count, groupStart, matchStr, inner) {
1104
+ const parts = [];
1105
+ let partpos = groupStart;
1106
+ for (let j = 1;j <= count; j++) {
1107
+ const subIdx = j + captureIndex;
1108
+ if (subIdx >= match.length || match[subIdx] == null || match[subIdx] === "")
848
1109
  continue;
849
- if (value !== "") {
850
- result += ` ${key}="${escapeAttr(value)}"`;
851
- } else {
852
- result += ` ${key}=""`;
1110
+ const subStr = match[subIdx];
1111
+ const subStart = str.indexOf(subStr, partpos);
1112
+ if (subStart < 0)
1113
+ continue;
1114
+ if (partDef[j]) {
1115
+ if (subStart > partpos) {
1116
+ parts.unshift({ class: inner, content: str.substring(partpos, subStart) });
1117
+ }
1118
+ parts.unshift({ class: partDef[j], content: subStr });
853
1119
  }
1120
+ partpos = subStart + subStr.length;
854
1121
  }
855
- return result;
1122
+ if (partpos < groupStart + matchStr.length) {
1123
+ parts.unshift({
1124
+ class: inner,
1125
+ content: str.substring(partpos, groupStart + matchStr.length)
1126
+ });
1127
+ }
1128
+ return parts;
856
1129
  }
857
1130
 
858
- // packages/render/src/elements/text.ts
859
- function renderText(ctx, data) {
860
- ctx.pushEscaped(data);
861
- }
862
- function renderRaw(ctx, data) {
863
- if (data === "")
864
- return;
865
- ctx.push(`<span style="white-space: pre-wrap;">`);
866
- ctx.push(escapeHtml(data).replace(/ /g, "&#32;"));
867
- ctx.push("</span>");
868
- }
869
- function renderEmail(ctx, email) {
870
- if (!isValidEmail(email)) {
871
- ctx.pushEscaped(email);
872
- return;
873
- }
874
- ctx.push(`<a href="mailto:${escapeAttr(email)}">${escapeHtml(email)}</a>`);
1131
+ // packages/render/src/libs/highlighter/engine/preprocess.ts
1132
+ function preprocessHighlightInput(input) {
1133
+ return input.replace(/\r\n/g, `
1134
+ `).replace(/^$/gm, " ").replace(/\t/g, " ").replace(/\s+$/, "");
875
1135
  }
876
1136
 
877
- // packages/render/src/elements/link.ts
878
- function renderLink(ctx, data) {
879
- let href = ctx.resolvePageLink(data.link);
880
- if (data.extra) {
881
- href += data.extra;
882
- }
883
- const isAnchorJsVoid = data.type === "anchor" && href === "javascript:;";
884
- if (!isAnchorJsVoid && isDangerousUrl(href)) {
885
- href = "#invalid-url";
886
- }
887
- const attrs = [`href="${escapeAttr(href)}"`];
888
- if (data.type === "page" && typeof data.link === "object") {
889
- const page = data.link.page;
890
- const isSpecialPage = page.startsWith("//") || page.includes("#/");
891
- if (!isSpecialPage) {
892
- const hashIdx = page.indexOf("#");
893
- const pageToCheck = hashIdx !== -1 ? page.slice(0, hashIdx) : page;
894
- const pageExists = ctx.page?.pageExists;
895
- const exists = pageExists ? pageExists(pageToCheck) : false;
896
- if (!exists) {
897
- attrs.push(`class="newpage"`);
898
- }
1137
+ // packages/render/src/libs/highlighter/engine/tokenizer.ts
1138
+ function tokenize(def, input) {
1139
+ const str = preprocessHighlightInput(input);
1140
+ const len = str.length;
1141
+ if (len === 0)
1142
+ return [];
1143
+ let state = -1;
1144
+ let pos = 0;
1145
+ let lastinner = def.defClass;
1146
+ let lastdelim = def.defClass;
1147
+ let endpattern = null;
1148
+ const stateStack = [];
1149
+ const tokenStack = [];
1150
+ const result = [];
1151
+ function getToken() {
1152
+ if (tokenStack.length > 0) {
1153
+ return tokenStack.pop();
899
1154
  }
900
- }
901
- if (data.target) {
902
- const targetMap = {
903
- "new-tab": "_blank",
904
- parent: "_parent",
905
- top: "_top",
906
- same: "_self"
907
- };
908
- const targetValue = targetMap[data.target] ?? "_blank";
909
- attrs.push(`target="${targetValue}"`);
910
- if (targetValue === "_blank") {
911
- attrs.push(`rel="noopener noreferrer"`);
1155
+ if (pos >= len) {
1156
+ return null;
912
1157
  }
913
- }
914
- ctx.push(`<a ${attrs.join(" ")}>`);
915
- renderLinkLabel(ctx, data);
916
- ctx.push("</a>");
917
- }
918
- function renderLinkLabel(ctx, data) {
919
- if (data.label === "page") {
920
- if (typeof data.link === "string") {
921
- ctx.pushEscaped(data.link);
922
- } else {
923
- ctx.pushEscaped(data.link.page);
1158
+ const endStateMatch = findEndStateMatch(str, pos, state, endpattern);
1159
+ const token2 = matchStateToken({
1160
+ def,
1161
+ str,
1162
+ pos,
1163
+ state,
1164
+ lastinner,
1165
+ lastdelim,
1166
+ endpattern,
1167
+ stateStack,
1168
+ tokenStack,
1169
+ endStateMatch,
1170
+ setPosition: (nextPos) => {
1171
+ pos = nextPos;
1172
+ },
1173
+ setState: (next) => {
1174
+ state = next.state;
1175
+ lastinner = next.lastinner;
1176
+ lastdelim = next.lastdelim;
1177
+ endpattern = next.endpattern;
1178
+ }
1179
+ });
1180
+ if (token2) {
1181
+ return token2;
924
1182
  }
925
- return;
926
- }
927
- if ("text" in data.label) {
928
- ctx.pushEscaped(data.label.text);
929
- return;
1183
+ if (endStateMatch.endpos > -1) {
1184
+ tokenStack.push({ class: lastdelim, content: endStateMatch.endmatch });
1185
+ if (endStateMatch.endpos > pos) {
1186
+ tokenStack.push({ class: lastinner, content: str.substring(pos, endStateMatch.endpos) });
1187
+ }
1188
+ const prev = stateStack.pop();
1189
+ state = prev.state;
1190
+ lastdelim = prev.lastdelim;
1191
+ lastinner = prev.lastinner;
1192
+ endpattern = prev.endpattern;
1193
+ pos = endStateMatch.endpos + endStateMatch.endmatch.length;
1194
+ if (tokenStack.length > 0) {
1195
+ return tokenStack.pop();
1196
+ }
1197
+ return getToken();
1198
+ }
1199
+ const p = pos;
1200
+ pos = len;
1201
+ return { class: lastinner, content: str.substring(p) };
930
1202
  }
931
- if ("url" in data.label) {
932
- const href = ctx.resolvePageLink(data.link);
933
- ctx.pushEscaped(data.label.url ?? href);
1203
+ let token;
1204
+ while ((token = getToken()) !== null) {
1205
+ result.push(token);
934
1206
  }
1207
+ return result;
935
1208
  }
936
- function renderAnchor(ctx, data) {
937
- const safe = sanitizeAttributes(data.attributes);
938
- const attrs = [];
939
- if (safe.href && isDangerousUrl(safe.href)) {
940
- safe.href = "#invalid-url";
1209
+ function findEndStateMatch(str, pos, state, endpattern) {
1210
+ if (state === -1 || !endpattern) {
1211
+ return { endpos: -1, endmatch: "" };
941
1212
  }
942
- const href = safe.href ?? "";
943
- attrs.push(`href="${escapeAttr(href)}"`);
944
- if (data.target) {
945
- const targetMap = {
946
- "new-tab": "_blank",
947
- parent: "_parent",
948
- top: "_top",
949
- same: "_self"
950
- };
951
- const targetValue = targetMap[data.target] ?? "_blank";
952
- attrs.push(`target="${targetValue}"`);
953
- if (targetValue === "_blank") {
954
- attrs.push(`rel="noopener noreferrer"`);
1213
+ endpattern.lastIndex = pos;
1214
+ const match = endpattern.exec(str);
1215
+ return match ? { endpos: match.index, endmatch: match[0] } : { endpos: -1, endmatch: "" };
1216
+ }
1217
+ function matchStateToken(args) {
1218
+ const reg = args.def.regs[args.state];
1219
+ if (!reg)
1220
+ return null;
1221
+ reg.lastIndex = args.pos;
1222
+ const match = reg.exec(args.str);
1223
+ if (!match)
1224
+ return null;
1225
+ const countsArr = args.def.counts[args.state];
1226
+ let captureIndex = 1;
1227
+ for (let patternIndex = 0;patternIndex < countsArr.length; patternIndex++) {
1228
+ const count = countsArr[patternIndex];
1229
+ if (captureIndex >= match.length)
1230
+ break;
1231
+ if (match[captureIndex] != null && (args.endStateMatch.endpos === -1 || match.index < args.endStateMatch.endpos)) {
1232
+ return emitMatchedPattern(args, match, patternIndex, captureIndex, count);
955
1233
  }
1234
+ captureIndex += count + 1;
956
1235
  }
957
- for (const [key, value] of Object.entries(safe)) {
958
- if (key === "href" || key === "target")
959
- continue;
960
- attrs.push(`${key}="${escapeAttr(value)}"`);
961
- }
962
- ctx.push(`<a ${attrs.join(" ")}>`);
963
- renderElements(ctx, data.elements);
964
- ctx.push("</a>");
965
- }
966
- function renderAnchorName(ctx, name) {
967
- ctx.push(`<a name="${escapeAttr(name)}"></a>`);
1236
+ return null;
968
1237
  }
969
-
970
- // packages/render/src/elements/image.ts
971
- function renderImage(ctx, data) {
972
- let src = ctx.resolveImageSource(data.source);
973
- if (src === null)
974
- return;
975
- if (isDangerousUrl(src)) {
976
- src = "#invalid-url";
977
- }
978
- const safeAttrs = sanitizeAttributes(data.attributes);
979
- const alt = safeAttrs.alt ?? getFilenameFromSource(data.source);
980
- const className = safeAttrs.class ?? "image";
981
- const imgAttrs = [`src="${escapeAttr(src)}"`];
982
- for (const [key, value] of Object.entries(safeAttrs)) {
983
- if (key === "alt" || key === "class" || key === "src" || key === "srcset")
984
- continue;
985
- imgAttrs.push(`${key}="${escapeAttr(value)}"`);
986
- }
987
- imgAttrs.push(`alt="${escapeAttr(alt)}"`);
988
- if (!safeAttrs.class) {
989
- imgAttrs.push(`class="${escapeAttr(className)}"`);
1238
+ function emitMatchedPattern(args, match, patternIndex, captureIndex, count) {
1239
+ const statesArr = args.def.states[args.state];
1240
+ const delimArr = args.def.delim[args.state];
1241
+ const matchStart = match.index;
1242
+ const matchStr = match[captureIndex];
1243
+ const groupStart = findGroupPosition(args.str, match, captureIndex, matchStart);
1244
+ if (statesArr[patternIndex] !== -1) {
1245
+ args.tokenStack.push({ class: delimArr[patternIndex], content: matchStr });
990
1246
  } else {
991
- imgAttrs.push(`class="${escapeAttr(safeAttrs.class)}"`);
1247
+ pushNonTransitionTokens(args, match, patternIndex, captureIndex, count, groupStart, matchStr);
992
1248
  }
993
- const imgTag = `<img ${imgAttrs.join(" ")} />`;
994
- let output = imgTag;
995
- if (data.link) {
996
- let href;
997
- if (typeof data.link === "string") {
998
- if (!data.link.startsWith("/") && !data.link.startsWith("#") && !data.link.startsWith("http://") && !data.link.startsWith("https://")) {
999
- href = `/${data.link}`;
1000
- } else {
1001
- href = data.link;
1002
- }
1003
- } else {
1004
- href = `/${data.link.page}`;
1005
- }
1006
- if (isDangerousUrl(href)) {
1007
- href = "#invalid-url";
1008
- }
1009
- output = `<a href="${escapeAttr(href)}">${imgTag}</a>`;
1249
+ if (groupStart > args.pos) {
1250
+ args.tokenStack.push({
1251
+ class: args.lastinner,
1252
+ content: args.str.substring(args.pos, groupStart)
1253
+ });
1010
1254
  }
1011
- if (data.alignment) {
1012
- const alignClass = getAlignmentClass(data.alignment.align, data.alignment.float);
1013
- ctx.push(`<div class="image-container ${alignClass}">`);
1014
- ctx.push(output);
1015
- ctx.push("</div>");
1016
- } else {
1017
- ctx.push(output);
1255
+ args.setPosition(groupStart + matchStr.length);
1256
+ if (statesArr[patternIndex] !== -1) {
1257
+ enterState(args, match, patternIndex, captureIndex, count);
1018
1258
  }
1259
+ return args.tokenStack.pop();
1019
1260
  }
1020
- function getAlignmentClass(align, isFloat) {
1021
- if (isFloat) {
1022
- switch (align) {
1023
- case "left":
1024
- return "floatleft";
1025
- case "right":
1026
- return "floatright";
1027
- case "center":
1028
- return "floatcenter";
1029
- default:
1030
- return `float${align}`;
1031
- }
1032
- }
1033
- switch (align) {
1034
- case "left":
1035
- return "alignleft";
1036
- case "right":
1037
- return "alignright";
1038
- case "center":
1039
- return "aligncenter";
1040
- default:
1041
- return `align${align}`;
1261
+ function pushNonTransitionTokens(args, match, patternIndex, captureIndex, count, groupStart, matchStr) {
1262
+ let inner = args.def.inner[args.state][patternIndex];
1263
+ const partDef = args.def.parts[args.state]?.[patternIndex];
1264
+ if (partDef) {
1265
+ pushPartTokens(args, match, partDef, captureIndex, count, groupStart, matchStr, inner);
1266
+ return;
1042
1267
  }
1268
+ inner = resolveKeywordClass(args.def, args.state, patternIndex, matchStr, inner);
1269
+ args.tokenStack.push({ class: inner, content: matchStr });
1043
1270
  }
1044
- function getFilenameFromSource(source) {
1045
- switch (source.type) {
1046
- case "url": {
1047
- const parts = source.data.split("/");
1048
- return parts[parts.length - 1] ?? source.data;
1049
- }
1050
- case "file1":
1051
- return source.data.file;
1052
- case "file2":
1053
- return source.data.file;
1054
- case "file3":
1055
- return source.data.file;
1056
- }
1271
+ function pushPartTokens(args, match, partDef, captureIndex, count, groupStart, matchStr, inner) {
1272
+ const parts = [];
1273
+ parts.push(...buildPartTokens(args.str, match, partDef, captureIndex, count, groupStart, matchStr, inner));
1274
+ args.tokenStack.push(...parts);
1057
1275
  }
1058
-
1059
- // packages/render/src/elements/list.ts
1060
- function trimTextElements(elements) {
1061
- if (elements.length === 0)
1062
- return elements;
1063
- let start = 0;
1064
- let end = elements.length;
1065
- while (start < end) {
1066
- const el = elements[start];
1067
- if (el.element === "text" && typeof el.data === "string" && el.data.trim() === "") {
1068
- start++;
1069
- } else {
1070
- break;
1071
- }
1072
- }
1073
- while (end > start) {
1074
- const el = elements[end - 1];
1075
- if (el.element === "text" && typeof el.data === "string" && el.data.trim() === "") {
1076
- end--;
1077
- } else {
1078
- break;
1079
- }
1080
- }
1081
- return elements.slice(start, end);
1082
- }
1083
- function isLiCloseTextParagraph(el) {
1084
- if (el.element !== "container")
1085
- return false;
1086
- const data = el.data;
1087
- if (data.type !== "paragraph")
1088
- return false;
1089
- const texts = data.elements.filter((e) => e.element === "text").map((e) => e.data);
1090
- const combined = texts.join("").trim();
1091
- return combined === "[[/li]]";
1092
- }
1093
- function renderNoMarkerElements(ctx, elements) {
1094
- const trimmed = trimTextElements(elements);
1095
- if (trimmed.length === 0)
1096
- return;
1097
- const paragraphIndices = [];
1098
- for (let i = 0;i < trimmed.length; i++) {
1099
- const el = trimmed[i];
1100
- if (el.element === "container" && el.data.type === "paragraph") {
1101
- paragraphIndices.push(i);
1102
- }
1103
- }
1104
- if (paragraphIndices.length === 0) {
1105
- renderElements(ctx, trimmed);
1106
- return;
1107
- }
1108
- const firstParagraphIdx = paragraphIndices[0];
1109
- const lastParagraphIdx = paragraphIndices[paragraphIndices.length - 1];
1110
- for (let i = 0;i < trimmed.length; i++) {
1111
- const el = trimmed[i];
1112
- if (el.element === "container" && el.data.type === "paragraph") {
1113
- const data = el.data;
1114
- if (i === firstParagraphIdx) {
1115
- renderElements(ctx, data.elements);
1116
- } else if (i === lastParagraphIdx && isLiCloseTextParagraph(el)) {
1117
- renderElements(ctx, data.elements);
1118
- } else {
1119
- ctx.push("<p>");
1120
- renderElements(ctx, data.elements);
1121
- ctx.push("</p>");
1122
- }
1123
- } else {
1124
- renderElement(ctx, el);
1125
- }
1126
- }
1127
- }
1128
- function renderList(ctx, data) {
1129
- const hasContent = data.items.some((item) => {
1130
- if (item["item-type"] === "sub-list")
1131
- return true;
1132
- if (item["item-type"] === "elements") {
1133
- const trimmed = trimTextElements(item.elements);
1134
- return trimmed.length > 0;
1135
- }
1136
- return false;
1276
+ function enterState(args, match, patternIndex, captureIndex, count) {
1277
+ const statesArr = args.def.states[args.state];
1278
+ const delimArr = args.def.delim[args.state];
1279
+ const innerArr = args.def.inner[args.state];
1280
+ args.stateStack.push({
1281
+ state: args.state,
1282
+ lastdelim: args.lastdelim,
1283
+ lastinner: args.lastinner,
1284
+ endpattern: args.endpattern
1137
1285
  });
1138
- if (!hasContent) {
1139
- return;
1140
- }
1141
- const tag = data.type === "numbered" ? "ol" : "ul";
1142
- ctx.push(`<${tag}${renderListAttrs(data.attributes)}>`);
1143
- const items = data.items;
1144
- let i = 0;
1145
- while (i < items.length) {
1146
- const item = items[i];
1147
- if (item["item-type"] === "elements") {
1148
- const hasNoMarker = item.attributes._noMarker === "true";
1149
- const styleAttr = hasNoMarker ? ' style="list-style: none"' : "";
1150
- ctx.push(`<li${renderListAttrs(item.attributes)}${styleAttr}>`);
1151
- if (hasNoMarker) {
1152
- renderNoMarkerElements(ctx, item.elements);
1153
- } else {
1154
- renderElements(ctx, trimTextElements(item.elements));
1155
- }
1156
- while (i + 1 < items.length && items[i + 1]["item-type"] === "sub-list") {
1157
- i++;
1158
- const subItem = items[i];
1159
- renderList(ctx, subItem.data);
1160
- }
1161
- ctx.push("</li>");
1162
- } else {
1163
- const subItem = item;
1164
- ctx.push(`<li style="list-style: none; display: inline">`);
1165
- renderList(ctx, subItem.data);
1166
- ctx.push("</li>");
1167
- }
1168
- i++;
1169
- }
1170
- ctx.push(`</${tag}>`);
1171
- }
1172
- function renderListAttrs(attributes) {
1173
- const safe = sanitizeAttributes(attributes);
1174
- let result = "";
1175
- for (const [key, value] of Object.entries(safe)) {
1176
- if (key.startsWith("_"))
1177
- continue;
1178
- result += ` ${key}="${escapeAttr(value)}"`;
1179
- }
1180
- return result;
1181
- }
1182
- function renderDefinitionList(ctx, items) {
1183
- ctx.push("<dl>");
1184
- for (const item of items) {
1185
- ctx.push("<dt>");
1186
- renderElements(ctx, item.key);
1187
- ctx.push("</dt>");
1188
- ctx.push("<dd>");
1189
- renderElements(ctx, item.value);
1190
- ctx.push("</dd>");
1191
- }
1192
- ctx.push("</dl>");
1193
- }
1194
-
1195
- // packages/render/src/elements/table.ts
1196
- function renderTable(ctx, data) {
1197
- const isPipeTable = data.attributes._source === "pipe";
1198
- const classAttr = isPipeTable ? ' class="wiki-content-table"' : "";
1199
- ctx.push(`<table${classAttr}${renderTableAttrs(data.attributes)}>`);
1200
- for (const row of data.rows) {
1201
- ctx.push(`<tr${renderTableAttrs(row.attributes)}>`);
1202
- for (const cell of row.cells) {
1203
- const tag = cell.header ? "th" : "td";
1204
- const attrs = [];
1205
- const safeCellAttrs = sanitizeAttributes(cell.attributes);
1206
- if (cell["column-span"] > 1) {
1207
- attrs.push(`colspan="${cell["column-span"]}"`);
1208
- }
1209
- if (safeCellAttrs.rowspan) {
1210
- const rowspan = parseInt(safeCellAttrs.rowspan, 10);
1211
- if (rowspan > 1) {
1212
- attrs.push(`rowspan="${rowspan}"`);
1213
- }
1214
- }
1215
- if (cell.align) {
1216
- const existingStyle = safeCellAttrs.style ?? "";
1217
- const alignStyle = `text-align: ${cell.align};`;
1218
- if (existingStyle) {
1219
- attrs.push(`style="${escapeAttr(existingStyle + "; " + alignStyle)}"`);
1220
- } else {
1221
- attrs.push(`style="${alignStyle}"`);
1222
- }
1223
- }
1224
- for (const [key, value] of Object.entries(safeCellAttrs)) {
1225
- if (key === "style" && cell.align)
1226
- continue;
1227
- if (key === "rowspan")
1228
- continue;
1229
- attrs.push(`${key}="${escapeAttr(value)}"`);
1230
- }
1231
- const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1232
- ctx.push(`<${tag}${attrStr}>`);
1233
- renderElements(ctx, cell.elements);
1234
- ctx.push(`</${tag}>`);
1235
- }
1236
- ctx.push("</tr>");
1237
- }
1238
- ctx.push("</table>");
1239
- }
1240
- function renderTableAttrs(attributes) {
1241
- const safe = sanitizeAttributes(attributes);
1242
- let result = "";
1243
- for (const [key, value] of Object.entries(safe)) {
1244
- if (key.startsWith("_"))
1245
- continue;
1246
- result += ` ${key}="${escapeAttr(value)}"`;
1247
- }
1248
- return result;
1249
- }
1250
-
1251
- // packages/render/src/elements/collapsible.ts
1252
- function renderCollapsible(ctx, data) {
1253
- const startOpen = data["start-open"];
1254
- const showTop = data["show-top"];
1255
- const showBottom = data["show-bottom"];
1256
- const showLabel = data["show-text"] ? formatLabelText(data["show-text"]) : formatCollapsibleText("+", "show block");
1257
- const hideLabel = data["hide-text"] ? formatLabelText(data["hide-text"]) : formatCollapsibleText("–", "hide block");
1258
- ctx.push(`<div class="collapsible-block">`);
1259
- const foldedStyle = startOpen ? ` style="display:none"` : "";
1260
- ctx.push(`<div class="collapsible-block-folded"${foldedStyle}>`);
1261
- ctx.push(`<a class="collapsible-block-link" href="javascript:;">${showLabel}</a>`);
1262
- ctx.push("</div>");
1263
- const unfoldedStyle = startOpen ? "" : ` style="display:none"`;
1264
- ctx.push(`<div class="collapsible-block-unfolded"${unfoldedStyle}>`);
1265
- if (showTop || !showTop && !showBottom) {
1266
- ctx.push(`<div class="collapsible-block-unfolded-link">`);
1267
- ctx.push(`<a class="collapsible-block-link" href="javascript:;">${hideLabel}</a>`);
1268
- ctx.push("</div>");
1269
- }
1270
- ctx.push(`<div class="collapsible-block-content">`);
1271
- renderElements(ctx, data.elements);
1272
- ctx.push("</div>");
1273
- if (showBottom) {
1274
- ctx.push(`<div class="collapsible-block-unfolded-link">`);
1275
- ctx.push(`<a class="collapsible-block-link" href="javascript:;">${hideLabel}</a>`);
1276
- ctx.push("</div>");
1277
- }
1278
- ctx.push("</div>");
1279
- ctx.push("</div>");
1280
- }
1281
- function formatCollapsibleText(prefix, text) {
1282
- const encoded = escapeHtml(text).replace(/ /g, "&nbsp;");
1283
- return `${prefix}&nbsp;${encoded}`;
1284
- }
1285
- function formatLabelText(text) {
1286
- return escapeHtml(text).replace(/ /g, "&nbsp;");
1287
- }
1288
-
1289
- // packages/render/src/libs/highlighter/engine.ts
1290
- function tokenize(def, input) {
1291
- let str = input.replace(/\r\n/g, `
1292
- `);
1293
- str = str.replace(/^$/gm, " ");
1294
- str = str.replace(/\t/g, " ");
1295
- str = str.replace(/\s+$/, "");
1296
- const len = str.length;
1297
- if (len === 0)
1298
- return [];
1299
- let state = -1;
1300
- let pos = 0;
1301
- let lastinner = def.defClass;
1302
- let lastdelim = def.defClass;
1303
- let endpattern = null;
1304
- const stateStack = [];
1305
- const tokenStack = [];
1306
- const result = [];
1307
- function getToken() {
1308
- if (tokenStack.length > 0) {
1309
- return tokenStack.pop();
1310
- }
1311
- if (pos >= len) {
1312
- return null;
1313
- }
1314
- let endpos = -1;
1315
- let endmatch = "";
1316
- if (state !== -1 && endpattern) {
1317
- endpattern.lastIndex = pos;
1318
- const em = endpattern.exec(str);
1319
- if (em) {
1320
- endpos = em.index;
1321
- endmatch = em[0];
1322
- }
1323
- }
1324
- const reg = def.regs[state];
1325
- if (reg) {
1326
- reg.lastIndex = pos;
1327
- const m = reg.exec(str);
1328
- if (m) {
1329
- const countsArr = def.counts[state];
1330
- const statesArr = def.states[state];
1331
- const delimArr = def.delim[state];
1332
- const innerArr = def.inner[state];
1333
- let n = 1;
1334
- for (let i = 0;i < countsArr.length; i++) {
1335
- const count = countsArr[i];
1336
- if (n >= m.length)
1337
- break;
1338
- if (m[n] != null && (endpos === -1 || m.index < endpos)) {
1339
- const matchStart = m.index;
1340
- const matchStr = m[n];
1341
- const groupStart = findGroupPosition(str, m, n, matchStart);
1342
- if (statesArr[i] !== -1) {
1343
- tokenStack.push({ class: delimArr[i], content: matchStr });
1344
- } else {
1345
- let inner = innerArr[i];
1346
- const partDef = def.parts[state]?.[i];
1347
- if (partDef) {
1348
- const parts = [];
1349
- let partpos = groupStart;
1350
- for (let j = 1;j <= count; j++) {
1351
- const subIdx = j + n;
1352
- if (subIdx >= m.length || m[subIdx] == null || m[subIdx] === "")
1353
- continue;
1354
- const subStr = m[subIdx];
1355
- const subStart = str.indexOf(subStr, partpos);
1356
- if (subStart < 0)
1357
- continue;
1358
- if (partDef[j]) {
1359
- if (subStart > partpos) {
1360
- parts.unshift({ class: inner, content: str.substring(partpos, subStart) });
1361
- }
1362
- parts.unshift({ class: partDef[j], content: subStr });
1363
- }
1364
- partpos = subStart + subStr.length;
1365
- }
1366
- if (partpos < groupStart + matchStr.length) {
1367
- parts.unshift({
1368
- class: inner,
1369
- content: str.substring(partpos, groupStart + matchStr.length)
1370
- });
1371
- }
1372
- tokenStack.push(...parts);
1373
- } else {
1374
- let kwDef = def.keywords[state]?.[i];
1375
- if (!kwDef || kwDef === -1 || typeof kwDef !== "object" || Object.keys(kwDef).length === 0) {
1376
- kwDef = def.keywords[-1]?.[i];
1377
- }
1378
- if (kwDef && kwDef !== -1 && typeof kwDef === "object") {
1379
- for (const [group, re] of Object.entries(kwDef)) {
1380
- if (re.test(matchStr)) {
1381
- inner = def.kwmap[group] ?? inner;
1382
- break;
1383
- }
1384
- }
1385
- }
1386
- tokenStack.push({ class: inner, content: matchStr });
1387
- }
1388
- }
1389
- if (groupStart > pos) {
1390
- tokenStack.push({ class: lastinner, content: str.substring(pos, groupStart) });
1391
- }
1392
- pos = groupStart + matchStr.length;
1393
- if (statesArr[i] !== -1) {
1394
- stateStack.push({ state, lastdelim, lastinner, endpattern });
1395
- lastinner = innerArr[i];
1396
- lastdelim = delimArr[i];
1397
- const prevState = state;
1398
- state = statesArr[i];
1399
- const endRe = def.end[state];
1400
- if (def.subst[prevState]?.[i] && endRe) {
1401
- let epSource = endRe.source;
1402
- for (let k = 0;k <= count; k++) {
1403
- const subIdx = n + k;
1404
- if (subIdx >= m.length || m[subIdx] == null)
1405
- break;
1406
- const quoted = escapeRegex(m[subIdx]);
1407
- epSource = epSource.replace(`%${k}%`, quoted);
1408
- epSource = epSource.replace(`%b${k}%`, matchingBrackets(quoted));
1409
- }
1410
- endpattern = new RegExp(epSource, endRe.flags);
1411
- } else {
1412
- endpattern = endRe ?? null;
1413
- }
1414
- }
1415
- return tokenStack.pop();
1416
- }
1417
- n += count + 1;
1418
- }
1419
- }
1420
- }
1421
- if (endpos > -1) {
1422
- tokenStack.push({ class: lastdelim, content: endmatch });
1423
- if (endpos > pos) {
1424
- tokenStack.push({ class: lastinner, content: str.substring(pos, endpos) });
1425
- }
1426
- const prev = stateStack.pop();
1427
- state = prev.state;
1428
- lastdelim = prev.lastdelim;
1429
- lastinner = prev.lastinner;
1430
- endpattern = prev.endpattern;
1431
- pos = endpos + endmatch.length;
1432
- if (tokenStack.length > 0) {
1433
- return tokenStack.pop();
1434
- }
1435
- return getToken();
1436
- }
1437
- const p = pos;
1438
- pos = len;
1439
- return { class: lastinner, content: str.substring(p) };
1440
- }
1441
- let token;
1442
- while ((token = getToken()) !== null) {
1443
- result.push(token);
1444
- }
1445
- return result;
1446
- }
1447
- function findGroupPosition(str, m, n, matchStart) {
1448
- const groupStr = m[n];
1449
- const idx = str.indexOf(groupStr, matchStart);
1450
- return idx >= 0 ? idx : matchStart;
1451
- }
1452
- function renderTokens(tokens) {
1453
- if (tokens.length === 0)
1454
- return "";
1455
- let html = "";
1456
- let lastClass = "";
1457
- for (const token of tokens) {
1458
- if (token.content.length === 0)
1459
- continue;
1460
- const escaped = escapeHtml2(token.content);
1461
- if (token.class !== lastClass) {
1462
- if (lastClass) {
1463
- html += "</span>";
1464
- }
1465
- html += `<span class="hl-${token.class}">`;
1466
- lastClass = token.class;
1467
- }
1468
- html += escaped;
1469
- }
1470
- if (lastClass) {
1471
- html += "</span>";
1472
- }
1473
- return `<div class="hl-main"><pre>${html}</pre></div>`;
1474
- }
1475
- function escapeHtml2(str) {
1476
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1477
- }
1478
- function escapeRegex(str) {
1479
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1480
- }
1481
- function matchingBrackets(str) {
1482
- return str.replace(/[()<>[\]{}]/g, (c) => {
1483
- const map = {
1484
- "(": ")",
1485
- ")": "(",
1486
- "<": ">",
1487
- ">": "<",
1488
- "[": "]",
1489
- "]": "[",
1490
- "{": "}",
1491
- "}": "{"
1492
- };
1493
- return map[c] ?? c;
1286
+ const prevState = args.state;
1287
+ const nextState = statesArr[patternIndex];
1288
+ const endRe = args.def.end[nextState];
1289
+ args.setState({
1290
+ state: nextState,
1291
+ lastinner: innerArr[patternIndex],
1292
+ lastdelim: delimArr[patternIndex],
1293
+ endpattern: buildEndPattern(args.def, prevState, patternIndex, count, captureIndex, match, endRe ?? undefined)
1494
1294
  });
1495
1295
  }
1496
-
1497
1296
  // packages/render/src/libs/highlighter/languages/css.ts
1498
1297
  var cssLang = {
1499
1298
  language: "css",
@@ -3985,138 +3784,1216 @@ var xmlLang = {
3985
3784
  2: [false, false, false],
3986
3785
  3: [false]
3987
3786
  }
3988
- };
3787
+ };
3788
+
3789
+ // packages/render/src/libs/highlighter/index.ts
3790
+ var LANGUAGES = {
3791
+ css: cssLang,
3792
+ cpp: cppLang,
3793
+ diff: diffLang,
3794
+ dtd: dtdLang,
3795
+ html: htmlLang,
3796
+ java: javaLang,
3797
+ javascript: javascriptLang,
3798
+ php: phpLang,
3799
+ python: pythonLang,
3800
+ ruby: rubyLang,
3801
+ sql: sqlLang,
3802
+ xml: xmlLang,
3803
+ xhtml: htmlLang
3804
+ };
3805
+ function highlight(code, language) {
3806
+ const def = LANGUAGES[language.toLowerCase()];
3807
+ if (!def)
3808
+ return null;
3809
+ const tokens = tokenize(def, code);
3810
+ return renderTokens(tokens);
3811
+ }
3812
+
3813
+ // packages/render/src/elements/code/contents.ts
3814
+ function renderCodeContents(data) {
3815
+ if (data.language) {
3816
+ const highlighted = highlight(data.contents, data.language);
3817
+ if (highlighted) {
3818
+ return highlighted;
3819
+ }
3820
+ }
3821
+ return renderPlainCode(data.contents);
3822
+ }
3823
+ function renderPlainCode(contents) {
3824
+ return `<pre><code>${escapeHtml(contents)}</code></pre>`;
3825
+ }
3826
+
3827
+ // packages/render/src/elements/code/index.ts
3828
+ function renderCode(ctx, data) {
3829
+ ctx.push(`<div class="code">`);
3830
+ if (data.contents !== "") {
3831
+ ctx.push(renderCodeContents(data));
3832
+ }
3833
+ ctx.push("</div>");
3834
+ }
3835
+
3836
+ // packages/render/src/elements/collapsible/labels.ts
3837
+ function getCollapsibleLabels(data) {
3838
+ return {
3839
+ show: data["show-text"] ? formatLabelText(data["show-text"]) : formatCollapsibleText("+", "show block"),
3840
+ hide: data["hide-text"] ? formatLabelText(data["hide-text"]) : formatCollapsibleText("–", "hide block")
3841
+ };
3842
+ }
3843
+ function formatCollapsibleText(prefix, text) {
3844
+ const encoded = escapeHtml(text).replace(/ /g, "&nbsp;");
3845
+ return `${prefix}&nbsp;${encoded}`;
3846
+ }
3847
+ function formatLabelText(text) {
3848
+ return escapeHtml(text).replace(/ /g, "&nbsp;");
3849
+ }
3850
+
3851
+ // packages/render/src/elements/collapsible/link.ts
3852
+ function renderCollapsibleLink(ctx, label) {
3853
+ ctx.push(`<a class="collapsible-block-link" href="javascript:;">${label}</a>`);
3854
+ }
3855
+ function renderHideLink(ctx, label) {
3856
+ ctx.push(`<div class="collapsible-block-unfolded-link">`);
3857
+ renderCollapsibleLink(ctx, label);
3858
+ ctx.push("</div>");
3859
+ }
3860
+
3861
+ // packages/render/src/elements/collapsible/sections.ts
3862
+ function renderFoldedSection(ctx, startOpen, labels) {
3863
+ const foldedStyle = startOpen ? ` style="display:none"` : "";
3864
+ ctx.push(`<div class="collapsible-block-folded"${foldedStyle}>`);
3865
+ renderCollapsibleLink(ctx, labels.show);
3866
+ ctx.push("</div>");
3867
+ }
3868
+ function renderUnfoldedSection(ctx, data, labels) {
3869
+ const unfoldedStyle = data["start-open"] ? "" : ` style="display:none"`;
3870
+ ctx.push(`<div class="collapsible-block-unfolded"${unfoldedStyle}>`);
3871
+ if (data["show-top"] || !data["show-top"] && !data["show-bottom"]) {
3872
+ renderHideLink(ctx, labels.hide);
3873
+ }
3874
+ ctx.push(`<div class="collapsible-block-content">`);
3875
+ renderElements(ctx, data.elements);
3876
+ ctx.push("</div>");
3877
+ if (data["show-bottom"]) {
3878
+ renderHideLink(ctx, labels.hide);
3879
+ }
3880
+ ctx.push("</div>");
3881
+ }
3882
+
3883
+ // packages/render/src/elements/collapsible/index.ts
3884
+ function renderCollapsible(ctx, data) {
3885
+ const startOpen = data["start-open"];
3886
+ const labels = getCollapsibleLabels(data);
3887
+ ctx.push(`<div class="collapsible-block">`);
3888
+ renderFoldedSection(ctx, startOpen, labels);
3889
+ renderUnfoldedSection(ctx, data, labels);
3890
+ ctx.push("</div>");
3891
+ }
3892
+
3893
+ // packages/render/src/elements/container/index.ts
3894
+ import { isAlignType, isHeaderType, isStringContainerType } from "@wdprlib/ast";
3895
+
3896
+ // packages/render/src/elements/container/attributes.ts
3897
+ function renderContainerAttrs(attributes) {
3898
+ if (!hasAttributes(attributes)) {
3899
+ return "";
3900
+ }
3901
+ const safe = sanitizeAttributes(attributes);
3902
+ let result = "";
3903
+ for (const key in safe) {
3904
+ if (key.startsWith("_")) {
3905
+ continue;
3906
+ }
3907
+ const value = safe[key];
3908
+ result += value !== "" ? ` ${key}="${escapeAttr(value)}"` : ` ${key}=""`;
3909
+ }
3910
+ return result;
3911
+ }
3912
+ function hasAttributes(attributes) {
3913
+ for (const _ in attributes) {
3914
+ return true;
3915
+ }
3916
+ return false;
3917
+ }
3918
+
3919
+ // packages/render/src/elements/container/header.ts
3920
+ function renderHeader(ctx, level, hasToc, attributes, elements) {
3921
+ const tag = `h${level}`;
3922
+ if (hasToc) {
3923
+ const tocId = ctx.generateId("toc", ctx.nextTocIndex());
3924
+ ctx.push(`<${tag} id="${tocId}"${renderContainerAttrs(attributes)}>`);
3925
+ } else {
3926
+ ctx.push(`<${tag}${renderContainerAttrs(attributes)}>`);
3927
+ }
3928
+ ctx.push("<span>");
3929
+ renderElements(ctx, elements);
3930
+ ctx.push("</span>");
3931
+ ctx.push(`</${tag}>`);
3932
+ }
3933
+
3934
+ // packages/render/src/elements/container/string-types.ts
3935
+ var WRAPPED_TAGS = {
3936
+ paragraph: "p",
3937
+ bold: "strong",
3938
+ italics: "em",
3939
+ superscript: "sup",
3940
+ subscript: "sub",
3941
+ monospace: "tt",
3942
+ span: "span",
3943
+ div: "div",
3944
+ blockquote: "blockquote",
3945
+ mark: "mark",
3946
+ insertion: "ins",
3947
+ deletion: "del",
3948
+ size: "span",
3949
+ ruby: "ruby",
3950
+ "ruby-text": "rt",
3951
+ "table-row": "tr",
3952
+ "table-cell": "td"
3953
+ };
3954
+ var STYLED_SPANS = {
3955
+ underline: "text-decoration: underline;",
3956
+ strikethrough: "text-decoration: line-through;",
3957
+ hidden: "display: none",
3958
+ invisible: "visibility: hidden"
3959
+ };
3960
+ var PLAIN_WRAPPED_TAGS = {
3961
+ "definition-list": "dl",
3962
+ "definition-list-key": "dt",
3963
+ "definition-list-value": "dd"
3964
+ };
3965
+ var CONTENTS_ONLY_TYPES = new Set(["heading", "collapsible", "definition-list-item"]);
3966
+ function getStringContainerRendering(type) {
3967
+ const wrappedTag = WRAPPED_TAGS[type];
3968
+ if (wrappedTag) {
3969
+ return { kind: "wrapped", tag: wrappedTag };
3970
+ }
3971
+ const style = STYLED_SPANS[type];
3972
+ if (style) {
3973
+ return { kind: "styled-span", style };
3974
+ }
3975
+ const plainTag = PLAIN_WRAPPED_TAGS[type];
3976
+ if (plainTag) {
3977
+ return { kind: "plain-wrapped", tag: plainTag };
3978
+ }
3979
+ return { kind: "contents" };
3980
+ }
3981
+ function isContentsOnlyStringContainer(type) {
3982
+ return CONTENTS_ONLY_TYPES.has(type);
3983
+ }
3984
+
3985
+ // packages/render/src/elements/container/wrappers.ts
3986
+ function renderWrapped(ctx, tag, attributes, elements) {
3987
+ ctx.push(`<${tag}${renderContainerAttrs(attributes)}>`);
3988
+ renderElements(ctx, elements);
3989
+ ctx.push(`</${tag}>`);
3990
+ }
3991
+ function renderPlainWrapped(ctx, tag, elements) {
3992
+ ctx.push(`<${tag}>`);
3993
+ renderElements(ctx, elements);
3994
+ ctx.push(`</${tag}>`);
3995
+ }
3996
+ function renderStyledSpan(ctx, style, attributes, elements) {
3997
+ ctx.push(`<span style="${style}"${renderContainerAttrs(attributes)}>`);
3998
+ renderElements(ctx, elements);
3999
+ ctx.push("</span>");
4000
+ }
4001
+
4002
+ // packages/render/src/elements/container/string-container.ts
4003
+ function renderStringContainer(ctx, type, attributes, elements) {
4004
+ if (type === "div" && elements.length === 0 && !hasAttributes(attributes)) {
4005
+ return;
4006
+ }
4007
+ if (isContentsOnlyStringContainer(type)) {
4008
+ renderElements(ctx, elements);
4009
+ return;
4010
+ }
4011
+ const rendering = getStringContainerRendering(type);
4012
+ switch (rendering.kind) {
4013
+ case "wrapped":
4014
+ renderWrapped(ctx, rendering.tag, attributes, elements);
4015
+ return;
4016
+ case "styled-span":
4017
+ renderStyledSpan(ctx, rendering.style, attributes, elements);
4018
+ return;
4019
+ case "plain-wrapped":
4020
+ renderPlainWrapped(ctx, rendering.tag, elements);
4021
+ return;
4022
+ case "contents":
4023
+ renderElements(ctx, elements);
4024
+ }
4025
+ }
4026
+
4027
+ // packages/render/src/elements/container/index.ts
4028
+ function renderContainer(ctx, data) {
4029
+ const { type, attributes, elements } = data;
4030
+ if (isHeaderType(type)) {
4031
+ renderHeader(ctx, type.header.level, type.header["has-toc"], attributes, elements);
4032
+ return;
4033
+ }
4034
+ if (isAlignType(type)) {
4035
+ ctx.push(`<div style="text-align: ${type.align};">`);
4036
+ renderElements(ctx, elements);
4037
+ ctx.push("</div>");
4038
+ return;
4039
+ }
4040
+ if (isStringContainerType(type)) {
4041
+ renderStringContainer(ctx, type, attributes, elements);
4042
+ }
4043
+ }
4044
+
4045
+ // packages/render/src/elements/color.ts
4046
+ function renderColor(ctx, data) {
4047
+ const safeColor = sanitizeCssColor(data.color, "inherit");
4048
+ ctx.push(`<span style="color: ${escapeAttr(safeColor)}">`);
4049
+ renderElements(ctx, data.elements);
4050
+ ctx.push("</span>");
4051
+ }
4052
+
4053
+ // packages/render/src/elements/date/format.ts
4054
+ function formatDate(date, format) {
4055
+ return format.replace(/%Y/g, String(date.getFullYear())).replace(/%m/g, String(date.getMonth() + 1).padStart(2, "0")).replace(/%d/g, String(date.getDate()).padStart(2, "0")).replace(/%H/g, String(date.getHours()).padStart(2, "0")).replace(/%M/g, String(date.getMinutes()).padStart(2, "0")).replace(/%S/g, String(date.getSeconds()).padStart(2, "0"));
4056
+ }
4057
+
4058
+ // packages/render/src/elements/date/output.ts
4059
+ function renderDateOutput(formatted, hover) {
4060
+ const escaped = escapeHtml(formatted);
4061
+ return hover ? `<span class="odate">${escaped}</span>` : escaped;
4062
+ }
4063
+
4064
+ // packages/render/src/elements/date/index.ts
4065
+ function renderDate(ctx, data) {
4066
+ const date = new Date(data.value.timestamp * 1000);
4067
+ const formatted = data.format ? formatDate(date, data.format) : date.toLocaleString();
4068
+ ctx.push(renderDateOutput(formatted, data.hover));
4069
+ }
4070
+
4071
+ // packages/render/src/elements/embed/iframe.ts
4072
+ function renderEmbedIframe(ctx, className, src) {
4073
+ ctx.push(`<div class="${className}">`);
4074
+ ctx.push(`<iframe src="${escapeAttr(src)}" frameborder="0" allowfullscreen></iframe>`);
4075
+ ctx.push("</div>");
4076
+ }
4077
+
4078
+ // packages/render/src/elements/embed/validation.ts
4079
+ function isValidVideoId(id) {
4080
+ return /^[a-zA-Z0-9_-]+$/.test(id);
4081
+ }
4082
+ function isValidGithubUsername(username) {
4083
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/.test(username);
4084
+ }
4085
+ function isValidGistHash(hash) {
4086
+ return /^[a-f0-9]+$/.test(hash);
4087
+ }
4088
+ function isValidGitlabSnippetId(id) {
4089
+ return /^[0-9]+$/.test(id);
4090
+ }
4091
+
4092
+ // packages/render/src/elements/embed/providers.ts
4093
+ function renderYoutube(ctx, videoId) {
4094
+ if (!isValidVideoId(videoId)) {
4095
+ ctx.push(`<!-- Invalid YouTube video ID -->`);
4096
+ return;
4097
+ }
4098
+ renderEmbedIframe(ctx, "embed-youtube", `https://www.youtube.com/embed/${videoId}`);
4099
+ }
4100
+ function renderVimeo(ctx, videoId) {
4101
+ if (!isValidVideoId(videoId)) {
4102
+ ctx.push(`<!-- Invalid Vimeo video ID -->`);
4103
+ return;
4104
+ }
4105
+ renderEmbedIframe(ctx, "embed-vimeo", `https://player.vimeo.com/video/${videoId}`);
4106
+ }
4107
+ function renderGithubGist(ctx, username, hash) {
4108
+ if (!isValidGithubUsername(username) || !isValidGistHash(hash)) {
4109
+ ctx.push(`<!-- Invalid GitHub Gist parameters -->`);
4110
+ return;
4111
+ }
4112
+ ctx.push(`<script src="https://gist.github.com/${escapeAttr(username)}/${escapeAttr(hash)}.js"></script>`);
4113
+ }
4114
+ function renderGitlabSnippet(ctx, snippetId) {
4115
+ if (!isValidGitlabSnippetId(snippetId)) {
4116
+ ctx.push(`<!-- Invalid GitLab snippet ID -->`);
4117
+ return;
4118
+ }
4119
+ ctx.push(`<script src="https://gitlab.com/snippets/${escapeAttr(snippetId)}.js"></script>`);
4120
+ }
4121
+
4122
+ // packages/render/src/elements/embed/index.ts
4123
+ function renderEmbed(ctx, data) {
4124
+ switch (data.embed) {
4125
+ case "youtube":
4126
+ renderYoutube(ctx, data.data["video-id"]);
4127
+ break;
4128
+ case "vimeo":
4129
+ renderVimeo(ctx, data.data["video-id"]);
4130
+ break;
4131
+ case "github-gist":
4132
+ renderGithubGist(ctx, data.data.username, data.data.hash);
4133
+ break;
4134
+ case "gitlab-snippet":
4135
+ renderGitlabSnippet(ctx, data.data["snippet-id"]);
4136
+ break;
4137
+ }
4138
+ }
4139
+
4140
+ // packages/render/src/elements/embed-block/allowlist.ts
4141
+ var DEFAULT_EMBED_ALLOWLIST = [
4142
+ { host: "*.youtube.com", pathPrefix: "/embed/" },
4143
+ { host: "*.youtube-nocookie.com", pathPrefix: "/embed/" },
4144
+ { host: "player.vimeo.com", pathPrefix: "/video/" },
4145
+ { host: "*.google.com", pathPrefix: "/maps/embed" },
4146
+ { host: "calendar.google.com", pathPrefix: "/calendar/embed" },
4147
+ { host: "open.spotify.com", pathPrefix: "/embed/" },
4148
+ { host: "w.soundcloud.com", pathPrefix: "/player/" },
4149
+ { host: "codepen.io" }
4150
+ ];
4151
+ function matchesAllowlistEntry(url, entry) {
4152
+ if (!matchesHostPattern(url.hostname, entry.host)) {
4153
+ return false;
4154
+ }
4155
+ if (entry.pathPrefix) {
4156
+ const pathLower = url.pathname.toLowerCase();
4157
+ const prefixLower = entry.pathPrefix.toLowerCase();
4158
+ if (!pathLower.startsWith(prefixLower)) {
4159
+ return false;
4160
+ }
4161
+ if (!prefixLower.endsWith("/")) {
4162
+ const remainder = pathLower.slice(prefixLower.length);
4163
+ if (remainder && !/^[/?#]/.test(remainder)) {
4164
+ return false;
4165
+ }
4166
+ }
4167
+ }
4168
+ return true;
4169
+ }
4170
+ function matchesHostPattern(hostname, pattern) {
4171
+ const lowerHostname = hostname.toLowerCase();
4172
+ const lowerPattern = pattern.toLowerCase();
4173
+ if (lowerPattern.startsWith("*.")) {
4174
+ const base = lowerPattern.slice(2);
4175
+ return lowerHostname === base || lowerHostname.endsWith("." + base);
4176
+ }
4177
+ return lowerHostname === lowerPattern;
4178
+ }
4179
+
4180
+ // packages/render/src/elements/embed-block/sanitize.ts
4181
+ import sanitizeHtml from "sanitize-html";
4182
+
4183
+ // packages/render/src/elements/embed-block/iframe.ts
4184
+ import { parseDocument } from "htmlparser2";
4185
+ function findIframes(html) {
4186
+ const doc = parseDocument(html);
4187
+ const iframes = [];
4188
+ function walk(nodes) {
4189
+ for (const node of nodes) {
4190
+ if (node.type !== "tag") {
4191
+ continue;
4192
+ }
4193
+ if (node.name === "iframe") {
4194
+ iframes.push(node);
4195
+ }
4196
+ if (node.children) {
4197
+ walk(node.children);
4198
+ }
4199
+ }
4200
+ }
4201
+ walk(doc.children);
4202
+ return iframes;
4203
+ }
4204
+ function parseIframeUrl(src, baseUrl) {
4205
+ try {
4206
+ if (src.startsWith("//")) {
4207
+ return new URL(src, baseUrl ?? "https://localhost");
4208
+ }
4209
+ return new URL(src);
4210
+ } catch {
4211
+ return null;
4212
+ }
4213
+ }
4214
+
4215
+ // packages/render/src/elements/embed-block/sanitize-config.ts
4216
+ var SANITIZE_CONFIG = {
4217
+ allowedTags: ["iframe"],
4218
+ allowedAttributes: {
4219
+ iframe: [
4220
+ "class",
4221
+ "src",
4222
+ "style",
4223
+ "allow",
4224
+ "allowfullscreen",
4225
+ "frameborder",
4226
+ "height",
4227
+ "loading",
4228
+ "referrerpolicy",
4229
+ "sandbox",
4230
+ "title",
4231
+ "width"
4232
+ ]
4233
+ },
4234
+ allowedSchemes: ["https", "http"]
4235
+ };
4236
+
4237
+ // packages/render/src/elements/embed-block/boolean-attributes.ts
4238
+ var BOOLEAN_ATTRIBUTES = [
4239
+ "allowfullscreen",
4240
+ "async",
4241
+ "autofocus",
4242
+ "autoplay",
4243
+ "checked",
4244
+ "controls",
4245
+ "default",
4246
+ "defer",
4247
+ "disabled",
4248
+ "formnovalidate",
4249
+ "hidden",
4250
+ "ismap",
4251
+ "loop",
4252
+ "multiple",
4253
+ "muted",
4254
+ "novalidate",
4255
+ "open",
4256
+ "readonly",
4257
+ "required",
4258
+ "reversed",
4259
+ "selected"
4260
+ ];
4261
+ function normalizeBooleanAttributes(html) {
4262
+ let result = html;
4263
+ for (const attr of BOOLEAN_ATTRIBUTES) {
4264
+ const standalonePattern = new RegExp(`\\s${attr}(?=\\s|>|/>)`, "gi");
4265
+ result = result.replace(standalonePattern, ` ${attr}="${attr}"`);
4266
+ const emptyValuePattern = new RegExp(`\\s${attr}=""`, "gi");
4267
+ result = result.replace(emptyValuePattern, ` ${attr}="${attr}"`);
4268
+ }
4269
+ return result;
4270
+ }
4271
+
4272
+ // packages/render/src/elements/embed-block/sanitize.ts
4273
+ function validateAndSanitizeEmbed(content, allowlist, baseUrl) {
4274
+ const sanitized = sanitizeHtml(content.trim(), SANITIZE_CONFIG);
4275
+ if (!sanitized.trim()) {
4276
+ return null;
4277
+ }
4278
+ const iframes = findIframes(sanitized);
4279
+ if (iframes.length !== 1) {
4280
+ return null;
4281
+ }
4282
+ const src = iframes[0].attribs.src?.trim();
4283
+ if (!src) {
4284
+ return null;
4285
+ }
4286
+ const url = parseIframeUrl(src, baseUrl);
4287
+ if (url === null) {
4288
+ return null;
4289
+ }
4290
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
4291
+ return null;
4292
+ }
4293
+ if (allowlist !== null && !allowlist.some((entry) => matchesAllowlistEntry(url, entry))) {
4294
+ return null;
4295
+ }
4296
+ return sanitized;
4297
+ }
4298
+
4299
+ // packages/render/src/elements/embed-block/index.ts
4300
+ function renderEmbedBlock(ctx, data) {
4301
+ const allowlist = ctx.options.embedAllowlist !== undefined ? ctx.options.embedAllowlist : DEFAULT_EMBED_ALLOWLIST;
4302
+ const sanitized = validateAndSanitizeEmbed(data.contents, allowlist, ctx.options.baseUrl);
4303
+ if (sanitized === null) {
4304
+ ctx.push('<div class="error-block">Sorry, no match for the embedded content.</div>');
4305
+ return;
4306
+ }
4307
+ ctx.push(normalizeBooleanAttributes(sanitized));
4308
+ }
4309
+
4310
+ // packages/render/src/elements/expr/index.ts
4311
+ import { isTruthy } from "@wdprlib/ast";
4312
+
4313
+ // packages/render/src/elements/expr/branch.ts
4314
+ function renderBranchElements(ctx, elements) {
4315
+ renderElements(ctx, elements.slice(0, findBranchRenderLength(elements)));
4316
+ }
4317
+ function findBranchRenderLength(elements) {
4318
+ let lastIdx = elements.length - 1;
4319
+ while (lastIdx >= 0 && isWhitespaceText(elements[lastIdx])) {
4320
+ lastIdx--;
4321
+ }
4322
+ return lastIdx + 1;
4323
+ }
4324
+ function isWhitespaceText(element) {
4325
+ return element.element === "text" && typeof element.data === "string" && element.data.trim() === "";
4326
+ }
4327
+
4328
+ // packages/render/src/elements/expr/result.ts
4329
+ import { evaluateExpression, formatExprValue } from "@wdprlib/ast";
4330
+ function evaluateExpressionOutput(expression) {
4331
+ const result = evaluateExpression(expression);
4332
+ if (result.success) {
4333
+ return formatExprValue(result.value);
4334
+ }
4335
+ if (result.error === "empty expression") {
4336
+ return null;
4337
+ }
4338
+ return `run-time error: ${result.error}`;
4339
+ }
4340
+ function evaluateIfExpressionValue(expression) {
4341
+ const result = evaluateExpression(expression);
4342
+ return result.success ? result.value : `run-time error: ${result.error}`;
4343
+ }
4344
+
4345
+ // packages/render/src/elements/expr/index.ts
4346
+ function renderExpr(ctx, data) {
4347
+ const output = evaluateExpressionOutput(data.expression);
4348
+ if (output !== null) {
4349
+ ctx.pushEscaped(output);
4350
+ }
4351
+ }
4352
+ function renderIf(ctx, data) {
4353
+ renderBranchElements(ctx, isTruthy(data.condition) ? data.then : data.else);
4354
+ }
4355
+ function renderIfExpr(ctx, data) {
4356
+ const value = evaluateIfExpressionValue(data.expression);
4357
+ if (typeof value === "string") {
4358
+ ctx.pushEscaped(value);
4359
+ return;
4360
+ }
4361
+ renderBranchElements(ctx, value !== 0 ? data.then : data.else);
4362
+ }
4363
+
4364
+ // packages/render/src/elements/footnote/body.ts
4365
+ function renderFootnoteBody(ctx, index, elements) {
4366
+ const fnId = ctx.generateId("footnote-", index);
4367
+ ctx.push(`<div class="footnote-footer" id="${fnId}">`);
4368
+ ctx.push(`<a href="javascript:;">${index}</a>. `);
4369
+ renderElements(ctx, elements);
4370
+ ctx.push("</div>");
4371
+ }
4372
+
4373
+ // packages/render/src/elements/footnote/ref.ts
4374
+ function renderFootnoteRef(ctx, index) {
4375
+ const id = ctx.generateId("footnoteref-", index);
4376
+ ctx.push(`<sup class="footnoteref">`);
4377
+ ctx.push(`<a id="${id}" href="javascript:;" class="footnoteref">${index}</a>`);
4378
+ ctx.push("</sup>");
4379
+ }
4380
+
4381
+ // packages/render/src/elements/footnote/index.ts
4382
+ function renderFootnoteBlock(ctx, data) {
4383
+ if (ctx.footnotes.length === 0)
4384
+ return;
4385
+ const title = data.title ?? "Footnotes";
4386
+ ctx.push(`<div class="footnotes-footer">`);
4387
+ ctx.push(`<div class="title">${escapeHtml(title)}</div>`);
4388
+ for (let i = 0;i < ctx.footnotes.length; i++) {
4389
+ renderFootnoteBody(ctx, i + 1, ctx.footnotes[i] ?? []);
4390
+ }
4391
+ ctx.push("</div>");
4392
+ }
4393
+
4394
+ // packages/render/src/elements/html/attributes.ts
4395
+ function getHtmlBlockAttributes(ctx, data) {
4396
+ return {
4397
+ sandbox: getSandboxAttribute(ctx),
4398
+ style: getStyleAttribute(data)
4399
+ };
4400
+ }
4401
+ function getSandboxAttribute(ctx) {
4402
+ const sandbox = ctx.options.htmlBlockSandbox;
4403
+ return sandbox ? ` sandbox="${escapeAttr(sandbox)}"` : "";
4404
+ }
4405
+ function getStyleAttribute(data) {
4406
+ return data.style ? ` style="${escapeAttr(sanitizeStyleValue(data.style))}"` : "";
4407
+ }
4408
+
4409
+ // packages/render/src/hash.ts
4410
+ function syncHashSha1(input) {
4411
+ return fnv1aHash(input, 40);
4412
+ }
4413
+ function syncHashMd5(input) {
4414
+ return fnv1aHash(input, 32);
4415
+ }
4416
+ function fnv1aHash(input, hexLen) {
4417
+ let result = "";
4418
+ const rounds = Math.ceil(hexLen / 8);
4419
+ for (let round = 0;round < rounds; round++) {
4420
+ let h = 2166136261 ^ round;
4421
+ for (let i = 0;i < input.length; i++) {
4422
+ h ^= input.charCodeAt(i);
4423
+ h = Math.imul(h, 16777619);
4424
+ }
4425
+ result += (h >>> 0).toString(16).padStart(8, "0");
4426
+ }
4427
+ return result.substring(0, hexLen);
4428
+ }
4429
+
4430
+ // packages/render/src/elements/html/url.ts
4431
+ function resolveHtmlBlockUrl(ctx, contents, index) {
4432
+ const callbackUrl = ctx.options.resolvers?.htmlBlockUrl?.(index);
4433
+ return callbackUrl || generateDefaultHtmlBlockUrl(ctx.page?.pageName ?? "", contents);
4434
+ }
4435
+ function generateDefaultHtmlBlockUrl(pageName, contents) {
4436
+ const hash = syncHashSha1(contents);
4437
+ const nonce = BigInt(contents.length) * 1000000000n + BigInt(hash.charCodeAt(0)) * 100000000n;
4438
+ return pageName ? `/${pageName}/html/${hash}-${nonce}` : `/html/${hash}-${nonce}`;
4439
+ }
4440
+
4441
+ // packages/render/src/elements/html/index.ts
4442
+ function renderHtmlBlock(ctx, data) {
4443
+ if (ctx.settings.allowHtmlBlocks === false) {
4444
+ return;
4445
+ }
4446
+ const index = ctx.nextHtmlBlockIndex();
4447
+ const src = resolveHtmlBlockUrl(ctx, data.contents, index);
4448
+ const attrs = getHtmlBlockAttributes(ctx, data);
4449
+ ctx.push(`<p><iframe src="${escapeAttr(src)}"${attrs.sandbox} allowtransparency="true" frameborder="0" class="html-block-iframe"${attrs.style}></iframe></p>`);
4450
+ }
4451
+
4452
+ // packages/render/src/elements/iframe/attributes.ts
4453
+ var IFRAME_ATTRIBUTES = [
4454
+ "align",
4455
+ "frameborder",
4456
+ "height",
4457
+ "scrolling",
4458
+ "width",
4459
+ "class",
4460
+ "style"
4461
+ ];
4462
+ function getIframeAttributes(data) {
4463
+ const url = isDangerousUrl(data.url) ? "#invalid-url" : data.url;
4464
+ const attrs = [`src="${escapeAttr(url)}"`];
4465
+ for (const attr of IFRAME_ATTRIBUTES) {
4466
+ attrs.push(`${attr}="${escapeAttr(getIframeAttributeValue(data, attr))}"`);
4467
+ }
4468
+ return attrs;
4469
+ }
4470
+ function getIframeAttributeValue(data, attr) {
4471
+ const value = data.attributes[attr] ?? "";
4472
+ return attr === "style" ? sanitizeStyleValue(value) : value;
4473
+ }
4474
+
4475
+ // packages/render/src/elements/iframe/index.ts
4476
+ function renderIframe(ctx, data) {
4477
+ ctx.push(`<p><iframe ${getIframeAttributes(data).join(" ")}></iframe></p>`);
4478
+ }
4479
+
4480
+ // packages/render/src/elements/iftags/tokens.ts
4481
+ function parseIfTagsConditionTokens(condition) {
4482
+ const tokens = {
4483
+ required: [],
4484
+ excluded: [],
4485
+ optional: []
4486
+ };
4487
+ for (const token of condition.split(/\s+/).filter(Boolean)) {
4488
+ if (token.startsWith("+")) {
4489
+ addTag(tokens.required, token.slice(1));
4490
+ } else if (token.startsWith("-")) {
4491
+ addTag(tokens.excluded, token.slice(1));
4492
+ } else {
4493
+ addTag(tokens.optional, token);
4494
+ }
4495
+ }
4496
+ return tokens;
4497
+ }
4498
+ function hasAnyIfTagsConditionToken(tokens) {
4499
+ return tokens.required.length > 0 || tokens.excluded.length > 0 || tokens.optional.length > 0;
4500
+ }
4501
+ function addTag(tags, rawTag) {
4502
+ const tag = rawTag.toLowerCase();
4503
+ if (tag) {
4504
+ tags.push(tag);
4505
+ }
4506
+ }
4507
+
4508
+ // packages/render/src/elements/iftags/condition.ts
4509
+ function evaluateIfTagsCondition(condition, pageTags) {
4510
+ const pageTagSet = new Set(pageTags.map((tag) => tag.toLowerCase()));
4511
+ const tokens = parseIfTagsConditionTokens(condition);
4512
+ if (!hasAnyIfTagsConditionToken(tokens)) {
4513
+ return false;
4514
+ }
4515
+ return hasRequiredTags(tokens.required, pageTagSet) && hasNoExcludedTags(tokens.excluded, pageTagSet) && hasOptionalTagIfNeeded(tokens.optional, pageTagSet);
4516
+ }
4517
+ function hasRequiredTags(required, pageTags) {
4518
+ return required.every((tag) => pageTags.has(tag));
4519
+ }
4520
+ function hasNoExcludedTags(excluded, pageTags) {
4521
+ return excluded.every((tag) => !pageTags.has(tag));
4522
+ }
4523
+ function hasOptionalTagIfNeeded(optional, pageTags) {
4524
+ return optional.length === 0 || optional.some((tag) => pageTags.has(tag));
4525
+ }
4526
+
4527
+ // packages/render/src/elements/iftags/style-slot.ts
4528
+ function withIfTagsStyleSlot(ctx, data, render) {
4529
+ const slotId = data._styleSlot;
4530
+ if (slotId !== undefined) {
4531
+ ctx.enterStyleSlot(slotId);
4532
+ }
4533
+ render();
4534
+ if (slotId !== undefined) {
4535
+ ctx.exitStyleSlot();
4536
+ }
4537
+ }
4538
+
4539
+ // packages/render/src/elements/iftags/index.ts
4540
+ function renderIfTags(ctx, data) {
4541
+ const pageTags = ctx.page?.tags ?? [];
4542
+ if (!evaluateIfTagsCondition(data.condition, pageTags)) {
4543
+ return;
4544
+ }
4545
+ const prev = ctx.renderInlineStyles;
4546
+ ctx.renderInlineStyles = true;
4547
+ withIfTagsStyleSlot(ctx, data, () => {
4548
+ renderElements(ctx, data.elements);
4549
+ });
4550
+ ctx.renderInlineStyles = prev;
4551
+ }
4552
+
4553
+ // packages/render/src/elements/image/source.ts
4554
+ function getFilenameFromSource(source) {
4555
+ switch (source.type) {
4556
+ case "url": {
4557
+ const slashIndex = source.data.lastIndexOf("/");
4558
+ return slashIndex === -1 ? source.data : source.data.slice(slashIndex + 1);
4559
+ }
4560
+ case "file1":
4561
+ return source.data.file;
4562
+ case "file2":
4563
+ return source.data.file;
4564
+ case "file3":
4565
+ return source.data.file;
4566
+ }
4567
+ }
4568
+
4569
+ // packages/render/src/elements/image/img-attributes.ts
4570
+ function getImageAttributes(src, source, attributes) {
4571
+ const safeAttrs = sanitizeAttributes(attributes);
4572
+ const imgAttrs = [`src="${escapeAttr(src)}"`];
4573
+ appendPassedImageAttributes(imgAttrs, safeAttrs);
4574
+ imgAttrs.push(`alt="${escapeAttr(safeAttrs.alt ?? getFilenameFromSource(source))}"`);
4575
+ imgAttrs.push(`class="${escapeAttr(safeAttrs.class ?? "image")}"`);
4576
+ return imgAttrs;
4577
+ }
4578
+ function appendPassedImageAttributes(imgAttrs, safeAttrs) {
4579
+ for (const key in safeAttrs) {
4580
+ if (key === "alt" || key === "class" || key === "src" || key === "srcset")
4581
+ continue;
4582
+ const value = safeAttrs[key];
4583
+ imgAttrs.push(`${key}="${escapeAttr(value)}"`);
4584
+ }
4585
+ }
4586
+
4587
+ // packages/render/src/elements/image/attributes.ts
4588
+ function buildImageTag(src, source, attributes) {
4589
+ return `<img ${getImageAttributes(src, source, attributes).join(" ")} />`;
4590
+ }
4591
+
4592
+ // packages/render/src/elements/image/alignment.ts
4593
+ function pushAlignedImage(ctx, output, alignment) {
4594
+ if (!alignment) {
4595
+ ctx.push(output);
4596
+ return;
4597
+ }
4598
+ const alignClass = getAlignmentClass(alignment.align, alignment.float);
4599
+ ctx.push(`<div class="image-container ${alignClass}">`);
4600
+ ctx.push(output);
4601
+ ctx.push("</div>");
4602
+ }
4603
+ function getAlignmentClass(align, isFloat) {
4604
+ if (isFloat) {
4605
+ switch (align) {
4606
+ case "left":
4607
+ return "floatleft";
4608
+ case "right":
4609
+ return "floatright";
4610
+ case "center":
4611
+ return "floatcenter";
4612
+ default:
4613
+ return `float${align}`;
4614
+ }
4615
+ }
4616
+ switch (align) {
4617
+ case "left":
4618
+ return "alignleft";
4619
+ case "right":
4620
+ return "alignright";
4621
+ case "center":
4622
+ return "aligncenter";
4623
+ default:
4624
+ return `align${align}`;
4625
+ }
4626
+ }
4627
+
4628
+ // packages/render/src/elements/image/link-href.ts
4629
+ function resolveSafeImageLinkHref(link) {
4630
+ const href = resolveImageLinkHref(link);
4631
+ return isDangerousUrl(href) ? "#invalid-url" : href;
4632
+ }
4633
+ function resolveImageLinkHref(link) {
4634
+ if (typeof link !== "string") {
4635
+ return `/${link.page}`;
4636
+ }
4637
+ if (!link.startsWith("/") && !link.startsWith("#") && !link.startsWith("http://") && !link.startsWith("https://")) {
4638
+ return `/${link}`;
4639
+ }
4640
+ return link;
4641
+ }
4642
+
4643
+ // packages/render/src/elements/image/link.ts
4644
+ function wrapImageLink(imageTag, link) {
4645
+ if (!link) {
4646
+ return imageTag;
4647
+ }
4648
+ const href = resolveSafeImageLinkHref(link);
4649
+ return `<a href="${escapeAttr(href)}">${imageTag}</a>`;
4650
+ }
4651
+
4652
+ // packages/render/src/elements/image/index.ts
4653
+ function renderImage(ctx, data) {
4654
+ let src = ctx.resolveImageSource(data.source);
4655
+ if (src === null)
4656
+ return;
4657
+ if (isDangerousUrl(src)) {
4658
+ src = "#invalid-url";
4659
+ }
4660
+ const imageTag = buildImageTag(src, data.source, data.attributes);
4661
+ const output = wrapImageLink(imageTag, data.link);
4662
+ pushAlignedImage(ctx, output, data.alignment);
4663
+ }
4664
+
4665
+ // packages/render/src/elements/include/missing.ts
4666
+ function renderMissingInclude(ctx, page) {
4667
+ const pageName = page.toLowerCase();
4668
+ const safePath = encodeIncludeEditPath(pageName);
4669
+ ctx.push(`<div class="error-block"><p>Included page "${escapeHtml(pageName)}" does not exist (<a href="/${escapeAttr(safePath)}/edit/true">create it now</a>)</p></div>`);
4670
+ }
4671
+ function encodeIncludeEditPath(pageName) {
4672
+ const encodedPageName = pageName.replace(/[^a-z0-9\-_:/]/g, (c) => encodeURIComponent(c));
4673
+ return encodedPageName.startsWith("/") ? encodedPageName.slice(1) : encodedPageName;
4674
+ }
4675
+
4676
+ // packages/render/src/elements/include/index.ts
4677
+ function renderInclude(ctx, data) {
4678
+ if (data.elements.length === 0) {
4679
+ renderMissingInclude(ctx, data.location.page);
4680
+ return;
4681
+ }
4682
+ renderElements(ctx, data.elements);
4683
+ }
3989
4684
 
3990
- // packages/render/src/libs/highlighter/index.ts
3991
- var LANGUAGES = {
3992
- css: cssLang,
3993
- cpp: cppLang,
3994
- diff: diffLang,
3995
- dtd: dtdLang,
3996
- html: htmlLang,
3997
- java: javaLang,
3998
- javascript: javascriptLang,
3999
- php: phpLang,
4000
- python: pythonLang,
4001
- ruby: rubyLang,
4002
- sql: sqlLang,
4003
- xml: xmlLang,
4004
- xhtml: htmlLang
4005
- };
4006
- function highlight(code, language) {
4007
- const def = LANGUAGES[language.toLowerCase()];
4008
- if (!def)
4009
- return null;
4010
- const tokens = tokenize(def, code);
4011
- return renderTokens(tokens);
4685
+ // packages/render/src/elements/line-break.ts
4686
+ function renderLineBreaks(ctx, count) {
4687
+ for (let i = 0;i < count; i++) {
4688
+ ctx.push("<br />");
4689
+ }
4012
4690
  }
4013
4691
 
4014
- // packages/render/src/elements/code.ts
4015
- function renderCode(ctx, data) {
4016
- ctx.push(`<div class="code">`);
4017
- if (data.contents === "") {
4018
- ctx.push("</div>");
4692
+ // packages/render/src/elements/link/target.ts
4693
+ var TARGET_VALUES = {
4694
+ "new-tab": "_blank",
4695
+ parent: "_parent",
4696
+ top: "_top",
4697
+ same: "_self"
4698
+ };
4699
+ function renderTargetAttributes(attrs, target) {
4700
+ if (!target) {
4019
4701
  return;
4020
4702
  }
4021
- if (data.language) {
4022
- const highlighted = highlight(data.contents, data.language);
4023
- if (highlighted) {
4024
- ctx.push(highlighted);
4703
+ const targetValue = TARGET_VALUES[target] ?? "_blank";
4704
+ attrs.push(`target="${targetValue}"`);
4705
+ if (targetValue === "_blank") {
4706
+ attrs.push(`rel="noopener noreferrer"`);
4707
+ }
4708
+ }
4709
+
4710
+ // packages/render/src/elements/link/anchor.ts
4711
+ function renderAnchor(ctx, data) {
4712
+ const safe = sanitizeAttributes(data.attributes);
4713
+ const attrs = [];
4714
+ if (safe.href && isDangerousUrl(safe.href)) {
4715
+ safe.href = "#invalid-url";
4716
+ }
4717
+ const href = safe.href ?? "";
4718
+ attrs.push(`href="${escapeAttr(href)}"`);
4719
+ renderTargetAttributes(attrs, data.target);
4720
+ for (const [key, value] of Object.entries(safe)) {
4721
+ if (key === "href" || key === "target")
4722
+ continue;
4723
+ attrs.push(`${key}="${escapeAttr(value)}"`);
4724
+ }
4725
+ ctx.push(`<a ${attrs.join(" ")}>`);
4726
+ renderElements(ctx, data.elements);
4727
+ ctx.push("</a>");
4728
+ }
4729
+
4730
+ // packages/render/src/elements/link/anchor-name.ts
4731
+ function renderAnchorName(ctx, name) {
4732
+ ctx.push(`<a name="${escapeAttr(name)}"></a>`);
4733
+ }
4734
+
4735
+ // packages/render/src/elements/link/attributes.ts
4736
+ function getLinkAttributes(ctx, data) {
4737
+ const attrs = [`href="${escapeAttr(resolveSafeHref(ctx, data))}"`];
4738
+ if (shouldAddNewPageClass(ctx, data)) {
4739
+ attrs.push(`class="newpage"`);
4740
+ }
4741
+ renderTargetAttributes(attrs, data.target);
4742
+ return attrs;
4743
+ }
4744
+ function resolveSafeHref(ctx, data) {
4745
+ let href = ctx.resolvePageLink(data.link);
4746
+ if (data.extra) {
4747
+ href += data.extra;
4748
+ }
4749
+ const isAnchorJsVoid = data.type === "anchor" && href === "javascript:;";
4750
+ if (!isAnchorJsVoid && isDangerousUrl(href)) {
4751
+ return "#invalid-url";
4752
+ }
4753
+ return href;
4754
+ }
4755
+ function shouldAddNewPageClass(ctx, data) {
4756
+ if (data.type !== "page" || typeof data.link !== "object") {
4757
+ return false;
4758
+ }
4759
+ const page = data.link.page;
4760
+ const isSpecialPage = page.startsWith("//") || page.includes("#/");
4761
+ if (isSpecialPage) {
4762
+ return false;
4763
+ }
4764
+ const hashIdx = page.indexOf("#");
4765
+ const pageToCheck = hashIdx !== -1 ? page.slice(0, hashIdx) : page;
4766
+ const pageExists = ctx.page?.pageExists;
4767
+ return pageExists ? !pageExists(pageToCheck) : true;
4768
+ }
4769
+
4770
+ // packages/render/src/elements/link/label.ts
4771
+ function renderLinkLabel(ctx, data) {
4772
+ if (data.label === "page") {
4773
+ if (typeof data.link === "string") {
4774
+ ctx.pushEscaped(data.link);
4025
4775
  } else {
4026
- ctx.push(`<pre><code>${escapeHtml(data.contents)}</code></pre>`);
4776
+ ctx.pushEscaped(data.link.page);
4027
4777
  }
4028
- } else {
4029
- ctx.push(`<pre><code>${escapeHtml(data.contents)}</code></pre>`);
4778
+ return;
4779
+ }
4780
+ if ("text" in data.label) {
4781
+ ctx.pushEscaped(data.label.text);
4782
+ return;
4783
+ }
4784
+ if ("url" in data.label) {
4785
+ const href = ctx.resolvePageLink(data.link);
4786
+ ctx.pushEscaped(data.label.url ?? href);
4030
4787
  }
4031
- ctx.push("</div>");
4032
4788
  }
4033
4789
 
4034
- // packages/render/src/hash.ts
4035
- function syncHashSha1(input) {
4036
- return fnv1aHash(input, 40);
4790
+ // packages/render/src/elements/link/index.ts
4791
+ function renderLink(ctx, data) {
4792
+ const attrs = getLinkAttributes(ctx, data);
4793
+ ctx.push(`<a ${attrs.join(" ")}>`);
4794
+ renderLinkLabel(ctx, data);
4795
+ ctx.push("</a>");
4037
4796
  }
4038
- function syncHashMd5(input) {
4039
- return fnv1aHash(input, 32);
4797
+
4798
+ // packages/render/src/elements/list/definition-list.ts
4799
+ function renderDefinitionList(ctx, items) {
4800
+ ctx.push("<dl>");
4801
+ for (const item of items) {
4802
+ ctx.push("<dt>");
4803
+ renderElements(ctx, item.key);
4804
+ ctx.push("</dt>");
4805
+ ctx.push("<dd>");
4806
+ renderElements(ctx, item.value);
4807
+ ctx.push("</dd>");
4808
+ }
4809
+ ctx.push("</dl>");
4040
4810
  }
4041
- function fnv1aHash(input, hexLen) {
4811
+
4812
+ // packages/render/src/elements/list/attributes.ts
4813
+ function renderListAttrs(attributes) {
4814
+ let hasAttributes2 = false;
4815
+ for (const _ in attributes) {
4816
+ hasAttributes2 = true;
4817
+ break;
4818
+ }
4819
+ if (!hasAttributes2)
4820
+ return "";
4821
+ const safe = sanitizeAttributes(attributes);
4042
4822
  let result = "";
4043
- const rounds = Math.ceil(hexLen / 8);
4044
- for (let round = 0;round < rounds; round++) {
4045
- let h = 2166136261 ^ round;
4046
- for (let i = 0;i < input.length; i++) {
4047
- h ^= input.charCodeAt(i);
4048
- h = Math.imul(h, 16777619);
4823
+ for (const key in safe) {
4824
+ if (key.startsWith("_"))
4825
+ continue;
4826
+ const value = safe[key];
4827
+ result += ` ${key}="${escapeAttr(value)}"`;
4828
+ }
4829
+ return result;
4830
+ }
4831
+
4832
+ // packages/render/src/elements/list/item-rendering.ts
4833
+ function getListItemOpenTag(attributes) {
4834
+ const styleAttr = hasNoMarker(attributes) ? ' style="list-style: none"' : "";
4835
+ return `<li${renderListAttrs(attributes)}${styleAttr}>`;
4836
+ }
4837
+ function hasNoMarker(attributes) {
4838
+ return attributes._noMarker === "true";
4839
+ }
4840
+ function renderFollowingSubLists(items, index, renderNestedList) {
4841
+ let nextIndex = index;
4842
+ while (nextIndex + 1 < items.length) {
4843
+ const nextItem = getSubListItem(items[nextIndex + 1]);
4844
+ if (!nextItem) {
4845
+ break;
4049
4846
  }
4050
- result += (h >>> 0).toString(16).padStart(8, "0");
4847
+ nextIndex++;
4848
+ renderNestedList(nextItem.data);
4051
4849
  }
4052
- return result.substring(0, hexLen);
4850
+ return nextIndex;
4851
+ }
4852
+ function getSubListItem(item) {
4853
+ return item?.["item-type"] === "sub-list" ? item : null;
4053
4854
  }
4054
4855
 
4055
- // packages/render/src/elements/tab-view.ts
4056
- function renderTabView(ctx, tabs) {
4057
- const labelString = tabs.map((t) => t.label).join("");
4058
- const hash = md5Hash(labelString);
4059
- const widgetId = ctx.generateFixedId(`wiki-tabview-${hash}`);
4060
- ctx.push(`<div id="${widgetId}" class="yui-navset">`);
4061
- ctx.push(`<ul class="yui-nav">`);
4062
- for (let i = 0;i < tabs.length; i++) {
4063
- const tab = tabs[i];
4064
- const selectedClass = i === 0 ? ` class="selected"` : "";
4065
- ctx.push(`<li${selectedClass}>`);
4066
- ctx.push(`<a href="javascript:;"><em>${escapeHtml(tab.label)}</em></a>`);
4067
- ctx.push("</li>");
4856
+ // packages/render/src/elements/list/paragraphs.ts
4857
+ function getParagraphIndices(elements) {
4858
+ const indices = [];
4859
+ for (let i = 0;i < elements.length; i++) {
4860
+ if (isParagraphElement(elements[i])) {
4861
+ indices.push(i);
4862
+ }
4068
4863
  }
4069
- ctx.push("</ul>");
4070
- ctx.push(`<div class="yui-content">`);
4071
- for (let i = 0;i < tabs.length; i++) {
4072
- const tab = tabs[i];
4073
- const displayStyle = i === 0 ? "" : ` style="display:none"`;
4074
- const tabId = ctx.generateId("wiki-tab-0-", i);
4075
- ctx.push(`<div id="${tabId}"${displayStyle}>`);
4076
- renderElements(ctx, tab.elements);
4077
- ctx.push("</div>");
4864
+ return indices;
4865
+ }
4866
+ function isParagraphElement(element) {
4867
+ return element?.element === "container" && element.data.type === "paragraph";
4868
+ }
4869
+ function isLiCloseTextParagraph(element) {
4870
+ let combined = "";
4871
+ for (const child of element.data.elements) {
4872
+ if (child.element === "text") {
4873
+ combined += child.data;
4874
+ }
4078
4875
  }
4079
- ctx.push("</div>");
4080
- ctx.push("</div>");
4876
+ return combined.trim() === "[[/li]]";
4877
+ }
4878
+
4879
+ // packages/render/src/elements/list/trim.ts
4880
+ function trimTextElements(elements) {
4881
+ if (elements.length === 0)
4882
+ return elements;
4883
+ let start = 0;
4884
+ let end = elements.length;
4885
+ while (start < end) {
4886
+ const el = elements[start];
4887
+ if (el.element === "text" && typeof el.data === "string" && el.data.trim() === "") {
4888
+ start++;
4889
+ } else {
4890
+ break;
4891
+ }
4892
+ }
4893
+ while (end > start) {
4894
+ const el = elements[end - 1];
4895
+ if (el.element === "text" && typeof el.data === "string" && el.data.trim() === "") {
4896
+ end--;
4897
+ } else {
4898
+ break;
4899
+ }
4900
+ }
4901
+ if (start === 0 && end === elements.length)
4902
+ return elements;
4903
+ return elements.slice(start, end);
4081
4904
  }
4082
- function md5Hash(input) {
4083
- return syncHashMd5(input);
4905
+ function hasNonWhitespaceElement(elements) {
4906
+ for (const el of elements) {
4907
+ if (el.element !== "text" || typeof el.data !== "string" || el.data.trim() !== "") {
4908
+ return true;
4909
+ }
4910
+ }
4911
+ return false;
4084
4912
  }
4085
4913
 
4086
- // packages/render/src/elements/footnote.ts
4087
- function renderFootnoteRef(ctx, index) {
4088
- const id = ctx.generateId("footnoteref-", index);
4089
- ctx.push(`<sup class="footnoteref">`);
4090
- ctx.push(`<a id="${id}" href="javascript:;" class="footnoteref">${index}</a>`);
4091
- ctx.push("</sup>");
4914
+ // packages/render/src/elements/list/no-marker.ts
4915
+ function renderNoMarkerElements(ctx, elements) {
4916
+ const trimmed = trimTextElements(elements);
4917
+ if (trimmed.length === 0)
4918
+ return;
4919
+ const paragraphIndices = getParagraphIndices(trimmed);
4920
+ if (paragraphIndices.length === 0) {
4921
+ renderElements(ctx, trimmed);
4922
+ return;
4923
+ }
4924
+ const firstParagraphIdx = paragraphIndices[0];
4925
+ const lastParagraphIdx = paragraphIndices[paragraphIndices.length - 1];
4926
+ for (let i = 0;i < trimmed.length; i++) {
4927
+ const el = trimmed[i];
4928
+ if (isParagraphElement(el)) {
4929
+ renderNoMarkerParagraph(ctx, el, i, firstParagraphIdx, lastParagraphIdx);
4930
+ } else {
4931
+ renderElement(ctx, el);
4932
+ }
4933
+ }
4092
4934
  }
4093
- function renderFootnoteBlock(ctx, data) {
4094
- if (ctx.footnotes.length === 0)
4935
+ function renderNoMarkerParagraph(ctx, element, index, firstParagraphIdx, lastParagraphIdx) {
4936
+ if (index === firstParagraphIdx || index === lastParagraphIdx && isLiCloseTextParagraph(element)) {
4937
+ renderElements(ctx, element.data.elements);
4095
4938
  return;
4096
- const title = data.title ?? "Footnotes";
4097
- ctx.push(`<div class="footnotes-footer">`);
4098
- ctx.push(`<div class="title">${escapeHtml(title)}</div>`);
4099
- for (let i = 0;i < ctx.footnotes.length; i++) {
4100
- const index = i + 1;
4101
- const elements = ctx.footnotes[i] ?? [];
4102
- const fnId = ctx.generateId("footnote-", index);
4103
- ctx.push(`<div class="footnote-footer" id="${fnId}">`);
4104
- ctx.push(`<a href="javascript:;">${index}</a>. `);
4105
- renderElements(ctx, elements);
4106
- ctx.push("</div>");
4107
4939
  }
4108
- ctx.push("</div>");
4940
+ ctx.push("<p>");
4941
+ renderElements(ctx, element.data.elements);
4942
+ ctx.push("</p>");
4109
4943
  }
4110
4944
 
4111
- // packages/render/src/elements/math.ts
4112
- import temml from "temml";
4113
- function needsAlignedWrapper(latex) {
4114
- if (/\\begin\s*\{/.test(latex)) {
4945
+ // packages/render/src/elements/list/items.ts
4946
+ function renderListItems(ctx, items, renderNestedList) {
4947
+ let i = 0;
4948
+ while (i < items.length) {
4949
+ const item = items[i];
4950
+ if (item["item-type"] === "elements") {
4951
+ i = renderElementsListItem(ctx, item, items, i, renderNestedList);
4952
+ } else {
4953
+ renderOrphanSubListItem(ctx, item, renderNestedList);
4954
+ }
4955
+ i++;
4956
+ }
4957
+ }
4958
+ function renderElementsListItem(ctx, item, items, index, renderNestedList) {
4959
+ const noMarker = hasNoMarker(item.attributes);
4960
+ ctx.push(getListItemOpenTag(item.attributes));
4961
+ if (noMarker) {
4962
+ renderNoMarkerElements(ctx, item.elements);
4963
+ } else {
4964
+ renderElements(ctx, trimTextElements(item.elements));
4965
+ }
4966
+ const nextIndex = renderFollowingSubLists(items, index, (data) => renderNestedList(ctx, data));
4967
+ ctx.push("</li>");
4968
+ return nextIndex;
4969
+ }
4970
+ function renderOrphanSubListItem(ctx, item, renderNestedList) {
4971
+ ctx.push(`<li style="list-style: none; display: inline">`);
4972
+ renderNestedList(ctx, item.data);
4973
+ ctx.push("</li>");
4974
+ }
4975
+
4976
+ // packages/render/src/elements/list/index.ts
4977
+ function renderList(ctx, data) {
4978
+ const hasContent = data.items.some((item) => {
4979
+ if (item["item-type"] === "sub-list")
4980
+ return true;
4981
+ if (item["item-type"] === "elements") {
4982
+ return hasNonWhitespaceElement(item.elements);
4983
+ }
4115
4984
  return false;
4985
+ });
4986
+ if (!hasContent) {
4987
+ return;
4116
4988
  }
4117
- const withoutEscaped = latex.replace(/\\&/g, "");
4118
- return withoutEscaped.includes("&");
4989
+ const tag = data.type === "numbered" ? "ol" : "ul";
4990
+ ctx.push(`<${tag}${renderListAttrs(data.attributes)}>`);
4991
+ renderListItems(ctx, data.items, renderList);
4992
+ ctx.push(`</${tag}>`);
4119
4993
  }
4994
+
4995
+ // packages/render/src/elements/math/latex.ts
4996
+ import temml from "temml";
4120
4997
  function renderLatexToMathML(latex, displayMode) {
4121
4998
  try {
4122
4999
  let processedLatex = latex;
@@ -4134,6 +5011,31 @@ ${latex}
4134
5011
  return "";
4135
5012
  }
4136
5013
  }
5014
+ function needsAlignedWrapper(latex) {
5015
+ if (/\\begin\s*\{/.test(latex)) {
5016
+ return false;
5017
+ }
5018
+ const withoutEscaped = latex.replace(/\\&/g, "");
5019
+ return withoutEscaped.includes("&");
5020
+ }
5021
+
5022
+ // packages/render/src/elements/math/source.ts
5023
+ function pushHiddenLatexSource(ctx, latex) {
5024
+ ctx.push(`<code class="math-source" hidden aria-hidden="true">`);
5025
+ ctx.push(escapeHtml(latex));
5026
+ ctx.push(`</code>`);
5027
+ }
5028
+ function pushMathRender(ctx, mathml, fallback) {
5029
+ ctx.push(`<span class="math-render">`);
5030
+ if (mathml) {
5031
+ ctx.push(mathml);
5032
+ } else {
5033
+ fallback();
5034
+ }
5035
+ ctx.push(`</span>`);
5036
+ }
5037
+
5038
+ // packages/render/src/elements/math/block.ts
4137
5039
  function renderMath(ctx, data) {
4138
5040
  const index = ctx.nextEquationIndex() + 1;
4139
5041
  const latex = data["latex-source"];
@@ -4144,38 +5046,28 @@ function renderMath(ctx, data) {
4144
5046
  if (data.name) {
4145
5047
  ctx.push(`<span class="equation-number">(${index})</span>`);
4146
5048
  }
4147
- ctx.push(`<code class="math-source" hidden aria-hidden="true">`);
4148
- ctx.push(escapeHtml(latex));
4149
- ctx.push(`</code>`);
4150
- ctx.push(`<span class="math-render">`);
4151
- if (mathml) {
4152
- ctx.push(mathml);
4153
- } else {
5049
+ pushHiddenLatexSource(ctx, latex);
5050
+ pushMathRender(ctx, mathml, () => {
4154
5051
  ctx.push(`<span class="math-error">`);
4155
5052
  ctx.push(escapeHtml(latex));
4156
5053
  ctx.push(`</span>`);
4157
- }
4158
- ctx.push(`</span>`);
5054
+ });
4159
5055
  ctx.push("</div>");
4160
5056
  }
5057
+ // packages/render/src/elements/math/inline.ts
4161
5058
  function renderMathInline(ctx, data) {
4162
5059
  const latex = data["latex-source"];
4163
5060
  const mathml = renderLatexToMathML(latex, false);
4164
5061
  ctx.push(`<span class="math-inline">`);
4165
- ctx.push(`<code class="math-source" hidden aria-hidden="true">`);
4166
- ctx.push(escapeHtml(latex));
4167
- ctx.push(`</code>`);
4168
- ctx.push(`<span class="math-render">`);
4169
- if (mathml) {
4170
- ctx.push(mathml);
4171
- } else {
5062
+ pushHiddenLatexSource(ctx, latex);
5063
+ pushMathRender(ctx, mathml, () => {
4172
5064
  ctx.push(`<span class="math-error">$`);
4173
5065
  ctx.push(escapeHtml(latex));
4174
5066
  ctx.push(`$</span>`);
4175
- }
4176
- ctx.push(`</span>`);
5067
+ });
4177
5068
  ctx.push("</span>");
4178
5069
  }
5070
+ // packages/render/src/elements/math/equation-ref.ts
4179
5071
  function renderEquationRef(ctx, name) {
4180
5072
  const id = ctx.generateId("equation-", name);
4181
5073
  ctx.push(`<span class="eref" data-target="${escapeAttr(id)}">`);
@@ -4185,62 +5077,82 @@ function renderEquationRef(ctx, name) {
4185
5077
  ctx.push(`<span class="eref-tooltip" aria-hidden="true"></span>`);
4186
5078
  ctx.push("</span>");
4187
5079
  }
5080
+ // packages/render/src/elements/module/empty-container.ts
5081
+ function renderEmptyModuleContainer(ctx, className) {
5082
+ ctx.push(`<div class="${className}">`);
5083
+ ctx.push("</div>");
5084
+ }
5085
+ function renderIndentedEmptyModuleContainer(ctx, className) {
5086
+ ctx.push(`<div class="${className}">
5087
+ </div>`);
5088
+ }
4188
5089
 
4189
5090
  // packages/render/src/elements/module/backlinks.ts
4190
5091
  function renderBacklinks(ctx, _data) {
4191
- ctx.push(`<div class="backlinks-module-box">
4192
- </div>`);
5092
+ renderIndentedEmptyModuleContainer(ctx, "backlinks-module-box");
4193
5093
  }
4194
5094
 
4195
5095
  // packages/render/src/elements/module/categories.ts
4196
5096
  function renderCategories(ctx, _data) {
4197
- ctx.push(`<div class="categories-module-box">`);
4198
- ctx.push("</div>");
5097
+ renderEmptyModuleContainer(ctx, "categories-module-box");
5098
+ }
5099
+
5100
+ // packages/render/src/elements/module/join-markup.ts
5101
+ function renderJoinMarkup(data) {
5102
+ const buttonText = data["button-text"] ?? "Join";
5103
+ const className = data.attributes?.class ?? "join-box";
5104
+ return `<div class="${escapeAttr(className)}"><a href="javascript:;">${escapeHtml(buttonText)}</a></div>`;
4199
5105
  }
4200
5106
 
4201
5107
  // packages/render/src/elements/module/join.ts
4202
5108
  function renderJoin(ctx, data) {
4203
- const buttonText = data["button-text"] ?? "Join";
4204
- const attrs = data.attributes ?? {};
4205
- const className = attrs.class ?? "join-box";
4206
- ctx.push(`<div class="${escapeAttr(className)}">`);
4207
- ctx.push(`<a href="javascript:;">${escapeHtml(buttonText)}</a>`);
4208
- ctx.push("</div>");
5109
+ ctx.push(renderJoinMarkup(data));
4209
5110
  }
4210
5111
 
4211
5112
  // packages/render/src/elements/module/page-tree.ts
4212
5113
  function renderPageTree(ctx, _data) {
4213
- ctx.push(`<div class="page-tree-module-box">`);
4214
- ctx.push("</div>");
5114
+ renderEmptyModuleContainer(ctx, "page-tree-module-box");
5115
+ }
5116
+
5117
+ // packages/render/src/elements/module/rate-markup.ts
5118
+ function getRateWidgetParts() {
5119
+ return [
5120
+ `<div class="page-rate-widget-box">`,
5121
+ `<span class="rate-points">rating:&nbsp;<span class="number prw54353">0</span></span>`,
5122
+ `<span class="rateup btn btn-default"><a title="I like it" href="javascript:;">+</a></span>`,
5123
+ `<span class="ratedown btn btn-default"><a title="I don't like it" href="javascript:;">&#8211;</a></span>`,
5124
+ `<span class="cancel btn btn-default"><a title="Cancel my vote" href="javascript:;">x</a></span>`,
5125
+ "</div>"
5126
+ ];
4215
5127
  }
4216
5128
 
4217
5129
  // packages/render/src/elements/module/rate.ts
4218
5130
  function renderRate(ctx) {
4219
- ctx.push(`<div class="page-rate-widget-box">`);
4220
- ctx.push(`<span class="rate-points">rating:&nbsp;<span class="number prw54353">0</span></span>`);
4221
- ctx.push(`<span class="rateup btn btn-default"><a title="I like it" href="javascript:;">+</a></span>`);
4222
- ctx.push(`<span class="ratedown btn btn-default"><a title="I don't like it" href="javascript:;">&#8211;</a></span>`);
4223
- ctx.push(`<span class="cancel btn btn-default"><a title="Cancel my vote" href="javascript:;">x</a></span>`);
4224
- ctx.push("</div>");
5131
+ for (const part of getRateWidgetParts()) {
5132
+ ctx.push(part);
5133
+ }
4225
5134
  }
4226
5135
 
4227
5136
  // packages/render/src/elements/module/listusers.ts
4228
5137
  function renderListUsers(ctx, _data) {
4229
- ctx.push(`<div class="list-users-module-box">`);
4230
- ctx.push("</div>");
5138
+ renderEmptyModuleContainer(ctx, "list-users-module-box");
4231
5139
  }
4232
5140
 
4233
5141
  // packages/render/src/elements/module/listpages.ts
4234
5142
  function renderListPages(ctx, _data) {
4235
- ctx.push(`<div class="list-pages-box">`);
4236
- ctx.push("</div>");
5143
+ renderEmptyModuleContainer(ctx, "list-pages-box");
5144
+ }
5145
+
5146
+ // packages/render/src/elements/module/unknown.ts
5147
+ function renderUnknownModule(ctx, data) {
5148
+ ctx.push(`<div class="error-block">[[module <em>${data.name}</em>]] No such module, please <a href="https://www.wikidot.com/doc:modules" target="_blank" rel="noopener noreferrer">check available modules</a> and fix this page.</div>`);
4237
5149
  }
4238
5150
 
4239
5151
  // packages/render/src/elements/module/index.ts
4240
5152
  function renderModule(ctx, data) {
4241
5153
  switch (data.module) {
4242
5154
  case "unknown":
4243
- ctx.push(`<div class="error-block">[[module <em>${data.name}</em>]] No such module, please <a href="https://www.wikidot.com/doc:modules" target="_blank" rel="noopener noreferrer">check available modules</a> and fix this page.</div>`);
5155
+ renderUnknownModule(ctx, data);
4244
5156
  break;
4245
5157
  case "backlinks":
4246
5158
  renderBacklinks(ctx, data);
@@ -4266,306 +5178,166 @@ function renderModule(ctx, data) {
4266
5178
  }
4267
5179
  }
4268
5180
 
4269
- // packages/render/src/elements/embed.ts
4270
- function isValidVideoId(id) {
4271
- return /^[a-zA-Z0-9_-]+$/.test(id);
4272
- }
4273
- function isValidGithubUsername(username) {
4274
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/.test(username);
4275
- }
4276
- function isValidGistHash(hash) {
4277
- return /^[a-f0-9]+$/.test(hash);
4278
- }
4279
- function isValidGitlabSnippetId(id) {
4280
- return /^[0-9]+$/.test(id);
4281
- }
4282
- function renderEmbed(ctx, data) {
4283
- switch (data.embed) {
4284
- case "youtube":
4285
- renderYoutube(ctx, data.data["video-id"]);
4286
- break;
4287
- case "vimeo":
4288
- renderVimeo(ctx, data.data["video-id"]);
4289
- break;
4290
- case "github-gist":
4291
- renderGithubGist(ctx, data.data.username, data.data.hash);
4292
- break;
4293
- case "gitlab-snippet":
4294
- renderGitlabSnippet(ctx, data.data["snippet-id"]);
4295
- break;
4296
- }
4297
- }
4298
- function renderYoutube(ctx, videoId) {
4299
- if (!isValidVideoId(videoId)) {
4300
- ctx.push(`<!-- Invalid YouTube video ID -->`);
4301
- return;
4302
- }
4303
- ctx.push(`<div class="embed-youtube">`);
4304
- ctx.push(`<iframe src="https://www.youtube.com/embed/${escapeAttr(videoId)}" ` + `frameborder="0" allowfullscreen></iframe>`);
4305
- ctx.push("</div>");
4306
- }
4307
- function renderVimeo(ctx, videoId) {
4308
- if (!isValidVideoId(videoId)) {
4309
- ctx.push(`<!-- Invalid Vimeo video ID -->`);
4310
- return;
4311
- }
4312
- ctx.push(`<div class="embed-vimeo">`);
4313
- ctx.push(`<iframe src="https://player.vimeo.com/video/${escapeAttr(videoId)}" ` + `frameborder="0" allowfullscreen></iframe>`);
4314
- ctx.push("</div>");
4315
- }
4316
- function renderGithubGist(ctx, username, hash) {
4317
- if (!isValidGithubUsername(username) || !isValidGistHash(hash)) {
4318
- ctx.push(`<!-- Invalid GitHub Gist parameters -->`);
4319
- return;
4320
- }
4321
- ctx.push(`<script src="https://gist.github.com/${escapeAttr(username)}/${escapeAttr(hash)}.js"></script>`);
4322
- }
4323
- function renderGitlabSnippet(ctx, snippetId) {
4324
- if (!isValidGitlabSnippetId(snippetId)) {
4325
- ctx.push(`<!-- Invalid GitLab snippet ID -->`);
4326
- return;
4327
- }
4328
- ctx.push(`<script src="https://gitlab.com/snippets/${escapeAttr(snippetId)}.js"></script>`);
4329
- }
4330
-
4331
- // packages/render/src/elements/embed-block.ts
4332
- import { parseDocument } from "htmlparser2";
4333
- import sanitizeHtml from "sanitize-html";
4334
- var BOOLEAN_ATTRIBUTES = [
4335
- "allowfullscreen",
4336
- "async",
4337
- "autofocus",
4338
- "autoplay",
4339
- "checked",
4340
- "controls",
4341
- "default",
4342
- "defer",
4343
- "disabled",
4344
- "formnovalidate",
4345
- "hidden",
4346
- "ismap",
4347
- "loop",
4348
- "multiple",
4349
- "muted",
4350
- "novalidate",
4351
- "open",
4352
- "readonly",
4353
- "required",
4354
- "reversed",
4355
- "selected"
4356
- ];
4357
- var DEFAULT_EMBED_ALLOWLIST = [
4358
- { host: "*.youtube.com", pathPrefix: "/embed/" },
4359
- { host: "*.youtube-nocookie.com", pathPrefix: "/embed/" },
4360
- { host: "player.vimeo.com", pathPrefix: "/video/" },
4361
- { host: "*.google.com", pathPrefix: "/maps/embed" },
4362
- { host: "calendar.google.com", pathPrefix: "/calendar/embed" },
4363
- { host: "open.spotify.com", pathPrefix: "/embed/" },
4364
- { host: "w.soundcloud.com", pathPrefix: "/player/" },
4365
- { host: "codepen.io" }
4366
- ];
4367
- var SANITIZE_CONFIG = {
4368
- allowedTags: ["iframe"],
4369
- allowedAttributes: {
4370
- iframe: [
4371
- "class",
4372
- "src",
4373
- "style",
4374
- "allow",
4375
- "allowfullscreen",
4376
- "frameborder",
4377
- "height",
4378
- "loading",
4379
- "referrerpolicy",
4380
- "sandbox",
4381
- "title",
4382
- "width"
4383
- ]
4384
- },
4385
- allowedSchemes: ["https", "http"]
4386
- };
4387
- function findIframes(html) {
4388
- const doc = parseDocument(html);
4389
- const iframes = [];
4390
- function walk(nodes) {
4391
- for (const node of nodes) {
4392
- if (node.type === "tag") {
4393
- if (node.name === "iframe") {
4394
- iframes.push(node);
4395
- }
4396
- if (node.children) {
4397
- walk(node.children);
4398
- }
4399
- }
4400
- }
4401
- }
4402
- walk(doc.children);
4403
- return iframes;
4404
- }
4405
- function matchesHostPattern(hostname, pattern) {
4406
- const lowerHostname = hostname.toLowerCase();
4407
- const lowerPattern = pattern.toLowerCase();
4408
- if (lowerPattern.startsWith("*.")) {
4409
- const base = lowerPattern.slice(2);
4410
- return lowerHostname === base || lowerHostname.endsWith("." + base);
4411
- }
4412
- return lowerHostname === lowerPattern;
4413
- }
4414
- function matchesAllowlistEntry(url, entry) {
4415
- if (!matchesHostPattern(url.hostname, entry.host)) {
4416
- return false;
4417
- }
4418
- if (entry.pathPrefix) {
4419
- const pathLower = url.pathname.toLowerCase();
4420
- const prefixLower = entry.pathPrefix.toLowerCase();
4421
- if (!pathLower.startsWith(prefixLower)) {
4422
- return false;
4423
- }
4424
- if (!prefixLower.endsWith("/")) {
4425
- const remainder = pathLower.slice(prefixLower.length);
4426
- if (remainder && !/^[/?#]/.test(remainder)) {
4427
- return false;
4428
- }
4429
- }
4430
- }
4431
- return true;
4432
- }
4433
- function validateAndSanitizeEmbed(content, allowlist, baseUrl) {
4434
- const sanitized = sanitizeHtml(content.trim(), SANITIZE_CONFIG);
4435
- if (!sanitized.trim()) {
4436
- return null;
4437
- }
4438
- const iframes = findIframes(sanitized);
4439
- if (iframes.length !== 1) {
4440
- return null;
4441
- }
4442
- const iframe = iframes[0];
4443
- const src = iframe.attribs.src?.trim();
4444
- if (!src) {
4445
- return null;
4446
- }
4447
- let url;
4448
- try {
4449
- if (src.startsWith("//")) {
4450
- const base = baseUrl ?? "https://localhost";
4451
- url = new URL(src, base);
4452
- } else {
4453
- url = new URL(src);
4454
- }
4455
- } catch {
4456
- return null;
4457
- }
4458
- if (url.protocol !== "https:" && url.protocol !== "http:") {
4459
- return null;
4460
- }
4461
- if (allowlist !== null) {
4462
- const matched = allowlist.some((entry) => matchesAllowlistEntry(url, entry));
4463
- if (!matched) {
4464
- return null;
4465
- }
4466
- }
4467
- return sanitized;
4468
- }
4469
- function normalizeBooleanAttributes(html) {
4470
- let result = html;
4471
- for (const attr of BOOLEAN_ATTRIBUTES) {
4472
- const standalonePattern = new RegExp(`\\s${attr}(?=\\s|>|/>)`, "gi");
4473
- result = result.replace(standalonePattern, ` ${attr}="${attr}"`);
4474
- const emptyValuePattern = new RegExp(`\\s${attr}=""`, "gi");
4475
- result = result.replace(emptyValuePattern, ` ${attr}="${attr}"`);
4476
- }
4477
- return result;
4478
- }
4479
- function renderEmbedBlock(ctx, data) {
4480
- const allowlist = ctx.options.embedAllowlist !== undefined ? ctx.options.embedAllowlist : DEFAULT_EMBED_ALLOWLIST;
4481
- const sanitized = validateAndSanitizeEmbed(data.contents, allowlist, ctx.options.baseUrl);
4482
- if (sanitized === null) {
4483
- ctx.push('<div class="error-block">Sorry, no match for the embedded content.</div>');
4484
- return;
5181
+ // packages/render/src/elements/table/cell-attributes.ts
5182
+ function renderTableCellAttrs(cell) {
5183
+ const attrs = [];
5184
+ const safeCellAttrs = sanitizeAttributes(cell.attributes);
5185
+ appendColumnSpan(attrs, cell);
5186
+ appendRowSpan(attrs, safeCellAttrs);
5187
+ appendAlignmentStyle(attrs, cell, safeCellAttrs);
5188
+ appendRemainingCellAttributes(attrs, cell, safeCellAttrs);
5189
+ return attrs.length > 0 ? " " + attrs.join(" ") : "";
5190
+ }
5191
+ function appendColumnSpan(attrs, cell) {
5192
+ if (cell["column-span"] > 1) {
5193
+ attrs.push(`colspan="${cell["column-span"]}"`);
4485
5194
  }
4486
- const normalized = normalizeBooleanAttributes(sanitized);
4487
- ctx.push(normalized);
4488
5195
  }
4489
-
4490
- // packages/render/src/elements/user.ts
4491
- function renderUser(ctx, data) {
4492
- const normalized = data.name.toLowerCase().trim();
4493
- if (normalized === "anonymous") {
4494
- ctx.push("Anonymous");
5196
+ function appendRowSpan(attrs, safeCellAttrs) {
5197
+ if (!safeCellAttrs.rowspan) {
4495
5198
  return;
4496
5199
  }
4497
- const resolved = ctx.options.resolvers?.user?.(data.name) ?? null;
4498
- if (resolved === null) {
4499
- ctx.push(escapeHtml(data.name));
5200
+ const rowspan = parseInt(safeCellAttrs.rowspan, 10);
5201
+ if (rowspan > 1) {
5202
+ attrs.push(`rowspan="${rowspan}"`);
5203
+ }
5204
+ }
5205
+ function appendAlignmentStyle(attrs, cell, safeCellAttrs) {
5206
+ if (!cell.align) {
4500
5207
  return;
4501
5208
  }
4502
- const displayName = resolved.name ?? data.name;
4503
- const hrefAttr = resolved.url ? ` href="${escapeAttr(resolved.url)}"` : "";
4504
- const showAvatar = data["show-avatar"] && resolved.url && resolved.avatarUrl;
4505
- if (showAvatar) {
4506
- const styleAttr = resolved.karmaUrl ? ` style="background-image:url(${escapeAttr(resolved.karmaUrl)})"` : "";
4507
- ctx.push(`<span class="printuser avatarhover">`);
4508
- ctx.push(`<a${hrefAttr}>`);
4509
- ctx.push(`<img class="small" src="${escapeAttr(resolved.avatarUrl)}" alt="${escapeAttr(displayName)}"${styleAttr} />`);
4510
- ctx.push("</a>");
4511
- ctx.push(`<a${hrefAttr}>`);
4512
- ctx.push(escapeHtml(displayName));
4513
- ctx.push("</a>");
4514
- ctx.push("</span>");
5209
+ const existingStyle = safeCellAttrs.style ?? "";
5210
+ const alignStyle = `text-align: ${cell.align};`;
5211
+ if (existingStyle) {
5212
+ attrs.push(`style="${escapeAttr(existingStyle + "; " + alignStyle)}"`);
4515
5213
  } else {
4516
- ctx.push(`<span class="printuser">`);
4517
- ctx.push(`<a${hrefAttr}>`);
4518
- ctx.push(escapeHtml(displayName));
4519
- ctx.push("</a>");
4520
- ctx.push("</span>");
5214
+ attrs.push(`style="${alignStyle}"`);
5215
+ }
5216
+ }
5217
+ function appendRemainingCellAttributes(attrs, cell, safeCellAttrs) {
5218
+ for (const key in safeCellAttrs) {
5219
+ if (key === "style" && cell.align)
5220
+ continue;
5221
+ if (key === "rowspan")
5222
+ continue;
5223
+ const value = safeCellAttrs[key];
5224
+ attrs.push(`${key}="${escapeAttr(value)}"`);
4521
5225
  }
4522
5226
  }
4523
5227
 
4524
- // packages/render/src/elements/bibliography.ts
4525
- function generateIdSuffix(label, counter) {
4526
- let h = 2166136261;
4527
- const input = label + counter;
4528
- for (let i = 0;i < input.length; i++) {
4529
- h ^= input.charCodeAt(i);
4530
- h = Math.imul(h, 16777619);
5228
+ // packages/render/src/elements/table/attributes.ts
5229
+ function renderTableAttrs(attributes) {
5230
+ let hasRenderableAttributes = false;
5231
+ for (const key in attributes) {
5232
+ if (!key.startsWith("_")) {
5233
+ hasRenderableAttributes = true;
5234
+ break;
5235
+ }
4531
5236
  }
4532
- return (h >>> 0).toString(16).slice(0, 6);
5237
+ if (!hasRenderableAttributes)
5238
+ return "";
5239
+ const safe = sanitizeAttributes(attributes);
5240
+ let result = "";
5241
+ for (const key in safe) {
5242
+ if (key.startsWith("_"))
5243
+ continue;
5244
+ const value = safe[key];
5245
+ result += ` ${key}="${escapeAttr(value)}"`;
5246
+ }
5247
+ return result;
4533
5248
  }
4534
- function renderBibliographyCite(ctx, data) {
4535
- const number = ctx.bibliographyMap.get(data.label);
4536
- const counter = ctx.nextBibciteCounter();
4537
- if (number === undefined) {
4538
- ctx.push(escapeHtml(data.label));
4539
- return;
5249
+
5250
+ // packages/render/src/elements/table/cell.ts
5251
+ function renderTableCell(ctx, cell) {
5252
+ const tag = cell.header ? "th" : "td";
5253
+ const attrStr = renderTableCellAttrs(cell);
5254
+ ctx.push(`<${tag}${attrStr}>`);
5255
+ renderElements(ctx, cell.elements);
5256
+ ctx.push(`</${tag}>`);
5257
+ }
5258
+
5259
+ // packages/render/src/elements/table/index.ts
5260
+ function renderTable(ctx, data) {
5261
+ const isPipeTable = data.attributes._source === "pipe";
5262
+ const classAttr = isPipeTable ? ' class="wiki-content-table"' : "";
5263
+ ctx.push(`<table${classAttr}${renderTableAttrs(data.attributes)}>`);
5264
+ for (const row of data.rows) {
5265
+ ctx.push(`<tr${renderTableAttrs(row.attributes)}>`);
5266
+ for (const cell of row.cells) {
5267
+ renderTableCell(ctx, cell);
5268
+ }
5269
+ ctx.push("</tr>");
4540
5270
  }
4541
- const idSuffix = generateIdSuffix(data.label, counter);
4542
- const id = ctx.generateId(`bibcite-${number}-`, idSuffix);
4543
- const bibitemId = ctx.generateId("bibitem-", number);
4544
- const onclick = `WIKIDOT.page.utils.scrollToReference('${bibitemId}')`;
4545
- ctx.push(`<a href="javascript:;" class="bibcite" id="${id}" onclick="${escapeAttr(onclick)}">`);
4546
- ctx.push(String(number));
4547
- ctx.push("</a>");
5271
+ ctx.push("</table>");
4548
5272
  }
4549
- function renderBibliographyBlock(ctx, data, renderElements2) {
4550
- if (data.hide)
4551
- return;
4552
- const title = data.title ?? "Bibliography";
4553
- ctx.push(`<div class="bibitems">`);
4554
- ctx.push(`<div class="title">${escapeHtml(title)}</div>`);
4555
- let index = 1;
4556
- for (const entry of data.entries) {
4557
- const itemId = ctx.generateId("bibitem-", index);
4558
- ctx.push(`<div class="bibitem" id="${itemId}">`);
4559
- ctx.push(`${index}. `);
4560
- renderElements2(ctx, entry.value);
5273
+
5274
+ // packages/render/src/elements/tab-view/ids.ts
5275
+ function getTabViewWidgetId(ctx, tabs, tabViewIndex) {
5276
+ const labelString = tabs.map((tab) => tab.label).join("");
5277
+ return ctx.generateFixedId(`wiki-tabview-${tabViewIndex}-${syncHashMd5(labelString)}`);
5278
+ }
5279
+ function getTabPanelId(ctx, tabViewIndex, tabIndex) {
5280
+ return ctx.generateId(`wiki-tab-${tabViewIndex}-`, tabIndex);
5281
+ }
5282
+
5283
+ // packages/render/src/elements/tab-view/panels.ts
5284
+ function renderTabPanels(ctx, tabs, tabViewIndex) {
5285
+ ctx.push(`<div class="yui-content">`);
5286
+ for (let i = 0;i < tabs.length; i++) {
5287
+ const tab = tabs[i];
5288
+ const displayStyle = i === 0 ? "" : ` style="display:none"`;
5289
+ ctx.push(`<div id="${getTabPanelId(ctx, tabViewIndex, i)}"${displayStyle}>`);
5290
+ renderElements(ctx, tab.elements);
4561
5291
  ctx.push("</div>");
4562
- index++;
4563
5292
  }
4564
5293
  ctx.push("</div>");
4565
5294
  }
4566
5295
 
4567
- // packages/render/src/elements/toc.ts
4568
- function extractLinkText(element) {
5296
+ // packages/render/src/elements/tab-view/navigation.ts
5297
+ function renderTabNavigation(ctx, tabs) {
5298
+ ctx.push(`<ul class="yui-nav">`);
5299
+ for (let i = 0;i < tabs.length; i++) {
5300
+ const tab = tabs[i];
5301
+ const selectedClass = i === 0 ? ` class="selected"` : "";
5302
+ ctx.push(`<li${selectedClass}>`);
5303
+ ctx.push(`<a href="javascript:;"><em>${escapeHtml(tab.label)}</em></a>`);
5304
+ ctx.push("</li>");
5305
+ }
5306
+ ctx.push("</ul>");
5307
+ }
5308
+
5309
+ // packages/render/src/elements/tab-view/index.ts
5310
+ function renderTabView(ctx, tabs) {
5311
+ const tabViewIndex = ctx.nextTabViewIndex();
5312
+ const widgetId = getTabViewWidgetId(ctx, tabs, tabViewIndex);
5313
+ ctx.push(`<div id="${widgetId}" class="yui-navset">`);
5314
+ renderTabNavigation(ctx, tabs);
5315
+ renderTabPanels(ctx, tabs, tabViewIndex);
5316
+ ctx.push("</div>");
5317
+ }
5318
+
5319
+ // packages/render/src/elements/text/email.ts
5320
+ function renderEmail(ctx, email) {
5321
+ if (!isValidEmail(email)) {
5322
+ ctx.pushEscaped(email);
5323
+ return;
5324
+ }
5325
+ ctx.push(`<a href="mailto:${escapeAttr(email)}">${escapeHtml(email)}</a>`);
5326
+ }
5327
+ // packages/render/src/elements/text/raw.ts
5328
+ function renderRaw(ctx, data) {
5329
+ if (data === "")
5330
+ return;
5331
+ ctx.push(`<span style="white-space: pre-wrap;">`);
5332
+ ctx.push(escapeHtml(data).replace(/ /g, "&#32;"));
5333
+ ctx.push("</span>");
5334
+ }
5335
+ // packages/render/src/elements/text/plain.ts
5336
+ function renderText(ctx, data) {
5337
+ ctx.pushEscaped(data);
5338
+ }
5339
+ // packages/render/src/elements/toc/link.ts
5340
+ function extractTocLink(element) {
4569
5341
  if (element.element !== "link")
4570
5342
  return null;
4571
5343
  const label = element.data.label;
@@ -4576,6 +5348,14 @@ function extractLinkText(element) {
4576
5348
  const href = typeof element.data.link === "string" ? element.data.link : "";
4577
5349
  return { href, text };
4578
5350
  }
5351
+ function rewriteTocAnchor(ctx, href) {
5352
+ const match = /^#toc(\d+)$/.exec(href);
5353
+ if (!match)
5354
+ return href;
5355
+ return `#${ctx.generateId("toc", Number(match[1]))}`;
5356
+ }
5357
+
5358
+ // packages/render/src/elements/toc/entries.ts
4579
5359
  function renderTocEntries(ctx, elements) {
4580
5360
  for (const element of elements) {
4581
5361
  if (element.element === "list") {
@@ -4588,16 +5368,10 @@ function renderTocList(ctx, listData, depth) {
4588
5368
  renderTocItem(ctx, item, depth);
4589
5369
  }
4590
5370
  }
4591
- function rewriteTocAnchor(ctx, href) {
4592
- const match = /^#toc(\d+)$/.exec(href);
4593
- if (!match)
4594
- return href;
4595
- return `#${ctx.generateId("toc", Number(match[1]))}`;
4596
- }
4597
5371
  function renderTocItem(ctx, item, depth) {
4598
5372
  if (item["item-type"] === "elements") {
4599
5373
  for (const el of item.elements) {
4600
- const link = extractLinkText(el);
5374
+ const link = extractTocLink(el);
4601
5375
  if (link) {
4602
5376
  const href = rewriteTocAnchor(ctx, link.href);
4603
5377
  ctx.push(`<div style="margin-left: ${depth}em;"><a href="${escapeAttr(href)}">${escapeHtml(link.text)}</a></div>`);
@@ -4607,222 +5381,122 @@ function renderTocItem(ctx, item, depth) {
4607
5381
  renderTocList(ctx, item.data, depth + 1);
4608
5382
  }
4609
5383
  }
4610
- function renderTableOfContents(ctx, data) {
4611
- const isFloat = data.align === "left" || data.align === "right";
4612
- if (!isFloat) {
5384
+
5385
+ // packages/render/src/elements/toc/body.ts
5386
+ function renderTocBody(ctx) {
5387
+ ctx.push(`<div id="toc-action-bar"><a href="javascript:;">Fold</a><a style="display: none" href="javascript:;">Unfold</a></div>`);
5388
+ ctx.push(`<div class="title">Table of Contents</div>`);
5389
+ ctx.push(`<div id="toc-list">`);
5390
+ renderTocEntries(ctx, ctx.tocElements);
5391
+ ctx.push("</div>");
5392
+ }
5393
+
5394
+ // packages/render/src/elements/toc/frame.ts
5395
+ function isFloatingToc(align) {
5396
+ return align === "left" || align === "right";
5397
+ }
5398
+ function openTocFrame(ctx, align) {
5399
+ if (!isFloatingToc(align)) {
4613
5400
  ctx.push(`<table style="margin:0; padding:0"><tr><td style="margin:0; padding:0">`);
4614
5401
  }
4615
- if (isFloat) {
4616
- const floatClass = data.align === "left" ? "floatleft" : "floatright";
5402
+ if (isFloatingToc(align)) {
5403
+ const floatClass = align === "left" ? "floatleft" : "floatright";
4617
5404
  ctx.push(`<div id="toc" class="${floatClass}">`);
4618
5405
  } else {
4619
5406
  ctx.push(`<div id="toc">`);
4620
5407
  }
4621
- ctx.push(`<div id="toc-action-bar"><a href="javascript:;">Fold</a><a style="display: none" href="javascript:;">Unfold</a></div>`);
4622
- ctx.push(`<div class="title">Table of Contents</div>`);
4623
- ctx.push(`<div id="toc-list">`);
4624
- renderTocEntries(ctx, ctx.tocElements);
4625
- ctx.push("</div>");
5408
+ }
5409
+ function closeTocFrame(ctx, align) {
4626
5410
  ctx.push("</div>");
4627
- if (!isFloat) {
5411
+ if (!isFloatingToc(align)) {
4628
5412
  ctx.push(`</td></tr></table>`);
4629
5413
  }
4630
5414
  }
4631
5415
 
4632
- // packages/render/src/elements/line-break.ts
4633
- function renderLineBreaks(ctx, count) {
4634
- for (let i = 0;i < count; i++) {
4635
- ctx.push("<br />");
4636
- }
4637
- }
4638
-
4639
- // packages/render/src/elements/clear-float.ts
4640
- function renderClearFloat(ctx, direction) {
4641
- ctx.push(`<div style="clear:${direction}; height: 0px; font-size: 1px"></div>`);
5416
+ // packages/render/src/elements/toc/index.ts
5417
+ function renderTableOfContents(ctx, data) {
5418
+ openTocFrame(ctx, data.align);
5419
+ renderTocBody(ctx);
5420
+ closeTocFrame(ctx, data.align);
4642
5421
  }
4643
5422
 
4644
- // packages/render/src/elements/iframe.ts
4645
- function renderIframe(ctx, data) {
4646
- const url = isDangerousUrl(data.url) ? "#invalid-url" : data.url;
4647
- const attrs = [`src="${escapeAttr(url)}"`];
4648
- const iframeAttrs = ["align", "frameborder", "height", "scrolling", "width", "class", "style"];
4649
- for (const attr of iframeAttrs) {
4650
- let value = data.attributes[attr] ?? "";
4651
- if (attr === "style") {
4652
- value = sanitizeStyleValue(value);
4653
- }
4654
- attrs.push(`${attr}="${escapeAttr(value)}"`);
4655
- }
4656
- ctx.push(`<p><iframe ${attrs.join(" ")}></iframe></p>`);
5423
+ // packages/render/src/elements/user/resolve.ts
5424
+ function getResolvedUser(ctx, username) {
5425
+ return ctx.options.resolvers?.user?.(username) ?? null;
4657
5426
  }
4658
5427
 
4659
- // packages/render/src/elements/html.ts
4660
- function generateDefaultUrl(pageName, contents) {
4661
- const hash = syncHashSha1(contents);
4662
- const nonce = BigInt(contents.length) * 1000000000n + BigInt(hash.charCodeAt(0)) * 100000000n;
4663
- const path = pageName ? `/${pageName}/html/${hash}-${nonce}` : `/html/${hash}-${nonce}`;
4664
- return path;
5428
+ // packages/render/src/elements/user/markup.ts
5429
+ function renderLinkedUser(ctx, username, user) {
5430
+ const displayName = user.name ?? username;
5431
+ const hrefAttr = user.url ? ` href="${escapeAttr(user.url)}"` : "";
5432
+ ctx.push(`<span class="printuser">`);
5433
+ ctx.push(`<a${hrefAttr}>`);
5434
+ ctx.push(escapeHtml(displayName));
5435
+ ctx.push("</a>");
5436
+ ctx.push("</span>");
4665
5437
  }
4666
- function renderHtmlBlock(ctx, data) {
4667
- if (ctx.settings.allowHtmlBlocks === false) {
4668
- return;
4669
- }
4670
- const index = ctx.nextHtmlBlockIndex();
4671
- const pageName = ctx.page?.pageName ?? "";
4672
- const callbackUrl = ctx.options.resolvers?.htmlBlockUrl?.(index);
4673
- const src = callbackUrl || generateDefaultUrl(pageName, data.contents);
4674
- const sandbox = ctx.options.htmlBlockSandbox;
4675
- const sandboxAttr = sandbox ? ` sandbox="${escapeAttr(sandbox)}"` : "";
4676
- const styleAttr = data.style ? ` style="${escapeAttr(sanitizeStyleValue(data.style))}"` : "";
4677
- ctx.push(`<p><iframe src="${escapeAttr(src)}"${sandboxAttr} allowtransparency="true" frameborder="0" class="html-block-iframe"${styleAttr}></iframe></p>`);
5438
+ function renderAvatarUser(ctx, username, user) {
5439
+ const displayName = user.name ?? username;
5440
+ const hrefAttr = user.url ? ` href="${escapeAttr(user.url)}"` : "";
5441
+ const avatarUrl = user.avatarUrl ?? "";
5442
+ const styleAttr = user.karmaUrl ? ` style="background-image:url(${escapeAttr(user.karmaUrl)})"` : "";
5443
+ ctx.push(`<span class="printuser avatarhover">`);
5444
+ ctx.push(`<a${hrefAttr}>`);
5445
+ ctx.push(`<img class="small" src="${escapeAttr(avatarUrl)}" alt="${escapeAttr(displayName)}"${styleAttr} />`);
5446
+ ctx.push("</a>");
5447
+ ctx.push(`<a${hrefAttr}>`);
5448
+ ctx.push(escapeHtml(displayName));
5449
+ ctx.push("</a>");
5450
+ ctx.push("</span>");
4678
5451
  }
4679
5452
 
4680
- // packages/render/src/elements/include.ts
4681
- function renderInclude(ctx, data) {
4682
- if (data.elements.length === 0) {
4683
- const pageName = data.location.page.toLowerCase();
4684
- const encodedPageName = pageName.replace(/[^a-z0-9\-_:/]/g, (c) => encodeURIComponent(c));
4685
- const safePath = encodedPageName.startsWith("/") ? encodedPageName.slice(1) : encodedPageName;
4686
- ctx.push(`<div class="error-block"><p>Included page "${escapeHtml(pageName)}" does not exist (<a href="/${escapeAttr(safePath)}/edit/true">create it now</a>)</p></div>`);
5453
+ // packages/render/src/elements/user/index.ts
5454
+ function renderUser(ctx, data) {
5455
+ const normalized = data.name.toLowerCase().trim();
5456
+ if (normalized === "anonymous") {
5457
+ ctx.push("Anonymous");
4687
5458
  return;
4688
5459
  }
4689
- renderElements(ctx, data.elements);
4690
- }
4691
-
4692
- // packages/render/src/elements/iftags.ts
4693
- function evaluateIfTagsCondition(condition, pageTags) {
4694
- const pageTagSet = new Set(pageTags.map((t) => t.toLowerCase()));
4695
- const tokens = condition.split(/\s+/).filter(Boolean);
4696
- if (tokens.length === 0) {
4697
- return false;
4698
- }
4699
- const required = [];
4700
- const excluded = [];
4701
- const optional = [];
4702
- for (const token of tokens) {
4703
- if (token.startsWith("+")) {
4704
- const tag = token.slice(1).toLowerCase();
4705
- if (tag)
4706
- required.push(tag);
4707
- } else if (token.startsWith("-")) {
4708
- const tag = token.slice(1).toLowerCase();
4709
- if (tag)
4710
- excluded.push(tag);
4711
- } else {
4712
- optional.push(token.toLowerCase());
4713
- }
4714
- }
4715
- if (required.length === 0 && excluded.length === 0 && optional.length === 0) {
4716
- return false;
4717
- }
4718
- for (const tag of required) {
4719
- if (!pageTagSet.has(tag))
4720
- return false;
4721
- }
4722
- for (const tag of excluded) {
4723
- if (pageTagSet.has(tag))
4724
- return false;
4725
- }
4726
- if (optional.length > 0) {
4727
- const hasAnyOptional = optional.some((tag) => pageTagSet.has(tag));
4728
- if (!hasAnyOptional)
4729
- return false;
5460
+ const resolved = getResolvedUser(ctx, data.name);
5461
+ if (resolved === null) {
5462
+ ctx.push(escapeHtml(data.name));
5463
+ return;
4730
5464
  }
4731
- return true;
4732
- }
4733
- function renderIfTags(ctx, data) {
4734
- const pageTags = ctx.page?.tags ?? [];
4735
- if (evaluateIfTagsCondition(data.condition, pageTags)) {
4736
- const prev = ctx.renderInlineStyles;
4737
- ctx.renderInlineStyles = true;
4738
- const slotId = data._styleSlot;
4739
- if (slotId !== undefined) {
4740
- ctx.enterStyleSlot(slotId);
4741
- }
4742
- renderElements(ctx, data.elements);
4743
- if (slotId !== undefined) {
4744
- ctx.exitStyleSlot();
4745
- }
4746
- ctx.renderInlineStyles = prev;
5465
+ const showAvatar = data["show-avatar"] && resolved.url && resolved.avatarUrl;
5466
+ if (showAvatar) {
5467
+ renderAvatarUser(ctx, data.name, resolved);
5468
+ } else {
5469
+ renderLinkedUser(ctx, data.name, resolved);
4747
5470
  }
4748
5471
  }
4749
5472
 
4750
- // packages/render/src/elements/color.ts
4751
- function renderColor(ctx, data) {
4752
- const safeColor = sanitizeCssColor(data.color, "inherit");
4753
- ctx.push(`<span style="color: ${escapeAttr(safeColor)}">`);
4754
- renderElements(ctx, data.elements);
4755
- ctx.push("</span>");
4756
- }
4757
-
4758
- // packages/render/src/elements/date.ts
4759
- function renderDate(ctx, data) {
4760
- const date = new Date(data.value.timestamp * 1000);
4761
- const formatted = data.format ? formatDate(date, data.format) : date.toLocaleString();
4762
- if (data.hover) {
4763
- ctx.push(`<span class="odate">${escapeHtml(formatted)}</span>`);
4764
- } else {
4765
- ctx.push(escapeHtml(formatted));
4766
- }
5473
+ // packages/render/src/render/primitives.ts
5474
+ function renderTextNode(ctx, text) {
5475
+ ctx.pushEscaped(text);
4767
5476
  }
4768
- function formatDate(date, format) {
4769
- return format.replace(/%Y/g, String(date.getFullYear())).replace(/%m/g, String(date.getMonth() + 1).padStart(2, "0")).replace(/%d/g, String(date.getDate()).padStart(2, "0")).replace(/%H/g, String(date.getHours()).padStart(2, "0")).replace(/%M/g, String(date.getMinutes()).padStart(2, "0")).replace(/%S/g, String(date.getSeconds()).padStart(2, "0"));
5477
+ function renderLineBreak(ctx) {
5478
+ ctx.push("<br />");
4770
5479
  }
4771
-
4772
- // packages/render/src/elements/expr.ts
4773
- import { evaluateExpression, formatExprValue, isTruthy } from "@wdprlib/ast";
4774
- function renderExpr(ctx, data) {
4775
- const result = evaluateExpression(data.expression);
4776
- if (result.success) {
4777
- ctx.pushEscaped(formatExprValue(result.value));
4778
- } else if (result.error !== "empty expression") {
4779
- ctx.pushEscaped(`run-time error: ${result.error}`);
4780
- }
5480
+ function renderHorizontalRule(ctx) {
5481
+ ctx.push("<hr />");
4781
5482
  }
4782
- function renderIf(ctx, data) {
4783
- const elements = isTruthy(data.condition) ? data.then : data.else;
4784
- renderBranchElements(ctx, elements);
5483
+ function renderContentSeparator(ctx) {
5484
+ ctx.push(`<div class="content-separator" style="display: none:"></div>`);
4785
5485
  }
4786
- function renderIfExpr(ctx, data) {
4787
- const result = evaluateExpression(data.expression);
4788
- if (!result.success) {
4789
- ctx.pushEscaped(`run-time error: ${result.error}`);
5486
+
5487
+ // packages/render/src/render/style.ts
5488
+ function renderStyleElement(ctx, css) {
5489
+ if (!ctx.renderInlineStyles || !ctx.settings.allowStyleElements) {
4790
5490
  return;
4791
5491
  }
4792
- const elements = result.value !== 0 ? data.then : data.else;
4793
- renderBranchElements(ctx, elements);
4794
- }
4795
- function renderBranchElements(ctx, elements) {
4796
- let lastIdx = elements.length - 1;
4797
- while (lastIdx >= 0) {
4798
- const el = elements[lastIdx];
4799
- if (el.element === "text" && typeof el.data === "string" && el.data.trim() === "") {
4800
- lastIdx--;
4801
- } else {
4802
- break;
4803
- }
5492
+ if (ctx.hasActiveStyleSlot()) {
5493
+ ctx.pushToStyleSlot(css);
5494
+ return;
4804
5495
  }
4805
- renderElements(ctx, elements.slice(0, lastIdx + 1));
5496
+ pushStyleTag(ctx, css);
4806
5497
  }
4807
5498
 
4808
- // packages/render/src/render.ts
4809
- function renderToHtml(tree, options = {}) {
4810
- const ctx = new RenderContext(tree, options);
4811
- renderElements(ctx, tree.elements);
4812
- if (ctx.settings.allowStyleElements && tree.styles?.length) {
4813
- for (const style of tree.styles) {
4814
- if (style.startsWith(STYLE_SLOT_PREFIX)) {
4815
- const slotId = parseInt(style.slice(STYLE_SLOT_PREFIX.length), 10);
4816
- for (const css of ctx.getStyleSlotContents(slotId)) {
4817
- ctx.push(`<style>${escapeStyleContent(css)}</style>`);
4818
- }
4819
- } else {
4820
- ctx.push(`<style>${escapeStyleContent(style)}</style>`);
4821
- }
4822
- }
4823
- }
4824
- return ctx.getOutput();
4825
- }
5499
+ // packages/render/src/render/dispatch.ts
4826
5500
  function renderElements(ctx, elements) {
4827
5501
  for (const element of elements) {
4828
5502
  renderElement(ctx, element);
@@ -4831,7 +5505,7 @@ function renderElements(ctx, elements) {
4831
5505
  function renderElement(ctx, element) {
4832
5506
  switch (element.element) {
4833
5507
  case "text":
4834
- ctx.pushEscaped(element.data);
5508
+ renderTextNode(ctx, element.data);
4835
5509
  break;
4836
5510
  case "raw":
4837
5511
  renderRaw(ctx, element.data);
@@ -4930,16 +5604,10 @@ function renderElement(ctx, element) {
4930
5604
  renderIfTags(ctx, element.data);
4931
5605
  break;
4932
5606
  case "style":
4933
- if (ctx.renderInlineStyles && ctx.settings.allowStyleElements) {
4934
- if (ctx.hasActiveStyleSlot()) {
4935
- ctx.pushToStyleSlot(element.data);
4936
- } else {
4937
- ctx.push(`<style>${escapeStyleContent(element.data)}</style>`);
4938
- }
4939
- }
5607
+ renderStyleElement(ctx, element.data);
4940
5608
  break;
4941
5609
  case "line-break":
4942
- ctx.push("<br />");
5610
+ renderLineBreak(ctx);
4943
5611
  break;
4944
5612
  case "line-breaks":
4945
5613
  renderLineBreaks(ctx, element.data);
@@ -4948,10 +5616,10 @@ function renderElement(ctx, element) {
4948
5616
  renderClearFloat(ctx, element.data);
4949
5617
  break;
4950
5618
  case "horizontal-rule":
4951
- ctx.push("<hr />");
5619
+ renderHorizontalRule(ctx);
4952
5620
  break;
4953
5621
  case "content-separator":
4954
- ctx.push(`<div class="content-separator" style="display: none:"></div>`);
5622
+ renderContentSeparator(ctx);
4955
5623
  break;
4956
5624
  case "expr":
4957
5625
  renderExpr(ctx, element.data);
@@ -4968,6 +5636,14 @@ function renderElement(ctx, element) {
4968
5636
  }
4969
5637
  }
4970
5638
 
5639
+ // packages/render/src/render/index.ts
5640
+ function renderToHtml(tree, options = {}) {
5641
+ const ctx = new RenderContext(tree, options);
5642
+ renderElements(ctx, tree.elements);
5643
+ renderCollectedStyles(ctx, tree.styles);
5644
+ return ctx.getOutput();
5645
+ }
5646
+
4971
5647
  // packages/render/src/index.ts
4972
5648
  import { createSettings, DEFAULT_SETTINGS as DEFAULT_SETTINGS2 } from "@wdprlib/ast";
4973
5649
  export {