@wdprlib/render 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/index.cjs +2332 -2032
  2. package/dist/index.d.cts +15 -13
  3. package/dist/index.d.ts +15 -13
  4. package/dist/index.js +2336 -2036
  5. package/package.json +5 -3
  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/clear-float.ts +27 -0
  20. package/src/elements/code/contents.ts +18 -0
  21. package/src/elements/code/index.ts +29 -0
  22. package/src/elements/collapsible/index.ts +31 -0
  23. package/src/elements/collapsible/labels.ts +35 -0
  24. package/src/elements/collapsible/link.ts +11 -0
  25. package/src/elements/collapsible/sections.ts +39 -0
  26. package/src/elements/color.ts +32 -0
  27. package/src/elements/container/attributes.ts +28 -0
  28. package/src/elements/container/header.ts +27 -0
  29. package/src/elements/container/index.ts +35 -0
  30. package/src/elements/container/string-container.ts +40 -0
  31. package/src/elements/container/string-types.ts +63 -0
  32. package/src/elements/container/wrappers.ts +32 -0
  33. package/src/elements/date/format.ts +20 -0
  34. package/src/elements/date/index.ts +34 -0
  35. package/src/elements/date/output.ts +6 -0
  36. package/src/elements/embed/iframe.ts +8 -0
  37. package/src/elements/embed/index.ts +28 -0
  38. package/src/elements/embed/providers.ts +43 -0
  39. package/src/elements/embed/validation.ts +15 -0
  40. package/src/elements/embed-block/allowlist.ts +60 -0
  41. package/src/elements/embed-block/boolean-attributes.ts +38 -0
  42. package/src/elements/embed-block/iframe.ts +33 -0
  43. package/src/elements/embed-block/index.ts +31 -0
  44. package/src/elements/embed-block/sanitize-config.ts +22 -0
  45. package/src/elements/embed-block/sanitize.ts +44 -0
  46. package/src/elements/expr/branch.ts +29 -0
  47. package/src/elements/expr/index.ts +63 -0
  48. package/src/elements/expr/result.ts +19 -0
  49. package/src/elements/footnote/body.ts +11 -0
  50. package/src/elements/footnote/index.ts +35 -0
  51. package/src/elements/footnote/ref.ts +16 -0
  52. package/src/elements/html/attributes.ts +24 -0
  53. package/src/elements/html/index.ts +39 -0
  54. package/src/elements/html/url.ts +19 -0
  55. package/src/elements/iframe/attributes.ts +28 -0
  56. package/src/elements/iframe/index.ts +22 -0
  57. package/src/elements/iftags/condition.ts +42 -0
  58. package/src/elements/iftags/index.ts +39 -0
  59. package/src/elements/iftags/style-slot.ts +23 -0
  60. package/src/elements/iftags/tokens.ts +36 -0
  61. package/src/elements/image/alignment.ts +44 -0
  62. package/src/elements/image/attributes.ts +10 -0
  63. package/src/elements/image/img-attributes.ts +26 -0
  64. package/src/elements/image/index.ts +36 -0
  65. package/src/elements/image/link-href.ts +24 -0
  66. package/src/elements/image/link.ts +13 -0
  67. package/src/elements/image/source.ts +16 -0
  68. package/src/elements/include/index.ts +35 -0
  69. package/src/elements/include/missing.ts +15 -0
  70. package/src/elements/index.ts +35 -0
  71. package/src/elements/line-break.ts +22 -0
  72. package/src/elements/link/anchor-name.ts +6 -0
  73. package/src/elements/link/anchor.ts +27 -0
  74. package/src/elements/link/attributes.ts +47 -0
  75. package/src/elements/link/index.ts +26 -0
  76. package/src/elements/link/label.ts +23 -0
  77. package/src/elements/link/target.ts +20 -0
  78. package/src/elements/list/attributes.ts +19 -0
  79. package/src/elements/list/definition-list.ts +16 -0
  80. package/src/elements/list/index.ts +48 -0
  81. package/src/elements/list/item-rendering.ts +38 -0
  82. package/src/elements/list/items.ts +61 -0
  83. package/src/elements/list/no-marker.ts +53 -0
  84. package/src/elements/list/paragraphs.ts +34 -0
  85. package/src/elements/list/trim.ts +38 -0
  86. package/src/elements/math/block.ts +29 -0
  87. package/src/elements/math/equation-ref.ts +12 -0
  88. package/src/elements/math/index.ts +14 -0
  89. package/src/elements/math/inline.ts +19 -0
  90. package/src/elements/math/latex.ts +27 -0
  91. package/src/elements/math/source.ts +18 -0
  92. package/src/elements/module/backlinks.ts +29 -0
  93. package/src/elements/module/categories.ts +27 -0
  94. package/src/elements/module/empty-container.ts +10 -0
  95. package/src/elements/module/index.ts +65 -0
  96. package/src/elements/module/join-markup.ts +10 -0
  97. package/src/elements/module/join.ts +28 -0
  98. package/src/elements/module/listpages.ts +27 -0
  99. package/src/elements/module/listusers.ts +27 -0
  100. package/src/elements/module/page-tree.ts +27 -0
  101. package/src/elements/module/rate-markup.ts +10 -0
  102. package/src/elements/module/rate.ts +35 -0
  103. package/src/elements/module/unknown.ts +11 -0
  104. package/src/elements/tab-view/ids.ts +16 -0
  105. package/src/elements/tab-view/index.ts +31 -0
  106. package/src/elements/tab-view/navigation.ts +15 -0
  107. package/src/elements/tab-view/panels.ts +16 -0
  108. package/src/elements/table/attributes.ts +23 -0
  109. package/src/elements/table/cell-attributes.ts +62 -0
  110. package/src/elements/table/cell.ts +13 -0
  111. package/src/elements/table/index.ts +27 -0
  112. package/src/elements/text/email.ts +20 -0
  113. package/src/elements/text/index.ts +11 -0
  114. package/src/elements/text/plain.ts +11 -0
  115. package/src/elements/text/raw.ts +20 -0
  116. package/src/elements/toc/body.ts +12 -0
  117. package/src/elements/toc/entries.ts +34 -0
  118. package/src/elements/toc/frame.ts +27 -0
  119. package/src/elements/toc/index.ts +17 -0
  120. package/src/elements/toc/link.ts +26 -0
  121. package/src/elements/user/index.ts +40 -0
  122. package/src/elements/user/markup.ts +34 -0
  123. package/src/elements/user/resolve.ts +6 -0
  124. package/src/escape/attribute-allowlists.ts +101 -0
  125. package/src/escape/attributes.ts +62 -0
  126. package/src/escape/css-color-functions.ts +18 -0
  127. package/src/escape/css-colors.ts +183 -0
  128. package/src/escape/css-danger.ts +22 -0
  129. package/src/escape/css-normalize.ts +54 -0
  130. package/src/escape/css-style.ts +78 -0
  131. package/src/escape/css-urls.ts +76 -0
  132. package/src/escape/css.ts +4 -0
  133. package/src/escape/email.ts +22 -0
  134. package/src/escape/html.ts +68 -0
  135. package/src/escape/index.ts +15 -0
  136. package/src/escape/url.ts +18 -0
  137. package/src/hash.ts +62 -0
  138. package/src/index.ts +26 -0
  139. package/src/libs/highlighter/engine/end-pattern.ts +26 -0
  140. package/src/libs/highlighter/engine/html.ts +19 -0
  141. package/src/libs/highlighter/engine/index.ts +3 -0
  142. package/src/libs/highlighter/engine/keywords.ts +22 -0
  143. package/src/libs/highlighter/engine/parts.ts +36 -0
  144. package/src/libs/highlighter/engine/preprocess.ts +10 -0
  145. package/src/libs/highlighter/engine/render.ts +31 -0
  146. package/src/libs/highlighter/engine/token.ts +7 -0
  147. package/src/libs/highlighter/engine/tokenizer.ts +266 -0
  148. package/src/libs/highlighter/engine/utils.ts +38 -0
  149. package/src/libs/highlighter/index.ts +70 -0
  150. package/src/libs/highlighter/languages/cpp.ts +345 -0
  151. package/src/libs/highlighter/languages/css.ts +104 -0
  152. package/src/libs/highlighter/languages/diff.ts +154 -0
  153. package/src/libs/highlighter/languages/dtd.ts +99 -0
  154. package/src/libs/highlighter/languages/html.ts +59 -0
  155. package/src/libs/highlighter/languages/java.ts +251 -0
  156. package/src/libs/highlighter/languages/javascript.ts +213 -0
  157. package/src/libs/highlighter/languages/php.ts +433 -0
  158. package/src/libs/highlighter/languages/python.ts +308 -0
  159. package/src/libs/highlighter/languages/ruby.ts +360 -0
  160. package/src/libs/highlighter/languages/sql.ts +125 -0
  161. package/src/libs/highlighter/languages/xml.ts +68 -0
  162. package/src/libs/highlighter/types.ts +44 -0
  163. package/src/render/collected-styles.ts +22 -0
  164. package/src/render/dispatch.ts +181 -0
  165. package/src/render/index.ts +28 -0
  166. package/src/render/primitives.ts +17 -0
  167. package/src/render/style-tag.ts +6 -0
  168. package/src/render/style.ts +15 -0
  169. package/src/types.ts +144 -0
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_ast4.createSettings,
48
- DEFAULT_SETTINGS: () => import_ast4.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_ast3 = 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;
3865
+ // packages/render/src/elements/code/contents.ts
3866
+ function renderCodeContents(data) {
3867
+ if (data.language) {
3868
+ const highlighted = highlight(data.contents, data.language);
3869
+ if (highlighted) {
3870
+ return highlighted;
3871
+ }
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));
3884
+ }
3885
+ ctx.push("</div>");
3886
+ }
3887
+
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
+ };
3894
+ }
3895
+ function formatCollapsibleText(prefix, text) {
3896
+ const encoded = escapeHtml(text).replace(/ /g, "&nbsp;");
3897
+ return `${prefix}&nbsp;${encoded}`;
3898
+ }
3899
+ function formatLabelText(text) {
3900
+ return escapeHtml(text).replace(/ /g, "&nbsp;");
3901
+ }
3902
+
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);
3925
+ }
3926
+ ctx.push(`<div class="collapsible-block-content">`);
3927
+ renderElements(ctx, data.elements);
3928
+ ctx.push("</div>");
3929
+ if (data["show-bottom"]) {
3930
+ renderHideLink(ctx, labels.hide);
3931
+ }
3932
+ ctx.push("</div>");
3933
+ }
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>");
3943
+ }
3944
+
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;
3963
+ }
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)) {
4057
+ return;
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};">`);
4088
+ renderElements(ctx, elements);
4089
+ ctx.push("</div>");
4090
+ return;
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>`);
4127
+ ctx.push("</div>");
4128
+ }
4129
+
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)) {
4205
+ return false;
4206
+ }
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;
4072
4800
  }
4073
- if (data.language) {
4074
- const highlighted = highlight(data.contents, data.language);
4075
- if (highlighted) {
4076
- ctx.push(highlighted);
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);
4077
4827
  } else {
4078
- ctx.push(`<pre><code>${escapeHtml(data.contents)}</code></pre>`);
4828
+ ctx.pushEscaped(data.link.page);
4079
4829
  }
4080
- } else {
4081
- ctx.push(`<pre><code>${escapeHtml(data.contents)}</code></pre>`);
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);
4082
4839
  }
4083
- ctx.push("</div>");
4084
4840
  }
4085
4841
 
4086
- // packages/render/src/hash.ts
4087
- function syncHashSha1(input) {
4088
- return fnv1aHash(input, 40);
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>");
4089
4848
  }
4090
- function syncHashMd5(input) {
4091
- return fnv1aHash(input, 32);
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>");
4092
4862
  }
4093
- function fnv1aHash(input, hexLen) {
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);
4094
4874
  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);
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;
4101
4898
  }
4102
- result += (h >>> 0).toString(16).padStart(8, "0");
4899
+ nextIndex++;
4900
+ renderNestedList(nextItem.data);
4103
4901
  }
4104
- return result.substring(0, hexLen);
4902
+ return nextIndex;
4903
+ }
4904
+ function getSubListItem(item) {
4905
+ return item?.["item-type"] === "sub-list" ? item : null;
4105
4906
  }
4106
4907
 
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>");
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
+ }
4120
4915
  }
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>");
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
+ }
4130
4927
  }
4131
- ctx.push("</div>");
4132
- ctx.push("</div>");
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);
4133
4956
  }
4134
- function md5Hash(input) {
4135
- return syncHashMd5(input);
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;
4136
4964
  }
4137
4965
 
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>");
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
+ }
4144
4986
  }
4145
- function renderFootnoteBlock(ctx, data) {
4146
- if (ctx.footnotes.length === 0)
4987
+ function renderNoMarkerParagraph(ctx, element, index, firstParagraphIdx, lastParagraphIdx) {
4988
+ if (index === firstParagraphIdx || index === lastParagraphIdx && isLiCloseTextParagraph(element)) {
4989
+ renderElements(ctx, element.data.elements);
4147
4990
  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>. `);
4157
- renderElements(ctx, elements);
4158
- ctx.push("</div>");
4159
4991
  }
4160
- ctx.push("</div>");
4992
+ ctx.push("<p>");
4993
+ renderElements(ctx, element.data.elements);
4994
+ ctx.push("</p>");
4161
4995
  }
4162
4996
 
4163
- // packages/render/src/elements/math.ts
4164
- var import_temml = __toESM(require("temml"));
4165
- function needsAlignedWrapper(latex) {
4166
- if (/\\begin\s*\{/.test(latex)) {
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
+ }
4167
5036
  return false;
5037
+ });
5038
+ if (!hasContent) {
5039
+ return;
4168
5040
  }
4169
- const withoutEscaped = latex.replace(/\\&/g, "");
4170
- return withoutEscaped.includes("&");
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
- ctx.push(`$</span>`);
4227
- }
4228
- ctx.push(`</span>`);
5118
+ 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);
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(" ") : "";
4333
5242
  }
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;
5243
+ function appendColumnSpan(attrs, cell) {
5244
+ if (cell["column-span"] > 1) {
5245
+ attrs.push(`colspan="${cell["column-span"]}"`);
4348
5246
  }
4349
5247
  }
4350
- function renderYoutube(ctx, videoId) {
4351
- if (!isValidVideoId(videoId)) {
4352
- ctx.push(`<!-- Invalid YouTube video ID -->`);
5248
+ function appendRowSpan(attrs, safeCellAttrs) {
5249
+ if (!safeCellAttrs.rowspan) {
4353
5250
  return;
4354
5251
  }
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;
5252
+ const rowspan = parseInt(safeCellAttrs.rowspan, 10);
5253
+ if (rowspan > 1) {
5254
+ attrs.push(`rowspan="${rowspan}"`);
4363
5255
  }
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
5256
  }
4368
- function renderGithubGist(ctx, username, hash) {
4369
- if (!isValidGithubUsername(username) || !isValidGistHash(hash)) {
4370
- ctx.push(`<!-- Invalid GitHub Gist parameters -->`);
5257
+ function appendAlignmentStyle(attrs, cell, safeCellAttrs) {
5258
+ if (!cell.align) {
4371
5259
  return;
4372
5260
  }
4373
- ctx.push(`<script src="https://gist.github.com/${escapeAttr(username)}/${escapeAttr(hash)}.js"></script>`);
5261
+ const existingStyle = safeCellAttrs.style ?? "";
5262
+ const alignStyle = `text-align: ${cell.align};`;
5263
+ if (existingStyle) {
5264
+ attrs.push(`style="${escapeAttr(existingStyle + "; " + alignStyle)}"`);
5265
+ } else {
5266
+ attrs.push(`style="${alignStyle}"`);
5267
+ }
4374
5268
  }
4375
- function renderGitlabSnippet(ctx, snippetId) {
4376
- if (!isValidGitlabSnippetId(snippetId)) {
4377
- ctx.push(`<!-- Invalid GitLab snippet ID -->`);
4378
- return;
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)}"`);
4379
5277
  }
4380
- ctx.push(`<script src="https://gitlab.com/snippets/${escapeAttr(snippetId)}.js"></script>`);
4381
5278
  }
4382
5279
 
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
- }
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;
4452
5287
  }
4453
5288
  }
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);
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)}"`;
4463
5298
  }
4464
- return lowerHostname === lowerPattern;
5299
+ return result;
4465
5300
  }
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;
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}>`);
4484
5309
  }
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;
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);
4517
5320
  }
5321
+ ctx.push("</tr>");
4518
5322
  }
4519
- return sanitized;
5323
+ ctx.push("</table>");
4520
5324
  }
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;
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)}`);
4530
5330
  }
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;
4537
- }
4538
- const normalized = normalizeBooleanAttributes(sanitized);
4539
- ctx.push(normalized);
5331
+ function getTabPanelId(ctx, tabViewIndex, tabIndex) {
5332
+ return ctx.generateId(`wiki-tab-${tabViewIndex}-`, tabIndex);
4540
5333
  }
4541
5334
 
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");
4547
- return;
4548
- }
4549
- const resolved = ctx.options.resolvers?.user?.(data.name) ?? null;
4550
- if (resolved === null) {
4551
- ctx.push(escapeHtml(data.name));
4552
- return;
4553
- }
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>");
4567
- } 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>");
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);
5343
+ ctx.push("</div>");
4573
5344
  }
5345
+ ctx.push("</div>");
4574
5346
  }
4575
5347
 
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);
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>");
4583
5357
  }
4584
- return (h >>> 0).toString(16).slice(0, 6);
5358
+ ctx.push("</ul>");
4585
5359
  }
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));
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);
4591
5375
  return;
4592
5376
  }
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>");
5377
+ ctx.push(`<a href="mailto:${escapeAttr(email)}">${escapeHtml(email)}</a>`);
4600
5378
  }
4601
- function renderBibliographyBlock(ctx, data, renderElements2) {
4602
- if (data.hide)
5379
+ // packages/render/src/elements/text/raw.ts
5380
+ function renderRaw(ctx, data) {
5381
+ if (data === "")
4603
5382
  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);
4613
- ctx.push("</div>");
4614
- index++;
4615
- }
4616
- ctx.push("</div>");
5383
+ ctx.push(`<span style="white-space: pre-wrap;">`);
5384
+ ctx.push(escapeHtml(data).replace(/ /g, "&#32;"));
5385
+ ctx.push("</span>");
4617
5386
  }
4618
-
4619
- // packages/render/src/elements/toc.ts
4620
- function extractLinkText(element) {
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,598 +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;
4782
- }
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;
5512
+ const resolved = getResolvedUser(ctx, data.name);
5513
+ if (resolved === null) {
5514
+ ctx.push(escapeHtml(data.name));
5515
+ return;
4799
5516
  }
4800
- }
4801
-
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>`);
5517
+ const showAvatar = data["show-avatar"] && resolved.url && resolved.avatarUrl;
5518
+ if (showAvatar) {
5519
+ renderAvatarUser(ctx, data.name, resolved);
4816
5520
  } else {
4817
- ctx.push(escapeHtml(formatted));
5521
+ renderLinkedUser(ctx, data.name, resolved);
4818
5522
  }
4819
5523
  }
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"));
4822
- }
4823
5524
 
4824
- // packages/render/src/utils/expr-eval.ts
4825
- var FALSE_VALUES = new Set(["false", "null", "", "0"]);
4826
- function isTruthy(value) {
4827
- return !FALSE_VALUES.has(value.toLowerCase().trim());
4828
- }
4829
- var MAX_EXPRESSION_LENGTH = 256;
4830
- function isTruthyNum(n) {
4831
- return n !== 0 && !Number.isNaN(n);
5525
+ // packages/render/src/render/primitives.ts
5526
+ function renderTextNode(ctx, text) {
5527
+ ctx.pushEscaped(text);
4832
5528
  }
4833
- function evaluateExpression(expr) {
4834
- try {
4835
- if (expr.length > MAX_EXPRESSION_LENGTH) {
4836
- return { success: false, error: "expression too long" };
4837
- }
4838
- if (expr.trim() === "") {
4839
- return { success: false, error: "empty expression" };
4840
- }
4841
- const tokens = tokenize2(expr);
4842
- if (tokens.length <= 1) {
4843
- return { success: false, error: "empty expression" };
4844
- }
4845
- const parser = new ExprParser(tokens);
4846
- const result = parser.parse();
4847
- if (!Number.isFinite(result)) {
4848
- return { success: false, error: "division by zero" };
4849
- }
4850
- return { success: true, value: result };
4851
- } catch (e) {
4852
- const msg = e instanceof Error ? e.message : "unknown error";
4853
- return { success: false, error: msg };
4854
- }
5529
+ function renderLineBreak(ctx) {
5530
+ ctx.push("<br />");
4855
5531
  }
4856
- function tokenize2(expr) {
4857
- const tokens = [];
4858
- let i = 0;
4859
- while (i < expr.length) {
4860
- const ch = expr[i];
4861
- if (/\s/.test(ch)) {
4862
- i++;
4863
- continue;
4864
- }
4865
- if (/\d/.test(ch) || ch === "." && /\d/.test(expr[i + 1] ?? "")) {
4866
- let numStr = "";
4867
- let hasDot = false;
4868
- while (i < expr.length) {
4869
- const c = expr[i];
4870
- if (c === ".") {
4871
- if (hasDot)
4872
- break;
4873
- hasDot = true;
4874
- } else if (!/\d/.test(c)) {
4875
- break;
4876
- }
4877
- numStr += c;
4878
- i++;
4879
- }
4880
- const num = parseFloat(numStr);
4881
- if (!Number.isFinite(num)) {
4882
- throw new Error("Invalid number");
4883
- }
4884
- tokens.push({ kind: "NUMBER", value: num });
4885
- continue;
4886
- }
4887
- if (/[a-zA-Z_]/.test(ch)) {
4888
- let id = "";
4889
- while (i < expr.length) {
4890
- const c = expr[i];
4891
- if (!/[a-zA-Z0-9_]/.test(c))
4892
- break;
4893
- id += c;
4894
- i++;
4895
- }
4896
- tokens.push({ kind: "IDENTIFIER", value: id.toLowerCase() });
4897
- continue;
4898
- }
4899
- if (ch === "<" && expr[i + 1] === "=") {
4900
- tokens.push({ kind: "LE", value: "<=" });
4901
- i += 2;
4902
- continue;
4903
- }
4904
- if (ch === ">" && expr[i + 1] === "=") {
4905
- tokens.push({ kind: "GE", value: ">=" });
4906
- i += 2;
4907
- continue;
4908
- }
4909
- if (ch === "!" && expr[i + 1] === "=") {
4910
- tokens.push({ kind: "NE", value: "!=" });
4911
- i += 2;
4912
- continue;
4913
- }
4914
- if (ch === "<" && expr[i + 1] === ">") {
4915
- tokens.push({ kind: "NE", value: "<>" });
4916
- i += 2;
4917
- continue;
4918
- }
4919
- switch (ch) {
4920
- case "+":
4921
- tokens.push({ kind: "PLUS", value: "+" });
4922
- break;
4923
- case "-":
4924
- tokens.push({ kind: "MINUS", value: "-" });
4925
- break;
4926
- case "*":
4927
- tokens.push({ kind: "STAR", value: "*" });
4928
- break;
4929
- case "/":
4930
- tokens.push({ kind: "SLASH", value: "/" });
4931
- break;
4932
- case "%":
4933
- tokens.push({ kind: "PERCENT", value: "%" });
4934
- break;
4935
- case "^":
4936
- tokens.push({ kind: "CARET", value: "^" });
4937
- break;
4938
- case "(":
4939
- tokens.push({ kind: "LPAREN", value: "(" });
4940
- break;
4941
- case ")":
4942
- tokens.push({ kind: "RPAREN", value: ")" });
4943
- break;
4944
- case ",":
4945
- tokens.push({ kind: "COMMA", value: "," });
4946
- break;
4947
- case "<":
4948
- tokens.push({ kind: "LT", value: "<" });
4949
- break;
4950
- case ">":
4951
- tokens.push({ kind: "GT", value: ">" });
4952
- break;
4953
- case "=":
4954
- tokens.push({ kind: "EQ", value: "=" });
4955
- break;
4956
- default:
4957
- throw new Error(`Unknown character: ${ch}`);
4958
- }
4959
- i++;
4960
- }
4961
- tokens.push({ kind: "EOF", value: "" });
4962
- return tokens;
5532
+ function renderHorizontalRule(ctx) {
5533
+ ctx.push("<hr />");
4963
5534
  }
4964
-
4965
- class ExprParser {
4966
- tokens;
4967
- pos = 0;
4968
- constructor(tokens) {
4969
- this.tokens = tokens;
4970
- }
4971
- parse() {
4972
- const result = this.parseOr();
4973
- if (this.current().kind !== "EOF") {
4974
- throw new Error("too many values in the stack");
4975
- }
4976
- return result;
4977
- }
4978
- current() {
4979
- return this.tokens[this.pos] ?? { kind: "EOF", value: "" };
4980
- }
4981
- advance() {
4982
- const token = this.current();
4983
- this.pos++;
4984
- return token;
4985
- }
4986
- parseOr() {
4987
- let left = this.parseAnd();
4988
- while (this.current().kind === "IDENTIFIER" && this.current().value === "or") {
4989
- this.advance();
4990
- const right = this.parseAnd();
4991
- left = isTruthyNum(left) || isTruthyNum(right) ? 1 : 0;
4992
- }
4993
- return left;
4994
- }
4995
- parseAnd() {
4996
- let left = this.parseNot();
4997
- while (this.current().kind === "IDENTIFIER" && this.current().value === "and") {
4998
- this.advance();
4999
- const right = this.parseNot();
5000
- left = isTruthyNum(left) && isTruthyNum(right) ? 1 : 0;
5001
- }
5002
- return left;
5003
- }
5004
- parseNot() {
5005
- if (this.current().kind === "IDENTIFIER" && this.current().value === "not") {
5006
- this.advance();
5007
- const value = this.parseNot();
5008
- return isTruthyNum(value) ? 0 : 1;
5009
- }
5010
- return this.parseComparison();
5011
- }
5012
- parseComparison() {
5013
- let left = this.parseAddition();
5014
- const kind = this.current().kind;
5015
- if (kind === "LT" || kind === "GT" || kind === "LE" || kind === "GE" || kind === "EQ" || kind === "NE") {
5016
- this.advance();
5017
- const right = this.parseAddition();
5018
- switch (kind) {
5019
- case "LT":
5020
- return left < right ? 1 : 0;
5021
- case "GT":
5022
- return left > right ? 1 : 0;
5023
- case "LE":
5024
- return left <= right ? 1 : 0;
5025
- case "GE":
5026
- return left >= right ? 1 : 0;
5027
- case "EQ":
5028
- return left === right ? 1 : 0;
5029
- case "NE":
5030
- return left !== right ? 1 : 0;
5031
- }
5032
- }
5033
- return left;
5034
- }
5035
- parseAddition() {
5036
- let left = this.parseMultiplication();
5037
- while (true) {
5038
- const kind = this.current().kind;
5039
- if (kind === "PLUS") {
5040
- this.advance();
5041
- left = left + this.parseMultiplication();
5042
- } else if (kind === "MINUS") {
5043
- this.advance();
5044
- left = left - this.parseMultiplication();
5045
- } else {
5046
- break;
5047
- }
5048
- }
5049
- return left;
5050
- }
5051
- parseMultiplication() {
5052
- let left = this.parsePower();
5053
- while (true) {
5054
- const kind = this.current().kind;
5055
- if (kind === "STAR") {
5056
- this.advance();
5057
- left = left * this.parsePower();
5058
- } else if (kind === "SLASH") {
5059
- this.advance();
5060
- left = left / this.parsePower();
5061
- } else if (kind === "PERCENT") {
5062
- this.advance();
5063
- left = left % this.parsePower();
5064
- } else {
5065
- break;
5066
- }
5067
- }
5068
- return left;
5069
- }
5070
- parsePower() {
5071
- const left = this.parseUnary();
5072
- if (this.current().kind === "CARET") {
5073
- this.advance();
5074
- const right = this.parsePower();
5075
- return Math.pow(left, right);
5076
- }
5077
- return left;
5078
- }
5079
- parseUnary() {
5080
- const kind = this.current().kind;
5081
- if (kind === "MINUS") {
5082
- this.advance();
5083
- return -this.parseUnary();
5084
- }
5085
- if (kind === "PLUS") {
5086
- this.advance();
5087
- return +this.parseUnary();
5088
- }
5089
- return this.parsePrimary();
5090
- }
5091
- parsePrimary() {
5092
- const token = this.current();
5093
- if (token.kind === "NUMBER") {
5094
- this.advance();
5095
- return token.value;
5096
- }
5097
- if (token.kind === "LPAREN") {
5098
- this.advance();
5099
- const value = this.parseOr();
5100
- if (this.current().kind !== "RPAREN") {
5101
- throw new Error("Expected )");
5102
- }
5103
- this.advance();
5104
- return value;
5105
- }
5106
- if (token.kind === "IDENTIFIER") {
5107
- const name = token.value;
5108
- this.advance();
5109
- if (this.current().kind === "LPAREN") {
5110
- return this.parseFunctionCall(name);
5111
- }
5112
- throw new Error(`undefined constant "${name}"`);
5113
- }
5114
- throw new Error("Expected expression");
5115
- }
5116
- parseFunctionCall(name) {
5117
- if (this.current().kind !== "LPAREN") {
5118
- throw new Error("Expected (");
5119
- }
5120
- this.advance();
5121
- const args = [];
5122
- if (this.current().kind !== "RPAREN") {
5123
- args.push(this.parseOr());
5124
- while (this.current().kind === "COMMA") {
5125
- this.advance();
5126
- args.push(this.parseOr());
5127
- }
5128
- }
5129
- if (this.current().kind !== "RPAREN") {
5130
- throw new Error("Expected )");
5131
- }
5132
- this.advance();
5133
- return this.callFunction(name, args);
5134
- }
5135
- callFunction(name, args) {
5136
- switch (name) {
5137
- case "abs":
5138
- this.checkArgs(name, args, 1);
5139
- return Math.abs(args[0]);
5140
- case "min":
5141
- this.checkArgsMin(name, args, 1);
5142
- return Math.min(...args);
5143
- case "max":
5144
- this.checkArgsMin(name, args, 1);
5145
- return Math.max(...args);
5146
- case "floor":
5147
- this.checkArgs(name, args, 1);
5148
- return Math.floor(args[0]);
5149
- case "ceil":
5150
- this.checkArgs(name, args, 1);
5151
- return Math.ceil(args[0]);
5152
- case "round":
5153
- this.checkArgs(name, args, 1);
5154
- return Math.round(args[0]);
5155
- case "sqrt":
5156
- this.checkArgs(name, args, 1);
5157
- return Math.sqrt(args[0]);
5158
- case "sin":
5159
- this.checkArgs(name, args, 1);
5160
- return Math.sin(args[0]);
5161
- case "cos":
5162
- this.checkArgs(name, args, 1);
5163
- return Math.cos(args[0]);
5164
- case "tan":
5165
- this.checkArgs(name, args, 1);
5166
- return Math.tan(args[0]);
5167
- case "ln":
5168
- this.checkArgs(name, args, 1);
5169
- return Math.log(args[0]);
5170
- case "log":
5171
- this.checkArgs(name, args, 1);
5172
- return Math.log10(args[0]);
5173
- case "exp":
5174
- this.checkArgs(name, args, 1);
5175
- return Math.exp(args[0]);
5176
- case "pow":
5177
- this.checkArgs(name, args, 2);
5178
- return Math.pow(args[0], args[1]);
5179
- default:
5180
- throw new Error(`undefined function "${name}"`);
5181
- }
5182
- }
5183
- checkArgs(name, args, expected) {
5184
- if (args.length !== expected) {
5185
- throw new Error(`${name}() expects ${expected} argument(s), got ${args.length}`);
5186
- }
5187
- }
5188
- checkArgsMin(name, args, min) {
5189
- if (args.length < min) {
5190
- throw new Error(`${name}() expects at least ${min} argument(s), got ${args.length}`);
5191
- }
5192
- }
5535
+ function renderContentSeparator(ctx) {
5536
+ ctx.push(`<div class="content-separator" style="display: none:"></div>`);
5193
5537
  }
5194
5538
 
5195
- // packages/render/src/elements/expr.ts
5196
- function renderExpr(ctx, data) {
5197
- const result = evaluateExpression(data.expression);
5198
- if (result.success) {
5199
- ctx.pushEscaped(formatNumber(result.value));
5200
- } else if (result.error !== "empty expression") {
5201
- ctx.pushEscaped(`run-time error: ${result.error}`);
5202
- }
5203
- }
5204
- function renderIf(ctx, data) {
5205
- const elements = isTruthy(data.condition) ? data.then : data.else;
5206
- renderBranchElements(ctx, elements);
5207
- }
5208
- function renderIfExpr(ctx, data) {
5209
- const result = evaluateExpression(data.expression);
5210
- if (!result.success) {
5211
- ctx.pushEscaped(`run-time error: ${result.error}`);
5539
+ // packages/render/src/render/style.ts
5540
+ function renderStyleElement(ctx, css) {
5541
+ if (!ctx.renderInlineStyles || !ctx.settings.allowStyleElements) {
5212
5542
  return;
5213
5543
  }
5214
- const elements = result.value !== 0 ? data.then : data.else;
5215
- renderBranchElements(ctx, elements);
5216
- }
5217
- function renderBranchElements(ctx, elements) {
5218
- let lastIdx = elements.length - 1;
5219
- while (lastIdx >= 0) {
5220
- const el = elements[lastIdx];
5221
- if (el.element === "text" && typeof el.data === "string" && el.data.trim() === "") {
5222
- lastIdx--;
5223
- } else {
5224
- break;
5225
- }
5226
- }
5227
- renderElements(ctx, elements.slice(0, lastIdx + 1));
5228
- }
5229
- function formatNumber(n) {
5230
- if (Number.isInteger(n)) {
5231
- return String(n);
5544
+ if (ctx.hasActiveStyleSlot()) {
5545
+ ctx.pushToStyleSlot(css);
5546
+ return;
5232
5547
  }
5233
- return n.toFixed(6).replace(/\.?0+$/, "");
5548
+ pushStyleTag(ctx, css);
5234
5549
  }
5235
5550
 
5236
- // packages/render/src/render.ts
5237
- function renderToHtml(tree, options = {}) {
5238
- const ctx = new RenderContext(tree, options);
5239
- renderElements(ctx, tree.elements);
5240
- if (ctx.settings.allowStyleElements && tree.styles?.length) {
5241
- for (const style of tree.styles) {
5242
- if (style.startsWith(import_ast3.STYLE_SLOT_PREFIX)) {
5243
- const slotId = parseInt(style.slice(import_ast3.STYLE_SLOT_PREFIX.length), 10);
5244
- for (const css of ctx.getStyleSlotContents(slotId)) {
5245
- ctx.push(`<style>${escapeStyleContent(css)}</style>`);
5246
- }
5247
- } else {
5248
- ctx.push(`<style>${escapeStyleContent(style)}</style>`);
5249
- }
5250
- }
5251
- }
5252
- return ctx.getOutput();
5253
- }
5551
+ // packages/render/src/render/dispatch.ts
5254
5552
  function renderElements(ctx, elements) {
5255
5553
  for (const element of elements) {
5256
5554
  renderElement(ctx, element);
@@ -5259,7 +5557,7 @@ function renderElements(ctx, elements) {
5259
5557
  function renderElement(ctx, element) {
5260
5558
  switch (element.element) {
5261
5559
  case "text":
5262
- ctx.pushEscaped(element.data);
5560
+ renderTextNode(ctx, element.data);
5263
5561
  break;
5264
5562
  case "raw":
5265
5563
  renderRaw(ctx, element.data);
@@ -5358,16 +5656,10 @@ function renderElement(ctx, element) {
5358
5656
  renderIfTags(ctx, element.data);
5359
5657
  break;
5360
5658
  case "style":
5361
- if (ctx.renderInlineStyles && ctx.settings.allowStyleElements) {
5362
- if (ctx.hasActiveStyleSlot()) {
5363
- ctx.pushToStyleSlot(element.data);
5364
- } else {
5365
- ctx.push(`<style>${escapeStyleContent(element.data)}</style>`);
5366
- }
5367
- }
5659
+ renderStyleElement(ctx, element.data);
5368
5660
  break;
5369
5661
  case "line-break":
5370
- ctx.push("<br />");
5662
+ renderLineBreak(ctx);
5371
5663
  break;
5372
5664
  case "line-breaks":
5373
5665
  renderLineBreaks(ctx, element.data);
@@ -5376,10 +5668,10 @@ function renderElement(ctx, element) {
5376
5668
  renderClearFloat(ctx, element.data);
5377
5669
  break;
5378
5670
  case "horizontal-rule":
5379
- ctx.push("<hr />");
5671
+ renderHorizontalRule(ctx);
5380
5672
  break;
5381
5673
  case "content-separator":
5382
- ctx.push(`<div class="content-separator" style="display: none:"></div>`);
5674
+ renderContentSeparator(ctx);
5383
5675
  break;
5384
5676
  case "expr":
5385
5677
  renderExpr(ctx, element.data);
@@ -5396,5 +5688,13 @@ function renderElement(ctx, element) {
5396
5688
  }
5397
5689
  }
5398
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
+
5399
5699
  // packages/render/src/index.ts
5400
- var import_ast4 = require("@wdprlib/ast");
5700
+ var import_ast6 = require("@wdprlib/ast");