@orz/ui-markdown 0.0.0-stage → 0.1.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/LICENSE +29 -0
- package/README.md +175 -2
- package/dist/attachment-links.d.ts +20 -0
- package/dist/attachment-links.d.ts.map +1 -0
- package/dist/attachment-links.js +27 -0
- package/dist/attachment-links.js.map +1 -0
- package/dist/content.d.ts +31 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +78 -0
- package/dist/content.js.map +1 -0
- package/dist/embeds.d.ts +18 -0
- package/dist/embeds.d.ts.map +1 -0
- package/dist/embeds.js +59 -0
- package/dist/embeds.js.map +1 -0
- package/dist/external-links.d.ts +23 -0
- package/dist/external-links.d.ts.map +1 -0
- package/dist/external-links.js +43 -0
- package/dist/external-links.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/markdown-processor.d.ts +23 -0
- package/dist/lib/markdown-processor.d.ts.map +1 -0
- package/dist/lib/markdown-processor.js +35 -0
- package/dist/lib/markdown-processor.js.map +1 -0
- package/dist/lib/rehype-details.d.ts +13 -0
- package/dist/lib/rehype-details.d.ts.map +1 -0
- package/dist/lib/rehype-details.js +256 -0
- package/dist/lib/rehype-details.js.map +1 -0
- package/dist/lib/rehype-source-lines.d.ts +23 -0
- package/dist/lib/rehype-source-lines.d.ts.map +1 -0
- package/dist/lib/rehype-source-lines.js +77 -0
- package/dist/lib/rehype-source-lines.js.map +1 -0
- package/dist/lib/remark-frontmatter-table.d.ts +119 -0
- package/dist/lib/remark-frontmatter-table.d.ts.map +1 -0
- package/dist/lib/remark-frontmatter-table.js +212 -0
- package/dist/lib/remark-frontmatter-table.js.map +1 -0
- package/dist/lib/remark-rejected-urls.d.ts +23 -0
- package/dist/lib/remark-rejected-urls.d.ts.map +1 -0
- package/dist/lib/remark-rejected-urls.js +79 -0
- package/dist/lib/remark-rejected-urls.js.map +1 -0
- package/dist/markdown.d.ts +30 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +146 -0
- package/dist/markdown.js.map +1 -0
- package/dist/plugin.d.ts +39 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +2 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +66 -3
- package/styles.css +293 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flavours `remark-frontmatter` is switched on for. A module constant because it is passed as a
|
|
3
|
+
* plugin option, and a fresh array on every render is a fresh plugin list:
|
|
4
|
+
* react-markdown rebuilds the whole document, and a rebuilt text node
|
|
5
|
+
* collapses whatever selection was sitting in it (T-60).
|
|
6
|
+
*/
|
|
7
|
+
export const FRONTMATTER_FLAVOURS = ["yaml", "toml"];
|
|
8
|
+
/** The separator each flavour writes between a key and its value. */
|
|
9
|
+
const SEPARATOR = { yaml: ":", toml: "=" };
|
|
10
|
+
/** Absolute offset each 1-based source line starts at. */
|
|
11
|
+
function lineStartsOf(source) {
|
|
12
|
+
const starts = [0];
|
|
13
|
+
for (let i = 0; i < source.length; i++) {
|
|
14
|
+
if (source[i] === "\n")
|
|
15
|
+
starts.push(i + 1);
|
|
16
|
+
}
|
|
17
|
+
return starts;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Cut a frontmatter block into fields, one line at a time — deliberately not
|
|
21
|
+
* by parsing YAML (T-240).
|
|
22
|
+
*
|
|
23
|
+
* Decorating the rendered text by source offset rests on every rendered
|
|
24
|
+
* character sitting at a known source offset: a word-level highlight is mapped
|
|
25
|
+
* back onto the source by addition, a text node is entered only while its span
|
|
26
|
+
* and its value agree character for character, and a reader's line/column
|
|
27
|
+
* selection is turned into an offset. Parsing and rendering the
|
|
28
|
+
* *result* breaks all three at once — a key can be normalised, the order
|
|
29
|
+
* reshuffled, `~` spelled `null` — so what comes out of here is only ever a
|
|
30
|
+
* verbatim slice of the source. That is also what lets an annotation anchor on
|
|
31
|
+
* one key or one value, and what lets the page's own search find them.
|
|
32
|
+
*
|
|
33
|
+
* The price, stated where a reader will look for it: a `meta:` with indented
|
|
34
|
+
* children lands whole in one value cell, because a continuation line has
|
|
35
|
+
* nothing but its indentation to say it is one. `white-space: pre-wrap` on the
|
|
36
|
+
* cell keeps that readable; expanding a nested map into rows of its own needs
|
|
37
|
+
* real YAML semantics and is out of scope here.
|
|
38
|
+
*/
|
|
39
|
+
export function frontmatterFields(source, block, sep) {
|
|
40
|
+
const starts = lineStartsOf(source);
|
|
41
|
+
const fields = [];
|
|
42
|
+
// Between the two fences: the opening one is the block's first line, the
|
|
43
|
+
// closing one its last.
|
|
44
|
+
for (let line = block.start.line + 1; line <= block.end.line - 1; line++) {
|
|
45
|
+
const from = starts[line - 1];
|
|
46
|
+
if (from === undefined)
|
|
47
|
+
continue;
|
|
48
|
+
const next = starts[line];
|
|
49
|
+
const to = next === undefined ? source.length : next - 1;
|
|
50
|
+
const text = source.slice(from, to);
|
|
51
|
+
const at = text.indexOf(sep);
|
|
52
|
+
// A key is one unindented token, so what stands before the FIRST separator
|
|
53
|
+
// decides: `url: https://x.test/a:b` is one field whose value keeps the
|
|
54
|
+
// second colon, while `two words: three` is prose continuing the field
|
|
55
|
+
// above. The padding TOML writes around `=` is not part of the token —
|
|
56
|
+
// testing the trimmed candidate is what lets `name = "bot-one"` open a
|
|
57
|
+
// field, which is how TOML is ordinarily spelled. Leading indentation
|
|
58
|
+
// survives the trim, which is what makes an indented line a continuation.
|
|
59
|
+
const key = at < 0 ? "" : text.slice(0, at).trimEnd();
|
|
60
|
+
const opens = at >= 0 && !/\s/.test(key);
|
|
61
|
+
const last = fields.at(-1);
|
|
62
|
+
if (!opens) {
|
|
63
|
+
if (last === undefined) {
|
|
64
|
+
fields.push({
|
|
65
|
+
key: null,
|
|
66
|
+
value: { start: from, end: to },
|
|
67
|
+
line,
|
|
68
|
+
endLine: line,
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
last.value.end = to;
|
|
73
|
+
last.endLine = line;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
let value = from + at + sep.length;
|
|
77
|
+
while (value < to && (source[value] === " " || source[value] === "\t")) {
|
|
78
|
+
value++;
|
|
79
|
+
}
|
|
80
|
+
fields.push({
|
|
81
|
+
// The trimmed token, so the padding around a TOML `=` belongs to neither
|
|
82
|
+
// cell — the same way the `|` between two table cells belongs to neither.
|
|
83
|
+
key: { start: from, end: from + key.length },
|
|
84
|
+
value: { start: value, end: to },
|
|
85
|
+
line,
|
|
86
|
+
endLine: line,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return fields;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A position for a generated node. Only the offsets are load-bearing — every
|
|
93
|
+
* consumer reads those, and `lineColAt` derives line and column off the source
|
|
94
|
+
* — so the columns are left at the line's start rather than invented.
|
|
95
|
+
*/
|
|
96
|
+
function spanOf(range, line, endLine) {
|
|
97
|
+
return {
|
|
98
|
+
start: { line, column: 1, offset: range.start },
|
|
99
|
+
end: { line: endLine, column: 1, offset: range.end },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* One cell, holding its slice of the source twice over.
|
|
104
|
+
*
|
|
105
|
+
* The mdast `text` child is what an index over the mdast reads, and `hChildren` is
|
|
106
|
+
* what gets rendered — because the two doors do not agree. `mdast-util-to-hast`
|
|
107
|
+
* runs every mdast `text` node through `trimLines`, which strips the leading
|
|
108
|
+
* indentation off each line of a value: the nested map that `white-space:
|
|
109
|
+
* pre-wrap` exists to keep readable would arrive on the page already flattened,
|
|
110
|
+
* and the rendered text would stop being a slice of the source, which is the
|
|
111
|
+
* invariant offset-based decoration stands on. `hChildren` is handed to hast
|
|
112
|
+
* verbatim. Both are cut from the same `slice`, so they cannot drift apart.
|
|
113
|
+
*/
|
|
114
|
+
function cellOf(type, tagName, source, range, line, endLine) {
|
|
115
|
+
const position = spanOf(range, line, endLine);
|
|
116
|
+
const value = source.slice(range.start, range.end);
|
|
117
|
+
// An empty range sends no child at all: there is nothing to slice, and a
|
|
118
|
+
// childless cell is what leaves it group-less in an index over the mdast — abstaining
|
|
119
|
+
// rather than claiming to hold prose it does not have.
|
|
120
|
+
const children = value === "" ? [] : [{ type: "text", value, position }];
|
|
121
|
+
return {
|
|
122
|
+
type,
|
|
123
|
+
data: {
|
|
124
|
+
hName: tagName,
|
|
125
|
+
hChildren: children.map((child) => ({ ...child })),
|
|
126
|
+
},
|
|
127
|
+
children,
|
|
128
|
+
position,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const keyCell = (source, range, line) => cellOf("frontmatterKey", "th", source, range, line, line);
|
|
132
|
+
const valueCell = (source, range, line, endLine) => cellOf("frontmatterValue", "td", source, range, line, endLine);
|
|
133
|
+
/** The subtree one frontmatter block becomes. */
|
|
134
|
+
function tableOf(source, block, fields) {
|
|
135
|
+
const rows = fields.map((field) => ({
|
|
136
|
+
type: "frontmatterField",
|
|
137
|
+
data: { hName: "tr" },
|
|
138
|
+
children: [
|
|
139
|
+
// Both cells always, even where the key is null: a consumer that reads
|
|
140
|
+
// this as a table takes its width from its first row, so a
|
|
141
|
+
// keyless field leading the block would narrow the whole frontmatter
|
|
142
|
+
// to one column and drop every value out of the diff. The CSS gives
|
|
143
|
+
// the row whose key cell is empty its full width back.
|
|
144
|
+
keyCell(source, field.key ?? { start: field.value.start, end: field.value.start }, field.line),
|
|
145
|
+
valueCell(source, field.value, field.line, field.endLine),
|
|
146
|
+
],
|
|
147
|
+
position: spanOf({ start: field.key?.start ?? field.value.start, end: field.value.end }, field.line, field.endLine),
|
|
148
|
+
}));
|
|
149
|
+
const first = fields[0];
|
|
150
|
+
const last = fields.at(-1);
|
|
151
|
+
const body = {
|
|
152
|
+
type: "frontmatterBody",
|
|
153
|
+
data: { hName: "tbody" },
|
|
154
|
+
children: rows,
|
|
155
|
+
position: spanOf({
|
|
156
|
+
start: first?.key?.start ?? first?.value.start ?? block.start.offset ?? 0,
|
|
157
|
+
end: last?.value.end ?? block.end.offset ?? 0,
|
|
158
|
+
}, first?.line ?? block.start.line, last?.endLine ?? block.end.line),
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
type: "frontmatter",
|
|
162
|
+
data: {
|
|
163
|
+
hName: "table",
|
|
164
|
+
hProperties: { className: ["markdown-frontmatter"] },
|
|
165
|
+
},
|
|
166
|
+
children: [body],
|
|
167
|
+
// The whole block, both fences included: that is the range a wholly-new
|
|
168
|
+
// frontmatter is highlighted over, and the source a removed one is quoted
|
|
169
|
+
// from.
|
|
170
|
+
position: block,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* remark plugin: swap each `yaml` / `toml` node for a two-column table of its
|
|
175
|
+
* fields (T-240). It consumes what `remark-frontmatter` produces, so it has to
|
|
176
|
+
* run after it.
|
|
177
|
+
*
|
|
178
|
+
* A table-shaped subtree rather than a bespoke one, because anything that
|
|
179
|
+
* already compares tables reads that shape: it knows `table` / `tableRow` /
|
|
180
|
+
* `tableCell`, pairs rows by their leftmost cell, and splices a removed row
|
|
181
|
+
* back where it stood. Frontmatter is a two-column table whose keys are the
|
|
182
|
+
* row keys, so nothing new has to be taught — the data is laid out at an
|
|
183
|
+
* entrance that exists.
|
|
184
|
+
*
|
|
185
|
+
* Custom node types plus `data.hName`, not real mdast `table` nodes: the
|
|
186
|
+
* `table` handler in `mdast-util-to-hast` makes row 0 a `<thead>` header, and
|
|
187
|
+
* frontmatter has no header — the first field would be drawn as one. `hName`
|
|
188
|
+
* renames an element after that handler has run and cannot undo what it did,
|
|
189
|
+
* so the unknown-node route is the only one that controls the output exactly.
|
|
190
|
+
* It also puts the `<tbody>` there explicitly, which is what keeps a pass
|
|
191
|
+
* that restores a removed field's stand-in row from appending it to the end of
|
|
192
|
+
* the table instead of into its body.
|
|
193
|
+
*/
|
|
194
|
+
export const remarkFrontmatterTable = () => (tree, file) => {
|
|
195
|
+
const source = String(file);
|
|
196
|
+
const children = [];
|
|
197
|
+
for (const child of tree.children) {
|
|
198
|
+
const sep = SEPARATOR[child.type];
|
|
199
|
+
if (sep === undefined || child.position === undefined) {
|
|
200
|
+
children.push(child);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const fields = frontmatterFields(source, child.position, sep);
|
|
204
|
+
// Nothing between the fences: the node goes, and with it the two `<hr>`s
|
|
205
|
+
// that used to stand in for it.
|
|
206
|
+
if (fields.length === 0)
|
|
207
|
+
continue;
|
|
208
|
+
children.push(tableOf(source, child.position, fields));
|
|
209
|
+
}
|
|
210
|
+
tree.children = children;
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=remark-frontmatter-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remark-frontmatter-table.js","sourceRoot":"","sources":["../../src/lib/remark-frontmatter-table.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Root } from "mdast";
|
|
2
|
+
import type { Plugin } from "unified";
|
|
3
|
+
/**
|
|
4
|
+
* Show a link whose protocol is refused as the source the author wrote.
|
|
5
|
+
*
|
|
6
|
+
* react-markdown blanks the `href` of anything outside its protocol list,
|
|
7
|
+
* which stops the script but leaves a link-shaped thing that reloads the page
|
|
8
|
+
* when clicked. The same principle unsupported HTML is held to applies here:
|
|
9
|
+
* what cannot be rendered is shown as what was typed.
|
|
10
|
+
*
|
|
11
|
+
* The substitution is byte-for-byte the node's own source, so an index built
|
|
12
|
+
* over the mdast keeps reading the block as exact and every decoration offset
|
|
13
|
+
* inside it stays where it was. Doing this on the hast side instead would leave
|
|
14
|
+
* such an index believing the block is a link the width of its label while the
|
|
15
|
+
* DOM holds the longer source, and a decoration pass would drop what it cannot
|
|
16
|
+
* place in silence.
|
|
17
|
+
*
|
|
18
|
+
* A node with no position — one another plugin synthesised — keeps the
|
|
19
|
+
* blanked attribute it has today: the script is still stopped, and only that
|
|
20
|
+
* one link goes on showing nothing of where it pointed.
|
|
21
|
+
*/
|
|
22
|
+
export declare const remarkRejectedUrlsAsText: Plugin<[], Root>;
|
|
23
|
+
//# sourceMappingURL=remark-rejected-urls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remark-rejected-urls.d.ts","sourceRoot":"","sources":["../../src/lib/remark-rejected-urls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,IAAI,EAAQ,MAAM,OAAO,CAAC;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AA+BtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,CA4BnD,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defaultUrlTransform } from "react-markdown";
|
|
2
|
+
/**
|
|
3
|
+
* Whether react-markdown will refuse this URL's protocol.
|
|
4
|
+
*
|
|
5
|
+
* Asked of the renderer's own transform rather than of a second protocol
|
|
6
|
+
* list: a safety predicate written twice is a safety predicate that will
|
|
7
|
+
* eventually disagree with itself, and this one already has an answer.
|
|
8
|
+
*/
|
|
9
|
+
function rejected(url) {
|
|
10
|
+
return url !== "" && defaultUrlTransform(url) === "";
|
|
11
|
+
}
|
|
12
|
+
/** The node's own source, carrying its position so the offsets still line up. */
|
|
13
|
+
function asSource(node, source) {
|
|
14
|
+
const start = node.position?.start.offset;
|
|
15
|
+
const end = node.position?.end.offset;
|
|
16
|
+
if (start === undefined || end === undefined)
|
|
17
|
+
return null;
|
|
18
|
+
return {
|
|
19
|
+
type: "text",
|
|
20
|
+
value: source.slice(start, end),
|
|
21
|
+
position: node.position,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function definitionsOf(node, found) {
|
|
25
|
+
if (node.type === "definition")
|
|
26
|
+
found.set(node.identifier, node.url);
|
|
27
|
+
if (!("children" in node))
|
|
28
|
+
return;
|
|
29
|
+
for (const child of node.children)
|
|
30
|
+
definitionsOf(child, found);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Show a link whose protocol is refused as the source the author wrote.
|
|
34
|
+
*
|
|
35
|
+
* react-markdown blanks the `href` of anything outside its protocol list,
|
|
36
|
+
* which stops the script but leaves a link-shaped thing that reloads the page
|
|
37
|
+
* when clicked. The same principle unsupported HTML is held to applies here:
|
|
38
|
+
* what cannot be rendered is shown as what was typed.
|
|
39
|
+
*
|
|
40
|
+
* The substitution is byte-for-byte the node's own source, so an index built
|
|
41
|
+
* over the mdast keeps reading the block as exact and every decoration offset
|
|
42
|
+
* inside it stays where it was. Doing this on the hast side instead would leave
|
|
43
|
+
* such an index believing the block is a link the width of its label while the
|
|
44
|
+
* DOM holds the longer source, and a decoration pass would drop what it cannot
|
|
45
|
+
* place in silence.
|
|
46
|
+
*
|
|
47
|
+
* A node with no position — one another plugin synthesised — keeps the
|
|
48
|
+
* blanked attribute it has today: the script is still stopped, and only that
|
|
49
|
+
* one link goes on showing nothing of where it pointed.
|
|
50
|
+
*/
|
|
51
|
+
export const remarkRejectedUrlsAsText = () => (tree, file) => {
|
|
52
|
+
const source = String(file);
|
|
53
|
+
// A reference's URL lives on the definition line, which markdown never
|
|
54
|
+
// renders — the reader is left with `[a][r]`, and the bad URL stays off
|
|
55
|
+
// the page exactly as it is today.
|
|
56
|
+
const definitions = new Map();
|
|
57
|
+
definitionsOf(tree, definitions);
|
|
58
|
+
const urlOf = (node) => {
|
|
59
|
+
if (node.type === "link" || node.type === "image")
|
|
60
|
+
return node.url;
|
|
61
|
+
if (node.type === "linkReference" || node.type === "imageReference") {
|
|
62
|
+
return definitions.get(node.identifier);
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
const visit = (parent) => {
|
|
67
|
+
parent.children = parent.children.map((child) => {
|
|
68
|
+
const url = urlOf(child);
|
|
69
|
+
if (url !== undefined && rejected(url)) {
|
|
70
|
+
return asSource(child, source) ?? child;
|
|
71
|
+
}
|
|
72
|
+
if ("children" in child)
|
|
73
|
+
visit(child);
|
|
74
|
+
return child;
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
visit(tree);
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=remark-rejected-urls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remark-rejected-urls.js","sourceRoot":"","sources":["../../src/lib/remark-rejected-urls.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGrD;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,GAAG,KAAK,EAAE,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;AACvD,CAAC;AAED,iFAAiF;AACjF,SAAS,QAAQ,CAAC,IAAW,EAAE,MAAc;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC;IACtC,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC;QAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,IAAW,EAAE,KAA0B;IAC5D,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IACrE,IAAI,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC;QAAE,OAAO;IAClC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ;QAAE,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;IACnB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5B,uEAAuE;IACvE,wEAAwE;IACxE,mCAAmC;IACnC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAEjC,MAAM,KAAK,GAAG,CAAC,IAAW,EAAsB,EAAE;QAChD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC;QACnE,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;YACpE,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,MAAc,EAAQ,EAAE;QACrC,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,IAAI,GAAG,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvC,OAAO,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC;YAC1C,CAAC;YACD,IAAI,UAAU,IAAI,KAAK;gBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACtC,OAAO,KAAK,CAAC;QACf,CAAC,CAA2B,CAAC;IAC/B,CAAC,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,CAAC;AACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Ref } from "react";
|
|
2
|
+
import type { MarkdownPlugin } from "./plugin.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The plugins of the nearest enclosing `<Markdown>`; undefined outside one. A document embedded
|
|
5
|
+
* inside a document reads them, so it is drawn the way its host is.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MarkdownPluginsContext: import("react").Context<readonly MarkdownPlugin[] | undefined>;
|
|
8
|
+
export type MarkdownProps = {
|
|
9
|
+
children: string;
|
|
10
|
+
/**
|
|
11
|
+
* Keep this array stable: a module constant, or a memo. See `<Markdown>`.
|
|
12
|
+
* The first plugin is outermost: its wrappers render first, and their `Next` reaches the second.
|
|
13
|
+
*/
|
|
14
|
+
plugins?: readonly MarkdownPlugin[];
|
|
15
|
+
/** Added to the `.markdown-body` root. */
|
|
16
|
+
className?: string;
|
|
17
|
+
ref?: Ref<HTMLDivElement>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* GitHub-style markdown: GFM, refused links drawn as their source, YAML and TOML frontmatter as a
|
|
21
|
+
* two-column table, and raw HTML escaped to its source except `<details>`/`<summary>` folds.
|
|
22
|
+
* Plugins append remark and rehype passes to that chain and wrap elements and code blocks; they
|
|
23
|
+
* cannot remove any of it. Code blocks are drawn through `@orz/ui-content`'s registry.
|
|
24
|
+
*
|
|
25
|
+
* Keep `plugins` stable. A fresh array, or a fresh plugin object in it, makes react-markdown
|
|
26
|
+
* reparse the document and React remount every overridden element, and either one collapses a live
|
|
27
|
+
* text selection inside it (T-60).
|
|
28
|
+
*/
|
|
29
|
+
export declare function Markdown({ children, plugins, className, ref, }: MarkdownProps): import("react").JSX.Element;
|
|
30
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,GAAG,EAET,MAAM,OAAO,CAAC;AAUf,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,aAAa,CAAC;AAIrB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,gEAEvB,CAAC;AAkJb,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,OAAoB,EACpB,SAAS,EACT,GAAG,GACJ,EAAE,aAAa,+BA6Cf"}
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Content } from "@orz/ui-content";
|
|
3
|
+
import { createContext, createElement, useMemo, } from "react";
|
|
4
|
+
import ReactMarkdown, {} from "react-markdown";
|
|
5
|
+
import { MARKDOWN_SYNTAX_PLUGINS } from "./lib/markdown-processor.js";
|
|
6
|
+
import { rehypeDetails } from "./lib/rehype-details.js";
|
|
7
|
+
import { CODE_CONTENT_START_ATTR, parseSourceLoc, SOURCE_LINE_ATTR, } from "./lib/rehype-source-lines.js";
|
|
8
|
+
const NO_PLUGINS = [];
|
|
9
|
+
/**
|
|
10
|
+
* The plugins of the nearest enclosing `<Markdown>`; undefined outside one. A document embedded
|
|
11
|
+
* inside a document reads them, so it is drawn the way its host is.
|
|
12
|
+
*/
|
|
13
|
+
export const MarkdownPluginsContext = createContext(undefined);
|
|
14
|
+
/**
|
|
15
|
+
* Pull the code text and fence tag out of a `<pre>` element's hast node.
|
|
16
|
+
* Returns null for shapes that aren't a plain fenced block, which then
|
|
17
|
+
* keep the default `<pre>` rendering.
|
|
18
|
+
*/
|
|
19
|
+
function parseFence(pre) {
|
|
20
|
+
const code = pre?.children.find((child) => child.type === "element" && child.tagName === "code");
|
|
21
|
+
if (code === undefined)
|
|
22
|
+
return null;
|
|
23
|
+
const className = Array.isArray(code.properties.className)
|
|
24
|
+
? code.properties.className
|
|
25
|
+
: [];
|
|
26
|
+
const tag = className
|
|
27
|
+
.find((name) => typeof name === "string" ? name.startsWith("language-") : false)
|
|
28
|
+
?.slice("language-".length);
|
|
29
|
+
const text = code.children
|
|
30
|
+
.filter((child) => child.type === "text")
|
|
31
|
+
.map((child) => child.value)
|
|
32
|
+
.join("");
|
|
33
|
+
// Fenced blocks always carry a trailing newline; a code renderer would
|
|
34
|
+
// show it as an empty last line.
|
|
35
|
+
return { text: text.replace(/\n$/, ""), tag };
|
|
36
|
+
}
|
|
37
|
+
function RegistryFence({ code, lang }) {
|
|
38
|
+
return (_jsx(Content, { source: { kind: "inline", text: code, lang }, placement: "document" }));
|
|
39
|
+
}
|
|
40
|
+
function fenceChain(plugins) {
|
|
41
|
+
let current = RegistryFence;
|
|
42
|
+
for (const plugin of [...plugins].reverse()) {
|
|
43
|
+
const Wrapper = plugin.fence;
|
|
44
|
+
if (Wrapper === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
const Inner = current;
|
|
47
|
+
current = function FenceWrapper(props) {
|
|
48
|
+
return _jsx(Wrapper, { ...props, Next: Inner });
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
function preFor(Fence) {
|
|
54
|
+
return function MarkdownPre({ node, children, ...props }) {
|
|
55
|
+
const fence = parseFence(node);
|
|
56
|
+
if (fence === null)
|
|
57
|
+
return _jsx("pre", { ...props, children: children });
|
|
58
|
+
// Whatever draws the fence must not drop the source-line stamp a caller
|
|
59
|
+
// anchors selections to (T-52). A wrapper re-carries it, plus where the
|
|
60
|
+
// code content starts: the stamped range opens on the ``` marker for
|
|
61
|
+
// fenced blocks but on the first code line for indented ones.
|
|
62
|
+
const stamp = props[SOURCE_LINE_ATTR];
|
|
63
|
+
const loc = typeof stamp === "string" ? parseSourceLoc(stamp) : null;
|
|
64
|
+
// Decoration classes ride on the <pre> too (T-158) and would otherwise
|
|
65
|
+
// vanish in the swap.
|
|
66
|
+
const className = ["markdown-fence", props.className]
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.join(" ");
|
|
69
|
+
const wrapperProps = loc === null
|
|
70
|
+
? { className }
|
|
71
|
+
: {
|
|
72
|
+
[SOURCE_LINE_ATTR]: stamp,
|
|
73
|
+
[CODE_CONTENT_START_ATTR]: (() => {
|
|
74
|
+
// Whether there is a marker line to skip is answered by the line
|
|
75
|
+
// count, not by reading the opening line: a fence inside a
|
|
76
|
+
// blockquote opens on "> ```ts", whose `>` survives trimStart().
|
|
77
|
+
const span = loc.end - loc.start + 1;
|
|
78
|
+
const contentLines = fence.text === "" ? 0 : fence.text.split("\n").length;
|
|
79
|
+
return loc.start + (span - contentLines >= 1 ? 1 : 0);
|
|
80
|
+
})(),
|
|
81
|
+
className,
|
|
82
|
+
};
|
|
83
|
+
return (_jsx("div", { ...wrapperProps, children: _jsx(Fence, { code: fence.text, lang: fence.tag, loc: loc }) }));
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function elementChain(tag, plugins) {
|
|
87
|
+
let current = function Plain({ node: _node, ...props }) {
|
|
88
|
+
return createElement(tag, props);
|
|
89
|
+
};
|
|
90
|
+
for (const plugin of [...plugins].reverse()) {
|
|
91
|
+
const Wrapper = plugin.components?.[tag];
|
|
92
|
+
if (Wrapper === undefined)
|
|
93
|
+
continue;
|
|
94
|
+
const Inner = current;
|
|
95
|
+
current = function ElementWrapper(props) {
|
|
96
|
+
return _jsx(Wrapper, { ...props, Next: Inner });
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return current;
|
|
100
|
+
}
|
|
101
|
+
function componentsFor(plugins) {
|
|
102
|
+
const components = {
|
|
103
|
+
pre: preFor(fenceChain(plugins)),
|
|
104
|
+
};
|
|
105
|
+
const tags = new Set(plugins.flatMap((plugin) => Object.keys(plugin.components ?? {})));
|
|
106
|
+
for (const tag of tags) {
|
|
107
|
+
if (tag === "pre")
|
|
108
|
+
continue;
|
|
109
|
+
components[tag] = elementChain(tag, plugins);
|
|
110
|
+
}
|
|
111
|
+
return components;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* GitHub-style markdown: GFM, refused links drawn as their source, YAML and TOML frontmatter as a
|
|
115
|
+
* two-column table, and raw HTML escaped to its source except `<details>`/`<summary>` folds.
|
|
116
|
+
* Plugins append remark and rehype passes to that chain and wrap elements and code blocks; they
|
|
117
|
+
* cannot remove any of it. Code blocks are drawn through `@orz/ui-content`'s registry.
|
|
118
|
+
*
|
|
119
|
+
* Keep `plugins` stable. A fresh array, or a fresh plugin object in it, makes react-markdown
|
|
120
|
+
* reparse the document and React remount every overridden element, and either one collapses a live
|
|
121
|
+
* text selection inside it (T-60).
|
|
122
|
+
*/
|
|
123
|
+
export function Markdown({ children, plugins = NO_PLUGINS, className, ref, }) {
|
|
124
|
+
// The override map must be referentially stable across re-renders: every
|
|
125
|
+
// entry is a component built here, and a fresh map makes React treat each
|
|
126
|
+
// one as a NEW component type, unmounting and rebuilding those DOM
|
|
127
|
+
// subtrees on every parent render. Rebuilt text nodes silently collapse
|
|
128
|
+
// any live text selection — which is what broke annotating a selection
|
|
129
|
+
// (T-60): a floating button's own appearance re-rendered the document and
|
|
130
|
+
// destroyed the selection it was offering to annotate.
|
|
131
|
+
const components = useMemo(() => componentsFor(plugins), [plugins]);
|
|
132
|
+
// `rehypeDetails` goes first so that every later pass walks the tree the
|
|
133
|
+
// reader will get, rather than one where a fold is still a pair of raw HTML
|
|
134
|
+
// strings. The memos are the identity rule above, applied to the plugin
|
|
135
|
+
// lists react-markdown compares.
|
|
136
|
+
const remarkPlugins = useMemo(() => [
|
|
137
|
+
...MARKDOWN_SYNTAX_PLUGINS,
|
|
138
|
+
...plugins.flatMap((plugin) => plugin.remarkPlugins ?? []),
|
|
139
|
+
], [plugins]);
|
|
140
|
+
const rehypePlugins = useMemo(() => [
|
|
141
|
+
rehypeDetails,
|
|
142
|
+
...plugins.flatMap((plugin) => plugin.rehypePlugins ?? []),
|
|
143
|
+
], [plugins]);
|
|
144
|
+
return (_jsx("div", { ref: ref, className: className ? `markdown-body ${className}` : "markdown-body", children: _jsx(MarkdownPluginsContext.Provider, { value: plugins, children: _jsx(ReactMarkdown, { remarkPlugins: remarkPlugins, rehypePlugins: rehypePlugins, components: components, children: children }) }) }));
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.tsx"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Element } from "hast";
|
|
2
|
+
import type { ComponentProps, ComponentType, JSX } from "react";
|
|
3
|
+
import type { PluggableList } from "unified";
|
|
4
|
+
/** What react-markdown hands an element override: the element's props, plus its hast node. */
|
|
5
|
+
export type ElementProps<Tag extends keyof JSX.IntrinsicElements> = ComponentProps<Tag> & {
|
|
6
|
+
node?: Element;
|
|
7
|
+
};
|
|
8
|
+
/** Tags a plugin may wrap. `<pre>` is `<Markdown>`'s own: it reads fences out of it. */
|
|
9
|
+
export type WrappableTag = Exclude<keyof JSX.IntrinsicElements, "pre">;
|
|
10
|
+
export type FenceProps = {
|
|
11
|
+
/** The contents, without the trailing newline every fence carries. */
|
|
12
|
+
code: string;
|
|
13
|
+
/** The info string's first word as written; undefined for an indented block. */
|
|
14
|
+
lang: string | undefined;
|
|
15
|
+
/** 1-based inclusive source lines of the whole block, markers included; null when unstamped. */
|
|
16
|
+
loc: {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
} | null;
|
|
20
|
+
};
|
|
21
|
+
export type ElementWrapper<Tag extends WrappableTag> = ComponentType<ElementProps<Tag> & {
|
|
22
|
+
Next: ComponentType<ElementProps<Tag>>;
|
|
23
|
+
}>;
|
|
24
|
+
export type MarkdownPlugin = {
|
|
25
|
+
name: string;
|
|
26
|
+
/** Appended, in list order, after the fixed syntax chain. */
|
|
27
|
+
remarkPlugins?: PluggableList;
|
|
28
|
+
/** Appended, in list order, after `rehypeDetails`. */
|
|
29
|
+
rehypePlugins?: PluggableList;
|
|
30
|
+
/** Wrap any element. `Next` renders it the way the plugins after this one would. */
|
|
31
|
+
components?: {
|
|
32
|
+
[Tag in WrappableTag]?: ElementWrapper<Tag>;
|
|
33
|
+
};
|
|
34
|
+
/** Wrap a parsed code block. The innermost `Next` is the registry. */
|
|
35
|
+
fence?: ComponentType<FenceProps & {
|
|
36
|
+
Next: ComponentType<FenceProps>;
|
|
37
|
+
}>;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,8FAA8F;AAC9F,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,MAAM,GAAG,CAAC,iBAAiB,IAC9D,cAAc,CAAC,GAAG,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3C,wFAAwF;AACxF,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;AAEvE,MAAM,MAAM,UAAU,GAAG;IACvB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,gGAAgG;IAChG,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,GAAG,SAAS,YAAY,IAAI,aAAa,CAClE,YAAY,CAAC,GAAG,CAAC,GAAG;IAAE,IAAI,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAA;CAAE,CAC/D,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,sDAAsD;IACtD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,oFAAoF;IACpF,UAAU,CAAC,EAAE;SAAG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC;KAAE,CAAC;IAC7D,sEAAsE;IACtE,KAAK,CAAC,EAAE,aAAa,CAAC,UAAU,GAAG;QAAE,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;KAAE,CAAC,CAAC;CACzE,CAAC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":""}
|