@wdprlib/render 2.1.0 → 3.0.1

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