@markmap/parse 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/html-image.d.ts +19 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +385 -0
- package/dist/parse.d.ts +14 -0
- package/dist/plugins.d.ts +9 -0
- package/dist/to-tree.d.ts +18 -0
- package/package.json +39 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Gerald
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @markmap/parse
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Parse Markdown into a Markmap tree.
|
|
4
|
+
|
|
5
|
+
See the [Markmap README](https://github.com/markmap/markmap#readme) for how the packages fit together.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
export interface HtmlImage {
|
|
3
|
+
src: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
/** In px. Missing when not given, or not in px (such as a percentage). */
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read one GitHub-style `<img>` tag: exactly one tag, nothing around it, well-formed
|
|
11
|
+
* attributes. Only `src`, `alt`, `width` and `height` are kept; other attributes are
|
|
12
|
+
* ignored. Returns `undefined` for anything else, which callers then drop.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseHtmlImage(html: string): HtmlImage | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Turn raw `<img>` tags into `image` nodes, inline and on their own line. All other raw
|
|
17
|
+
* HTML is left alone for later steps to drop.
|
|
18
|
+
*/
|
|
19
|
+
export declare function remarkHtmlImages(): (tree: Root) => void;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
2
|
+
import remarkGfm from "remark-gfm";
|
|
3
|
+
import remarkParse from "remark-parse";
|
|
4
|
+
import { unified } from "unified";
|
|
5
|
+
import { parse as parse$1 } from "yaml";
|
|
6
|
+
import { assignIds } from "@markmap/core";
|
|
7
|
+
// src/html-image.ts
|
|
8
|
+
var TAG = /^<img((?:\s+[a-z][a-z0-9-]*(?:\s*=\s*(?:"[^"<>]*"|'[^'<>]*'|[^\s"'=<>`]+))?)*)\s*\/?>$/i;
|
|
9
|
+
var ATTR = /([a-z][a-z0-9-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gi;
|
|
10
|
+
var PIXELS = /^(\d+(?:\.\d+)?)(?:px)?$/;
|
|
11
|
+
var ENTITIES = {
|
|
12
|
+
"&": "&",
|
|
13
|
+
""": "\"",
|
|
14
|
+
"'": "'",
|
|
15
|
+
"<": "<",
|
|
16
|
+
">": ">"
|
|
17
|
+
};
|
|
18
|
+
var decode = (value) => value.replace(/&(?:amp|quot|#39|lt|gt);/g, (e) => ENTITIES[e]);
|
|
19
|
+
function pixels(value) {
|
|
20
|
+
const match = value && PIXELS.exec(value.trim());
|
|
21
|
+
const n = match ? Number(match[1]) : 0;
|
|
22
|
+
return n > 0 ? n : void 0;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Read one GitHub-style `<img>` tag: exactly one tag, nothing around it, well-formed
|
|
26
|
+
* attributes. Only `src`, `alt`, `width` and `height` are kept; other attributes are
|
|
27
|
+
* ignored. Returns `undefined` for anything else, which callers then drop.
|
|
28
|
+
*/
|
|
29
|
+
function parseHtmlImage(html) {
|
|
30
|
+
const match = TAG.exec(html.trim());
|
|
31
|
+
if (!match) return void 0;
|
|
32
|
+
const attrs = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const [, name, ...values] of match[1].matchAll(ATTR)) {
|
|
34
|
+
const key = name.toLowerCase();
|
|
35
|
+
if (!attrs.has(key)) attrs.set(key, decode(values.find((v) => v != null) ?? ""));
|
|
36
|
+
}
|
|
37
|
+
const src = attrs.get("src")?.trim();
|
|
38
|
+
if (!src) return void 0;
|
|
39
|
+
return {
|
|
40
|
+
src,
|
|
41
|
+
alt: attrs.get("alt") ?? "",
|
|
42
|
+
width: pixels(attrs.get("width")),
|
|
43
|
+
height: pixels(attrs.get("height"))
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function toImage(html) {
|
|
47
|
+
const parsed = parseHtmlImage(html.value);
|
|
48
|
+
if (!parsed) return void 0;
|
|
49
|
+
const size = {
|
|
50
|
+
...parsed.width && { width: parsed.width },
|
|
51
|
+
...parsed.height && { height: parsed.height }
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
type: "image",
|
|
55
|
+
url: parsed.src,
|
|
56
|
+
alt: parsed.alt,
|
|
57
|
+
data: {
|
|
58
|
+
...size,
|
|
59
|
+
hProperties: size
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Parents whose children are blocks, where an `<img>` line becomes its own paragraph. */
|
|
64
|
+
var FLOW = /* @__PURE__ */ new Set([
|
|
65
|
+
"root",
|
|
66
|
+
"listItem",
|
|
67
|
+
"blockquote",
|
|
68
|
+
"footnoteDefinition"
|
|
69
|
+
]);
|
|
70
|
+
/**
|
|
71
|
+
* Turn raw `<img>` tags into `image` nodes, inline and on their own line. All other raw
|
|
72
|
+
* HTML is left alone for later steps to drop.
|
|
73
|
+
*/
|
|
74
|
+
function remarkHtmlImages() {
|
|
75
|
+
const visit = (node) => {
|
|
76
|
+
if (!("children" in node)) return;
|
|
77
|
+
const parent = node;
|
|
78
|
+
parent.children = parent.children.map((child) => {
|
|
79
|
+
if (child.type !== "html") {
|
|
80
|
+
visit(child);
|
|
81
|
+
return child;
|
|
82
|
+
}
|
|
83
|
+
const image = toImage(child);
|
|
84
|
+
if (!image) return child;
|
|
85
|
+
if (!FLOW.has(parent.type)) return image;
|
|
86
|
+
return {
|
|
87
|
+
type: "paragraph",
|
|
88
|
+
children: [image]
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
return (tree) => visit(tree);
|
|
93
|
+
}
|
|
94
|
+
// src/plugins.ts
|
|
95
|
+
function visitParents(node, fn) {
|
|
96
|
+
if (!("children" in node)) return;
|
|
97
|
+
for (const child of node.children) visitParents(child, fn);
|
|
98
|
+
fn(node);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Turn `==text==` into `mark` nodes. Like `**`, the opening `==` must be followed by
|
|
102
|
+
* a non-space and the closing one preceded by a non-space. Delimiters can wrap other
|
|
103
|
+
* inline nodes (`==a **b**==`) but not cross block boundaries.
|
|
104
|
+
*/
|
|
105
|
+
function remarkMark() {
|
|
106
|
+
return (tree) => visitParents(tree, (parent) => {
|
|
107
|
+
if (parent.children.some((child) => child.type === "text" && child.value.includes("=="))) parent.children = markChildren(parent.children);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function markChildren(children) {
|
|
111
|
+
const tokens = [];
|
|
112
|
+
for (const child of children) {
|
|
113
|
+
if (child.type !== "text") {
|
|
114
|
+
tokens.push({
|
|
115
|
+
delim: false,
|
|
116
|
+
node: child
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
child.value.split(/(==)/).forEach((part) => {
|
|
121
|
+
if (part === "==") tokens.push({
|
|
122
|
+
delim: true,
|
|
123
|
+
canOpen: false,
|
|
124
|
+
canClose: false
|
|
125
|
+
});
|
|
126
|
+
else if (part) tokens.push({
|
|
127
|
+
delim: false,
|
|
128
|
+
node: {
|
|
129
|
+
type: "text",
|
|
130
|
+
value: part
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const edgeChar = (token, end) => {
|
|
136
|
+
if (!token) return "";
|
|
137
|
+
if (token.delim) return "=";
|
|
138
|
+
if (token.node.type === "text") return end ? token.node.value.at(-1) : token.node.value[0];
|
|
139
|
+
return "x";
|
|
140
|
+
};
|
|
141
|
+
tokens.forEach((token, i) => {
|
|
142
|
+
if (!token.delim) return;
|
|
143
|
+
const next = edgeChar(tokens[i + 1], false);
|
|
144
|
+
const prev = edgeChar(tokens[i - 1], true);
|
|
145
|
+
token.canOpen = !!next && !/\s/.test(next);
|
|
146
|
+
token.canClose = !!prev && !/\s/.test(prev);
|
|
147
|
+
});
|
|
148
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
149
|
+
let open = -1;
|
|
150
|
+
tokens.forEach((token, i) => {
|
|
151
|
+
if (!token.delim) return;
|
|
152
|
+
if (open >= 0 && token.canClose && i > open + 1) {
|
|
153
|
+
pairs.set(open, i);
|
|
154
|
+
open = -1;
|
|
155
|
+
} else if (open < 0 && token.canOpen) open = i;
|
|
156
|
+
});
|
|
157
|
+
const build = (from, to) => {
|
|
158
|
+
const result = [];
|
|
159
|
+
const pushText = (value) => {
|
|
160
|
+
const last = result.at(-1);
|
|
161
|
+
if (last?.type === "text") last.value += value;
|
|
162
|
+
else result.push({
|
|
163
|
+
type: "text",
|
|
164
|
+
value
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
for (let i = from; i < to; i += 1) {
|
|
168
|
+
const token = tokens[i];
|
|
169
|
+
const close = pairs.get(i);
|
|
170
|
+
if (close != null) {
|
|
171
|
+
result.push({
|
|
172
|
+
type: "mark",
|
|
173
|
+
children: build(i + 1, close)
|
|
174
|
+
});
|
|
175
|
+
i = close;
|
|
176
|
+
} else if (token.delim) pushText("==");
|
|
177
|
+
else if (token.node.type === "text") pushText(token.node.value);
|
|
178
|
+
else result.push(token.node);
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
};
|
|
182
|
+
return build(0, tokens.length);
|
|
183
|
+
}
|
|
184
|
+
/** Replace `[text][ref]` and `![alt][ref]` with plain links and images, and drop definitions. */
|
|
185
|
+
function remarkResolveReferences() {
|
|
186
|
+
return (tree) => {
|
|
187
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
188
|
+
visitParents(tree, (parent) => {
|
|
189
|
+
for (const child of parent.children) if (child.type === "definition") definitions.set(child.identifier, child);
|
|
190
|
+
});
|
|
191
|
+
visitParents(tree, (parent) => {
|
|
192
|
+
parent.children = parent.children.flatMap((child) => {
|
|
193
|
+
if (child.type === "linkReference") {
|
|
194
|
+
const def = definitions.get(child.identifier);
|
|
195
|
+
return def ? [{
|
|
196
|
+
type: "link",
|
|
197
|
+
url: def.url,
|
|
198
|
+
title: def.title,
|
|
199
|
+
children: child.children
|
|
200
|
+
}] : child.children;
|
|
201
|
+
}
|
|
202
|
+
if (child.type === "imageReference") {
|
|
203
|
+
const def = definitions.get(child.identifier);
|
|
204
|
+
return def ? [{
|
|
205
|
+
type: "image",
|
|
206
|
+
url: def.url,
|
|
207
|
+
title: def.title,
|
|
208
|
+
alt: child.alt
|
|
209
|
+
}] : [{
|
|
210
|
+
type: "text",
|
|
211
|
+
value: child.alt ?? ""
|
|
212
|
+
}];
|
|
213
|
+
}
|
|
214
|
+
return [child];
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// src/to-tree.ts
|
|
220
|
+
/**
|
|
221
|
+
* Convert an mdast tree into a Markmap tree.
|
|
222
|
+
*
|
|
223
|
+
* - Headings nest by depth; list items nest under the heading or item that contains them.
|
|
224
|
+
* - Other blocks become notes of the node that owns them. Blocks before the first
|
|
225
|
+
* heading attach to the root.
|
|
226
|
+
* - A lone top-level H1 becomes the root, unless `title` is given.
|
|
227
|
+
* - Raw HTML, frontmatter and link definitions are dropped. A `<!-- markmap: … -->`
|
|
228
|
+
* comment in a label sets node options: `fold` folds the node, `foldAll` folds it
|
|
229
|
+
* and everything below.
|
|
230
|
+
*/
|
|
231
|
+
function toTree(mdast, options = {}) {
|
|
232
|
+
let root = createNode("root", options.title ? [{
|
|
233
|
+
type: "text",
|
|
234
|
+
value: options.title
|
|
235
|
+
}] : []);
|
|
236
|
+
const stack = [{
|
|
237
|
+
depth: 0,
|
|
238
|
+
node: root
|
|
239
|
+
}];
|
|
240
|
+
for (const block of mdast.children) if (block.type === "heading") {
|
|
241
|
+
while (stack.length > 1 && stack.at(-1).depth >= block.depth) stack.pop();
|
|
242
|
+
const node = createNode("heading", block.children, block);
|
|
243
|
+
node.data.depth = block.depth;
|
|
244
|
+
stack.at(-1).node.children.push(node);
|
|
245
|
+
stack.push({
|
|
246
|
+
depth: block.depth,
|
|
247
|
+
node
|
|
248
|
+
});
|
|
249
|
+
} else addBlock(stack.at(-1).node, block);
|
|
250
|
+
if (!options.title) root = promoteSingleH1(root);
|
|
251
|
+
return assignIds(root);
|
|
252
|
+
}
|
|
253
|
+
function createNode(type, label, source) {
|
|
254
|
+
const { content, directives } = readLabel(label);
|
|
255
|
+
const node = {
|
|
256
|
+
id: "",
|
|
257
|
+
type,
|
|
258
|
+
content,
|
|
259
|
+
notes: [],
|
|
260
|
+
data: {},
|
|
261
|
+
children: []
|
|
262
|
+
};
|
|
263
|
+
if (directives.has("fold")) node.data.fold = true;
|
|
264
|
+
if (directives.has("foldAll")) node.data.foldAll = true;
|
|
265
|
+
const start = source?.position?.start.offset;
|
|
266
|
+
const end = source?.position?.end.offset;
|
|
267
|
+
if (start != null && end != null) node.position = {
|
|
268
|
+
start,
|
|
269
|
+
end
|
|
270
|
+
};
|
|
271
|
+
return node;
|
|
272
|
+
}
|
|
273
|
+
function addBlock(parent, block) {
|
|
274
|
+
switch (block.type) {
|
|
275
|
+
case "list":
|
|
276
|
+
addList(parent, block);
|
|
277
|
+
return;
|
|
278
|
+
case "html":
|
|
279
|
+
case "yaml":
|
|
280
|
+
case "definition": return;
|
|
281
|
+
default: attach(parent, block);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function addList(parent, list) {
|
|
285
|
+
for (const item of list.children) parent.children.push(fromListItem(item, list));
|
|
286
|
+
}
|
|
287
|
+
function fromListItem(item, list) {
|
|
288
|
+
const [first, ...rest] = item.children;
|
|
289
|
+
const hasLabel = first?.type === "paragraph" || first?.type === "heading";
|
|
290
|
+
const node = createNode("listItem", hasLabel ? first.children : [], item);
|
|
291
|
+
if (list.ordered) node.data.ordered = true;
|
|
292
|
+
if (item.checked != null) node.data.checked = item.checked;
|
|
293
|
+
for (const block of hasLabel ? rest : item.children) addBlock(node, block);
|
|
294
|
+
return node;
|
|
295
|
+
}
|
|
296
|
+
function attach(node, block) {
|
|
297
|
+
const note = {
|
|
298
|
+
block: stripPositions(block),
|
|
299
|
+
after: node.children.length
|
|
300
|
+
};
|
|
301
|
+
node.notes.push(note);
|
|
302
|
+
}
|
|
303
|
+
function promoteSingleH1(root) {
|
|
304
|
+
const [only] = root.children;
|
|
305
|
+
if (root.children.length !== 1 || only.type !== "heading" || only.data.depth !== 1) return root;
|
|
306
|
+
only.notes = [...root.notes.map((a) => ({
|
|
307
|
+
...a,
|
|
308
|
+
after: 0
|
|
309
|
+
})), ...only.notes];
|
|
310
|
+
return only;
|
|
311
|
+
}
|
|
312
|
+
/** Drop mdast `position` fields so the tree stays small and serializable. */
|
|
313
|
+
function stripPositions(value) {
|
|
314
|
+
if (Array.isArray(value)) return value.map(stripPositions);
|
|
315
|
+
if (value && typeof value === "object") {
|
|
316
|
+
const result = {};
|
|
317
|
+
for (const [key, child] of Object.entries(value)) if (key !== "position") result[key] = stripPositions(child);
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
var DIRECTIVE = /^<!--\s*markmap:\s*([\s\S]*?)\s*-->$/;
|
|
323
|
+
/** Drop raw HTML from a label, collecting `<!-- markmap: … -->` directives on the way. */
|
|
324
|
+
function readLabel(label) {
|
|
325
|
+
const directives = /* @__PURE__ */ new Set();
|
|
326
|
+
const clean = (nodes) => nodes.flatMap((node) => {
|
|
327
|
+
if (node.type === "html") {
|
|
328
|
+
const match = node.value.trim().match(DIRECTIVE);
|
|
329
|
+
for (const word of match?.[1].split(/\s+/) ?? []) if (word) directives.add(word);
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
if ("children" in node) return [{
|
|
333
|
+
...node,
|
|
334
|
+
children: clean(node.children)
|
|
335
|
+
}];
|
|
336
|
+
return [node];
|
|
337
|
+
});
|
|
338
|
+
const content = stripPositions(clean(label));
|
|
339
|
+
const last = content.at(-1);
|
|
340
|
+
if (last?.type === "text") {
|
|
341
|
+
last.value = last.value.trimEnd();
|
|
342
|
+
if (!last.value) content.pop();
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
content,
|
|
346
|
+
directives
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// src/parse.ts
|
|
350
|
+
function parse(markdown, options = {}) {
|
|
351
|
+
const { mdast, gfm } = parseMdast(markdown, options.plugins ?? []);
|
|
352
|
+
const frontmatter = readFrontmatter(mdast);
|
|
353
|
+
return {
|
|
354
|
+
root: toTree(mdast, { title: typeof frontmatter.title === "string" ? frontmatter.title : void 0 }),
|
|
355
|
+
frontmatter,
|
|
356
|
+
gfm
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* GFM depends on the most third-party code, and old browsers reject syntax it may use, like
|
|
361
|
+
* regex lookbehind before Safari 16.4. A map without GFM is better than no map at all.
|
|
362
|
+
*/
|
|
363
|
+
function parseMdast(markdown, plugins, gfm = true) {
|
|
364
|
+
const processor = unified().use(remarkParse).use(gfm ? [remarkGfm] : []).use(remarkFrontmatter, ["yaml"]).use(remarkHtmlImages).use(remarkMark).use(remarkResolveReferences).use(plugins);
|
|
365
|
+
try {
|
|
366
|
+
return {
|
|
367
|
+
mdast: processor.runSync(processor.parse(markdown)),
|
|
368
|
+
gfm
|
|
369
|
+
};
|
|
370
|
+
} catch (error) {
|
|
371
|
+
if (!gfm || !(error instanceof SyntaxError)) throw error;
|
|
372
|
+
return parseMdast(markdown, plugins, false);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function readFrontmatter(mdast) {
|
|
376
|
+
const node = mdast.children.find((child) => child.type === "yaml");
|
|
377
|
+
if (!node) return {};
|
|
378
|
+
try {
|
|
379
|
+
const data = parse$1(node.value);
|
|
380
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
381
|
+
} catch {
|
|
382
|
+
return {};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
export { parse, parseHtmlImage, remarkHtmlImages, remarkMark, remarkResolveReferences, toTree };
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MarkmapNode } from '@markmap/core';
|
|
2
|
+
import { type PluggableList } from 'unified';
|
|
3
|
+
export interface ParseOptions {
|
|
4
|
+
/** Extra unified plugins, applied after the built-in ones (gfm, frontmatter, highlight, references). */
|
|
5
|
+
plugins?: PluggableList;
|
|
6
|
+
}
|
|
7
|
+
export interface ParseResult {
|
|
8
|
+
root: MarkmapNode;
|
|
9
|
+
/** Parsed YAML frontmatter. Markmap options live under `markmap`; validate them with the theme in use. */
|
|
10
|
+
frontmatter: Record<string, unknown>;
|
|
11
|
+
/** Whether GFM was applied. It is skipped when the browser cannot run it, see `parseMdast`. */
|
|
12
|
+
gfm: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function parse(markdown: string, options?: ParseOptions): ParseResult;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* Turn `==text==` into `mark` nodes. Like `**`, the opening `==` must be followed by
|
|
4
|
+
* a non-space and the closing one preceded by a non-space. Delimiters can wrap other
|
|
5
|
+
* inline nodes (`==a **b**==`) but not cross block boundaries.
|
|
6
|
+
*/
|
|
7
|
+
export declare function remarkMark(): (tree: Root) => void;
|
|
8
|
+
/** Replace `[text][ref]` and `![alt][ref]` with plain links and images, and drop definitions. */
|
|
9
|
+
export declare function remarkResolveReferences(): (tree: Root) => void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MarkmapNode } from '@markmap/core';
|
|
2
|
+
import type { Root } from 'mdast';
|
|
3
|
+
export interface ToTreeOptions {
|
|
4
|
+
/** Title for a synthesized root, usually from frontmatter. */
|
|
5
|
+
title?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Convert an mdast tree into a Markmap tree.
|
|
9
|
+
*
|
|
10
|
+
* - Headings nest by depth; list items nest under the heading or item that contains them.
|
|
11
|
+
* - Other blocks become notes of the node that owns them. Blocks before the first
|
|
12
|
+
* heading attach to the root.
|
|
13
|
+
* - A lone top-level H1 becomes the root, unless `title` is given.
|
|
14
|
+
* - Raw HTML, frontmatter and link definitions are dropped. A `<!-- markmap: … -->`
|
|
15
|
+
* comment in a label sets node options: `fold` folds the node, `foldAll` folds it
|
|
16
|
+
* and everything below.
|
|
17
|
+
*/
|
|
18
|
+
export declare function toTree(mdast: Root, options?: ToTreeOptions): MarkmapNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markmap/parse",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Parse Markdown into a Markmap tree",
|
|
5
|
+
"homepage": "https://github.com/markmap/markmap#readme",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/markmap/markmap.git",
|
|
10
|
+
"directory": "packages/parse"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@markmap/core": "1.0.0",
|
|
28
|
+
"remark-frontmatter": "^5.0.0",
|
|
29
|
+
"remark-gfm": "^4.0.1",
|
|
30
|
+
"remark-parse": "^11.0.0",
|
|
31
|
+
"unified": "^11.0.5",
|
|
32
|
+
"yaml": "^2.9.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/mdast": "^4.0.4"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
39
|
+
"build:types": "tsc -b",
|
|
40
|
+
"build:js": "vite build"
|
|
41
|
+
}
|
|
6
42
|
}
|