@markupme/core 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +37 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.js +5 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/parse.d.ts +6 -0
- package/dist/parse.js +66 -0
- package/dist/plugins/alerts.d.ts +8 -0
- package/dist/plugins/alerts.js +50 -0
- package/dist/plugins/directives.d.ts +16 -0
- package/dist/plugins/directives.js +126 -0
- package/dist/plugins/emoji.d.ts +3 -0
- package/dist/plugins/emoji.js +22 -0
- package/dist/plugins/frontmatter.d.ts +4 -0
- package/dist/plugins/frontmatter.js +37 -0
- package/dist/plugins/headings.d.ts +18 -0
- package/dist/plugins/headings.js +69 -0
- package/dist/plugins/math-syntax.d.ts +20 -0
- package/dist/plugins/math-syntax.js +279 -0
- package/dist/plugins/math.d.ts +2 -0
- package/dist/plugins/math.js +17 -0
- package/dist/plugins/toc.d.ts +18 -0
- package/dist/plugins/toc.js +103 -0
- package/dist/processor.d.ts +46 -0
- package/dist/processor.js +127 -0
- package/dist/render/html.d.ts +10 -0
- package/dist/render/html.js +217 -0
- package/dist/render/katex.d.ts +13 -0
- package/dist/render/katex.js +79 -0
- package/dist/render/schema.d.ts +7 -0
- package/dist/render/schema.js +51 -0
- package/dist/render/text.d.ts +3 -0
- package/dist/render/text.js +48 -0
- package/dist/serialize.d.ts +16 -0
- package/dist/serialize.js +51 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.js +2 -0
- package/dist/util.d.ts +48 -0
- package/dist/util.js +162 -0
- package/dist/validate.d.ts +5 -0
- package/dist/validate.js +209 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +82 -4
- package/README.md +0 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
`@markupme/core` contains code ported to TypeScript from these MIT-licensed projects:
|
|
4
|
+
|
|
5
|
+
| File | Ported from | Copyright |
|
|
6
|
+
| ---- | ----------- | --------- |
|
|
7
|
+
| `src/render/katex.ts` | [rehype-katex](https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex) 7.0.1 | Copyright (c) Junyoung Choi <fluke8259@gmail.com> |
|
|
8
|
+
| `src/plugins/math.ts` | [remark-math](https://github.com/remarkjs/remark-math/tree/main/packages/remark-math) 6.0.0 | Copyright (c) Junyoung Choi <fluke8259@gmail.com> |
|
|
9
|
+
| `src/plugins/math-syntax.ts` | [micromark-extension-math](https://github.com/micromark/micromark-extension-math) 3.1.0 | Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com> |
|
|
10
|
+
|
|
11
|
+
All three are distributed under the MIT License:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
(The MIT License)
|
|
15
|
+
|
|
16
|
+
Copyright (c) Junyoung Choi <fluke8259@gmail.com>
|
|
17
|
+
Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
20
|
+
a copy of this software and associated documentation files (the
|
|
21
|
+
'Software'), to deal in the Software without restriction, including
|
|
22
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
23
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
24
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
25
|
+
the following conditions:
|
|
26
|
+
|
|
27
|
+
The above copyright notice and this permission notice shall be
|
|
28
|
+
included in all copies or substantial portions of the Software.
|
|
29
|
+
|
|
30
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
31
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
32
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
33
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
34
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
35
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
36
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
37
|
+
```
|
package/dist/format.d.ts
ADDED
package/dist/format.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** `file:line:col severity code message (rule)`: the format editors and CI annotate from. */
|
|
2
|
+
export function formatDiagnostic(d) {
|
|
3
|
+
const where = [d.file ?? '<input>', d.line, d.column].filter((x) => x !== undefined).join(':');
|
|
4
|
+
return `${where} ${d.severity} ${d.code} ${d.message} (${d.rule})`;
|
|
5
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { formatDiagnostic } from './format.js';
|
|
2
|
+
export { ALERT_TITLES, ALERT_TYPES, type AlertType } from './plugins/alerts.js';
|
|
3
|
+
export { CALLOUTS, CONTAINERS, LEAF_DIRECTIVES, TEXT_DIRECTIVES } from './plugins/directives.js';
|
|
4
|
+
export { headingId, type OutlineItem, outline } from './plugins/headings.js';
|
|
5
|
+
export { buildToc, insertToc, type TocNode } from './plugins/toc.js';
|
|
6
|
+
export { type BuiltinTransform, createProcessor, MarkupMeError, type Processor, parse, render, serialize, type TransformOptions, type TransformResult, transform, validate, } from './processor.js';
|
|
7
|
+
export { markupmeSchema } from './render/schema.js';
|
|
8
|
+
export type { SerializeFormat, SerializeOptions } from './serialize.js';
|
|
9
|
+
export * from './types.js';
|
|
10
|
+
export { type BlockChange, type DocumentStats, diff, escapeHtml, query, stats, stripPositions, visit, } from './util.js';
|
|
11
|
+
export { RULES } from './validate.js';
|
|
12
|
+
export { VERSION } from './version.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { formatDiagnostic } from './format.js';
|
|
2
|
+
export { ALERT_TITLES, ALERT_TYPES } from './plugins/alerts.js';
|
|
3
|
+
export { CALLOUTS, CONTAINERS, LEAF_DIRECTIVES, TEXT_DIRECTIVES } from './plugins/directives.js';
|
|
4
|
+
export { headingId, outline } from './plugins/headings.js';
|
|
5
|
+
export { buildToc, insertToc } from './plugins/toc.js';
|
|
6
|
+
export { createProcessor, MarkupMeError, parse, render, serialize, transform, validate, } from './processor.js';
|
|
7
|
+
export { markupmeSchema } from './render/schema.js';
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export { diff, escapeHtml, query, stats, stripPositions, visit, } from './util.js';
|
|
10
|
+
export { RULES } from './validate.js';
|
|
11
|
+
export { VERSION } from './version.js';
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type PluggableList } from 'unified';
|
|
2
|
+
import { type MarkupMeDocument, type ParseOptions, type ProcessorOptions, type TocOptions } from './types.js';
|
|
3
|
+
/** Syntax extensions shared by the parser and the serializer, so round trips keep every construct. */
|
|
4
|
+
export declare function syntaxPlugins(opts: ProcessorOptions): PluggableList;
|
|
5
|
+
export declare function tocOptions(opts: ProcessorOptions): TocOptions | undefined;
|
|
6
|
+
export declare function parseDocument(source: string, opts: ProcessorOptions, parseOpts?: ParseOptions): MarkupMeDocument;
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import remarkDirective from 'remark-directive';
|
|
2
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
3
|
+
import remarkGfm from 'remark-gfm';
|
|
4
|
+
import remarkParse from 'remark-parse';
|
|
5
|
+
import { unified } from 'unified';
|
|
6
|
+
import { annotateAlerts } from './plugins/alerts.js';
|
|
7
|
+
import { normalizeDirectives } from './plugins/directives.js';
|
|
8
|
+
import { readFrontmatter } from './plugins/frontmatter.js';
|
|
9
|
+
import { addHeadingIds } from './plugins/headings.js';
|
|
10
|
+
import { remarkMath } from './plugins/math.js';
|
|
11
|
+
import { insertToc } from './plugins/toc.js';
|
|
12
|
+
import { SCHEMA_VERSION, } from './types.js';
|
|
13
|
+
/** Syntax extensions shared by the parser and the serializer, so round trips keep every construct. */
|
|
14
|
+
export function syntaxPlugins(opts) {
|
|
15
|
+
const list = [];
|
|
16
|
+
if (opts.gfm !== false)
|
|
17
|
+
list.push(remarkGfm);
|
|
18
|
+
if (opts.frontmatter !== false)
|
|
19
|
+
list.push([remarkFrontmatter, ['yaml', 'toml']]);
|
|
20
|
+
if (opts.directives !== false)
|
|
21
|
+
list.push(remarkDirective);
|
|
22
|
+
if (opts.math === true)
|
|
23
|
+
list.push(remarkMath);
|
|
24
|
+
for (const p of opts.plugins ?? [])
|
|
25
|
+
if (p.remarkPlugins)
|
|
26
|
+
list.push(...p.remarkPlugins);
|
|
27
|
+
return list;
|
|
28
|
+
}
|
|
29
|
+
export function tocOptions(opts) {
|
|
30
|
+
if (opts.toc === false)
|
|
31
|
+
return undefined;
|
|
32
|
+
return opts.toc === true || opts.toc === undefined ? {} : opts.toc;
|
|
33
|
+
}
|
|
34
|
+
export function parseDocument(source, opts, parseOpts = {}) {
|
|
35
|
+
const processor = unified().use(remarkParse).use(syntaxPlugins(opts));
|
|
36
|
+
const tree = processor.runSync(processor.parse(source));
|
|
37
|
+
const diagnostics = [];
|
|
38
|
+
const file = parseOpts.file;
|
|
39
|
+
const report = (d) => diagnostics.push(file ? { ...d, file } : d);
|
|
40
|
+
const doc = {
|
|
41
|
+
type: 'markupme-document',
|
|
42
|
+
schemaVersion: SCHEMA_VERSION,
|
|
43
|
+
source,
|
|
44
|
+
tree,
|
|
45
|
+
diagnostics,
|
|
46
|
+
...(file ? { file } : {}),
|
|
47
|
+
};
|
|
48
|
+
if (opts.frontmatter !== false) {
|
|
49
|
+
const fm = readFrontmatter(tree, report);
|
|
50
|
+
if (fm)
|
|
51
|
+
doc.frontmatter = fm;
|
|
52
|
+
}
|
|
53
|
+
if (opts.directives !== false)
|
|
54
|
+
normalizeDirectives(tree, report);
|
|
55
|
+
if (opts.alerts !== false)
|
|
56
|
+
annotateAlerts(tree);
|
|
57
|
+
if (opts.headingIds !== false)
|
|
58
|
+
addHeadingIds(tree, report);
|
|
59
|
+
const toc = tocOptions(opts);
|
|
60
|
+
if (toc)
|
|
61
|
+
insertToc(tree, toc);
|
|
62
|
+
const ctx = { doc, options: opts, report };
|
|
63
|
+
for (const p of opts.plugins ?? [])
|
|
64
|
+
p.parse?.(tree, ctx);
|
|
65
|
+
return doc;
|
|
66
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
export declare const ALERT_TYPES: readonly ['note', 'tip', 'important', 'warning', 'caution'];
|
|
3
|
+
export type AlertType = (typeof ALERT_TYPES)[number];
|
|
4
|
+
export declare const ALERT_TITLES: Record<AlertType, string>;
|
|
5
|
+
/** Parse time: records `data.alert` on `> [!NOTE]` blockquotes. The tree text is untouched. */
|
|
6
|
+
export declare function annotateAlerts(tree: Root): void;
|
|
7
|
+
/** Render time (on a copy): turns annotated blockquotes into GitHub-style alert boxes. */
|
|
8
|
+
export declare function presentAlerts(tree: Root): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
export const ALERT_TYPES = ['note', 'tip', 'important', 'warning', 'caution'];
|
|
3
|
+
const MARKER = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*(?:\r?\n|$)/i;
|
|
4
|
+
export const ALERT_TITLES = {
|
|
5
|
+
note: 'Note',
|
|
6
|
+
tip: 'Tip',
|
|
7
|
+
important: 'Important',
|
|
8
|
+
warning: 'Warning',
|
|
9
|
+
caution: 'Caution',
|
|
10
|
+
};
|
|
11
|
+
/** Parse time: records `data.alert` on `> [!NOTE]` blockquotes. The tree text is untouched. */
|
|
12
|
+
export function annotateAlerts(tree) {
|
|
13
|
+
visit(tree, 'blockquote', (node) => {
|
|
14
|
+
const first = node.children[0];
|
|
15
|
+
if (first?.type !== 'paragraph')
|
|
16
|
+
return;
|
|
17
|
+
const text = first.children[0];
|
|
18
|
+
if (text?.type !== 'text')
|
|
19
|
+
return;
|
|
20
|
+
const m = MARKER.exec(text.value);
|
|
21
|
+
if (!m?.[1])
|
|
22
|
+
return;
|
|
23
|
+
node.data = { ...node.data, alert: m[1].toLowerCase() };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Render time (on a copy): turns annotated blockquotes into GitHub-style alert boxes. */
|
|
27
|
+
export function presentAlerts(tree) {
|
|
28
|
+
visit(tree, 'blockquote', (node) => {
|
|
29
|
+
const type = node.data?.alert;
|
|
30
|
+
if (!type)
|
|
31
|
+
return;
|
|
32
|
+
const first = node.children[0];
|
|
33
|
+
const text = first.children[0];
|
|
34
|
+
if (text?.type === 'text')
|
|
35
|
+
text.value = text.value.replace(MARKER, '');
|
|
36
|
+
if (first.children.length === 1 && text?.type === 'text' && text.value === '')
|
|
37
|
+
node.children.shift();
|
|
38
|
+
const title = {
|
|
39
|
+
type: 'paragraph',
|
|
40
|
+
children: [{ type: 'text', value: ALERT_TITLES[type] }],
|
|
41
|
+
data: { hProperties: { className: ['markdown-alert-title'] } },
|
|
42
|
+
};
|
|
43
|
+
node.children.unshift(title);
|
|
44
|
+
node.data = {
|
|
45
|
+
...node.data,
|
|
46
|
+
hName: 'div',
|
|
47
|
+
hProperties: { className: ['markdown-alert', `markdown-alert-${type}`], role: 'note' },
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
import type { Diagnostic } from '../types.js';
|
|
3
|
+
/** `:::name` containers rendered as callout boxes. */
|
|
4
|
+
export declare const CALLOUTS: string[];
|
|
5
|
+
export declare const CONTAINERS: string[];
|
|
6
|
+
/** `:name[label]` inline directives. */
|
|
7
|
+
export declare const TEXT_DIRECTIVES: string[];
|
|
8
|
+
/** `::name` leaf directives. `toc` is handled by the TOC plugin. */
|
|
9
|
+
export declare const LEAF_DIRECTIVES: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Parse time: a text directive with no label and no attributes (`10:30`, `note:this`) is almost
|
|
12
|
+
* always prose, so it goes back to plain text. Unknown directives are reported, never dropped.
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeDirectives(tree: Root, report: (d: Diagnostic) => void): void;
|
|
15
|
+
/** Render time (on a copy): maps directives to HTML elements. */
|
|
16
|
+
export declare function presentDirectives(tree: Root): void;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { toString as mdToString } from 'mdast-util-to-string';
|
|
2
|
+
import { SKIP, visit } from 'unist-util-visit';
|
|
3
|
+
/** `:::name` containers rendered as callout boxes. */
|
|
4
|
+
export const CALLOUTS = [
|
|
5
|
+
'note',
|
|
6
|
+
'tip',
|
|
7
|
+
'info',
|
|
8
|
+
'important',
|
|
9
|
+
'warning',
|
|
10
|
+
'caution',
|
|
11
|
+
'danger',
|
|
12
|
+
'success',
|
|
13
|
+
];
|
|
14
|
+
export const CONTAINERS = [...CALLOUTS, 'details', 'figure', 'columns', 'column'];
|
|
15
|
+
/** `:name[label]` inline directives. */
|
|
16
|
+
export const TEXT_DIRECTIVES = ['kbd', 'abbr', 'mark', 'sub', 'sup', 'badge'];
|
|
17
|
+
/** `::name` leaf directives. `toc` is handled by the TOC plugin. */
|
|
18
|
+
export const LEAF_DIRECTIVES = ['toc', 'br'];
|
|
19
|
+
function isLabel(node) {
|
|
20
|
+
return (node?.type === 'paragraph' &&
|
|
21
|
+
node.data?.directiveLabel === true);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse time: a text directive with no label and no attributes (`10:30`, `note:this`) is almost
|
|
25
|
+
* always prose, so it goes back to plain text. Unknown directives are reported, never dropped.
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeDirectives(tree, report) {
|
|
28
|
+
visit(tree, (node, index, parent) => {
|
|
29
|
+
if (node.type !== 'textDirective' &&
|
|
30
|
+
node.type !== 'leafDirective' &&
|
|
31
|
+
node.type !== 'containerDirective')
|
|
32
|
+
return;
|
|
33
|
+
const d = node;
|
|
34
|
+
const hasAttrs = d.attributes && Object.keys(d.attributes).length > 0;
|
|
35
|
+
if (d.type === 'textDirective' &&
|
|
36
|
+
d.children.length === 0 &&
|
|
37
|
+
!hasAttrs &&
|
|
38
|
+
parent &&
|
|
39
|
+
index !== undefined) {
|
|
40
|
+
parent.children.splice(index, 1, { type: 'text', value: `:${d.name}` });
|
|
41
|
+
return [SKIP, index];
|
|
42
|
+
}
|
|
43
|
+
const known = d.type === 'textDirective'
|
|
44
|
+
? TEXT_DIRECTIVES
|
|
45
|
+
: d.type === 'leafDirective'
|
|
46
|
+
? LEAF_DIRECTIVES
|
|
47
|
+
: CONTAINERS;
|
|
48
|
+
if (!known.includes(d.name)) {
|
|
49
|
+
report({
|
|
50
|
+
code: 'MM009',
|
|
51
|
+
rule: 'unknown-directive',
|
|
52
|
+
severity: 'warning',
|
|
53
|
+
message: `Unknown directive "${d.name}" (${d.type.replace('Directive', '')}); its content is rendered as-is`,
|
|
54
|
+
line: d.position?.start.line,
|
|
55
|
+
column: d.position?.start.column,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function setH(node, hName, hProperties = {}) {
|
|
62
|
+
node.data = { ...node.data, hName, hProperties: { ...hProperties } };
|
|
63
|
+
}
|
|
64
|
+
/** Render time (on a copy): maps directives to HTML elements. */
|
|
65
|
+
export function presentDirectives(tree) {
|
|
66
|
+
visit(tree, (node) => {
|
|
67
|
+
if (node.type !== 'textDirective' &&
|
|
68
|
+
node.type !== 'leafDirective' &&
|
|
69
|
+
node.type !== 'containerDirective')
|
|
70
|
+
return;
|
|
71
|
+
const d = node;
|
|
72
|
+
const attrs = d.attributes ?? {};
|
|
73
|
+
if (d.type === 'containerDirective') {
|
|
74
|
+
const label = isLabel(d.children[0]) ? d.children.shift() : undefined;
|
|
75
|
+
const title = attrs.title ?? (label ? mdToString(label) : undefined);
|
|
76
|
+
if (CALLOUTS.includes(d.name)) {
|
|
77
|
+
setH(d, 'div', { className: ['mm-callout', `mm-callout-${d.name}`], role: 'note' });
|
|
78
|
+
const heading = title ?? d.name.charAt(0).toUpperCase() + d.name.slice(1);
|
|
79
|
+
d.children.unshift({
|
|
80
|
+
type: 'paragraph',
|
|
81
|
+
children: label && !attrs.title ? label.children : [{ type: 'text', value: heading }],
|
|
82
|
+
data: { hProperties: { className: ['mm-callout-title'] } },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (d.name === 'details') {
|
|
86
|
+
setH(d, 'details', attrs.open !== undefined ? { open: true } : {});
|
|
87
|
+
d.children.unshift({
|
|
88
|
+
type: 'paragraph',
|
|
89
|
+
children: label ? label.children : [{ type: 'text', value: title ?? 'Details' }],
|
|
90
|
+
data: { hName: 'summary' },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else if (d.name === 'figure') {
|
|
94
|
+
setH(d, 'figure', { className: ['mm-figure'] });
|
|
95
|
+
if (label)
|
|
96
|
+
d.children.push({
|
|
97
|
+
type: 'paragraph',
|
|
98
|
+
children: label.children,
|
|
99
|
+
data: { hName: 'figcaption' },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else if (d.name === 'columns')
|
|
103
|
+
setH(d, 'div', { className: ['mm-columns'] });
|
|
104
|
+
else if (d.name === 'column')
|
|
105
|
+
setH(d, 'div', { className: ['mm-column'] });
|
|
106
|
+
else
|
|
107
|
+
setH(d, 'div', { className: ['mm-directive', `mm-directive-${d.name}`] });
|
|
108
|
+
}
|
|
109
|
+
else if (d.type === 'textDirective') {
|
|
110
|
+
if (d.name === 'kbd' || d.name === 'mark' || d.name === 'sub' || d.name === 'sup')
|
|
111
|
+
setH(d, d.name);
|
|
112
|
+
else if (d.name === 'abbr')
|
|
113
|
+
setH(d, 'abbr', attrs.title ? { title: attrs.title } : {});
|
|
114
|
+
else if (d.name === 'badge')
|
|
115
|
+
setH(d, 'span', { className: ['mm-badge', `mm-badge-${attrs.type ?? 'default'}`] });
|
|
116
|
+
else
|
|
117
|
+
setH(d, 'span', { className: ['mm-directive', `mm-directive-${d.name}`] });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (d.name === 'br')
|
|
121
|
+
setH(d, 'br');
|
|
122
|
+
else
|
|
123
|
+
setH(d, 'div', { className: ['mm-directive', `mm-directive-${d.name}`] });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { gemoji } from 'gemoji';
|
|
2
|
+
import { visit } from 'unist-util-visit';
|
|
3
|
+
let names;
|
|
4
|
+
function lookup() {
|
|
5
|
+
if (!names) {
|
|
6
|
+
names = new Map();
|
|
7
|
+
for (const entry of gemoji)
|
|
8
|
+
for (const name of entry.names)
|
|
9
|
+
names.set(name, entry.emoji);
|
|
10
|
+
}
|
|
11
|
+
return names;
|
|
12
|
+
}
|
|
13
|
+
const SHORTCODE = /:([a-z0-9_+-]+):/g;
|
|
14
|
+
/** Render time: replaces GitHub `:shortcode:` names with emoji. Unknown names stay as written. */
|
|
15
|
+
export function presentEmoji(tree) {
|
|
16
|
+
const map = lookup();
|
|
17
|
+
visit(tree, 'text', (node) => {
|
|
18
|
+
if (!node.value.includes(':'))
|
|
19
|
+
return;
|
|
20
|
+
node.value = node.value.replace(SHORTCODE, (whole, name) => map.get(name) ?? whole);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
import type { Diagnostic } from '../types.js';
|
|
3
|
+
/** Reads the first YAML or TOML frontmatter block. Invalid data is reported, never thrown. */
|
|
4
|
+
export declare function readFrontmatter(tree: Root, report: (d: Diagnostic) => void): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { parse as parseToml } from 'smol-toml';
|
|
2
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
/** Reads the first YAML or TOML frontmatter block. Invalid data is reported, never thrown. */
|
|
4
|
+
export function readFrontmatter(tree, report) {
|
|
5
|
+
const node = tree.children[0];
|
|
6
|
+
if (!node || (node.type !== 'yaml' && node.type !== 'toml'))
|
|
7
|
+
return undefined;
|
|
8
|
+
const value = node.value;
|
|
9
|
+
try {
|
|
10
|
+
const data = node.type === 'yaml' ? parseYaml(value) : parseToml(value);
|
|
11
|
+
if (data === null || data === undefined)
|
|
12
|
+
return {};
|
|
13
|
+
if (typeof data !== 'object' || Array.isArray(data)) {
|
|
14
|
+
report({
|
|
15
|
+
code: 'MM010',
|
|
16
|
+
rule: 'invalid-frontmatter',
|
|
17
|
+
severity: 'error',
|
|
18
|
+
message: 'Frontmatter must be a mapping of keys to values',
|
|
19
|
+
line: node.position?.start.line,
|
|
20
|
+
column: 1,
|
|
21
|
+
});
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
report({
|
|
28
|
+
code: 'MM010',
|
|
29
|
+
rule: 'invalid-frontmatter',
|
|
30
|
+
severity: 'error',
|
|
31
|
+
message: `Invalid ${node.type.toUpperCase()} frontmatter: ${error.message.split('\n')[0]}`,
|
|
32
|
+
line: node.position?.start.line,
|
|
33
|
+
column: 1,
|
|
34
|
+
});
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Heading, Root } from 'mdast';
|
|
2
|
+
import type { Diagnostic } from '../types.js';
|
|
3
|
+
export declare function headingId(node: Heading): string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Gives every heading a GitHub-compatible id in `data.hProperties.id`.
|
|
6
|
+
* A trailing `{#custom-id}` sets the id explicitly; the marker is kept in `data.customId` so
|
|
7
|
+
* serialization can write it back.
|
|
8
|
+
*/
|
|
9
|
+
export declare function addHeadingIds(tree: Root, report: (d: Diagnostic) => void): void;
|
|
10
|
+
export interface OutlineItem {
|
|
11
|
+
depth: number;
|
|
12
|
+
text: string;
|
|
13
|
+
id: string;
|
|
14
|
+
line?: number;
|
|
15
|
+
children: OutlineItem[];
|
|
16
|
+
}
|
|
17
|
+
/** The heading tree of a document. */
|
|
18
|
+
export declare function outline(tree: Root): OutlineItem[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import GithubSlugger from 'github-slugger';
|
|
2
|
+
import { toString as mdToString } from 'mdast-util-to-string';
|
|
3
|
+
import { visit } from 'unist-util-visit';
|
|
4
|
+
const CUSTOM_ID = /\s*\{#([A-Za-z][\w:.-]*)\}\s*$/;
|
|
5
|
+
export function headingId(node) {
|
|
6
|
+
const id = node.data?.hProperties?.id;
|
|
7
|
+
return typeof id === 'string' ? id : undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Gives every heading a GitHub-compatible id in `data.hProperties.id`.
|
|
11
|
+
* A trailing `{#custom-id}` sets the id explicitly; the marker is kept in `data.customId` so
|
|
12
|
+
* serialization can write it back.
|
|
13
|
+
*/
|
|
14
|
+
export function addHeadingIds(tree, report) {
|
|
15
|
+
const slugger = new GithubSlugger();
|
|
16
|
+
const seen = new Map();
|
|
17
|
+
visit(tree, 'heading', (node) => {
|
|
18
|
+
let id;
|
|
19
|
+
const last = node.children[node.children.length - 1];
|
|
20
|
+
if (last && last.type === 'text') {
|
|
21
|
+
const m = CUSTOM_ID.exec(last.value);
|
|
22
|
+
if (m?.[1]) {
|
|
23
|
+
id = m[1];
|
|
24
|
+
last.value = last.value.slice(0, m.index);
|
|
25
|
+
if (last.value === '')
|
|
26
|
+
node.children.pop();
|
|
27
|
+
node.data = { ...node.data, customId: id };
|
|
28
|
+
slugger.slug(id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
id ??= slugger.slug(mdToString(node));
|
|
32
|
+
if (seen.has(id)) {
|
|
33
|
+
report({
|
|
34
|
+
code: 'MM003',
|
|
35
|
+
rule: 'duplicate-heading-id',
|
|
36
|
+
severity: 'warning',
|
|
37
|
+
message: `Heading id "${id}" is already used on line ${seen.get(id)}`,
|
|
38
|
+
line: node.position?.start.line,
|
|
39
|
+
column: node.position?.start.column,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
seen.set(id, node.position?.start.line ?? 0);
|
|
44
|
+
node.data = { ...node.data, hProperties: { ...node.data?.hProperties, id } };
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** The heading tree of a document. */
|
|
48
|
+
export function outline(tree) {
|
|
49
|
+
const roots = [];
|
|
50
|
+
const stack = [];
|
|
51
|
+
visit(tree, 'heading', (node) => {
|
|
52
|
+
const item = {
|
|
53
|
+
depth: node.depth,
|
|
54
|
+
text: mdToString(node),
|
|
55
|
+
id: headingId(node) ?? '',
|
|
56
|
+
line: node.position?.start.line,
|
|
57
|
+
children: [],
|
|
58
|
+
};
|
|
59
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= item.depth)
|
|
60
|
+
stack.pop();
|
|
61
|
+
const parent = stack[stack.length - 1];
|
|
62
|
+
if (parent)
|
|
63
|
+
parent.children.push(item);
|
|
64
|
+
else
|
|
65
|
+
roots.push(item);
|
|
66
|
+
stack.push(item);
|
|
67
|
+
});
|
|
68
|
+
return roots;
|
|
69
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Extension } from 'micromark-util-types';
|
|
2
|
+
declare module 'micromark-util-types' {
|
|
3
|
+
interface TokenTypeMap {
|
|
4
|
+
mathFlow: 'mathFlow';
|
|
5
|
+
mathFlowFence: 'mathFlowFence';
|
|
6
|
+
mathFlowFenceMeta: 'mathFlowFenceMeta';
|
|
7
|
+
mathFlowFenceSequence: 'mathFlowFenceSequence';
|
|
8
|
+
mathFlowValue: 'mathFlowValue';
|
|
9
|
+
mathText: 'mathText';
|
|
10
|
+
mathTextData: 'mathTextData';
|
|
11
|
+
mathTextPadding: 'mathTextPadding';
|
|
12
|
+
mathTextSequence: 'mathTextSequence';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export interface MathSyntaxOptions {
|
|
16
|
+
/** Whether `$x$` (one dollar) is text math. Default `true`. */
|
|
17
|
+
singleDollarTextMath?: boolean | null | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** micromark extension for math syntax. */
|
|
20
|
+
export declare function mathSyntax(options?: MathSyntaxOptions | null): Extension;
|