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