@godxjp/markdown 33.20.0 → 33.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1011 +1,31 @@
1
1
  "use client";
2
+ import {
3
+ MARKDOWN_FIXTURES,
4
+ MARKDOWN_FORMAT,
5
+ MarkdownCore,
6
+ MermaidDiagram,
7
+ RENDERER_VERSION,
8
+ checkMermaidSvg,
9
+ extendSchema,
10
+ markdownSchema,
11
+ rehypeHeadingIds,
12
+ safeUrl
13
+ } from "./chunk-RRNN4HLQ.js";
14
+ import {
15
+ rehypeHighlight
16
+ } from "./chunk-XXBFDQOW.js";
17
+ import "./chunk-ANYWBQJT.js";
18
+ import {
19
+ rehypeMath
20
+ } from "./chunk-JJ2A3D7U.js";
21
+ import "./chunk-AKKOCR2Y.js";
2
22
 
3
23
  // src/markdown.tsx
4
- import * as React3 from "react";
5
- import ReactMarkdown from "react-markdown";
6
- import rehypeSanitize from "rehype-sanitize";
7
- import remarkGfm from "remark-gfm";
8
- import { visit as visit3 } from "unist-util-visit";
9
-
10
- // src/headings.ts
11
- import GithubSlugger from "github-slugger";
12
- import { visit } from "unist-util-visit";
13
- var HEADING = /^h([1-6])$/;
14
- function textOf(node) {
15
- if (node.type === "text") return node.value;
16
- if ("children" in node) return node.children.map(textOf).join("");
17
- return "";
18
- }
19
- function rehypeHeadingIds(options = {}) {
20
- return (tree) => {
21
- const slugger = new GithubSlugger();
22
- let index = 0;
23
- visit(tree, "element", (node) => {
24
- const match = HEADING.exec(node.tagName);
25
- if (!match) return;
26
- if (node.properties?.id != null) return;
27
- const text = textOf(node);
28
- const info = { depth: Number(match[1]), text, index: index++ };
29
- const id = options.resolve ? options.resolve(info) : slugger.slug(text);
30
- if (id) node.properties = { ...node.properties, id };
31
- });
32
- };
33
- }
34
-
35
- // src/highlight.ts
36
- import { createLowlight } from "lowlight";
37
- import bash from "highlight.js/lib/languages/bash";
38
- import css from "highlight.js/lib/languages/css";
39
- import diff from "highlight.js/lib/languages/diff";
40
- import dockerfile from "highlight.js/lib/languages/dockerfile";
41
- import go from "highlight.js/lib/languages/go";
42
- import ini from "highlight.js/lib/languages/ini";
43
- import java from "highlight.js/lib/languages/java";
44
- import javascript from "highlight.js/lib/languages/javascript";
45
- import json from "highlight.js/lib/languages/json";
46
- import markdown from "highlight.js/lib/languages/markdown";
47
- import php from "highlight.js/lib/languages/php";
48
- import python from "highlight.js/lib/languages/python";
49
- import rust from "highlight.js/lib/languages/rust";
50
- import shell from "highlight.js/lib/languages/shell";
51
- import sql from "highlight.js/lib/languages/sql";
52
- import typescript from "highlight.js/lib/languages/typescript";
53
- import xml from "highlight.js/lib/languages/xml";
54
- import yaml from "highlight.js/lib/languages/yaml";
55
- import { visit as visit2 } from "unist-util-visit";
56
- var lowlight = createLowlight({
57
- bash,
58
- css,
59
- diff,
60
- dockerfile,
61
- go,
62
- ini,
63
- java,
64
- javascript,
65
- json,
66
- markdown,
67
- php,
68
- python,
69
- rust,
70
- shell,
71
- sql,
72
- typescript,
73
- xml,
74
- yaml
75
- });
76
- var CODE_TOKENS = [
77
- "comment",
78
- "keyword",
79
- "string",
80
- "string-expression",
81
- "function",
82
- "constant",
83
- "parameter",
84
- "punctuation",
85
- "link",
86
- "inserted",
87
- "deleted",
88
- "changed"
89
- ];
90
- var SCOPES = {
91
- comment: "comment",
92
- quote: "comment",
93
- doctag: "keyword",
94
- keyword: "keyword",
95
- built_in: "keyword",
96
- type: "keyword",
97
- "selector-tag": "keyword",
98
- tag: "punctuation",
99
- name: "keyword",
100
- section: "keyword",
101
- string: "string",
102
- char: "string",
103
- regexp: "string-expression",
104
- subst: "string-expression",
105
- "template-variable": "string-expression",
106
- "title.function": "function",
107
- title: "function",
108
- "title.class": "constant",
109
- number: "constant",
110
- literal: "constant",
111
- symbol: "constant",
112
- "variable.constant": "constant",
113
- "variable.language": "constant",
114
- "selector-id": "constant",
115
- "selector-class": "constant",
116
- bullet: "constant",
117
- params: "parameter",
118
- attr: "parameter",
119
- attribute: "parameter",
120
- property: "parameter",
121
- "template-tag": "parameter",
122
- punctuation: "punctuation",
123
- operator: "punctuation",
124
- link: "link",
125
- addition: "inserted",
126
- deletion: "deleted"
127
- };
128
- function tokenOf(className) {
129
- if (!Array.isArray(className) || !String(className[0]).startsWith("hljs-")) return void 0;
130
- const parts = className.map(
131
- (name, i) => i === 0 ? String(name).slice(5) : String(name).replace(/_+$/, "")
132
- );
133
- for (let n = parts.length; n > 0; n--) {
134
- const token = SCOPES[parts.slice(0, n).join(".")];
135
- if (token) return token;
136
- }
137
- return void 0;
138
- }
139
- function retag(nodes) {
140
- return nodes.flatMap((node) => {
141
- if (node.type !== "element") return [node];
142
- const children = retag(node.children);
143
- const token = tokenOf(node.properties?.className);
144
- return token ? [{ type: "element", tagName: "span", properties: { dataCodeToken: token }, children }] : children;
145
- });
146
- }
147
- var SKIP = /* @__PURE__ */ new Set(["mermaid", "math"]);
148
- function rehypeHighlight() {
149
- return (tree) => {
150
- visit2(tree, "element", (node, _index, parent) => {
151
- if (node.tagName !== "code" || parent?.type !== "element" || parent.tagName !== "pre") return;
152
- const classes = node.properties?.className ?? [];
153
- const language = classes.find((name) => name.startsWith("language-"))?.slice(9);
154
- if (!language || SKIP.has(language) || !lowlight.registered(language)) return;
155
- if (!node.children.every((child) => child.type === "text")) return;
156
- const text = node.children.map((child) => child.type === "text" ? child.value : "").join("");
157
- node.children = retag(lowlight.highlight(language, text).children);
158
- });
159
- };
160
- }
161
-
162
- // src/math.ts
163
- import rehypeKatex from "rehype-katex";
164
- var KATEX_OPTIONS = {
165
- trust: false,
166
- strict: "error",
167
- maxSize: 10,
168
- output: "htmlAndMathml"
169
- };
170
- function rehypeMath() {
171
- return rehypeKatex(KATEX_OPTIONS);
172
- }
173
- var KATEX_CLASSES = `accent accent-body accent-full amsrm angl anglpad arraycolsep base boldsymbol
174
- boxpad brace-center brace-left brace-right cancel-lap cancel-pad cd-arrow-pad cd-label-left
175
- cd-label-right cd-vert-arrow cjk_fallback clap col-align-c col-align-l col-align-r colorbox com
176
- delim-size1 delim-size4 delimcenter delimsizing eqn-num fbox fcolorbox fix fleqn fontsize-ensurer
177
- frac-line halfarrow-left halfarrow-right hbox hdashline hide-tail hline inner katex katex-display
178
- katex-error katex-html katex-mathml large-op leqno llap mainrm mathbb mathbf mathboldfrak mathboldsf
179
- mathcal mathfrak mathit mathitsf mathnormal mathrm mathscr mathsf mathsfit mathtt mbin mclose mfrac
180
- minner mml-eqn-num mop mopen mord mover mpunct mrel mspace msupsub mtable mtight mtr-glue mult
181
- munder newline nobreak nulldelimiter op-limits op-symbol overlay overline overline-line pstrut
182
- reset-size1 reset-size10 reset-size11 reset-size2 reset-size3 reset-size4 reset-size5 reset-size6
183
- reset-size7 reset-size8 reset-size9 rlap root rule size1 size10 size11 size2 size3 size4 size5
184
- size6 size7 size8 size9 sizing small-op smash sout sqrt stretchy strut svg-align tag text textbb
185
- textbf textboldfrak textboldsf textfrak textit textitsf textrm textscr textsf texttt thinbox
186
- underline underline-line vbox vertical-separator vlist vlist-r vlist-s vlist-t vlist-t2 x-arrow
187
- x-arrow-pad`.split(/\s+/);
188
- var KATEX_STYLE = /^(?:\s*(?:background-color|border(?:-(?:top|right|bottom|left))?(?:-(?:width|style|color))?|bottom|color|height|left|margin(?:-(?:top|right|bottom|left))?|max-width|min-width|padding(?:-(?:top|right|bottom|left))?|position|right|top|vertical-align|width)\s*:\s*[-#.%\w ]+;?)*\s*$/i;
189
- var MATHML_TAGS = `math semantics annotation mrow mi mn mo ms mtext mspace mpadded mphantom mstyle
190
- menclose mfrac msqrt mroot msub msup msubsup munder mover munderover mtable mtr mtd mlabeledtr`.split(
191
- /\s+/
192
- );
193
- var katexSchema = {
194
- tagNames: [...MATHML_TAGS, "svg", "path", "line"],
195
- attributes: {
196
- span: [
197
- ["className", ...KATEX_CLASSES],
198
- ["style", KATEX_STYLE],
199
- ["ariaHidden", "true"]
200
- ],
201
- math: [
202
- ["xmlns", "http://www.w3.org/1998/Math/MathML"],
203
- ["display", "block", "inline"]
204
- ],
205
- annotation: [["encoding", "application/x-tex"]],
206
- mi: ["mathvariant"],
207
- mo: ["stretchy", "fence", "separator", "minsize", "maxsize", "lspace", "rspace", "accent"],
208
- mfrac: ["linethickness"],
209
- mtable: ["rowspacing", "rowlines", "columnalign", "columnspacing", "columnlines", "width"],
210
- mtd: ["columnalign"],
211
- mstyle: ["scriptlevel", "displaystyle", "mathcolor", "mathsize", "mathbackground"],
212
- mover: ["accent"],
213
- munder: ["accentunder"],
214
- munderover: ["accent", "accentunder"],
215
- mpadded: [
216
- "width",
217
- "height",
218
- "depth",
219
- "lspace",
220
- "voffset",
221
- "mathbackground",
222
- ["style", KATEX_STYLE]
223
- ],
224
- mspace: ["width", "height", "depth", "mathbackground"],
225
- menclose: ["notation", "mathbackground"],
226
- svg: [
227
- ["xmlns", "http://www.w3.org/2000/svg"],
228
- "width",
229
- "height",
230
- "viewBox",
231
- "preserveAspectRatio",
232
- ["style", KATEX_STYLE]
233
- ],
234
- path: ["d"],
235
- line: ["x1", "y1", "x2", "y2", "strokeWidth"]
236
- }
237
- };
238
-
239
- // src/mermaid.tsx
240
- import * as React from "react";
241
- import { jsx, jsxs } from "react/jsx-runtime";
242
- var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
243
- "svg",
244
- "g",
245
- "path",
246
- "rect",
247
- "circle",
248
- "ellipse",
249
- "line",
250
- "polyline",
251
- "polygon",
252
- "text",
253
- "tspan",
254
- "textpath",
255
- "marker",
256
- "defs",
257
- "style",
258
- "title",
259
- "desc",
260
- "lineargradient",
261
- "radialgradient",
262
- "stop",
263
- "clippath",
264
- "mask",
265
- "pattern",
266
- "symbol",
267
- "use",
268
- "filter",
269
- "fegaussianblur",
270
- "feoffset",
271
- "feblend",
272
- "feflood",
273
- "fecomposite",
274
- "femerge",
275
- "femergenode",
276
- "fedropshadow"
277
- ]);
278
- var URL_REF = /url\s*\(\s*(['"]?)([^'")]*)\1\s*\)/gi;
279
- var CSS_HAZARD = /@import|expression\s*\(|javascript:|behavior\s*:|-moz-binding|<\//i;
280
- function urlsAreLocal(value) {
281
- for (const match of value.matchAll(URL_REF)) {
282
- if (!match[2].trim().startsWith("#")) return false;
283
- }
284
- return true;
285
- }
286
- function checkMermaidSvg(svg) {
287
- if (typeof DOMParser === "undefined") return { ok: false, reason: "no DOMParser" };
288
- const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
289
- if (doc.getElementsByTagName("parsererror").length > 0) return { ok: false, reason: "parse" };
290
- const root = doc.documentElement;
291
- if (root.localName.toLowerCase() !== "svg") return { ok: false, reason: "root" };
292
- const elements = [root, ...Array.from(root.getElementsByTagName("*"))];
293
- for (const element2 of elements) {
294
- const name = element2.localName.toLowerCase();
295
- if (!ALLOWED_ELEMENTS.has(name)) return { ok: false, reason: `element ${name}` };
296
- if (name === "style") {
297
- const css2 = element2.textContent ?? "";
298
- if (CSS_HAZARD.test(css2) || !urlsAreLocal(css2)) return { ok: false, reason: "style" };
299
- }
300
- for (const attribute of Array.from(element2.attributes)) {
301
- const attr = attribute.localName.toLowerCase();
302
- const value = attribute.value;
303
- if (attr.startsWith("on")) return { ok: false, reason: `handler ${attr}` };
304
- if ((attr === "href" || attr === "src") && !value.trim().startsWith("#")) {
305
- return { ok: false, reason: `ref ${attr}` };
306
- }
307
- if (attr === "style" && CSS_HAZARD.test(value)) return { ok: false, reason: "style attr" };
308
- if (!urlsAreLocal(value)) return { ok: false, reason: `url ${attr}` };
309
- }
310
- }
311
- return { ok: true, svg: root };
312
- }
313
- var loading = null;
314
- var initializedTheme = null;
315
- async function loadMermaid() {
316
- loading ??= import("mermaid").then((module) => module.default).catch(() => null);
317
- const api = await loading;
318
- const theme = document.documentElement.dataset.theme === "dark" ? "dark" : "default";
319
- if (api && theme !== initializedTheme) {
320
- api.initialize({
321
- startOnLoad: false,
322
- securityLevel: "strict",
323
- // SVG text labels, not HTML in <foreignObject> — which the gate rejects outright.
324
- htmlLabels: false,
325
- flowchart: { htmlLabels: false },
326
- // Journey wraps its labels in <switch><foreignObject> by default ("element switch" at the
327
- // gate, so every journey fell back to code); tspan placement draws them as SVG text.
328
- journey: { textPlacement: "tspan" },
329
- theme
330
- });
331
- initializedTheme = theme;
332
- }
333
- return api;
334
- }
335
- function MermaidCode({ source }) {
336
- return /* @__PURE__ */ jsx("pre", { "data-mermaid": "code", children: /* @__PURE__ */ jsx("code", { className: "language-mermaid", children: source }) });
337
- }
338
- function MermaidDiagram({ source, label }) {
339
- const host = React.useRef(null);
340
- const id = `godx-mermaid-${React.useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
341
- const [state, setState] = React.useState("code");
342
- React.useEffect(() => {
343
- let cancelled = false;
344
- setState("code");
345
- void (async () => {
346
- const api = await loadMermaid();
347
- if (!api || cancelled) return;
348
- try {
349
- const { svg } = await api.render(id, source);
350
- const checked = checkMermaidSvg(svg);
351
- if (cancelled || !checked.ok || !host.current) return;
352
- host.current.replaceChildren(document.importNode(checked.svg, true));
353
- setState("diagram");
354
- } catch {
355
- } finally {
356
- document.getElementById(`d${id}`)?.remove();
357
- }
358
- })();
359
- return () => {
360
- cancelled = true;
361
- };
362
- }, [id, source]);
363
- return /* @__PURE__ */ jsxs("figure", { "data-mermaid": state, "aria-label": state === "diagram" ? label : void 0, children: [
364
- /* @__PURE__ */ jsx("div", { ref: host, hidden: state !== "diagram" }),
365
- state === "code" ? /* @__PURE__ */ jsx(MermaidCode, { source }) : null
366
- ] });
367
- }
368
-
369
- // src/table-scroll.tsx
370
- import * as React2 from "react";
371
- import { jsx as jsx2 } from "react/jsx-runtime";
372
- function TableScroll({ children }) {
373
- const ref = React2.useRef(null);
374
- const [scrolls, setScrolls] = React2.useState(false);
375
- React2.useLayoutEffect(() => {
376
- const box = ref.current;
377
- if (!box || typeof ResizeObserver === "undefined") return void 0;
378
- const measure = () => setScrolls(box.scrollWidth > box.clientWidth + 1);
379
- const observer = new ResizeObserver(measure);
380
- observer.observe(box);
381
- if (box.firstElementChild) observer.observe(box.firstElementChild);
382
- measure();
383
- return () => observer.disconnect();
384
- }, []);
385
- return /* @__PURE__ */ jsx2(
386
- "div",
387
- {
388
- ref,
389
- className: "ui-prose-table-scroll",
390
- "data-slot": "prose-table-scroll",
391
- tabIndex: scrolls ? 0 : void 0,
392
- children
393
- }
394
- );
395
- }
396
-
397
- // src/schema.ts
398
- import { defaultSchema } from "rehype-sanitize";
399
- var markdownSchema = {
400
- ...defaultSchema,
401
- tagNames: [...defaultSchema.tagNames ?? [], ...katexSchema.tagNames],
402
- clobberPrefix: "",
403
- protocols: {
404
- ...defaultSchema.protocols,
405
- href: ["http", "https", "mailto"],
406
- src: ["http", "https"],
407
- cite: ["http", "https"]
408
- },
409
- attributes: {
410
- ...defaultSchema.attributes,
411
- ...katexSchema.attributes,
412
- span: [...katexSchema.attributes.span, ["dataCodeToken", ...CODE_TOKENS]],
413
- // Heading anchors (rehype-headings, below) and the code fence language.
414
- h1: [...defaultSchema.attributes?.h1 ?? [], "id"],
415
- h2: [...defaultSchema.attributes?.h2 ?? [], "id"],
416
- h3: [...defaultSchema.attributes?.h3 ?? [], "id"],
417
- h4: [...defaultSchema.attributes?.h4 ?? [], "id"],
418
- h5: [...defaultSchema.attributes?.h5 ?? [], "id"],
419
- h6: [...defaultSchema.attributes?.h6 ?? [], "id"],
420
- // Grammar v1 (gh#1156): the renderer's own callout / toggle / column markup. Every value is
421
- // pinned — a body cannot mint any other class, kind, role or inline style through these.
422
- div: [
423
- ...defaultSchema.attributes?.div ?? [],
424
- [
425
- "className",
426
- "ui-prose-callout",
427
- "ui-prose-columns",
428
- "ui-prose-column",
429
- "ui-prose-directive"
430
- ],
431
- ["dataKind", "note", "tip", "important", "warning", "caution"],
432
- ["role", "note"],
433
- ["style", /^--prose-column-grow:\d+(?:\.\d+)?$/]
434
- ],
435
- p: [...defaultSchema.attributes?.p ?? [], ["className", "ui-prose-callout-title"]],
436
- details: [...defaultSchema.attributes?.details ?? [], ["className", "ui-prose-toggle"]],
437
- summary: [
438
- ...defaultSchema.attributes?.summary ?? [],
439
- ["className", "ui-prose-toggle-summary"]
440
- ]
441
- }
442
- };
443
- var ruleName = (rule) => Array.isArray(rule) ? rule[0] : rule;
444
- function extendSchema(extension = {}) {
445
- const attributes = { ...markdownSchema.attributes };
446
- for (const [tag, list] of Object.entries(extension.attributes ?? {})) {
447
- const merged = [...attributes[tag] ?? []];
448
- for (const rule of list) {
449
- const name = ruleName(rule);
450
- const at = merged.findIndex((existing2) => ruleName(existing2) === name);
451
- if (at === -1) {
452
- merged.push(rule);
453
- continue;
454
- }
455
- const existing = merged[at];
456
- if (!Array.isArray(existing)) continue;
457
- merged[at] = Array.isArray(rule) ? [name, ...existing.slice(1), ...rule.slice(1)] : rule;
458
- }
459
- attributes[tag] = merged;
460
- }
461
- return {
462
- ...markdownSchema,
463
- tagNames: [...markdownSchema.tagNames ?? [], ...extension.tagNames ?? []],
464
- attributes
465
- };
466
- }
467
- var SAFE_HREF = /^(https?:|mailto:)/i;
468
- var SAFE_SRC = /^https?:/i;
469
- var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
470
- function safeUrl(url, key) {
471
- const value = url.trim();
472
- const probe = value.replace(/[\x00-\x20\x7f]/g, "");
473
- if (!SCHEME.test(probe)) return value;
474
- if (key === "src") return SAFE_SRC.test(probe) ? value : "";
475
- return SAFE_HREF.test(probe) ? value : "";
476
- }
477
-
478
- // src/codec/model.ts
479
- var CALLOUT_KINDS = ["note", "tip", "important", "warning", "caution"];
480
- var COLUMNS_MIN = 2;
481
- var COLUMNS_MAX = 4;
482
-
483
- // src/codec/readers.ts
484
- var CALLOUT = /^\[!([A-Za-z]+)\](?:[ \t]+([^\n]*))?$/;
485
- function readCallout(node) {
486
- const [first, ...rest] = node.children;
487
- if (first?.type !== "paragraph") return null;
488
- const [head, ...tail] = first.children;
489
- if (head?.type !== "text") return null;
490
- const newline = head.value.indexOf("\n");
491
- if (newline === -1 && tail.length > 0) return null;
492
- const line = newline === -1 ? head.value : head.value.slice(0, newline);
493
- const match = CALLOUT.exec(line);
494
- const kind = match?.[1]?.toLowerCase();
495
- if (!match || !kind || !CALLOUT_KINDS.includes(kind)) return null;
496
- const remainder = [
497
- ...newline === -1 ? [] : [{ ...head, value: head.value.slice(newline + 1) }],
498
- ...tail
499
- ].filter((n) => !(n.type === "text" && n.value === ""));
500
- return {
501
- kind,
502
- title: match[2]?.trim() || null,
503
- body: [
504
- ...remainder.length ? [{ type: "paragraph", children: remainder }] : [],
505
- ...rest
506
- ]
507
- };
508
- }
509
- function labelOf(node) {
510
- const first = node.children[0];
511
- return first?.type === "paragraph" && first.data?.directiveLabel ? first : null;
512
- }
513
- function readToggle(node) {
514
- if (node.name !== "toggle" || Object.keys(node.attributes ?? {}).length > 0) return null;
515
- const label = labelOf(node);
516
- return {
517
- summary: label?.children ?? [],
518
- body: label ? node.children.slice(1) : node.children
519
- };
520
- }
521
- function readColumns(node, nested) {
522
- if (node.name !== "columns" || nested || labelOf(node) || Object.keys(node.attributes ?? {}).length) {
523
- return null;
524
- }
525
- const columns = node.children;
526
- if (columns.length < COLUMNS_MIN || columns.length > COLUMNS_MAX) return null;
527
- const parts = [];
528
- for (const column of columns) {
529
- if (column.type !== "containerDirective" || column.name !== "column" || labelOf(column))
530
- return null;
531
- const attributes = Object.entries(column.attributes ?? {});
532
- if (attributes.length > 1) return null;
533
- let width = null;
534
- if (attributes.length === 1) {
535
- const [key, value] = attributes[0];
536
- if (key !== "width" || typeof value !== "string" || !/^(?:[1-9]\d?|100)$/.test(value))
537
- return null;
538
- width = Number(value);
539
- }
540
- parts.push({ width, body: column.children });
541
- }
542
- return parts;
543
- }
544
- function columnShares(columns) {
545
- const explicit = columns.reduce((sum, c) => sum + (c.width ?? 0), 0);
546
- const open = columns.filter((c) => c.width == null).length;
547
- const each = open ? Math.max(1, (100 - explicit) / open) : 0;
548
- return columns.map((c) => c.width ?? Math.round(each * 100) / 100);
549
- }
550
-
551
- // src/codec/syntax.ts
552
- import { directive } from "micromark-extension-directive";
553
- import { gfm } from "micromark-extension-gfm";
554
- import { cjkFriendlyExtension } from "micromark-extension-cjk-friendly";
555
- import { frontmatter } from "micromark-extension-frontmatter";
556
- import { math } from "micromark-extension-math";
557
- import { frontmatterFromMarkdown, frontmatterToMarkdown } from "mdast-util-frontmatter";
558
- import { mathFromMarkdown, mathToMarkdown } from "mdast-util-math";
559
- import { directiveFromMarkdown } from "mdast-util-directive";
560
- import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
561
- function micromarkExtensions() {
562
- const [container] = directive().flow[58];
563
- const containerOnly = { flow: { 58: container } };
564
- return [gfm(), cjkFriendlyExtension(), frontmatter(["yaml"]), dollarMath(), containerOnly];
565
- }
566
- var DOLLAR = 36;
567
- var isSpace = (code) => code === null || code === void 0 || code < 0 || code === 32;
568
- var isDigit = (code) => code != null && code >= 48 && code <= 57;
569
- var BACKSLASH = 92;
570
- function dollarMath() {
571
- const extension = math();
572
- const text = extension.text[DOLLAR];
573
- const tokenize = function(effects, ok, nok) {
574
- const codes = [];
575
- let done = false;
576
- const finish = (code) => {
577
- done = true;
578
- const single = codes[1] !== DOLLAR;
579
- const after = codes[codes.length - 1];
580
- const before = codes[codes.length - 3];
581
- const refused = isSpace(codes[1]) || isSpace(before) || before === BACKSLASH || isDigit(after);
582
- if (single && refused) return nok(code);
583
- return ok(code);
584
- };
585
- const watch = (state) => (code) => {
586
- if (done) return state(code);
587
- codes.push(code);
588
- const next = state(code);
589
- return next && !done ? watch(next) : next;
590
- };
591
- return watch(
592
- text.tokenize.call(this, effects, finish, (code) => {
593
- done = true;
594
- return nok(code);
595
- })
596
- );
597
- };
598
- return { ...extension, text: { [DOLLAR]: { ...text, tokenize } } };
599
- }
600
- function fromMarkdownExtensions() {
601
- return [
602
- gfmFromMarkdown(),
603
- directiveFromMarkdown(),
604
- frontmatterFromMarkdown(["yaml"]),
605
- mathFromMarkdown()
606
- ];
607
- }
608
-
609
- // src/directives.ts
610
- var DEFAULT_CALLOUT_TITLES = {
611
- note: "Note",
612
- tip: "Tip",
613
- important: "Important",
614
- warning: "Warning",
615
- caution: "Caution"
616
- };
617
- var element = (hName, hProperties = {}) => ({
618
- hName,
619
- hProperties
620
- });
621
- function remarkKitSyntax(options = {}) {
622
- const titles = { ...DEFAULT_CALLOUT_TITLES, ...options.calloutTitles };
623
- return function() {
624
- const data = this.data();
625
- (data.micromarkExtensions ??= []).push(...micromarkExtensions().slice(1));
626
- (data.fromMarkdownExtensions ??= []).push(...fromMarkdownExtensions().slice(1));
627
- const walk = (node, inColumn) => {
628
- if (node.type === "blockquote") callout(node, titles);
629
- else if (node.type === "containerDirective") directive2(node, inColumn);
630
- if (!("children" in node)) return;
631
- const isColumn = node.type === "containerDirective" && node.name === "column";
632
- for (const child of node.children) walk(child, inColumn || isColumn);
633
- };
634
- return (tree) => {
635
- tree.children = tree.children.filter((node) => node.type !== "yaml");
636
- walk(tree, false);
637
- };
638
- };
639
- }
640
- function callout(node, titles) {
641
- const parts = readCallout(node);
642
- if (!parts) return;
643
- const title = {
644
- type: "paragraph",
645
- data: element("p", { className: ["ui-prose-callout-title"] }),
646
- children: [{ type: "text", value: parts.title ?? titles[parts.kind] }]
647
- };
648
- node.data = element("div", {
649
- className: ["ui-prose-callout"],
650
- dataKind: parts.kind,
651
- role: "note"
652
- });
653
- node.children = [title, ...parts.body];
654
- }
655
- function directive2(node, nested) {
656
- const toggle = readToggle(node);
657
- if (toggle) {
658
- const summary = {
659
- type: "paragraph",
660
- data: element("summary", { className: ["ui-prose-toggle-summary"] }),
661
- children: toggle.summary
662
- };
663
- node.data = element("details", { className: ["ui-prose-toggle"] });
664
- node.children = [summary, ...toggle.body];
665
- return;
666
- }
667
- const columns = readColumns(node, nested);
668
- if (columns) {
669
- const shares = columnShares(columns);
670
- node.data = element("div", { className: ["ui-prose-columns"] });
671
- node.children = columns.map((column, i) => ({
672
- type: "containerDirective",
673
- name: "column",
674
- attributes: {},
675
- data: element("div", {
676
- className: ["ui-prose-column"],
677
- style: `--prose-column-grow:${shares[i]}`
678
- }),
679
- children: column.body
680
- }));
681
- return;
682
- }
683
- if (!node.data?.hName) node.data = element("div", { className: ["ui-prose-directive"] });
684
- }
685
-
686
- // src/markdown.tsx
687
- import { jsx as jsx3 } from "react/jsx-runtime";
688
- var MARKDOWN_FORMAT = "md";
689
- var RENDERER_VERSION = 6;
690
- var URL_ATTRIBUTES = {
691
- a: "href",
692
- img: "src",
693
- blockquote: "cite",
694
- q: "cite"
695
- };
696
- function rehypeResolveUrls(options) {
697
- return (tree) => {
698
- if (!options.resolve) return;
699
- visit3(tree, "element", (node) => {
700
- const key = URL_ATTRIBUTES[node.tagName];
701
- const value = key ? node.properties?.[key] : void 0;
702
- if (typeof value !== "string") return;
703
- const resolved = options.resolve(value, key);
704
- if (resolved !== void 0) node.properties = { ...node.properties, [key]: resolved };
705
- });
706
- };
24
+ import { jsx } from "react/jsx-runtime";
25
+ var EAGER = { math: rehypeMath, highlight: rehypeHighlight };
26
+ function Markdown(props) {
27
+ return /* @__PURE__ */ jsx(MarkdownCore, { ...props, runtime: EAGER });
707
28
  }
708
- var graphemes = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : void 0;
709
- function textOf2(node) {
710
- let text = "";
711
- let hasImage = false;
712
- visit3(node, (child) => {
713
- if (child.type === "text") text += child.value;
714
- else if (child.type === "element" && child.tagName === "img") hasImage = true;
715
- });
716
- return { text, hasImage };
717
- }
718
- function rehypeShortCells(options) {
719
- return (tree) => {
720
- const { max } = options;
721
- if (max === false || max <= 0) return;
722
- visit3(tree, "element", (node) => {
723
- if (node.tagName !== "td" && node.tagName !== "th") return;
724
- const { text, hasImage } = textOf2(node);
725
- const token = text.trim();
726
- if (hasImage || token === "" || /\s/.test(token)) return;
727
- const length = graphemes ? [...graphemes.segment(token)].length : [...token].length;
728
- if (length <= max) node.properties = { ...node.properties, dataShort: "" };
729
- });
730
- };
731
- }
732
- function rehypeExternalLinks(options) {
733
- return (tree) => {
734
- if (options.policy !== "new-tab") return;
735
- const origin = options.origin ?? globalThis.location?.origin;
736
- visit3(tree, "element", (node) => {
737
- if (node.tagName !== "a") return;
738
- const href = node.properties?.href;
739
- if (typeof href !== "string" || !/^https?:\/\//i.test(href)) return;
740
- let target;
741
- try {
742
- target = new URL(href);
743
- } catch {
744
- return;
745
- }
746
- if (origin && target.origin === origin) return;
747
- node.properties = { ...node.properties, target: "_blank", rel: ["noopener", "noreferrer"] };
748
- });
749
- };
750
- }
751
- function omitNode(props) {
752
- const { node: _node, ...rest } = props;
753
- return rest;
754
- }
755
- function fenceOf(node) {
756
- const code = node?.children?.[0];
757
- if (!code || code.type !== "element" || code.tagName !== "code") return void 0;
758
- const classes = code.properties?.className ?? [];
759
- const language = classes.find((name) => name.startsWith("language-"))?.slice(9);
760
- const text = code.children.map((child) => child.type === "text" ? child.value : "").join("");
761
- return { language, text };
762
- }
763
- function Markdown({
764
- children,
765
- remarkPlugins = [],
766
- rehypePlugins = [],
767
- schema,
768
- components,
769
- resolveUrl,
770
- headingId,
771
- mermaid = true,
772
- highlight = true,
773
- allowedElements,
774
- disallowedElements,
775
- allowElement,
776
- unwrapDisallowed,
777
- shortCellLength = 24,
778
- calloutTitles,
779
- externalLinks = "same-tab",
780
- origin
781
- }) {
782
- const sanitizeSchema = React3.useMemo(() => extendSchema(schema), [schema]);
783
- const kitSyntax = React3.useMemo(() => remarkKitSyntax({ calloutTitles }), [calloutTitles]);
784
- const merged = React3.useMemo(() => {
785
- const HostTable = components?.table;
786
- const table = (props) => /* @__PURE__ */ jsx3(TableScroll, { children: HostTable ? typeof HostTable === "string" ? React3.createElement(HostTable, props) : /* @__PURE__ */ jsx3(HostTable, { ...props }) : /* @__PURE__ */ jsx3("table", { ...omitNode(props) }) });
787
- if (!mermaid) return { ...components, table };
788
- const HostPre = components?.pre;
789
- const pre = (props) => {
790
- const fence = fenceOf(props.node);
791
- if (fence?.language === "mermaid") return /* @__PURE__ */ jsx3(MermaidDiagram, { source: fence.text });
792
- if (HostPre)
793
- return typeof HostPre === "string" ? React3.createElement(HostPre, props) : /* @__PURE__ */ jsx3(HostPre, { ...props });
794
- const { node: _node, children: inner, ...rest } = props;
795
- return /* @__PURE__ */ jsx3("pre", { ...rest, children: inner });
796
- };
797
- return { ...components, table, pre };
798
- }, [components, mermaid]);
799
- return /* @__PURE__ */ jsx3(
800
- ReactMarkdown,
801
- {
802
- remarkPlugins: [remarkGfm, kitSyntax, ...remarkPlugins],
803
- rehypePlugins: [
804
- [rehypeResolveUrls, { resolve: resolveUrl }],
805
- [rehypeHeadingIds, { resolve: headingId }],
806
- // Both BEFORE the sanitiser, which judges their output (schema.ts).
807
- rehypeMath,
808
- ...highlight ? [rehypeHighlight] : [],
809
- [rehypeSanitize, sanitizeSchema],
810
- [rehypeShortCells, { max: shortCellLength }],
811
- [rehypeExternalLinks, { policy: externalLinks, origin }],
812
- ...rehypePlugins
813
- ],
814
- urlTransform: safeUrl,
815
- components: merged,
816
- allowedElements,
817
- disallowedElements,
818
- allowElement,
819
- unwrapDisallowed,
820
- children
821
- }
822
- );
823
- }
824
-
825
- // src/fixtures.ts
826
- var MARKDOWN_FIXTURES = [
827
- {
828
- name: "headings get GitHub anchors, de-duplicated",
829
- markdown: "# \u6982\u8981\n\n## Setup\n\n## Setup",
830
- html: '<h1 id="\u6982\u8981">\u6982\u8981</h1>\n<h2 id="setup">Setup</h2>\n<h2 id="setup-1">Setup</h2>'
831
- },
832
- {
833
- name: "emphasis, strong, strikethrough, inline code",
834
- markdown: "*em* **strong** ~~gone~~ `code`",
835
- html: "<p><em>em</em> <strong>strong</strong> <del>gone</del> <code>code</code></p>"
836
- },
837
- {
838
- name: "GFM table with alignment, in its scroll box",
839
- markdown: "| a | b |\n|:--|--:|\n| 1 | 2 |",
840
- // Every table sits in its own scroll box (gh#1131, renderer v2); a single short token per
841
- // cell carries `data-short` (gh#1150, renderer v3).
842
- html: '<div class="ui-prose-table-scroll" data-slot="prose-table-scroll"><table><thead><tr><th data-short="" style="text-align:left">a</th><th data-short="" style="text-align:right">b</th></tr></thead><tbody><tr><td data-short="" style="text-align:left">1</td><td data-short="" style="text-align:right">2</td></tr></tbody></table></div>'
843
- },
844
- {
845
- name: "task list stays read-only",
846
- markdown: "- [x] done\n- [ ] todo",
847
- html: '<ul class="contains-task-list">\n<li class="task-list-item"><input type="checkbox" disabled="" checked=""/> done</li>\n<li class="task-list-item"><input type="checkbox" disabled=""/> todo</li>\n</ul>'
848
- },
849
- {
850
- name: "fenced code keeps its language class, tokenised (renderer v6)",
851
- markdown: "```ts\nconst a = 1;\n```",
852
- html: '<pre><code class="language-ts"><span data-code-token="keyword">const</span> a = <span data-code-token="constant">1</span>;\n</code></pre>'
853
- },
854
- {
855
- name: "links and autolinks",
856
- markdown: "[docs](https://example.com/a) <https://example.com> mail@example.com",
857
- html: '<p><a href="https://example.com/a">docs</a> <a href="https://example.com">https://example.com</a> <a href="mailto:mail@example.com">mail@example.com</a></p>'
858
- },
859
- {
860
- name: "relative and fragment links pass",
861
- markdown: "[a](/wiki/Page) [b](#setup) [c](other)",
862
- html: '<p><a href="/wiki/Page">a</a> <a href="#setup">b</a> <a href="other">c</a></p>'
863
- },
864
- {
865
- name: "image by URL",
866
- markdown: "![chart](https://cdn.example.com/c.png)",
867
- html: '<p><img src="https://cdn.example.com/c.png" alt="chart"/></p>'
868
- },
869
- {
870
- name: "footnote link and its target agree",
871
- markdown: "Text[^1]\n\n[^1]: Note.",
872
- html: '<p>Text<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup></p>\n<section data-footnotes="true" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>\n<ol>\n<li id="user-content-fn-1">\n<p>Note. <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">\u21A9</a></p>\n</li>\n</ol>\n</section>'
873
- },
874
- {
875
- name: "mermaid fence before the diagram has rendered",
876
- markdown: "```mermaid\ngraph TD; A-->B\n```",
877
- html: '<figure data-mermaid="code"><div hidden=""></div><pre data-mermaid="code"><code class="language-mermaid">graph TD; A--&gt;B\n</code></pre></figure>'
878
- },
879
- {
880
- name: "raw HTML blocks are dropped, never parsed",
881
- hostile: true,
882
- markdown: '<script>alert(1)</script>\n\n<img src=x onerror="alert(1)">\n\n<div id="app-sidebar">x</div>',
883
- html: "\n\n",
884
- forbidden: ["<script", "<img", "onerror", "app-sidebar"]
885
- },
886
- {
887
- name: "inline raw HTML is dropped, its text kept",
888
- hostile: true,
889
- markdown: 'a <b onclick="alert(1)">bold</b> <iframe src="https://evil.example"></iframe> z',
890
- html: "<p>a bold z</p>",
891
- forbidden: ["<b", "onclick", "<iframe", "evil.example"]
892
- },
893
- {
894
- name: "javascript: and data: links are dropped",
895
- hostile: true,
896
- markdown: "[a](javascript:alert(1)) [b](JAVASCRIPT:alert(1)) [c](data:text/html,<script>alert(1)</script>) [d](vbscript:x)",
897
- html: "<p><a>a</a> <a>b</a> <a>c</a> <a>d</a></p>",
898
- forbidden: ["javascript:", "JAVASCRIPT:", "data:", "vbscript:"]
899
- },
900
- {
901
- name: "a scheme split by control characters is dropped",
902
- hostile: true,
903
- markdown: "[a](<java script:alert(1)>)",
904
- html: "<p><a>a</a></p>",
905
- forbidden: ["script:"]
906
- },
907
- {
908
- name: "data: and javascript: images are dropped",
909
- hostile: true,
910
- markdown: "![x](data:image/svg+xml;base64,PHN2Zz4=) ![y](javascript:alert(1))",
911
- html: '<p><img alt="x"/> <img alt="y"/></p>',
912
- forbidden: ["data:", "javascript:"]
913
- },
914
- // Grammar v1 (gh#1156, renderer v4): the constructs the codec and @godxjp/block-editor model.
915
- {
916
- name: "CJK-friendly emphasis next to \u3001\u3002\u300C\u300D\uFF08\uFF09",
917
- markdown: "\u306E**\u300C\u5F37\u8ABF\u300D**\u3067\u3059\u3002\uFF08*\u6CE8*\uFF09\u300C[\u30EA\u30F3\u30AF](https://example.com/)\u300D\u3001**\u592A\u5B57**\u3002",
918
- html: '<p>\u306E<strong>\u300C\u5F37\u8ABF\u300D</strong>\u3067\u3059\u3002\uFF08<em>\u6CE8</em>\uFF09\u300C<a href="https://example.com/">\u30EA\u30F3\u30AF</a>\u300D\u3001<strong>\u592A\u5B57</strong>\u3002</p>'
919
- },
920
- {
921
- name: "callout with a title, and one without",
922
- markdown: "> [!WARNING] Careful\n> Body\n\n> [!tip]\n> No title",
923
- html: '<div class="ui-prose-callout" data-kind="warning" role="note">\n<p class="ui-prose-callout-title">Careful</p>\n<p>Body</p>\n</div>\n<div class="ui-prose-callout" data-kind="tip" role="note">\n<p class="ui-prose-callout-title">Tip</p>\n<p>No title</p>\n</div>'
924
- },
925
- {
926
- name: "a fold marker or unknown kind stays a quote",
927
- markdown: "> [!NOTE]- folded\n\n> [!INFO] unknown",
928
- html: "<blockquote>\n<p>[!NOTE]- folded</p>\n</blockquote>\n<blockquote>\n<p>[!INFO] unknown</p>\n</blockquote>"
929
- },
930
- {
931
- name: "toggle with an inline summary",
932
- markdown: ":::toggle[Read *more*]\nBody\n:::",
933
- html: '<details class="ui-prose-toggle"><summary class="ui-prose-toggle-summary">Read <em>more</em></summary><p>Body</p></details>'
934
- },
935
- {
936
- name: "columns with a width and an equal share",
937
- markdown: "::::columns\n:::column{width=40}\nLeft\n:::\n\n:::column\nRight\n:::\n::::",
938
- html: '<div class="ui-prose-columns"><div class="ui-prose-column" style="--prose-column-grow:40"><p>Left</p></div><div class="ui-prose-column" style="--prose-column-grow:60"><p>Right</p></div></div>'
939
- },
940
- {
941
- name: "wikilinks, embeds and times stay text",
942
- markdown: "See [[Page#Intro|intro]] at 10:30.\n\n![[Diagram]]",
943
- html: "<p>See [[Page#Intro|intro]] at 10:30.</p>\n<p>![[Diagram]]</p>"
944
- },
945
- {
946
- // Renderer v5 (gh#1163): YAML front matter is metadata, not body. Before v5 it rendered as an
947
- // <hr> plus a setext <h2> holding the YAML text.
948
- name: "YAML front matter renders nothing",
949
- markdown: "---\ntitle: Spec\ntags: [a]\n---\n\n# Heading\n\nBody",
950
- html: '<h1 id="heading">Heading</h1>\n<p>Body</p>',
951
- forbidden: ["title: Spec", "<hr"]
952
- },
953
- // Renderer v6: math (KaTeX, before the sanitiser) and syntax colour (`data-code-token`).
954
- {
955
- name: "inline and display math are drawn by KaTeX; `\\,` keeps its backslash",
956
- markdown: "Inline $a\\,b$ and display:\n\n$$\nx^2\n$$",
957
- html: '<p>Inline <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mtext>\u2009</mtext><mi>b</mi></mrow><annotation encoding="application/x-tex">a\\,b</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.1667em"></span><span class="mord mathnormal">b</span></span></span></span> and display:</p>\n<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>x</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">x^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8641em"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em"><span style="top:-3.113em;margin-right:0.05em"><span class="pstrut" style="height:2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>'
958
- },
959
- {
960
- name: "a ```math fence is display math",
961
- markdown: "```math\n\\frac{1}{2}\n```",
962
- html: '<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\\frac{1}{2}\n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.0074em;vertical-align:-0.686em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em"><span style="top:-2.314em"><span class="pstrut" style="height:3em"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.23em"><span class="pstrut" style="height:3em"></span><span class="frac-line" style="border-bottom-width:0.04em"></span></span><span style="top:-3.677em"><span class="pstrut" style="height:3em"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">\u200B</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>'
963
- },
964
- {
965
- name: "prices stay text: a single `$` follows Pandoc's rule",
966
- markdown: "$5 and $10, $5-$10, \\$x\\$",
967
- html: "<p>$5 and $10, $5-$10, $x$</p>",
968
- forbidden: ["katex"]
969
- },
970
- {
971
- name: "a formula that does not parse shows its source",
972
- markdown: "$\\frac{1$",
973
- html: '<p><span class="katex-error" title="ParseError: KaTeX parse error: Unexpected end of input in a macro argument, expected &#x27;}&#x27; at end of input: \\frac{1" style="color:#cc0000">\\frac{1</span></p>'
974
- },
975
- {
976
- name: "KaTeX \\href, \\url and \\includegraphics stay text (trust: false)",
977
- hostile: true,
978
- markdown: "$\\href{javascript:alert(1)}{a}$ $\\url{https://evil.example}$ $\\includegraphics{https://evil.example/i.png}$",
979
- html: '<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#cc0000"><mtext>\\href</mtext></mstyle></mrow><annotation encoding="application/x-tex">\\href{javascript:alert(1)}{a}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord text" style="color:#cc0000"><span class="mord" style="color:#cc0000">\\href</span></span></span></span></span> <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#cc0000"><mtext>\\url</mtext></mstyle></mrow><annotation encoding="application/x-tex">\\url{https://evil.example}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord text" style="color:#cc0000"><span class="mord" style="color:#cc0000">\\url</span></span></span></span></span> <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#cc0000"><mtext>\\includegraphics</mtext></mstyle></mrow><annotation encoding="application/x-tex">\\includegraphics{https://evil.example/i.png}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord text" style="color:#cc0000"><span class="mord" style="color:#cc0000">\\includegraphics</span></span></span></span></span></p>',
980
- forbidden: ["<a ", "href=", "<img", "src="]
981
- },
982
- {
983
- name: "KaTeX \\htmlClass, \\htmlId, \\htmlStyle and \\htmlData stay text",
984
- hostile: true,
985
- markdown: "$\\htmlClass{ui-pwn}{a}$ $\\htmlId{pwn}{b}$ $\\htmlStyle{background:url(https://evil.example)}{c}$ $\\htmlData{pwn=1}{d}$",
986
- html: '<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#cc0000"><mtext>\\htmlClass</mtext></mstyle></mrow><annotation encoding="application/x-tex">\\htmlClass{ui-pwn}{a}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord text" style="color:#cc0000"><span class="mord" style="color:#cc0000">\\htmlClass</span></span></span></span></span> <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#cc0000"><mtext>\\htmlId</mtext></mstyle></mrow><annotation encoding="application/x-tex">\\htmlId{pwn}{b}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord text" style="color:#cc0000"><span class="mord" style="color:#cc0000">\\htmlId</span></span></span></span></span> <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#cc0000"><mtext>\\htmlStyle</mtext></mstyle></mrow><annotation encoding="application/x-tex">\\htmlStyle{background:url(https://evil.example)}{c}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord text" style="color:#cc0000"><span class="mord" style="color:#cc0000">\\htmlStyle</span></span></span></span></span> <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#cc0000"><mtext>\\htmlData</mtext></mstyle></mrow><annotation encoding="application/x-tex">\\htmlData{pwn=1}{d}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord text" style="color:#cc0000"><span class="mord" style="color:#cc0000">\\htmlData</span></span></span></span></span></p>',
987
- forbidden: ['class="ui-pwn"', 'id="pwn"', "data-pwn", 'style="background']
988
- },
989
- {
990
- name: "a user-sized KaTeX box is capped at 10em",
991
- hostile: true,
992
- markdown: "$\\rule{9999em}{9999em}$",
993
- html: '<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mpadded height="0em" voffset="0em"><mspace mathbackground="black" width="10em" height="10em"></mspace></mpadded></mrow><annotation encoding="application/x-tex">\\rule{9999em}{9999em}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:10em"></span><span class="mord rule" style="border-right-width:10em;border-top-width:10em;bottom:0em"></span></span></span></span></p>',
994
- forbidden: ["width:9999em", 'width="9999em"']
995
- },
996
- {
997
- name: "HTML in a highlighted fence stays text",
998
- hostile: true,
999
- markdown: "```html\n<script>alert(1)</script>\n```",
1000
- html: '<pre><code class="language-html"><span data-code-token="punctuation">&lt;<span data-code-token="keyword">script</span>&gt;</span>alert(1)<span data-code-token="punctuation">&lt;/<span data-code-token="keyword">script</span>&gt;</span>\n</code></pre>',
1001
- forbidden: ["<script"]
1002
- },
1003
- {
1004
- name: "a fence in an unknown language stays plain",
1005
- markdown: "```unknown\nlet a = 1\n```",
1006
- html: '<pre><code class="language-unknown">let a = 1\n</code></pre>'
1007
- }
1008
- ];
1009
29
  export {
1010
30
  MARKDOWN_FIXTURES,
1011
31
  MARKDOWN_FORMAT,