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