@godxjp/markdown 33.21.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/README.md +58 -2
- package/dist/chunk-AKKOCR2Y.js +79 -0
- package/dist/chunk-ANYWBQJT.js +21 -0
- package/dist/chunk-JJ2A3D7U.js +14 -0
- package/dist/chunk-RRNN4HLQ.js +940 -0
- package/dist/chunk-XXBFDQOW.js +118 -0
- package/dist/codec/index.js +97 -66
- package/dist/fixtures-CKcZEUes.d.ts +246 -0
- package/dist/highlight-TYIW3L3V.js +11 -0
- package/dist/index.d.ts +10 -213
- package/dist/index.js +24 -1004
- package/dist/katex-styles-JVRNFDYG.js +4 -0
- package/dist/lazy.d.ts +16 -0
- package/dist/lazy.js +126 -0
- package/dist/math-DOI7WLLZ.js +13 -0
- package/package.json +7 -2
- package/dist/codec/index.d.ts +0 -71
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/highlight.ts
|
|
4
|
+
import { createLowlight } from "lowlight";
|
|
5
|
+
import bash from "highlight.js/lib/languages/bash";
|
|
6
|
+
import css from "highlight.js/lib/languages/css";
|
|
7
|
+
import diff from "highlight.js/lib/languages/diff";
|
|
8
|
+
import dockerfile from "highlight.js/lib/languages/dockerfile";
|
|
9
|
+
import go from "highlight.js/lib/languages/go";
|
|
10
|
+
import ini from "highlight.js/lib/languages/ini";
|
|
11
|
+
import java from "highlight.js/lib/languages/java";
|
|
12
|
+
import javascript from "highlight.js/lib/languages/javascript";
|
|
13
|
+
import json from "highlight.js/lib/languages/json";
|
|
14
|
+
import markdown from "highlight.js/lib/languages/markdown";
|
|
15
|
+
import php from "highlight.js/lib/languages/php";
|
|
16
|
+
import python from "highlight.js/lib/languages/python";
|
|
17
|
+
import rust from "highlight.js/lib/languages/rust";
|
|
18
|
+
import shell from "highlight.js/lib/languages/shell";
|
|
19
|
+
import sql from "highlight.js/lib/languages/sql";
|
|
20
|
+
import typescript from "highlight.js/lib/languages/typescript";
|
|
21
|
+
import xml from "highlight.js/lib/languages/xml";
|
|
22
|
+
import yaml from "highlight.js/lib/languages/yaml";
|
|
23
|
+
import { visit } from "unist-util-visit";
|
|
24
|
+
var lowlight = createLowlight({
|
|
25
|
+
bash,
|
|
26
|
+
css,
|
|
27
|
+
diff,
|
|
28
|
+
dockerfile,
|
|
29
|
+
go,
|
|
30
|
+
ini,
|
|
31
|
+
java,
|
|
32
|
+
javascript,
|
|
33
|
+
json,
|
|
34
|
+
markdown,
|
|
35
|
+
php,
|
|
36
|
+
python,
|
|
37
|
+
rust,
|
|
38
|
+
shell,
|
|
39
|
+
sql,
|
|
40
|
+
typescript,
|
|
41
|
+
xml,
|
|
42
|
+
yaml
|
|
43
|
+
});
|
|
44
|
+
var SCOPES = {
|
|
45
|
+
comment: "comment",
|
|
46
|
+
quote: "comment",
|
|
47
|
+
doctag: "keyword",
|
|
48
|
+
keyword: "keyword",
|
|
49
|
+
built_in: "keyword",
|
|
50
|
+
type: "keyword",
|
|
51
|
+
"selector-tag": "keyword",
|
|
52
|
+
tag: "punctuation",
|
|
53
|
+
name: "keyword",
|
|
54
|
+
section: "keyword",
|
|
55
|
+
string: "string",
|
|
56
|
+
char: "string",
|
|
57
|
+
regexp: "string-expression",
|
|
58
|
+
subst: "string-expression",
|
|
59
|
+
"template-variable": "string-expression",
|
|
60
|
+
"title.function": "function",
|
|
61
|
+
title: "function",
|
|
62
|
+
"title.class": "constant",
|
|
63
|
+
number: "constant",
|
|
64
|
+
literal: "constant",
|
|
65
|
+
symbol: "constant",
|
|
66
|
+
"variable.constant": "constant",
|
|
67
|
+
"variable.language": "constant",
|
|
68
|
+
"selector-id": "constant",
|
|
69
|
+
"selector-class": "constant",
|
|
70
|
+
bullet: "constant",
|
|
71
|
+
params: "parameter",
|
|
72
|
+
attr: "parameter",
|
|
73
|
+
attribute: "parameter",
|
|
74
|
+
property: "parameter",
|
|
75
|
+
"template-tag": "parameter",
|
|
76
|
+
punctuation: "punctuation",
|
|
77
|
+
operator: "punctuation",
|
|
78
|
+
link: "link",
|
|
79
|
+
addition: "inserted",
|
|
80
|
+
deletion: "deleted"
|
|
81
|
+
};
|
|
82
|
+
function tokenOf(className) {
|
|
83
|
+
if (!Array.isArray(className) || !String(className[0]).startsWith("hljs-")) return void 0;
|
|
84
|
+
const parts = className.map(
|
|
85
|
+
(name, i) => i === 0 ? String(name).slice(5) : String(name).replace(/_+$/, "")
|
|
86
|
+
);
|
|
87
|
+
for (let n = parts.length; n > 0; n--) {
|
|
88
|
+
const token = SCOPES[parts.slice(0, n).join(".")];
|
|
89
|
+
if (token) return token;
|
|
90
|
+
}
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
function retag(nodes) {
|
|
94
|
+
return nodes.flatMap((node) => {
|
|
95
|
+
if (node.type !== "element") return [node];
|
|
96
|
+
const children = retag(node.children);
|
|
97
|
+
const token = tokenOf(node.properties?.className);
|
|
98
|
+
return token ? [{ type: "element", tagName: "span", properties: { dataCodeToken: token }, children }] : children;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
var SKIP = /* @__PURE__ */ new Set(["mermaid", "math"]);
|
|
102
|
+
function rehypeHighlight() {
|
|
103
|
+
return (tree) => {
|
|
104
|
+
visit(tree, "element", (node, _index, parent) => {
|
|
105
|
+
if (node.tagName !== "code" || parent?.type !== "element" || parent.tagName !== "pre") return;
|
|
106
|
+
const classes = node.properties?.className ?? [];
|
|
107
|
+
const language = classes.find((name) => name.startsWith("language-"))?.slice(9);
|
|
108
|
+
if (!language || SKIP.has(language) || !lowlight.registered(language)) return;
|
|
109
|
+
if (!node.children.every((child) => child.type === "text")) return;
|
|
110
|
+
const text = node.children.map((child) => child.type === "text" ? child.value : "").join("");
|
|
111
|
+
node.children = retag(lowlight.highlight(language, text).children);
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
rehypeHighlight
|
|
118
|
+
};
|
package/dist/codec/index.js
CHANGED
|
@@ -1,73 +1,15 @@
|
|
|
1
1
|
// src/codec/parse.ts
|
|
2
2
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
3
|
-
import { toMarkdown } from "mdast-util-to-markdown";
|
|
3
|
+
import { toMarkdown as toMarkdown2 } from "mdast-util-to-markdown";
|
|
4
4
|
|
|
5
5
|
// src/codec/model.ts
|
|
6
6
|
var CALLOUT_KINDS = ["note", "tip", "important", "warning", "caution"];
|
|
7
7
|
var COLUMNS_MIN = 2;
|
|
8
8
|
var COLUMNS_MAX = 4;
|
|
9
|
+
var MODELLED_DIRECTIVES = ["toggle", "columns", "column"];
|
|
9
10
|
|
|
10
|
-
// src/codec/
|
|
11
|
-
|
|
12
|
-
function readCallout(node) {
|
|
13
|
-
const [first, ...rest] = node.children;
|
|
14
|
-
if (first?.type !== "paragraph") return null;
|
|
15
|
-
const [head, ...tail] = first.children;
|
|
16
|
-
if (head?.type !== "text") return null;
|
|
17
|
-
const newline = head.value.indexOf("\n");
|
|
18
|
-
if (newline === -1 && tail.length > 0) return null;
|
|
19
|
-
const line = newline === -1 ? head.value : head.value.slice(0, newline);
|
|
20
|
-
const match = CALLOUT.exec(line);
|
|
21
|
-
const kind = match?.[1]?.toLowerCase();
|
|
22
|
-
if (!match || !kind || !CALLOUT_KINDS.includes(kind)) return null;
|
|
23
|
-
const remainder = [
|
|
24
|
-
...newline === -1 ? [] : [{ ...head, value: head.value.slice(newline + 1) }],
|
|
25
|
-
...tail
|
|
26
|
-
].filter((n) => !(n.type === "text" && n.value === ""));
|
|
27
|
-
return {
|
|
28
|
-
kind,
|
|
29
|
-
title: match[2]?.trim() || null,
|
|
30
|
-
body: [
|
|
31
|
-
...remainder.length ? [{ type: "paragraph", children: remainder }] : [],
|
|
32
|
-
...rest
|
|
33
|
-
]
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function labelOf(node) {
|
|
37
|
-
const first = node.children[0];
|
|
38
|
-
return first?.type === "paragraph" && first.data?.directiveLabel ? first : null;
|
|
39
|
-
}
|
|
40
|
-
function readToggle(node) {
|
|
41
|
-
if (node.name !== "toggle" || Object.keys(node.attributes ?? {}).length > 0) return null;
|
|
42
|
-
const label = labelOf(node);
|
|
43
|
-
return {
|
|
44
|
-
summary: label?.children ?? [],
|
|
45
|
-
body: label ? node.children.slice(1) : node.children
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function readColumns(node, nested) {
|
|
49
|
-
if (node.name !== "columns" || nested || labelOf(node) || Object.keys(node.attributes ?? {}).length) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const columns = node.children;
|
|
53
|
-
if (columns.length < COLUMNS_MIN || columns.length > COLUMNS_MAX) return null;
|
|
54
|
-
const parts = [];
|
|
55
|
-
for (const column of columns) {
|
|
56
|
-
if (column.type !== "containerDirective" || column.name !== "column" || labelOf(column))
|
|
57
|
-
return null;
|
|
58
|
-
const attributes = Object.entries(column.attributes ?? {});
|
|
59
|
-
if (attributes.length > 1) return null;
|
|
60
|
-
let width = null;
|
|
61
|
-
if (attributes.length === 1) {
|
|
62
|
-
const [key, value] = attributes[0];
|
|
63
|
-
if (key !== "width" || typeof value !== "string" || !/^(?:[1-9]\d?|100)$/.test(value))
|
|
64
|
-
return null;
|
|
65
|
-
width = Number(value);
|
|
66
|
-
}
|
|
67
|
-
parts.push({ width, body: column.children });
|
|
68
|
-
}
|
|
69
|
-
return parts;
|
|
70
|
-
}
|
|
11
|
+
// src/codec/directive.ts
|
|
12
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
71
13
|
|
|
72
14
|
// src/codec/syntax.ts
|
|
73
15
|
import { directive } from "micromark-extension-directive";
|
|
@@ -186,6 +128,87 @@ function toMarkdownOptions() {
|
|
|
186
128
|
};
|
|
187
129
|
}
|
|
188
130
|
|
|
131
|
+
// src/codec/directive.ts
|
|
132
|
+
var markdownOf = (node) => toMarkdown(node, toMarkdownOptions()).replace(/\n+$/, "");
|
|
133
|
+
function directiveParts(node) {
|
|
134
|
+
const [first, ...rest] = node.children;
|
|
135
|
+
const hasLabel = first?.type === "paragraph" && Boolean(first.data?.directiveLabel);
|
|
136
|
+
const attributes = {};
|
|
137
|
+
for (const [key, value] of Object.entries(node.attributes ?? {})) attributes[key] = value ?? "";
|
|
138
|
+
return {
|
|
139
|
+
name: node.name,
|
|
140
|
+
label: hasLabel ? markdownOf({ type: "paragraph", children: first.children }) : null,
|
|
141
|
+
attributes,
|
|
142
|
+
source: markdownOf(node),
|
|
143
|
+
body: markdownOf({
|
|
144
|
+
type: "root",
|
|
145
|
+
children: hasLabel ? rest : node.children
|
|
146
|
+
})
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/codec/readers.ts
|
|
151
|
+
var CALLOUT = /^\[!([A-Za-z]+)\](?:[ \t]+([^\n]*))?$/;
|
|
152
|
+
function readCallout(node) {
|
|
153
|
+
const [first, ...rest] = node.children;
|
|
154
|
+
if (first?.type !== "paragraph") return null;
|
|
155
|
+
const [head, ...tail] = first.children;
|
|
156
|
+
if (head?.type !== "text") return null;
|
|
157
|
+
const newline = head.value.indexOf("\n");
|
|
158
|
+
if (newline === -1 && tail.length > 0) return null;
|
|
159
|
+
const line = newline === -1 ? head.value : head.value.slice(0, newline);
|
|
160
|
+
const match = CALLOUT.exec(line);
|
|
161
|
+
const kind = match?.[1]?.toLowerCase();
|
|
162
|
+
if (!match || !kind || !CALLOUT_KINDS.includes(kind)) return null;
|
|
163
|
+
const remainder = [
|
|
164
|
+
...newline === -1 ? [] : [{ ...head, value: head.value.slice(newline + 1) }],
|
|
165
|
+
...tail
|
|
166
|
+
].filter((n) => !(n.type === "text" && n.value === ""));
|
|
167
|
+
return {
|
|
168
|
+
kind,
|
|
169
|
+
title: match[2]?.trim() || null,
|
|
170
|
+
body: [
|
|
171
|
+
...remainder.length ? [{ type: "paragraph", children: remainder }] : [],
|
|
172
|
+
...rest
|
|
173
|
+
]
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function labelOf(node) {
|
|
177
|
+
const first = node.children[0];
|
|
178
|
+
return first?.type === "paragraph" && first.data?.directiveLabel ? first : null;
|
|
179
|
+
}
|
|
180
|
+
function readToggle(node) {
|
|
181
|
+
if (node.name !== "toggle" || Object.keys(node.attributes ?? {}).length > 0) return null;
|
|
182
|
+
const label = labelOf(node);
|
|
183
|
+
return {
|
|
184
|
+
summary: label?.children ?? [],
|
|
185
|
+
body: label ? node.children.slice(1) : node.children
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function readColumns(node, nested) {
|
|
189
|
+
if (node.name !== "columns" || nested || labelOf(node) || Object.keys(node.attributes ?? {}).length) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const columns = node.children;
|
|
193
|
+
if (columns.length < COLUMNS_MIN || columns.length > COLUMNS_MAX) return null;
|
|
194
|
+
const parts = [];
|
|
195
|
+
for (const column of columns) {
|
|
196
|
+
if (column.type !== "containerDirective" || column.name !== "column" || labelOf(column))
|
|
197
|
+
return null;
|
|
198
|
+
const attributes = Object.entries(column.attributes ?? {});
|
|
199
|
+
if (attributes.length > 1) return null;
|
|
200
|
+
let width = null;
|
|
201
|
+
if (attributes.length === 1) {
|
|
202
|
+
const [key, value] = attributes[0];
|
|
203
|
+
if (key !== "width" || typeof value !== "string" || !/^(?:[1-9]\d?|100)$/.test(value))
|
|
204
|
+
return null;
|
|
205
|
+
width = Number(value);
|
|
206
|
+
}
|
|
207
|
+
parts.push({ width, body: column.children });
|
|
208
|
+
}
|
|
209
|
+
return parts;
|
|
210
|
+
}
|
|
211
|
+
|
|
189
212
|
// src/codec/parse.ts
|
|
190
213
|
var WIKILINK = /!\[\[([^[\]\n]+)\]\]|\[\[([^[\]\n|#]+)(?:#([^[\]\n|]+))?(?:\|([^[\]\n]+))?\]\]/g;
|
|
191
214
|
function parseMdast(markdown) {
|
|
@@ -195,7 +218,7 @@ function parseMdast(markdown) {
|
|
|
195
218
|
});
|
|
196
219
|
}
|
|
197
220
|
function raw(node, inline2) {
|
|
198
|
-
const source = node.type === "html" ? node.value :
|
|
221
|
+
const source = node.type === "html" ? node.value : toMarkdown2(node, toMarkdownOptions()).replace(/\n+$/, "");
|
|
199
222
|
return { type: inline2 ? "rawInline" : "rawBlock", attrs: { source } };
|
|
200
223
|
}
|
|
201
224
|
function withMark(marks, mark) {
|
|
@@ -342,7 +365,12 @@ function directiveBlock(node, depth) {
|
|
|
342
365
|
}))
|
|
343
366
|
};
|
|
344
367
|
}
|
|
345
|
-
return raw(node, false);
|
|
368
|
+
if (MODELLED_DIRECTIVES.includes(node.name)) return raw(node, false);
|
|
369
|
+
return { type: "directive", attrs: { name: node.name, source: directiveParts(node).source } };
|
|
370
|
+
}
|
|
371
|
+
function readDirective(source) {
|
|
372
|
+
const [only, ...rest] = parseMdast(source.replace(/\r\n?/g, "\n")).children;
|
|
373
|
+
return only?.type === "containerDirective" && rest.length === 0 ? directiveParts(only) : null;
|
|
346
374
|
}
|
|
347
375
|
function nonEmpty(content2) {
|
|
348
376
|
return content2.length ? content2 : [{ type: "paragraph" }];
|
|
@@ -421,7 +449,7 @@ function parse(markdown) {
|
|
|
421
449
|
}
|
|
422
450
|
|
|
423
451
|
// src/codec/serialize.ts
|
|
424
|
-
import { toMarkdown as
|
|
452
|
+
import { toMarkdown as toMarkdown3 } from "mdast-util-to-markdown";
|
|
425
453
|
var MARK_RANK = ["link", "bold", "italic", "strike"];
|
|
426
454
|
var hasMark = (node, type) => node.marks?.some((m) => m.type === type) ?? false;
|
|
427
455
|
var linkOf = (node) => node.marks?.find((m) => m.type === "link");
|
|
@@ -601,6 +629,7 @@ function block2(node) {
|
|
|
601
629
|
case "embed":
|
|
602
630
|
return [{ type: "html", value: `![[${String(attrs.target)}]]` }];
|
|
603
631
|
case "rawBlock":
|
|
632
|
+
case "directive":
|
|
604
633
|
return [{ type: "html", value: String(attrs.source) }];
|
|
605
634
|
case "frontmatter":
|
|
606
635
|
return [{ type: "yaml", value: String(attrs.source) }];
|
|
@@ -635,7 +664,7 @@ function decodeDollarEscapes(markdown) {
|
|
|
635
664
|
}
|
|
636
665
|
function serialize(doc) {
|
|
637
666
|
const root = { type: "root", children: flow(doc.content ?? []) };
|
|
638
|
-
const markdown =
|
|
667
|
+
const markdown = toMarkdown3(root, toMarkdownOptions());
|
|
639
668
|
if (markdown.trim() === "") return "";
|
|
640
669
|
const tidy = markdown.replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").replace(/\n+$/, "\n");
|
|
641
670
|
return decodeDollarEscapes(decodeAttentionReferences(tidy));
|
|
@@ -675,9 +704,11 @@ export {
|
|
|
675
704
|
CODEC_ID,
|
|
676
705
|
COLUMNS_MAX,
|
|
677
706
|
COLUMNS_MIN,
|
|
707
|
+
MODELLED_DIRECTIVES,
|
|
678
708
|
frontmatterOf,
|
|
679
709
|
normalize,
|
|
680
710
|
outline,
|
|
681
711
|
parse,
|
|
712
|
+
readDirective,
|
|
682
713
|
serialize
|
|
683
714
|
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Options as Options$1, Components } from 'react-markdown';
|
|
3
|
+
import { Root } from 'hast';
|
|
4
|
+
import { Options } from 'rehype-sanitize';
|
|
5
|
+
|
|
6
|
+
/** What a host is told about each heading, in document order. */
|
|
7
|
+
type HeadingInfo = {
|
|
8
|
+
depth: number;
|
|
9
|
+
text: string;
|
|
10
|
+
index: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Chooses a heading's anchor. Return `undefined` to give the heading no id.
|
|
14
|
+
*
|
|
15
|
+
* The default is GitHub's slug (`github-slugger`, the same rule `rehype-slug` uses), de-duplicated
|
|
16
|
+
* in document order (`a`, `a-1`, `a-2`). A host whose server already assigned anchors (godx-task
|
|
17
|
+
* keeps its outline server-side, so a link and the page agree) passes its own: computing "the same"
|
|
18
|
+
* slug in two languages is one rule written twice, and it drifts.
|
|
19
|
+
*/
|
|
20
|
+
type HeadingIdResolver = (heading: HeadingInfo) => string | undefined;
|
|
21
|
+
/** rehype plugin: an `id` on every heading. Runs BEFORE the sanitiser, which allows `id` on h1–h6. */
|
|
22
|
+
declare function rehypeHeadingIds(options?: {
|
|
23
|
+
resolve?: HeadingIdResolver;
|
|
24
|
+
}): (tree: Root) => void;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* THE ONE SANITISER SCHEMA (gh#1108). godx-task and godx-content each carried their own copy of
|
|
28
|
+
* this; every consumer now renders through this one, so a policy change happens once.
|
|
29
|
+
*
|
|
30
|
+
* Built on `rehype-sanitize`'s `defaultSchema` (GitHub's), with three deliberate changes:
|
|
31
|
+
*
|
|
32
|
+
* - `clobberPrefix: ""`. The default re-prefixes every `id`, but remark-gfm already writes footnote
|
|
33
|
+
* ids and their `href`s as a matched `user-content-` pair, so the second prefix split the pair and
|
|
34
|
+
* footnote links went nowhere (measured in godx-task). The clobber exists to stop a body minting
|
|
35
|
+
* an id that collides with the host's; this renderer never parses raw HTML (no `rehype-raw`), so
|
|
36
|
+
* the only ids in the tree are the ones the pipeline generated. `raw-html.test` pins that.
|
|
37
|
+
* - `protocols`: links and images allow only `http`, `https`, `mailto` (links) and relative URLs.
|
|
38
|
+
* `javascript:`, `data:`, `vbscript:` and every other scheme are dropped.
|
|
39
|
+
* - Task-list checkboxes stay `disabled` (the default): ticking one would be a write to the body.
|
|
40
|
+
* - KaTeX's output (math.ts `katexSchema`: MathML, its own classes, geometry-only inline styles,
|
|
41
|
+
* `svg` paths for radicals and stretchy arrows) and the code tokens (`data-code-token`, the kit's
|
|
42
|
+
* twelve names) — both produced by the pipeline before this schema judges them (renderer v6).
|
|
43
|
+
*
|
|
44
|
+
* Hosts extend it with `extendSchema`, never by editing a copy.
|
|
45
|
+
*/
|
|
46
|
+
declare const markdownSchema: Options;
|
|
47
|
+
/** Extra tags / attributes a host needs (a callout marker, a section element). Additive only. */
|
|
48
|
+
type SchemaExtension = {
|
|
49
|
+
tagNames?: string[];
|
|
50
|
+
attributes?: Record<string, NonNullable<Options["attributes"]>[string]>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The schema plus a host's additions. ADDITIVE: an extension can allow a tag or an attribute, never
|
|
54
|
+
* widen `protocols` or bring raw HTML back, so a consumer cannot loosen the URL policy by accident.
|
|
55
|
+
*
|
|
56
|
+
* A rule for an attribute the base ALREADY constrains is MERGED into that rule (gh#1116): the
|
|
57
|
+
* sanitiser reads the first rule it finds for an attribute, so appending `["className",
|
|
58
|
+
* "math-inline"]` beside the default `["className", /^language-./]` was silently ignored and the
|
|
59
|
+
* formula class stripped. Merged, it is one rule allowing both. An attribute the base allows with
|
|
60
|
+
* no value constraint stays unconstrained.
|
|
61
|
+
*/
|
|
62
|
+
declare function extendSchema(extension?: SchemaExtension): Options;
|
|
63
|
+
/**
|
|
64
|
+
* react-markdown's `urlTransform`, with the same allow-list as the schema, so a URL is checked
|
|
65
|
+
* where it is written as well as where it is sanitised. Relative URLs (`/x`, `x`, `#a`, `?q`) pass;
|
|
66
|
+
* a scheme outside the list becomes `""`. A host scheme such as `asset:` is resolved by the host's
|
|
67
|
+
* `resolveUrl` BEFORE this check, so it can only turn into an allowed URL or be dropped.
|
|
68
|
+
*/
|
|
69
|
+
declare function safeUrl(url: string, key: string): string;
|
|
70
|
+
|
|
71
|
+
/** GitHub alert kinds, the only callouts v1 knows. Serialized uppercase. */
|
|
72
|
+
declare const CALLOUT_KINDS: readonly ["note", "tip", "important", "warning", "caution"];
|
|
73
|
+
type CalloutKind = (typeof CALLOUT_KINDS)[number];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* DIRECTIVE BLOCKS: a container directive the kit does not model itself (`:::poll[…]{…}`,
|
|
77
|
+
* `::::form`), read once so the codec, the renderer and the block editor hand a host the same
|
|
78
|
+
* parts. Here everything is the CANONICAL Markdown the codec writes (what the editor saves);
|
|
79
|
+
* `<Markdown directives>` slices the same parts from the body as written, which is the same text for
|
|
80
|
+
* a body the editor saved.
|
|
81
|
+
*/
|
|
82
|
+
type DirectiveBlock = {
|
|
83
|
+
/** The directive's name: `poll` for `:::poll`. */
|
|
84
|
+
name: string;
|
|
85
|
+
/** The `[label]` as inline Markdown, or `null` when the directive has none. */
|
|
86
|
+
label: string | null;
|
|
87
|
+
/** The `{…}` attributes; a bare flag (`{anonymous}`) is `""`, `{#x}` is `id`, `{.x}` is `class`. */
|
|
88
|
+
attributes: Record<string, string>;
|
|
89
|
+
/** The whole directive, fences included: canonical from the codec, as written from `<Markdown>`. */
|
|
90
|
+
source: string;
|
|
91
|
+
/** The content between the fences (without the label) as Markdown; `""` when empty. */
|
|
92
|
+
body: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type PluggableList = NonNullable<Options$1["remarkPlugins"]>;
|
|
96
|
+
/** What a host's directive renderer receives: the block's parts plus its content, already rendered. */
|
|
97
|
+
type DirectiveRenderProps = DirectiveBlock & {
|
|
98
|
+
/** The body as the renderer draws it (the label excluded) — a `::::form`'s `:::poll` cards. */
|
|
99
|
+
children: React.ReactNode;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Draws one container directive (`:::poll[…]{…}`). Return `undefined` to leave the block as the
|
|
103
|
+
* kit renders an unknown directive (its content in a `div.ui-prose-directive`) — a block the host
|
|
104
|
+
* judges invalid. Called during render: return elements, call no hooks here.
|
|
105
|
+
*/
|
|
106
|
+
type DirectiveRenderer = (block: DirectiveRenderProps) => React.ReactNode | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* The stored body's format, and the renderer generation that produced its output. A consumer
|
|
109
|
+
* records both on every saved version (content-media contracts § 6.6) and never re-interprets an
|
|
110
|
+
* old version under a different format. `RENDERER_VERSION` moves when the SAME input renders
|
|
111
|
+
* differently — a schema change, a new default plugin — not on every release.
|
|
112
|
+
*/
|
|
113
|
+
declare const MARKDOWN_FORMAT = "md";
|
|
114
|
+
declare const RENDERER_VERSION = 6;
|
|
115
|
+
type MarkdownProps = {
|
|
116
|
+
/** The Markdown source. */
|
|
117
|
+
children: string;
|
|
118
|
+
/**
|
|
119
|
+
* remark plugins run after GFM (a callout or embed marker plugin). Whatever they emit still goes
|
|
120
|
+
* through the sanitiser, so a marker attribute must be allowed with `schema`.
|
|
121
|
+
*/
|
|
122
|
+
remarkPlugins?: PluggableList;
|
|
123
|
+
/**
|
|
124
|
+
* rehype plugins run AFTER the sanitiser, on a tree that is already safe. Presentation only: a
|
|
125
|
+
* plugin here must not introduce URLs, raw HTML or event handlers — it is past the gate.
|
|
126
|
+
*/
|
|
127
|
+
rehypePlugins?: PluggableList;
|
|
128
|
+
/** Tags / attributes a host plugin needs. Additive; cannot widen the URL policy. */
|
|
129
|
+
schema?: SchemaExtension;
|
|
130
|
+
/**
|
|
131
|
+
* Element overrides (react-markdown `components`). A host `pre` renders every fence EXCEPT
|
|
132
|
+
* ```mermaid, which is drawn through the gate first; `mermaid={false}` hands those to it too.
|
|
133
|
+
*/
|
|
134
|
+
components?: Components;
|
|
135
|
+
/**
|
|
136
|
+
* Resolves a host-specific URL (`asset:…`, a wiki link) to a real one. Runs before the sanitiser
|
|
137
|
+
* and the scheme allow-list, so whatever it returns is still checked; `undefined` keeps the URL.
|
|
138
|
+
*/
|
|
139
|
+
resolveUrl?: (url: string, key: "href" | "src" | string) => string | undefined;
|
|
140
|
+
/** Heading anchors: GitHub slugs by default; a host with server-side anchors supplies its own. */
|
|
141
|
+
headingId?: HeadingIdResolver;
|
|
142
|
+
/**
|
|
143
|
+
* Render ```mermaid fences as gated diagrams (default true). They are drawn BEFORE a host `pre`
|
|
144
|
+
* is consulted, so a host code renderer keeps Mermaid; `false` hands mermaid fences to the host
|
|
145
|
+
* `pre` (or plain code) as well (gh#1116).
|
|
146
|
+
*/
|
|
147
|
+
mermaid?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Tokenise fenced code in a known language as `<span data-code-token="keyword">` … (default
|
|
150
|
+
* true), which `Prose` colours. `false` leaves the code as plain text — a host `pre` that runs
|
|
151
|
+
* its own highlighter.
|
|
152
|
+
*/
|
|
153
|
+
highlight?: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* NARROW the elements a body may produce — an activity feed that shows only inline formatting.
|
|
156
|
+
* react-markdown's own options, applied after the sanitiser; they can only remove, never allow
|
|
157
|
+
* what the schema strips (gh#1116).
|
|
158
|
+
*/
|
|
159
|
+
allowedElements?: Options$1["allowedElements"];
|
|
160
|
+
disallowedElements?: Options$1["disallowedElements"];
|
|
161
|
+
allowElement?: Options$1["allowElement"];
|
|
162
|
+
/** With an element filter: keep a removed element's children (its text) instead of dropping it. */
|
|
163
|
+
unwrapDisallowed?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* A table cell whose trimmed text has no whitespace and at most this many characters — a code,
|
|
166
|
+
* an id, a date, 優先度 — is stamped `data-short`, and `Prose` keeps it on one line: CJK otherwise
|
|
167
|
+
* breaks between any two ideographs in a narrow column (gh#1150). A sentence still wraps.
|
|
168
|
+
* Characters are user-perceived (grapheme clusters). Default 24; `false` stamps nothing.
|
|
169
|
+
*/
|
|
170
|
+
shortCellLength?: number | false;
|
|
171
|
+
/**
|
|
172
|
+
* Visible callout titles per kind when a callout has none of its own (`> [!WARNING]`). English by
|
|
173
|
+
* default — this package carries no i18n; a host passes its localized strings.
|
|
174
|
+
*/
|
|
175
|
+
calloutTitles?: Partial<Record<CalloutKind, string>>;
|
|
176
|
+
/**
|
|
177
|
+
* Where an EXTERNAL link opens. An external link is an absolute http(s) URL whose origin differs
|
|
178
|
+
* from `origin`. `"new-tab"` gives it `target="_blank"` and `rel="noopener noreferrer"`. Relative
|
|
179
|
+
* links, `#anchors`, wikilinks resolved to a path, and `mailto:` stay in the tab. The default,
|
|
180
|
+
* `"same-tab"`, changes nothing. Markdown has no syntax for `target`, so this is a renderer policy.
|
|
181
|
+
*/
|
|
182
|
+
externalLinks?: "same-tab" | "new-tab";
|
|
183
|
+
/**
|
|
184
|
+
* The page origin that `externalLinks` compares against. Defaults to `location.origin`. Pass it
|
|
185
|
+
* when rendering on the server, or every absolute link counts as external there and hydration
|
|
186
|
+
* disagrees on same-origin ones.
|
|
187
|
+
*/
|
|
188
|
+
origin?: string;
|
|
189
|
+
/**
|
|
190
|
+
* Host renderers for container directives the kit does not model, by name:
|
|
191
|
+
* `{ poll: (block) => <PollCard … /> }`. Each receives the directive's name, label, attributes,
|
|
192
|
+
* canonical source and body Markdown (`DirectiveBlock`, the same parts the codec and the block
|
|
193
|
+
* editor give) and its rendered content. A directive without a renderer renders as before.
|
|
194
|
+
* `toggle`, `columns` and `column` are the kit's own and cannot be overridden.
|
|
195
|
+
*/
|
|
196
|
+
directives?: Readonly<Record<string, DirectiveRenderer>>;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
type MermaidSvgCheck = {
|
|
200
|
+
ok: true;
|
|
201
|
+
svg: SVGSVGElement;
|
|
202
|
+
} | {
|
|
203
|
+
ok: false;
|
|
204
|
+
reason: string;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Parses `svg` as XML and checks every element and attribute. On success returns the PARSED root,
|
|
208
|
+
* which the caller inserts as a node — never re-serialised into HTML, so what was checked is what
|
|
209
|
+
* is shown (an HTML re-parse of an SVG string can differ from the XML parse: mutation XSS).
|
|
210
|
+
*/
|
|
211
|
+
declare function checkMermaidSvg(svg: string): MermaidSvgCheck;
|
|
212
|
+
type MermaidDiagramProps = {
|
|
213
|
+
/** The fence's text. */
|
|
214
|
+
source: string;
|
|
215
|
+
/** Accessible name of the rendered diagram (the figure). */
|
|
216
|
+
label?: string;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* A ```mermaid fence. Shows the code until the diagram has rendered AND passed `checkMermaidSvg`,
|
|
220
|
+
* and keeps showing it if either fails — so the page is never blank and never unsafe.
|
|
221
|
+
*/
|
|
222
|
+
declare function MermaidDiagram({ source, label }: MermaidDiagramProps): React.JSX.Element;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* THE SHARED FIXTURE CORPUS (gh#1108). Input Markdown → the exact sanitised HTML this renderer
|
|
226
|
+
* produces with its defaults. Browser, server and export paths (a PHP exporter included) conform to
|
|
227
|
+
* these rather than to each other: "same output" is checked against one list, not argued about.
|
|
228
|
+
*
|
|
229
|
+
* `hostile` fixtures carry an attack; `forbidden` lists substrings that must never appear in their
|
|
230
|
+
* output, so a schema change that re-opens one fails even if somebody re-blesses the HTML.
|
|
231
|
+
*
|
|
232
|
+
* Changing an expected `html` for an existing input is a renderer change: bump `RENDERER_VERSION`.
|
|
233
|
+
*
|
|
234
|
+
* The HTML is the renderer's, not React's: compare after removing the `<link rel="preload">` React's
|
|
235
|
+
* static renderer adds for each image (`normalizeFixtureHtml` in the tests).
|
|
236
|
+
*/
|
|
237
|
+
type MarkdownFixture = {
|
|
238
|
+
name: string;
|
|
239
|
+
markdown: string;
|
|
240
|
+
html: string;
|
|
241
|
+
hostile?: boolean;
|
|
242
|
+
forbidden?: string[];
|
|
243
|
+
};
|
|
244
|
+
declare const MARKDOWN_FIXTURES: MarkdownFixture[];
|
|
245
|
+
|
|
246
|
+
export { type DirectiveBlock as D, type HeadingIdResolver as H, MARKDOWN_FIXTURES as M, RENDERER_VERSION as R, type SchemaExtension as S, type DirectiveRenderProps as a, type DirectiveRenderer as b, type HeadingInfo as c, MARKDOWN_FORMAT as d, type MarkdownFixture as e, type MarkdownProps as f, MermaidDiagram as g, type MermaidDiagramProps as h, type MermaidSvgCheck as i, checkMermaidSvg as j, extendSchema as k, markdownSchema as m, rehypeHeadingIds as r, safeUrl as s };
|