@godxjp/markdown 33.16.0 → 33.18.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/README.md +40 -2
- package/dist/codec/index.d.ts +1 -1
- package/dist/codec/index.js +78 -6
- package/dist/index.d.ts +12 -3
- package/dist/index.js +332 -19
- package/katex.css +11 -0
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ pnpm add @godxjp/markdown # + `mermaid` (optional) to draw ```mermaid fen
|
|
|
8
8
|
````
|
|
9
9
|
|
|
10
10
|
```tsx
|
|
11
|
+
import "@godxjp/markdown/katex.css"; // once, in the app entry: KaTeX's stylesheet + fonts
|
|
11
12
|
import { Prose } from "@godxjp/ui/data-display";
|
|
12
13
|
import { Markdown } from "@godxjp/markdown";
|
|
13
14
|
|
|
@@ -22,6 +23,29 @@ GFM — tables, task lists (read-only), fenced code with `language-*` classes, a
|
|
|
22
23
|
strikethrough — and an `id` on every heading (GitHub slugs, de-duplicated). The output is the same
|
|
23
24
|
as `react-markdown` + `remark-gfm` + `rehype-sanitize`, which is what the apps rendered before.
|
|
24
25
|
|
|
26
|
+
**Math** — `$…$` and `$$…$$` inline, a `$$` block or a ` ```math ` fence for display — is
|
|
27
|
+
drawn by KaTeX at parse time (no DOM: the same HTML in a browser, on a server, in a Worker), as
|
|
28
|
+
HTML plus MathML, so a screen reader reads the formula. The content is literal TeX: `\,` keeps its
|
|
29
|
+
backslash. A single `$` follows Pandoc's rule, so prices stay prose: `$5 and $10` and `$5-$10` are
|
|
30
|
+
text (the opening `$` is followed by a non-space, the closing one preceded by a non-space and not
|
|
31
|
+
by `\`, and not followed by a digit). Write `\$` for a literal dollar anywhere. A formula that does
|
|
32
|
+
not parse shows its source in `.katex-error`.
|
|
33
|
+
|
|
34
|
+
**The KaTeX stylesheet is the host's to load**, once: `import "@godxjp/markdown/katex.css"`. It
|
|
35
|
+
`@import`s `katex/dist/katex.min.css` from the KaTeX this package depends on (so the CSS always
|
|
36
|
+
matches the renderer; any bundler that resolves CSS `@import` from `node_modules` — Vite, webpack,
|
|
37
|
+
esbuild — copies the fonts) and lets a wide display formula scroll instead of widening the page.
|
|
38
|
+
Without it, formulas render as unstyled text plus MathML.
|
|
39
|
+
|
|
40
|
+
**Syntax colour** — a fence in a known language is tokenised at parse time and every token is a
|
|
41
|
+
`<span data-code-token="…">`, with the kit's names (`comment`, `keyword`, `string`,
|
|
42
|
+
`string-expression`, `function`, `constant`, `parameter`, `punctuation`, `link`, `inserted`,
|
|
43
|
+
`deleted`, `changed`). `Prose` colours them from the `--code-block-token-*` knobs, in light and dark —
|
|
44
|
+
this package ships no code palette. Languages: bash, shell, css, diff, dockerfile, go, ini/toml,
|
|
45
|
+
java, javascript, json, markdown, php, python, rust, sql, typescript, xml/html, yaml (with their
|
|
46
|
+
aliases: `ts`, `js`, `jsx`, `tsx`, `sh`, `zsh`, `yml`, `html`, `py`, `rs`, …). Any other language,
|
|
47
|
+
a fence without one, ` ```mermaid ` and ` ```math ` stay plain text.
|
|
48
|
+
|
|
25
49
|
Every table renders inside a scroll box (`.ui-prose-table-scroll`, styled by `Prose`), keyboard-
|
|
26
50
|
focusable only while it overflows, so a wide table scrolls instead of widening the page.
|
|
27
51
|
|
|
@@ -31,6 +55,15 @@ focusable only while it overflows, so a wide table scrolls instead of widening t
|
|
|
31
55
|
- **One schema** (`markdownSchema`): links allow `http`, `https`, `mailto` and relative URLs;
|
|
32
56
|
images allow `http`, `https` and relative URLs. `javascript:`, `data:` and every other scheme are
|
|
33
57
|
removed — including a scheme split by tabs or newlines. SVG is never inlined from a URL.
|
|
58
|
+
- **KaTeX runs with `trust: false`, `strict: "error"` and `maxSize: 10`**, before the sanitiser,
|
|
59
|
+
and the schema allows exactly what KaTeX emits: MathML elements, KaTeX's own class names,
|
|
60
|
+
geometry-only inline styles (lengths, colours, keywords — no `url()`, no parentheses at all) and
|
|
61
|
+
`svg` / `path` / `line` for radicals and stretchy arrows (no `href`, no `<image>`). `\href`,
|
|
62
|
+
`\url`, `\includegraphics`, `\htmlClass`, `\htmlId`, `\htmlStyle` and `\htmlData` render as their
|
|
63
|
+
own source text. A non-standard construct still draws (rehype-katex retries leniently, `trust` is
|
|
64
|
+
never relaxed); a user-sized box (`\rule{9999em}{9999em}`) is capped at 10em.
|
|
65
|
+
- **Code tokens** are `span`s whose only attribute is `data-code-token`, pinned to the twelve names;
|
|
66
|
+
a body cannot mint one (raw HTML is dropped).
|
|
34
67
|
- **Mermaid is a picture only after its SVG passes `checkMermaidSvg`**: no `<script>`,
|
|
35
68
|
`<foreignObject>`, `<a>`, `<image>`, event handlers, non-fragment `href`, `url()` that leaves the
|
|
36
69
|
document, or `@import`. A diagram that fails, or a host without `mermaid`, shows the code.
|
|
@@ -46,6 +79,7 @@ focusable only while it overflows, so a wide table scrolls instead of widening t
|
|
|
46
79
|
| `components` | Element overrides. Mermaid fences are drawn before a host `pre` is asked, so a host code renderer keeps Mermaid. |
|
|
47
80
|
| `rehypePlugins` | Run after the sanitiser — presentation only. |
|
|
48
81
|
| `mermaid={false}` | Keep ```mermaid fences as code (or hand them to the host `pre`). |
|
|
82
|
+
| `highlight={false}` | Leave fenced code as plain text — a host `pre` that runs its own highlighter. A host `pre` otherwise receives the tokenised `<code>`. |
|
|
49
83
|
| `allowedElements` / `unwrapDisallowed` | Narrow a body (an activity feed: `strong`, `em`, `del`, `code`). Only removes. |
|
|
50
84
|
| `shortCellLength` | A table cell holding one short token (no whitespace, ≤ 24 characters by default) gets `data-short`; `Prose` keeps it on one line. `false` turns it off. |
|
|
51
85
|
|
|
@@ -55,7 +89,7 @@ so a narrow table scrolls instead of squashing them.
|
|
|
55
89
|
|
|
56
90
|
## Grammar v1 and the codec (`@godxjp/markdown/codec`)
|
|
57
91
|
|
|
58
|
-
The renderer (`RENDERER_VERSION`
|
|
92
|
+
The renderer (`RENDERER_VERSION` 6), the codec and `@godxjp/block-editor` read one syntax, from one
|
|
59
93
|
set of parsers, so a body means the same thing in the editor, on a server and on the page.
|
|
60
94
|
|
|
61
95
|
### Syntax
|
|
@@ -91,6 +125,10 @@ set of parsers, so a body means the same thing in the editor, on a server and on
|
|
|
91
125
|
|
|
92
126
|
- **Wikilinks and embeds, verbatim** — `[[target]]`, `[[target#heading]]`, `[[target|label]]`,
|
|
93
127
|
`![[embed]]` (a block when alone on its line; the inner text is kept verbatim — `![[B#見出し]]`, `![[img.png|300]]`). Marks around them survive (`**[[x]]**`). Inside a table cell write `[[x\|label]]`.
|
|
128
|
+
- **Math** — `$x$`, `$$x$$`, a `$$` block, a ` ```math ` fence (Pandoc's single-dollar rule,
|
|
129
|
+
above). The codec keeps math verbatim as a raw inline / raw block (a fence is an ordinary
|
|
130
|
+
`codeBlock{language: "math"}`), so the block editor needs no math node. Because `$` is now
|
|
131
|
+
special, a literal `$x$` in text is written `\$x$`; a lone `$` (a price) is never escaped.
|
|
94
132
|
- **`asset:` images** — `` round-trips as written (the renderer resolves it through
|
|
95
133
|
`resolveUrl`).
|
|
96
134
|
- **Raw blocks** — anything the document does not model (raw HTML, footnote definitions, link
|
|
@@ -102,7 +140,7 @@ set of parsers, so a body means the same thing in the editor, on a server and on
|
|
|
102
140
|
```ts
|
|
103
141
|
import { CODEC_ID, normalize, parse, serialize } from "@godxjp/markdown/codec";
|
|
104
142
|
|
|
105
|
-
CODEC_ID; // "kit-md@
|
|
143
|
+
CODEC_ID; // "kit-md@2" — bumped whenever serialize would write a body differently (@2: math)
|
|
106
144
|
const doc = parse(markdown); // plain JSON, ProseMirror/Tiptap shape
|
|
107
145
|
const md = serialize(doc); // canonical Markdown
|
|
108
146
|
normalize(markdown) === serialize(parse(markdown)); // what the editor saves
|
package/dist/codec/index.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ declare function serialize(doc: Doc | DocNode): string;
|
|
|
64
64
|
* Identifies the serializer's output. Bumped whenever `serialize` would write a stored body
|
|
65
65
|
* differently, so a host can tell which normalization a saved `normalized_body` came from.
|
|
66
66
|
*/
|
|
67
|
-
declare const CODEC_ID = "kit-md@
|
|
67
|
+
declare const CODEC_ID = "kit-md@2";
|
|
68
68
|
/** `serialize(parse(markdown))` — the canonical form of a body, exactly what the editor saves. */
|
|
69
69
|
declare function normalize(markdown: string): string;
|
|
70
70
|
|
package/dist/codec/index.js
CHANGED
|
@@ -74,16 +74,57 @@ import { directive } from "micromark-extension-directive";
|
|
|
74
74
|
import { gfm } from "micromark-extension-gfm";
|
|
75
75
|
import { cjkFriendlyExtension } from "micromark-extension-cjk-friendly";
|
|
76
76
|
import { frontmatter } from "micromark-extension-frontmatter";
|
|
77
|
+
import { math } from "micromark-extension-math";
|
|
77
78
|
import { frontmatterFromMarkdown, frontmatterToMarkdown } from "mdast-util-frontmatter";
|
|
79
|
+
import { mathFromMarkdown, mathToMarkdown } from "mdast-util-math";
|
|
78
80
|
import { directiveFromMarkdown } from "mdast-util-directive";
|
|
79
81
|
import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
|
|
80
82
|
function micromarkExtensions() {
|
|
81
83
|
const [container] = directive().flow[58];
|
|
82
84
|
const containerOnly = { flow: { 58: container } };
|
|
83
|
-
return [gfm(), cjkFriendlyExtension(), frontmatter(["yaml"]), containerOnly];
|
|
85
|
+
return [gfm(), cjkFriendlyExtension(), frontmatter(["yaml"]), dollarMath(), containerOnly];
|
|
86
|
+
}
|
|
87
|
+
var DOLLAR = 36;
|
|
88
|
+
var isSpace = (code) => code === null || code === void 0 || code < 0 || code === 32;
|
|
89
|
+
var isDigit = (code) => code != null && code >= 48 && code <= 57;
|
|
90
|
+
var BACKSLASH = 92;
|
|
91
|
+
function dollarMath() {
|
|
92
|
+
const extension = math();
|
|
93
|
+
const text2 = extension.text[DOLLAR];
|
|
94
|
+
const tokenize = function(effects, ok, nok) {
|
|
95
|
+
const codes = [];
|
|
96
|
+
let done = false;
|
|
97
|
+
const finish = (code) => {
|
|
98
|
+
done = true;
|
|
99
|
+
const single = codes[1] !== DOLLAR;
|
|
100
|
+
const after = codes[codes.length - 1];
|
|
101
|
+
const before = codes[codes.length - 3];
|
|
102
|
+
const refused = isSpace(codes[1]) || isSpace(before) || before === BACKSLASH || isDigit(after);
|
|
103
|
+
if (single && refused) return nok(code);
|
|
104
|
+
return ok(code);
|
|
105
|
+
};
|
|
106
|
+
const watch = (state) => (code) => {
|
|
107
|
+
if (done) return state(code);
|
|
108
|
+
codes.push(code);
|
|
109
|
+
const next = state(code);
|
|
110
|
+
return next && !done ? watch(next) : next;
|
|
111
|
+
};
|
|
112
|
+
return watch(
|
|
113
|
+
text2.tokenize.call(this, effects, finish, (code) => {
|
|
114
|
+
done = true;
|
|
115
|
+
return nok(code);
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
return { ...extension, text: { [DOLLAR]: { ...text2, tokenize } } };
|
|
84
120
|
}
|
|
85
121
|
function fromMarkdownExtensions() {
|
|
86
|
-
return [
|
|
122
|
+
return [
|
|
123
|
+
gfmFromMarkdown(),
|
|
124
|
+
directiveFromMarkdown(),
|
|
125
|
+
frontmatterFromMarkdown(["yaml"]),
|
|
126
|
+
mathFromMarkdown()
|
|
127
|
+
];
|
|
87
128
|
}
|
|
88
129
|
var WIDE = /[\u1100-\u115F\u2E80-\u303E\u3041-\u33FF\u3400-\u4DBF\u4E00-\u9FFF\uA000-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6\u{1F300}-\u{1F64F}\u{1F900}-\u{1F9FF}\u{20000}-\u{3FFFD}]/u;
|
|
89
130
|
function displayWidth(value) {
|
|
@@ -97,6 +138,9 @@ function toMarkdownGfm() {
|
|
|
97
138
|
function toMarkdownFrontmatter() {
|
|
98
139
|
return frontmatterToMarkdown(["yaml"]);
|
|
99
140
|
}
|
|
141
|
+
function toMarkdownMath() {
|
|
142
|
+
return mathToMarkdown();
|
|
143
|
+
}
|
|
100
144
|
|
|
101
145
|
// src/codec/options.ts
|
|
102
146
|
function containerDirective(node, _, state, info) {
|
|
@@ -136,6 +180,7 @@ function toMarkdownOptions() {
|
|
|
136
180
|
extensions: [
|
|
137
181
|
toMarkdownGfm(),
|
|
138
182
|
toMarkdownFrontmatter(),
|
|
183
|
+
toMarkdownMath(),
|
|
139
184
|
{ handlers: { containerDirective } }
|
|
140
185
|
]
|
|
141
186
|
};
|
|
@@ -187,9 +232,22 @@ function withMarks(node, marks) {
|
|
|
187
232
|
function leafText(value, marks) {
|
|
188
233
|
return marks.length ? { type: "text", text: value, marks } : { type: "text", text: value };
|
|
189
234
|
}
|
|
235
|
+
function inlineMathSource(value, after) {
|
|
236
|
+
let size = /^\s|[\s\\]$/.test(value) || /^\d/.test(after) ? 2 : 1;
|
|
237
|
+
const runs = new Set([...value.matchAll(/\$+/g)].map((run) => run[0].length));
|
|
238
|
+
while (runs.has(size)) size++;
|
|
239
|
+
const fence = "$".repeat(size);
|
|
240
|
+
const pad = /^\$|\$$/.test(value) || /^\s[^]*\s$/.test(value) ? " " : "";
|
|
241
|
+
return `${fence}${pad}${value}${pad}${fence}`;
|
|
242
|
+
}
|
|
190
243
|
function inline(nodes, marks = []) {
|
|
191
|
-
return nodes.flatMap((node) => {
|
|
244
|
+
return nodes.flatMap((node, index) => {
|
|
192
245
|
switch (node.type) {
|
|
246
|
+
case "inlineMath": {
|
|
247
|
+
const next = nodes[index + 1];
|
|
248
|
+
const source = inlineMathSource(node.value, next?.type === "text" ? next.value : "");
|
|
249
|
+
return [withMarks({ type: "rawInline", attrs: { source } }, marks)];
|
|
250
|
+
}
|
|
193
251
|
case "text":
|
|
194
252
|
return text(node.value, marks);
|
|
195
253
|
case "emphasis":
|
|
@@ -562,19 +620,33 @@ function decodeAttentionReferences(markdown) {
|
|
|
562
620
|
}
|
|
563
621
|
return out;
|
|
564
622
|
}
|
|
623
|
+
function decodeDollarEscapes(markdown) {
|
|
624
|
+
if (!markdown.includes("\\$")) return markdown;
|
|
625
|
+
const target = JSON.stringify(parse(markdown));
|
|
626
|
+
const all = markdown.replace(/\\\$/g, "$");
|
|
627
|
+
if (JSON.stringify(parse(all)) === target) return all;
|
|
628
|
+
let out = markdown;
|
|
629
|
+
for (const escape of [...markdown.matchAll(/\\\$/g)].reverse()) {
|
|
630
|
+
const at = escape.index;
|
|
631
|
+
const candidate = out.slice(0, at) + out.slice(at + 1);
|
|
632
|
+
if (JSON.stringify(parse(candidate)) === target) out = candidate;
|
|
633
|
+
}
|
|
634
|
+
return out;
|
|
635
|
+
}
|
|
565
636
|
function serialize(doc) {
|
|
566
637
|
const root = { type: "root", children: flow(doc.content ?? []) };
|
|
567
638
|
const markdown = toMarkdown2(root, toMarkdownOptions());
|
|
568
639
|
if (markdown.trim() === "") return "";
|
|
569
640
|
const tidy = markdown.replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").replace(/\n+$/, "\n");
|
|
570
|
-
return decodeAttentionReferences(tidy);
|
|
641
|
+
return decodeDollarEscapes(decodeAttentionReferences(tidy));
|
|
571
642
|
}
|
|
572
643
|
|
|
573
644
|
// src/codec/outline.ts
|
|
574
645
|
import GithubSlugger from "github-slugger";
|
|
575
646
|
import { visit } from "unist-util-visit";
|
|
576
647
|
function textOf(node) {
|
|
577
|
-
if (node.type === "text" || node.type === "inlineCode" || node.type === "
|
|
648
|
+
if (node.type === "text" || node.type === "inlineCode" || node.type === "inlineMath" || node.type === "html")
|
|
649
|
+
return node.value;
|
|
578
650
|
if (node.type === "image" || node.type === "imageReference") return "";
|
|
579
651
|
if ("children" in node) return node.children.map(textOf).join("");
|
|
580
652
|
return "";
|
|
@@ -594,7 +666,7 @@ function frontmatterOf(markdown) {
|
|
|
594
666
|
}
|
|
595
667
|
|
|
596
668
|
// src/codec/index.ts
|
|
597
|
-
var CODEC_ID = "kit-md@
|
|
669
|
+
var CODEC_ID = "kit-md@2";
|
|
598
670
|
function normalize(markdown) {
|
|
599
671
|
return serialize(parse(markdown));
|
|
600
672
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,9 @@ declare function rehypeHeadingIds(options?: {
|
|
|
37
37
|
* - `protocols`: links and images allow only `http`, `https`, `mailto` (links) and relative URLs.
|
|
38
38
|
* `javascript:`, `data:`, `vbscript:` and every other scheme are dropped.
|
|
39
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).
|
|
40
43
|
*
|
|
41
44
|
* Hosts extend it with `extendSchema`, never by editing a copy.
|
|
42
45
|
*/
|
|
@@ -77,7 +80,7 @@ type PluggableList = NonNullable<Options$1["remarkPlugins"]>;
|
|
|
77
80
|
* differently — a schema change, a new default plugin — not on every release.
|
|
78
81
|
*/
|
|
79
82
|
declare const MARKDOWN_FORMAT = "md";
|
|
80
|
-
declare const RENDERER_VERSION =
|
|
83
|
+
declare const RENDERER_VERSION = 6;
|
|
81
84
|
type MarkdownProps = {
|
|
82
85
|
/** The Markdown source. */
|
|
83
86
|
children: string;
|
|
@@ -111,6 +114,12 @@ type MarkdownProps = {
|
|
|
111
114
|
* `pre` (or plain code) as well (gh#1116).
|
|
112
115
|
*/
|
|
113
116
|
mermaid?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Tokenise fenced code in a known language as `<span data-code-token="keyword">` … (default
|
|
119
|
+
* true), which `Prose` colours. `false` leaves the code as plain text — a host `pre` that runs
|
|
120
|
+
* its own highlighter.
|
|
121
|
+
*/
|
|
122
|
+
highlight?: boolean;
|
|
114
123
|
/**
|
|
115
124
|
* NARROW the elements a body may produce — an activity feed that shows only inline formatting.
|
|
116
125
|
* react-markdown's own options, applied after the sanitiser; they can only remove, never allow
|
|
@@ -149,7 +158,7 @@ type MarkdownProps = {
|
|
|
149
158
|
};
|
|
150
159
|
/**
|
|
151
160
|
* THE ONE RENDERER (gh#1108). GFM — tables, task lists, fenced code, autolinks, footnotes,
|
|
152
|
-
* strikethrough — plus heading anchors, sanitised by `markdownSchema`, with ```mermaid drawn only
|
|
161
|
+
* strikethrough — plus heading anchors, KaTeX math, highlighted code, sanitised by `markdownSchema`, with ```mermaid drawn only
|
|
153
162
|
* through the SVG gate. Raw HTML in the source is never parsed into elements (no `rehype-raw`): it
|
|
154
163
|
* stays visible text. Output matches `react-markdown` + `remark-gfm` + `rehype-sanitize`, which is
|
|
155
164
|
* what godx-task and godx-content rendered before, so the swap is drop-in.
|
|
@@ -157,7 +166,7 @@ type MarkdownProps = {
|
|
|
157
166
|
* Renders bare elements; the host wraps it (`<Prose>` from `@godxjp/ui/data-display`), so this
|
|
158
167
|
* package carries no UI-kit dependency.
|
|
159
168
|
*/
|
|
160
|
-
declare function Markdown({ children, remarkPlugins, rehypePlugins, schema, components, resolveUrl, headingId, mermaid, allowedElements, disallowedElements, allowElement, unwrapDisallowed, shortCellLength, calloutTitles, externalLinks, origin, }: MarkdownProps): React.JSX.Element;
|
|
169
|
+
declare function Markdown({ children, remarkPlugins, rehypePlugins, schema, components, resolveUrl, headingId, mermaid, highlight, allowedElements, disallowedElements, allowElement, unwrapDisallowed, shortCellLength, calloutTitles, externalLinks, origin, }: MarkdownProps): React.JSX.Element;
|
|
161
170
|
|
|
162
171
|
type MermaidSvgCheck = {
|
|
163
172
|
ok: true;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as React3 from "react";
|
|
|
5
5
|
import ReactMarkdown from "react-markdown";
|
|
6
6
|
import rehypeSanitize from "rehype-sanitize";
|
|
7
7
|
import remarkGfm from "remark-gfm";
|
|
8
|
-
import { visit as
|
|
8
|
+
import { visit as visit3 } from "unist-util-visit";
|
|
9
9
|
|
|
10
10
|
// src/headings.ts
|
|
11
11
|
import GithubSlugger from "github-slugger";
|
|
@@ -32,6 +32,210 @@ function rehypeHeadingIds(options = {}) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
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
|
+
|
|
35
239
|
// src/mermaid.tsx
|
|
36
240
|
import * as React from "react";
|
|
37
241
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -90,8 +294,8 @@ function checkMermaidSvg(svg) {
|
|
|
90
294
|
const name = element2.localName.toLowerCase();
|
|
91
295
|
if (!ALLOWED_ELEMENTS.has(name)) return { ok: false, reason: `element ${name}` };
|
|
92
296
|
if (name === "style") {
|
|
93
|
-
const
|
|
94
|
-
if (CSS_HAZARD.test(
|
|
297
|
+
const css2 = element2.textContent ?? "";
|
|
298
|
+
if (CSS_HAZARD.test(css2) || !urlsAreLocal(css2)) return { ok: false, reason: "style" };
|
|
95
299
|
}
|
|
96
300
|
for (const attribute of Array.from(element2.attributes)) {
|
|
97
301
|
const attr = attribute.localName.toLowerCase();
|
|
@@ -107,20 +311,26 @@ function checkMermaidSvg(svg) {
|
|
|
107
311
|
return { ok: true, svg: root };
|
|
108
312
|
}
|
|
109
313
|
var loading = null;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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) {
|
|
113
320
|
api.initialize({
|
|
114
321
|
startOnLoad: false,
|
|
115
322
|
securityLevel: "strict",
|
|
116
323
|
// SVG text labels, not HTML in <foreignObject> — which the gate rejects outright.
|
|
117
324
|
htmlLabels: false,
|
|
118
325
|
flowchart: { htmlLabels: false },
|
|
119
|
-
|
|
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
|
|
120
330
|
});
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
return
|
|
331
|
+
initializedTheme = theme;
|
|
332
|
+
}
|
|
333
|
+
return api;
|
|
124
334
|
}
|
|
125
335
|
function MermaidCode({ source }) {
|
|
126
336
|
return /* @__PURE__ */ jsx("pre", { "data-mermaid": "code", children: /* @__PURE__ */ jsx("code", { className: "language-mermaid", children: source }) });
|
|
@@ -188,6 +398,7 @@ function TableScroll({ children }) {
|
|
|
188
398
|
import { defaultSchema } from "rehype-sanitize";
|
|
189
399
|
var markdownSchema = {
|
|
190
400
|
...defaultSchema,
|
|
401
|
+
tagNames: [...defaultSchema.tagNames ?? [], ...katexSchema.tagNames],
|
|
191
402
|
clobberPrefix: "",
|
|
192
403
|
protocols: {
|
|
193
404
|
...defaultSchema.protocols,
|
|
@@ -197,6 +408,8 @@ var markdownSchema = {
|
|
|
197
408
|
},
|
|
198
409
|
attributes: {
|
|
199
410
|
...defaultSchema.attributes,
|
|
411
|
+
...katexSchema.attributes,
|
|
412
|
+
span: [...katexSchema.attributes.span, ["dataCodeToken", ...CODE_TOKENS]],
|
|
200
413
|
// Heading anchors (rehype-headings, below) and the code fence language.
|
|
201
414
|
h1: [...defaultSchema.attributes?.h1 ?? [], "id"],
|
|
202
415
|
h2: [...defaultSchema.attributes?.h2 ?? [], "id"],
|
|
@@ -340,16 +553,57 @@ import { directive } from "micromark-extension-directive";
|
|
|
340
553
|
import { gfm } from "micromark-extension-gfm";
|
|
341
554
|
import { cjkFriendlyExtension } from "micromark-extension-cjk-friendly";
|
|
342
555
|
import { frontmatter } from "micromark-extension-frontmatter";
|
|
556
|
+
import { math } from "micromark-extension-math";
|
|
343
557
|
import { frontmatterFromMarkdown, frontmatterToMarkdown } from "mdast-util-frontmatter";
|
|
558
|
+
import { mathFromMarkdown, mathToMarkdown } from "mdast-util-math";
|
|
344
559
|
import { directiveFromMarkdown } from "mdast-util-directive";
|
|
345
560
|
import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
|
|
346
561
|
function micromarkExtensions() {
|
|
347
562
|
const [container] = directive().flow[58];
|
|
348
563
|
const containerOnly = { flow: { 58: container } };
|
|
349
|
-
return [gfm(), cjkFriendlyExtension(), frontmatter(["yaml"]), containerOnly];
|
|
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 } } };
|
|
350
599
|
}
|
|
351
600
|
function fromMarkdownExtensions() {
|
|
352
|
-
return [
|
|
601
|
+
return [
|
|
602
|
+
gfmFromMarkdown(),
|
|
603
|
+
directiveFromMarkdown(),
|
|
604
|
+
frontmatterFromMarkdown(["yaml"]),
|
|
605
|
+
mathFromMarkdown()
|
|
606
|
+
];
|
|
353
607
|
}
|
|
354
608
|
|
|
355
609
|
// src/directives.ts
|
|
@@ -432,7 +686,7 @@ function directive2(node, nested) {
|
|
|
432
686
|
// src/markdown.tsx
|
|
433
687
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
434
688
|
var MARKDOWN_FORMAT = "md";
|
|
435
|
-
var RENDERER_VERSION =
|
|
689
|
+
var RENDERER_VERSION = 6;
|
|
436
690
|
var URL_ATTRIBUTES = {
|
|
437
691
|
a: "href",
|
|
438
692
|
img: "src",
|
|
@@ -442,7 +696,7 @@ var URL_ATTRIBUTES = {
|
|
|
442
696
|
function rehypeResolveUrls(options) {
|
|
443
697
|
return (tree) => {
|
|
444
698
|
if (!options.resolve) return;
|
|
445
|
-
|
|
699
|
+
visit3(tree, "element", (node) => {
|
|
446
700
|
const key = URL_ATTRIBUTES[node.tagName];
|
|
447
701
|
const value = key ? node.properties?.[key] : void 0;
|
|
448
702
|
if (typeof value !== "string") return;
|
|
@@ -455,7 +709,7 @@ var graphemes = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Se
|
|
|
455
709
|
function textOf2(node) {
|
|
456
710
|
let text = "";
|
|
457
711
|
let hasImage = false;
|
|
458
|
-
|
|
712
|
+
visit3(node, (child) => {
|
|
459
713
|
if (child.type === "text") text += child.value;
|
|
460
714
|
else if (child.type === "element" && child.tagName === "img") hasImage = true;
|
|
461
715
|
});
|
|
@@ -465,7 +719,7 @@ function rehypeShortCells(options) {
|
|
|
465
719
|
return (tree) => {
|
|
466
720
|
const { max } = options;
|
|
467
721
|
if (max === false || max <= 0) return;
|
|
468
|
-
|
|
722
|
+
visit3(tree, "element", (node) => {
|
|
469
723
|
if (node.tagName !== "td" && node.tagName !== "th") return;
|
|
470
724
|
const { text, hasImage } = textOf2(node);
|
|
471
725
|
const token = text.trim();
|
|
@@ -479,7 +733,7 @@ function rehypeExternalLinks(options) {
|
|
|
479
733
|
return (tree) => {
|
|
480
734
|
if (options.policy !== "new-tab") return;
|
|
481
735
|
const origin = options.origin ?? globalThis.location?.origin;
|
|
482
|
-
|
|
736
|
+
visit3(tree, "element", (node) => {
|
|
483
737
|
if (node.tagName !== "a") return;
|
|
484
738
|
const href = node.properties?.href;
|
|
485
739
|
if (typeof href !== "string" || !/^https?:\/\//i.test(href)) return;
|
|
@@ -515,6 +769,7 @@ function Markdown({
|
|
|
515
769
|
resolveUrl,
|
|
516
770
|
headingId,
|
|
517
771
|
mermaid = true,
|
|
772
|
+
highlight = true,
|
|
518
773
|
allowedElements,
|
|
519
774
|
disallowedElements,
|
|
520
775
|
allowElement,
|
|
@@ -548,6 +803,9 @@ function Markdown({
|
|
|
548
803
|
rehypePlugins: [
|
|
549
804
|
[rehypeResolveUrls, { resolve: resolveUrl }],
|
|
550
805
|
[rehypeHeadingIds, { resolve: headingId }],
|
|
806
|
+
// Both BEFORE the sanitiser, which judges their output (schema.ts).
|
|
807
|
+
rehypeMath,
|
|
808
|
+
...highlight ? [rehypeHighlight] : [],
|
|
551
809
|
[rehypeSanitize, sanitizeSchema],
|
|
552
810
|
[rehypeShortCells, { max: shortCellLength }],
|
|
553
811
|
[rehypeExternalLinks, { policy: externalLinks, origin }],
|
|
@@ -589,9 +847,9 @@ var MARKDOWN_FIXTURES = [
|
|
|
589
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>'
|
|
590
848
|
},
|
|
591
849
|
{
|
|
592
|
-
name: "fenced code keeps its language class",
|
|
850
|
+
name: "fenced code keeps its language class, tokenised (renderer v6)",
|
|
593
851
|
markdown: "```ts\nconst a = 1;\n```",
|
|
594
|
-
html: '<pre><code class="language-ts">const a = 1
|
|
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>'
|
|
595
853
|
},
|
|
596
854
|
{
|
|
597
855
|
name: "links and autolinks",
|
|
@@ -691,6 +949,61 @@ var MARKDOWN_FIXTURES = [
|
|
|
691
949
|
markdown: "---\ntitle: Spec\ntags: [a]\n---\n\n# Heading\n\nBody",
|
|
692
950
|
html: '<h1 id="heading">Heading</h1>\n<p>Body</p>',
|
|
693
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 '}' 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"><<span data-code-token="keyword">script</span>></span>alert(1)<span data-code-token="punctuation"></<span data-code-token="keyword">script</span>></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>'
|
|
694
1007
|
}
|
|
695
1008
|
];
|
|
696
1009
|
export {
|
package/katex.css
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* KaTeX's stylesheet and fonts, at the version this package renders with (renderer v6).
|
|
3
|
+
* `import "@godxjp/markdown/katex.css";` once in the app — math draws unstyled without it.
|
|
4
|
+
*/
|
|
5
|
+
@import "katex/dist/katex.min.css";
|
|
6
|
+
|
|
7
|
+
/* A display formula wider than the column scrolls, as a wide table does, instead of widening the page. */
|
|
8
|
+
.katex-display {
|
|
9
|
+
overflow-x: auto;
|
|
10
|
+
overflow-y: hidden;
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/markdown",
|
|
3
|
-
"version": "33.
|
|
4
|
-
"description": "The one Markdown renderer for GoDX apps: GFM + heading anchors, one sanitiser schema, a shared fixture corpus, and Mermaid drawn only after its SVG passes a fail-closed gate.",
|
|
3
|
+
"version": "33.18.0",
|
|
4
|
+
"description": "The one Markdown renderer for GoDX apps: GFM + heading anchors, KaTeX math, highlighted code, one sanitiser schema, a shared fixture corpus, and Mermaid drawn only after its SVG passes a fail-closed gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -15,11 +15,15 @@
|
|
|
15
15
|
"types": "./dist/codec/index.d.ts",
|
|
16
16
|
"import": "./dist/codec/index.js"
|
|
17
17
|
},
|
|
18
|
+
"./katex.css": "./katex.css",
|
|
18
19
|
"./package.json": "./package.json"
|
|
19
20
|
},
|
|
20
|
-
"sideEffects":
|
|
21
|
+
"sideEffects": [
|
|
22
|
+
"*.css"
|
|
23
|
+
],
|
|
21
24
|
"files": [
|
|
22
25
|
"dist",
|
|
26
|
+
"katex.css",
|
|
23
27
|
"README.md"
|
|
24
28
|
],
|
|
25
29
|
"publishConfig": {
|
|
@@ -34,17 +38,23 @@
|
|
|
34
38
|
"license": "Apache-2.0",
|
|
35
39
|
"dependencies": {
|
|
36
40
|
"github-slugger": "^2.0.0",
|
|
41
|
+
"highlight.js": "~11.11.1",
|
|
42
|
+
"katex": "^0.16.47",
|
|
43
|
+
"lowlight": "^3.3.0",
|
|
37
44
|
"mdast-util-directive": "^3.1.1",
|
|
38
45
|
"mdast-util-from-markdown": "^2.1.0",
|
|
39
46
|
"mdast-util-frontmatter": "^2.0.1",
|
|
40
47
|
"mdast-util-gfm": "^3.1.0",
|
|
48
|
+
"mdast-util-math": "^3.0.0",
|
|
41
49
|
"mdast-util-to-markdown": "^2.2.0",
|
|
42
50
|
"micromark-extension-cjk-friendly": "^2.0.2",
|
|
43
51
|
"micromark-extension-directive": "^4.0.0",
|
|
44
52
|
"micromark-extension-frontmatter": "^2.0.0",
|
|
45
53
|
"micromark-extension-gfm": "^3.0.0",
|
|
54
|
+
"micromark-extension-math": "^3.1.0",
|
|
46
55
|
"micromark-util-types": "^2.0.2",
|
|
47
56
|
"react-markdown": "^10.1.0",
|
|
57
|
+
"rehype-katex": "^7.0.1",
|
|
48
58
|
"rehype-sanitize": "^6.0.0",
|
|
49
59
|
"remark-gfm": "^4.0.1",
|
|
50
60
|
"unist-util-visit": "^5.1.0"
|
|
@@ -63,6 +73,8 @@
|
|
|
63
73
|
"gfm",
|
|
64
74
|
"sanitize",
|
|
65
75
|
"mermaid",
|
|
76
|
+
"katex",
|
|
77
|
+
"highlight",
|
|
66
78
|
"godxjp"
|
|
67
79
|
]
|
|
68
80
|
}
|