@jarenjs/md 0.34.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 +520 -0
- package/dist/types/ast.d.ts +181 -0
- package/dist/types/bake.d.ts +61 -0
- package/dist/types/compiler.d.ts +141 -0
- package/dist/types/component/index.d.ts +101 -0
- package/dist/types/directives.d.ts +126 -0
- package/dist/types/entities.d.ts +40 -0
- package/dist/types/footnotes.d.ts +83 -0
- package/dist/types/frontmatter.d.ts +67 -0
- package/dist/types/html.d.ts +72 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/loader.d.ts +84 -0
- package/dist/types/mdx.d.ts +45 -0
- package/dist/types/parser.d.ts +116 -0
- package/dist/types/plugins/highlight.d.ts +64 -0
- package/dist/types/plugins/index.d.ts +64 -0
- package/dist/types/plugins/mermaid.d.ts +12 -0
- package/dist/types/scanner.d.ts +240 -0
- package/dist/types/to-html.d.ts +104 -0
- package/dist/types/to-md.d.ts +23 -0
- package/dist/types/to-vnode.d.ts +161 -0
- package/dist/types/utils.d.ts +63 -0
- package/docs/LOADER.md +92 -0
- package/docs/MD-FORMAT.md +502 -0
- package/docs/PLUGINS.md +277 -0
- package/package.json +80 -0
- package/schemas/jaren-md-ast.schema.json +296 -0
- package/src/ast.js +346 -0
- package/src/bake.js +104 -0
- package/src/compiler.js +167 -0
- package/src/component/index.js +191 -0
- package/src/directives.js +371 -0
- package/src/entities.js +107 -0
- package/src/footnotes.js +180 -0
- package/src/frontmatter.js +947 -0
- package/src/html.js +281 -0
- package/src/index.js +76 -0
- package/src/loader.js +0 -0
- package/src/mdx.js +219 -0
- package/src/parser.js +1685 -0
- package/src/plugins/highlight.js +325 -0
- package/src/plugins/index.js +75 -0
- package/src/plugins/mermaid.js +14 -0
- package/src/scanner.js +832 -0
- package/src/to-html.js +425 -0
- package/src/to-md.js +396 -0
- package/src/to-vnode.js +766 -0
- package/src/utils.js +107 -0
- package/styles/md.css +238 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file GFM footnotes: which definitions a document actually cites, in
|
|
3
|
+
* which order, and what to call them.
|
|
4
|
+
*
|
|
5
|
+
* Definitions stay where the author wrote them (the AST is the
|
|
6
|
+
* document, not the rendering), so both emitters need the same answer to
|
|
7
|
+
* the same three questions before they emit anything: which definitions
|
|
8
|
+
* are cited, what number each one gets, and which identifiers the
|
|
9
|
+
* reference and its back-references carry. That answer lives here once —
|
|
10
|
+
* two emitters minting ids from two implementations is exactly how a
|
|
11
|
+
* back-reference ends up pointing at nothing.
|
|
12
|
+
*/
|
|
13
|
+
export type MdNode = import('./ast.js').MdNode;
|
|
14
|
+
export type Footnotes = {
|
|
15
|
+
defs: MdNode[];
|
|
16
|
+
numbers: Map<string, number>;
|
|
17
|
+
counts: Map<string, number>;
|
|
18
|
+
refs: WeakMap<MdNode, {
|
|
19
|
+
number: number;
|
|
20
|
+
occurrence: number;
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {import('./ast.js').MdNode} MdNode
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {{ defs: MdNode[],
|
|
28
|
+
* numbers: Map<string, number>,
|
|
29
|
+
* counts: Map<string, number>,
|
|
30
|
+
* refs: WeakMap<MdNode, { number: number, occurrence: number }> }} Footnotes
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The default identifier prefix for footnotes — GitHub's own.
|
|
34
|
+
*
|
|
35
|
+
* Unlike a heading id (MD-FORMAT.md §4.5), which defaults to no prefix
|
|
36
|
+
* because CommonMark asserts a bare heading and the primary consumer is
|
|
37
|
+
* a document the host wrote, a footnote id is emitted by the DEFAULT
|
|
38
|
+
* rendering of a feature whose whole point is a link between two places
|
|
39
|
+
* on one page. No spec example asserts a bare `fn-1`, so the safe
|
|
40
|
+
* default costs nothing and a host page keeps its own `#fn-1` for
|
|
41
|
+
* itself. `slugPrefix`, when given, replaces it.
|
|
42
|
+
*/
|
|
43
|
+
export declare const FOOTNOTE_PREFIX = "user-content-";
|
|
44
|
+
/** The back-reference glyph GitHub uses. */
|
|
45
|
+
export declare const BACKREF_MARK = "\u21A9";
|
|
46
|
+
/**
|
|
47
|
+
* Collect a document's cited footnotes.
|
|
48
|
+
*
|
|
49
|
+
* Returns `null` when the AST holds no `footnoteDefinition` at all — the
|
|
50
|
+
* overwhelmingly common case, and the one that must cost nothing: the
|
|
51
|
+
* emitters compare that `null` to decide whether anything about footnote
|
|
52
|
+
* rendering can differ between two emissions.
|
|
53
|
+
*
|
|
54
|
+
* The result is memoized on the AST array, so re-rendering one document
|
|
55
|
+
* reuses it (and with it the vnode emitter's per-node memo); a
|
|
56
|
+
* transformed document is a different array and correctly gets fresh
|
|
57
|
+
* numbering.
|
|
58
|
+
*
|
|
59
|
+
* @param {MdNode[]} ast
|
|
60
|
+
* @returns {Footnotes|null}
|
|
61
|
+
*/
|
|
62
|
+
export declare function collectFootnotes(ast: MdNode[]): Footnotes | null;
|
|
63
|
+
/**
|
|
64
|
+
* The `id` of a rendered footnote (the `<li>` in the section).
|
|
65
|
+
* @param {string} prefix @param {number} number
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
export declare function footnoteId(prefix: string, number: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* The `id` of one citation. A footnote cited more than once needs one
|
|
71
|
+
* landing place per citation, or every back-reference would return the
|
|
72
|
+
* reader to the first.
|
|
73
|
+
* @param {string} prefix @param {number} number @param {number} occurrence
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
export declare function footnoteRefId(prefix: string, number: number, occurrence: number): string;
|
|
77
|
+
/**
|
|
78
|
+
* The accessible name of a back-reference: `↩` alone names nothing, and
|
|
79
|
+
* a footnote with several of them needs them told apart.
|
|
80
|
+
* @param {number} number @param {number} occurrence
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
export declare function backrefLabel(number: number, occurrence: number): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Frontmatter extraction: YAML subset, JSON and TOML → plain JSON.
|
|
3
|
+
*
|
|
4
|
+
* Frontmatter is detected at the very top of the source only:
|
|
5
|
+
*
|
|
6
|
+
* - `---` opens a YAML-subset block, closed by `---` or `...`
|
|
7
|
+
* - `---json` opens a JSON block, closed by `---`
|
|
8
|
+
* - `{` (as the first character) opens a JSON object closed by a
|
|
9
|
+
* line that is exactly `}`
|
|
10
|
+
* - `+++` opens a TOML block, closed by `+++`
|
|
11
|
+
*
|
|
12
|
+
* Whatever the syntax, the result normalizes to one plain JSON value on
|
|
13
|
+
* the document. The parsers are written from scratch and dependency-free;
|
|
14
|
+
* an external TOML parser (e.g. `parseToml` from `@jarenjs/josl`) can be
|
|
15
|
+
* injected through `options.toml` to replace the built-in TOML subset.
|
|
16
|
+
*
|
|
17
|
+
* The YAML subset (normative limits in docs/MD-FORMAT.md §3):
|
|
18
|
+
* scalars (null/booleans/numbers/strings), single- and double-quoted
|
|
19
|
+
* strings, block maps and sequences by indentation, flow arrays and
|
|
20
|
+
* maps (multi-line while brackets are open), literal `|` and folded `>`
|
|
21
|
+
* block scalars with `-` chomping, and `#` comments. No anchors, no
|
|
22
|
+
* aliases, no tags, no multi-document streams, no complex keys.
|
|
23
|
+
*/
|
|
24
|
+
/** Raised for malformed frontmatter inside a detected fence. */
|
|
25
|
+
export declare class MdFrontmatterError extends Error {
|
|
26
|
+
line: number;
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} message
|
|
29
|
+
* @param {number} line 0-based line index inside the frontmatter block
|
|
30
|
+
*/
|
|
31
|
+
constructor(message: string, line: number);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Split frontmatter off the top of a Markdown source.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} source
|
|
37
|
+
* @param {{ toml?: (text: string) => any }} [options]
|
|
38
|
+
* @returns {{ data: any, body: string, lang: 'yaml'|'json'|'toml'|null }}
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseFrontmatter(source: string, options?: {
|
|
41
|
+
toml?: (text: string) => any;
|
|
42
|
+
}): {
|
|
43
|
+
data: any;
|
|
44
|
+
body: string;
|
|
45
|
+
lang: 'yaml' | 'json' | 'toml' | null;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Parse the YAML subset into plain JSON.
|
|
49
|
+
* @param {string} text
|
|
50
|
+
* @returns {any}
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseYamlSubset(text: string): any;
|
|
53
|
+
export type YamlState = {
|
|
54
|
+
lines: string[];
|
|
55
|
+
pos: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Parse the built-in TOML subset: `[table]` and `[[array-of-tables]]`
|
|
59
|
+
* headers with dotted paths, bare/quoted/dotted keys, basic and literal
|
|
60
|
+
* strings, integers (decimal/hex/octal/binary, `_` separators), floats,
|
|
61
|
+
* booleans, single- or multi-line flow arrays, inline tables, and `#`
|
|
62
|
+
* comments. Datetimes are kept as strings; multi-line strings are not
|
|
63
|
+
* supported (normative limits in docs/MD-FORMAT.md §3.3).
|
|
64
|
+
* @param {string} text
|
|
65
|
+
* @returns {Record<string, any>}
|
|
66
|
+
*/
|
|
67
|
+
export declare function parseTomlSubset(text: string): Record<string, any>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Raw HTML in Markdown, parsed to vnodes through an allow-list.
|
|
3
|
+
*
|
|
4
|
+
* The vnode format has no unescaped output — deliberately: a tree of
|
|
5
|
+
* arrays cannot carry a half-open tag or an injected `<script>`, which
|
|
6
|
+
* is why `mdToVnode` has always either dropped raw HTML or shown it as
|
|
7
|
+
* text. This module is the third answer: PARSE the HTML and keep what an
|
|
8
|
+
* allow-list recognises, so `<details>` and `<img>` in a document render
|
|
9
|
+
* as themselves.
|
|
10
|
+
*
|
|
11
|
+
* The safety argument is structural rather than a promise about
|
|
12
|
+
* filtering strings. The output is a vnode tree, so:
|
|
13
|
+
*
|
|
14
|
+
* - an element not on the list contributes nothing but its children's
|
|
15
|
+
* text — there is no path by which its markup reaches the DOM;
|
|
16
|
+
* - an attribute not on the list is dropped, so `on*` handlers,
|
|
17
|
+
* `style` and `srcdoc` never exist to begin with;
|
|
18
|
+
* - `href`/`src` go through the same {@link sanitizeUrl} policy as
|
|
19
|
+
* Markdown's own links, so `javascript:` cannot ride in on raw HTML
|
|
20
|
+
* when it cannot ride in on `[x](…)`.
|
|
21
|
+
*
|
|
22
|
+
* What this is NOT: an HTML5 parser. It does not implement implicit
|
|
23
|
+
* end tags, foster parenting, or the tokenizer's error recovery, and it
|
|
24
|
+
* does not attempt to reproduce what a browser would build from
|
|
25
|
+
* malformed input. Unbalanced input closes at the end of the fragment.
|
|
26
|
+
* For hostile input the goal is that nothing survives that should not,
|
|
27
|
+
* not that the shape matches what a browser would have made of it.
|
|
28
|
+
*
|
|
29
|
+
* A host that wants different rules injects its own parser instead
|
|
30
|
+
* (`mdToVnode`'s `parseHtml` option) — this is the default, not the
|
|
31
|
+
* only, implementation.
|
|
32
|
+
*/
|
|
33
|
+
export type HtmlParseOptions = {
|
|
34
|
+
/**
|
|
35
|
+
* URL policy for
|
|
36
|
+
* `href`/`src` (default: the shared deny-list, as Markdown links use)
|
|
37
|
+
*/
|
|
38
|
+
sanitizeUrl?: (url: string) => (string | null);
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {object} HtmlParseOptions
|
|
42
|
+
* @property {(url: string) => (string|null)} [sanitizeUrl] URL policy for
|
|
43
|
+
* `href`/`src` (default: the shared deny-list, as Markdown links use)
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Parse an HTML fragment into vnodes, keeping only what the allow-list
|
|
47
|
+
* recognises.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} source the raw HTML of one Markdown html node
|
|
50
|
+
* @param {HtmlParseOptions} [options]
|
|
51
|
+
* @returns {any[]} vnodes and strings; empty when nothing survived
|
|
52
|
+
* @example
|
|
53
|
+
* parseHtmlFragment('<details><summary>More</summary>text</details>');
|
|
54
|
+
* // [['details', {}, ['summary', {}, 'More'], 'text']]
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseHtmlFragment(source: string, options?: HtmlParseOptions): any[];
|
|
57
|
+
/**
|
|
58
|
+
* Classify ONE tag — what CommonMark's inline phase hands out, since at
|
|
59
|
+
* that level a tag is not an element (`<b>bold</b>` is three siblings).
|
|
60
|
+
* Returns null when the source is not exactly one tag.
|
|
61
|
+
* @param {string} source
|
|
62
|
+
* @param {HtmlParseOptions} [options]
|
|
63
|
+
* @returns {{name: string, closing: boolean, complete: boolean,
|
|
64
|
+
* drop: boolean, props: Record<string, any>|null} | null}
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseHtmlTag(source: string, options?: HtmlParseOptions): {
|
|
67
|
+
name: string;
|
|
68
|
+
closing: boolean;
|
|
69
|
+
complete: boolean;
|
|
70
|
+
drop: boolean;
|
|
71
|
+
props: Record<string, any> | null;
|
|
72
|
+
} | null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jarenjs/md - Markdown + frontmatter as JSON documents.
|
|
3
|
+
*
|
|
4
|
+
* parseMarkdown turns Markdown (CommonMark core + GFM tables,
|
|
5
|
+
* strikethrough, task lists, footnotes and autolink literals +
|
|
6
|
+
* YAML/JSON/TOML frontmatter) into a plain
|
|
7
|
+
* JSON AST the rest of the suite consumes natively: JSLT/JTLT and
|
|
8
|
+
* query documents transform it, `mdToVnode` projects it to
|
|
9
|
+
* @jarenjs/view vnodes with content-hash keys, `toMarkdown` prints
|
|
10
|
+
* canonical round-trip text, and `loadMarkdown`/`streamMarkdown` pull
|
|
11
|
+
* documents lazily from any URL with caching, AbortSignal and
|
|
12
|
+
* block-by-block streaming. Extensibility is compile-time plugins
|
|
13
|
+
* (`definePlugin`, `@jarenjs/md/plugins`) baked into dispatch tables.
|
|
14
|
+
*
|
|
15
|
+
* The normative contracts: docs/MD-FORMAT.md (AST + frontmatter),
|
|
16
|
+
* docs/PLUGINS.md (plugin system), docs/LOADER.md (loader).
|
|
17
|
+
*/
|
|
18
|
+
export { parseMarkdown, createIncrementalParser, buildPluginTables, } from './parser.js';
|
|
19
|
+
export { compileMarkdown, frontmatterExternals, mdToForm, } from './compiler.js';
|
|
20
|
+
export { loadMarkdown, streamMarkdown, createMdCache, defaultMdCache, } from './loader.js';
|
|
21
|
+
export { toMarkdown } from './to-md.js';
|
|
22
|
+
export { toHtml } from './to-html.js';
|
|
23
|
+
export { mdToVnode, createMdRenderer, } from './to-vnode.js';
|
|
24
|
+
export { scanDirectives, replaceDirectives, scanSourceDirectives, parseMarker, } from './directives.js';
|
|
25
|
+
export { bake } from './bake.js';
|
|
26
|
+
export { definePlugin } from './plugins/index.js';
|
|
27
|
+
export { parseHtmlFragment, parseHtmlTag } from './html.js';
|
|
28
|
+
export { MD_VERSION, walkAst, visitAst, textOf, isContainerNode, } from './ast.js';
|
|
29
|
+
export { parseFrontmatter, parseYamlSubset, parseTomlSubset, MdFrontmatterError, } from './frontmatter.js';
|
|
30
|
+
export { hashContent } from './utils.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The lazy URL loader (docs/LOADER.md): cache, abort, streaming.
|
|
3
|
+
*
|
|
4
|
+
* `loadMarkdown` resolves any URL the platform `fetch` accepts into a
|
|
5
|
+
* compiled document. The shared LRU cache keys on normalized URL +
|
|
6
|
+
* plugin set, remembers `ETag`/`Last-Modified` validators, shares
|
|
7
|
+
* in-flight fetches, and revalidates stale entries in the background.
|
|
8
|
+
* When the response body is a `ReadableStream` the parser runs
|
|
9
|
+
* block-by-block as chunks arrive — `streamMarkdown` exposes that as
|
|
10
|
+
* an async iterator of completed top-level blocks.
|
|
11
|
+
*/
|
|
12
|
+
export type MdNode = import('./ast.js').MdNode;
|
|
13
|
+
export type MdDocument = import('./ast.js').MdDocument;
|
|
14
|
+
export type CompiledMd = import('./compiler.js').CompiledMd;
|
|
15
|
+
export type MdCompileOptions = import('./compiler.js').MdCompileOptions;
|
|
16
|
+
export type MdLoadOptions = MdCompileOptions & {
|
|
17
|
+
base?: string | URL;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
cache?: MdCache | false;
|
|
20
|
+
fetch?: typeof globalThis.fetch;
|
|
21
|
+
};
|
|
22
|
+
export type MdCache = {
|
|
23
|
+
get: (key: string) => any;
|
|
24
|
+
set: (key: string, entry: any) => void;
|
|
25
|
+
delete: (key: string) => boolean;
|
|
26
|
+
clear: () => void;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {import('./ast.js').MdNode} MdNode
|
|
30
|
+
* @typedef {import('./ast.js').MdDocument} MdDocument
|
|
31
|
+
* @typedef {import('./compiler.js').CompiledMd} CompiledMd
|
|
32
|
+
* @typedef {import('./compiler.js').MdCompileOptions} MdCompileOptions
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {MdCompileOptions & {
|
|
36
|
+
* base?: string | URL,
|
|
37
|
+
* signal?: AbortSignal,
|
|
38
|
+
* cache?: MdCache | false,
|
|
39
|
+
* fetch?: typeof globalThis.fetch,
|
|
40
|
+
* }} MdLoadOptions
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {object} MdCache
|
|
44
|
+
* @property {(key: string) => any} get
|
|
45
|
+
* @property {(key: string, entry: any) => void} set
|
|
46
|
+
* @property {(key: string) => boolean} delete
|
|
47
|
+
* @property {() => void} clear
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* A small LRU cache (Map re-insertion order).
|
|
51
|
+
* @param {number} [limit]
|
|
52
|
+
* @returns {MdCache}
|
|
53
|
+
*/
|
|
54
|
+
export declare function createMdCache(limit?: number): MdCache;
|
|
55
|
+
/** The shared default cache instance. */
|
|
56
|
+
export declare const defaultMdCache: MdCache;
|
|
57
|
+
/**
|
|
58
|
+
* Load and compile a Markdown document from a URL (docs/LOADER.md §1).
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const md = await loadMarkdown('/docs/intro.md', { base: location.href });
|
|
62
|
+
* md.frontmatter; md.toVnode();
|
|
63
|
+
*
|
|
64
|
+
* @param {string | URL} url
|
|
65
|
+
* @param {MdLoadOptions} [options]
|
|
66
|
+
* @returns {Promise<CompiledMd>}
|
|
67
|
+
*/
|
|
68
|
+
export declare function loadMarkdown(url: string | URL, options?: MdLoadOptions): Promise<CompiledMd>;
|
|
69
|
+
/**
|
|
70
|
+
* Stream a Markdown source, yielding completed top-level block nodes
|
|
71
|
+
* as their end becomes certain; the generator's return value is the
|
|
72
|
+
* finished MdDocument (docs/LOADER.md §4).
|
|
73
|
+
*
|
|
74
|
+
* Accepts a URL, a `Response`, a `ReadableStream`, or any (async)
|
|
75
|
+
* iterable of string/Uint8Array chunks.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* for await (const block of streamMarkdown('/big.md')) render(block);
|
|
79
|
+
*
|
|
80
|
+
* @param {any} urlOrStream
|
|
81
|
+
* @param {MdLoadOptions} [options]
|
|
82
|
+
* @returns {AsyncGenerator<MdNode, MdDocument>}
|
|
83
|
+
*/
|
|
84
|
+
export declare function streamMarkdown(urlOrStream: any, options?: MdLoadOptions): AsyncGenerator<MdNode, MdDocument>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file mdx = markdown × data. A markdown document renders DYNAMICALLY
|
|
3
|
+
* against a data document: inline interpolation, conditional sections
|
|
4
|
+
* and repeated sections, all driven by the data — still a pure
|
|
5
|
+
* `(doc, data) → doc` transform over the parsed AST, so everything
|
|
6
|
+
* downstream (`mdToVnode`, `toMarkdown`, the plugins) works unchanged.
|
|
7
|
+
*
|
|
8
|
+
* The template vocabulary reuses the suite's own query expressions —
|
|
9
|
+
* no new mini-language:
|
|
10
|
+
*
|
|
11
|
+
* - `{$.path}` inline in TEXT interpolates a query expression over the
|
|
12
|
+
* data (`$` is the data document; `$name` externals come from the
|
|
13
|
+
* frontmatter and the enclosing `each` bindings). Code spans, code
|
|
14
|
+
* blocks and raw HTML never interpolate.
|
|
15
|
+
* - a paragraph of exactly `{#if <expr>}` … `{/if}` keeps its section
|
|
16
|
+
* only when the expression is truthy (the query engine's boolean
|
|
17
|
+
* view: null/false/''/0/[] are false).
|
|
18
|
+
* - a paragraph of exactly `{#each <expr> as <name>}` … `{/each}`
|
|
19
|
+
* repeats its section once per item, binding each as the external
|
|
20
|
+
* `$<name>`. Sections nest.
|
|
21
|
+
*
|
|
22
|
+
* A directive must form its OWN paragraph — surround it with blank
|
|
23
|
+
* lines, or markdown's lazy continuation folds the next line into it
|
|
24
|
+
* and the directive reads as plain text.
|
|
25
|
+
*
|
|
26
|
+
* The expression COMPILER is injected (`compileJsonQuery` from
|
|
27
|
+
* `@jarenjs/json`), so this package's engine layer keeps its
|
|
28
|
+
* core+view-only dependency contract — the same seam philosophy the
|
|
29
|
+
* play surface uses for its renderers.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Build the mdx transformer around an injected expression compiler.
|
|
33
|
+
* @param {{ compileQuery: (expr: string) => (data: any, externals?: any) => any }} options
|
|
34
|
+
* `compileQuery` compiles one query expression (e.g. `compileJsonQuery`
|
|
35
|
+
* from `@jarenjs/json`); compiled expressions are cached per source text
|
|
36
|
+
* @returns {{ transform: (doc: any, data: any) => any }}
|
|
37
|
+
* `transform(doc, data)` — a parsed markdown doc + a data document in,
|
|
38
|
+
* a NEW doc (directives resolved, text interpolated) out; never throws
|
|
39
|
+
* on a bad expression — the error message renders in place, honestly
|
|
40
|
+
*/
|
|
41
|
+
export declare function createMdx(options: {
|
|
42
|
+
compileQuery: (expr: string) => (data: any, externals?: any) => any;
|
|
43
|
+
}): {
|
|
44
|
+
transform: (doc: any, data: any) => any;
|
|
45
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The Markdown parser: block structure + inline parsing → AST.
|
|
3
|
+
*
|
|
4
|
+
* One pass over the source builds the block tree (a container stack of
|
|
5
|
+
* blockquotes/lists/items plus one open leaf), buffering each leaf's
|
|
6
|
+
* raw text; inline parsing runs once per leaf when it closes. Plugin
|
|
7
|
+
* extension points — fence claims, block rules, inline rules — are
|
|
8
|
+
* prebuilt tables consulted by indexed lookup in the hot loop
|
|
9
|
+
* (docs/PLUGINS.md); with no plugins the tables are shared empty maps.
|
|
10
|
+
*
|
|
11
|
+
* The same machinery runs batch (`parseMarkdown`) and incrementally
|
|
12
|
+
* (`createIncrementalParser`): blocks land in the output only when
|
|
13
|
+
* closed, so the incremental parser can hand out completed top-level
|
|
14
|
+
* blocks while later chunks are still arriving (docs/LOADER.md §4).
|
|
15
|
+
*/
|
|
16
|
+
export type MdNode = import('./ast.js').MdNode;
|
|
17
|
+
export type MdDocument = import('./ast.js').MdDocument;
|
|
18
|
+
export type MdParseOptions = {
|
|
19
|
+
/**
|
|
20
|
+
* compiled-in plugins (docs/PLUGINS.md)
|
|
21
|
+
*/
|
|
22
|
+
plugins?: any[];
|
|
23
|
+
/**
|
|
24
|
+
* GFM tables/strikethrough/task lists (default true)
|
|
25
|
+
*/
|
|
26
|
+
gfm?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* detect frontmatter (default true)
|
|
29
|
+
*/
|
|
30
|
+
frontmatter?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* injectable TOML frontmatter parser
|
|
33
|
+
*/
|
|
34
|
+
toml?: (text: string) => any;
|
|
35
|
+
/**
|
|
36
|
+
* recorded in `meta.sourceUrl`
|
|
37
|
+
*/
|
|
38
|
+
sourceUrl?: string | null;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Merge a plugin array into the four dispatch tables (first plugin
|
|
42
|
+
* wins on every collision). Memoized by array identity so module-level
|
|
43
|
+
* plugin arrays compile exactly once.
|
|
44
|
+
* @param {any[] | undefined} plugins
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildPluginTables(plugins: any[] | undefined): any;
|
|
47
|
+
export type InlineCtx = {
|
|
48
|
+
defs: Map<string, {
|
|
49
|
+
url: string;
|
|
50
|
+
title: string | null;
|
|
51
|
+
}>;
|
|
52
|
+
footnotes?: Map<string, MdNode>;
|
|
53
|
+
inlines: Map<number, any[]>;
|
|
54
|
+
gfm: boolean;
|
|
55
|
+
ctx: any;
|
|
56
|
+
unresolved?: boolean;
|
|
57
|
+
deferred?: {
|
|
58
|
+
node: any;
|
|
59
|
+
raw: string;
|
|
60
|
+
}[];
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The inline parsing context threaded through one document.
|
|
64
|
+
* @typedef {{ defs: Map<string, {url: string, title: string|null}>,
|
|
65
|
+
* footnotes?: Map<string, MdNode>,
|
|
66
|
+
* inlines: Map<number, any[]>, gfm: boolean, ctx: any,
|
|
67
|
+
* unresolved?: boolean, deferred?: {node: any, raw: string}[] }} InlineCtx
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* Parse inline Markdown text into inline AST nodes.
|
|
71
|
+
* @param {string} src
|
|
72
|
+
* @param {InlineCtx} ictx
|
|
73
|
+
* @returns {MdNode[]}
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseInlines(src: string, ictx: InlineCtx): MdNode[];
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the buffered raw text of finished blocks into inline
|
|
78
|
+
* children (paragraphs, headings, table cells, task-list markers).
|
|
79
|
+
* Runs once per block, after which the `raw` buffers are gone.
|
|
80
|
+
* @param {MdNode[]} blocks
|
|
81
|
+
* @param {InlineCtx} ictx
|
|
82
|
+
*/
|
|
83
|
+
export declare function finishBlocks(blocks: MdNode[], ictx: InlineCtx): void;
|
|
84
|
+
/**
|
|
85
|
+
* Parse Markdown source into an MdDocument (frontmatter + AST).
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* parseMarkdown('# Hi').ast
|
|
89
|
+
* // [{ type: 'heading', depth: 1, children: [{ type: 'text', value: 'Hi' }] }]
|
|
90
|
+
*
|
|
91
|
+
* @param {string} source
|
|
92
|
+
* @param {MdParseOptions} [options]
|
|
93
|
+
* @returns {MdDocument}
|
|
94
|
+
*/
|
|
95
|
+
export declare function parseMarkdown(source: string, options?: MdParseOptions): MdDocument;
|
|
96
|
+
/**
|
|
97
|
+
* The incremental parsing core behind `streamMarkdown` (docs/LOADER.md
|
|
98
|
+
* §4): feed chunks, collect completed top-level blocks per feed, and
|
|
99
|
+
* flush the tail with `end()`.
|
|
100
|
+
*
|
|
101
|
+
* Frontmatter resolves as soon as its closing fence arrives; reference
|
|
102
|
+
* definitions apply to blocks completed after them (the documented
|
|
103
|
+
* streaming limitation).
|
|
104
|
+
*
|
|
105
|
+
* @param {MdParseOptions} [options]
|
|
106
|
+
* @returns {{
|
|
107
|
+
* feed: (chunk: string) => MdNode[],
|
|
108
|
+
* end: () => MdDocument,
|
|
109
|
+
* frontmatter: any,
|
|
110
|
+
* }}
|
|
111
|
+
*/
|
|
112
|
+
export declare function createIncrementalParser(options?: MdParseOptions): {
|
|
113
|
+
feed: (chunk: string) => MdNode[];
|
|
114
|
+
end: () => MdDocument;
|
|
115
|
+
frontmatter: any;
|
|
116
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Syntax highlighting for fenced code blocks (PLUGINS.md §6.2).
|
|
3
|
+
*
|
|
4
|
+
* Built-in mode is a zero-dependency single-pass tokenizer driven by
|
|
5
|
+
* compact grammar tables — a keyword set, comment/string delimiters
|
|
6
|
+
* and punctuation classes — compiled to closures once at module load.
|
|
7
|
+
* No regex runs in the token loop. Adapter mode puts shiki/prism/
|
|
8
|
+
* highlight.js behind the same `{ kind, value }` token contract.
|
|
9
|
+
*
|
|
10
|
+
* Token kinds: kw str num com pun id op lit — rendered as
|
|
11
|
+
* `span.tok-{kind}` (plain `id` runs render as bare text).
|
|
12
|
+
*/
|
|
13
|
+
export type Token = {
|
|
14
|
+
kind: 'kw' | 'str' | 'num' | 'com' | 'pun' | 'id' | 'op' | 'lit';
|
|
15
|
+
value: string;
|
|
16
|
+
};
|
|
17
|
+
export type MdGrammar = {
|
|
18
|
+
keywords?: string[];
|
|
19
|
+
literals?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* comment-to-end-of-line prefixes
|
|
22
|
+
*/
|
|
23
|
+
lineComments?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* open/close pair
|
|
26
|
+
*/
|
|
27
|
+
blockComment?: [string, string];
|
|
28
|
+
/**
|
|
29
|
+
* string delimiter characters
|
|
30
|
+
*/
|
|
31
|
+
strings?: string;
|
|
32
|
+
/**
|
|
33
|
+
* recognize number literals
|
|
34
|
+
*/
|
|
35
|
+
numbers?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* extra identifier characters (e.g. `-` for CSS)
|
|
38
|
+
*/
|
|
39
|
+
extraId?: string;
|
|
40
|
+
};
|
|
41
|
+
/** The canonical names of the shipped grammars (aliases resolve too). */
|
|
42
|
+
export declare const GRAMMAR_NAMES: readonly string[];
|
|
43
|
+
/**
|
|
44
|
+
* Tokenize `code` with the built-in grammar for `lang`. Returns null
|
|
45
|
+
* when no grammar covers the language.
|
|
46
|
+
* @param {string} code
|
|
47
|
+
* @param {string|null} lang
|
|
48
|
+
* @returns {Token[] | null}
|
|
49
|
+
*/
|
|
50
|
+
export declare function tokenizeCode(code: string, lang: string | null): Token[] | null;
|
|
51
|
+
/**
|
|
52
|
+
* The syntax-highlighting plugin: takes over rendering of `code`
|
|
53
|
+
* nodes. `adapter(code, lang)` may return tokens (shiki/prism/hljs
|
|
54
|
+
* behind the shared contract) or null to fall back to the built-in
|
|
55
|
+
* grammars, then to plain text.
|
|
56
|
+
*
|
|
57
|
+
* @param {{ grammars?: Record<string, MdGrammar>,
|
|
58
|
+
* adapter?: (code: string, lang: string|null) => (Token[] | null) }} [config]
|
|
59
|
+
* @returns {import('./index.js').MdPlugin}
|
|
60
|
+
*/
|
|
61
|
+
export declare function highlightPlugin(config?: {
|
|
62
|
+
grammars?: Record<string, MdGrammar>;
|
|
63
|
+
adapter?: (code: string, lang: string | null) => (Token[] | null);
|
|
64
|
+
}): import('./index.js').MdPlugin;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Plugin registry helpers and the reference plugins.
|
|
3
|
+
*
|
|
4
|
+
* A plugin is data, validated and frozen at definition time; the
|
|
5
|
+
* parser and vnode emitter bake plugin arrays into dispatch tables at
|
|
6
|
+
* compile time (the normative contract lives in docs/PLUGINS.md).
|
|
7
|
+
*/
|
|
8
|
+
export type MdPlugin = {
|
|
9
|
+
/**
|
|
10
|
+
* unique kebab-case identifier
|
|
11
|
+
*/
|
|
12
|
+
name: string;
|
|
13
|
+
/**
|
|
14
|
+
* fenced-code claims by info-string first word
|
|
15
|
+
*/
|
|
16
|
+
fences?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* block rule descriptors ({ chars, start, continue, close })
|
|
19
|
+
*/
|
|
20
|
+
blocks?: any[];
|
|
21
|
+
/**
|
|
22
|
+
* inline rule descriptors ({ char, scan })
|
|
23
|
+
*/
|
|
24
|
+
inlines?: any[];
|
|
25
|
+
/**
|
|
26
|
+
* the AST type this plugin emits/renders
|
|
27
|
+
*/
|
|
28
|
+
node?: string;
|
|
29
|
+
/**
|
|
30
|
+
* pure vnode renderer
|
|
31
|
+
*/
|
|
32
|
+
render?: (node: any, h: any, ctx: any) => any;
|
|
33
|
+
/**
|
|
34
|
+
* pure HTML-string
|
|
35
|
+
* renderer, for `toHtml`. Independent of `render`: a plugin may serve
|
|
36
|
+
* one emitter, the other, or both (docs/PLUGINS.md §5.1)
|
|
37
|
+
*/
|
|
38
|
+
toHtml?: (node: any, ctx: any) => string;
|
|
39
|
+
/**
|
|
40
|
+
* browser-only upgrade
|
|
41
|
+
*/
|
|
42
|
+
hydrate?: (el: any, node: any, ctx: any) => any;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {object} MdPlugin
|
|
46
|
+
* @property {string} name unique kebab-case identifier
|
|
47
|
+
* @property {string[]} [fences] fenced-code claims by info-string first word
|
|
48
|
+
* @property {any[]} [blocks] block rule descriptors ({ chars, start, continue, close })
|
|
49
|
+
* @property {any[]} [inlines] inline rule descriptors ({ char, scan })
|
|
50
|
+
* @property {string} [node] the AST type this plugin emits/renders
|
|
51
|
+
* @property {(node: any, h: any, ctx: any) => any} [render] pure vnode renderer
|
|
52
|
+
* @property {(node: any, ctx: any) => string} [toHtml] pure HTML-string
|
|
53
|
+
* renderer, for `toHtml`. Independent of `render`: a plugin may serve
|
|
54
|
+
* one emitter, the other, or both (docs/PLUGINS.md §5.1)
|
|
55
|
+
* @property {(el: any, node: any, ctx: any) => any} [hydrate] browser-only upgrade
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Validate and freeze a plugin spec (docs/PLUGINS.md §1).
|
|
59
|
+
* @param {MdPlugin} spec
|
|
60
|
+
* @returns {MdPlugin}
|
|
61
|
+
*/
|
|
62
|
+
export declare function definePlugin(spec: MdPlugin): MdPlugin;
|
|
63
|
+
export { highlightPlugin, tokenizeCode, GRAMMAR_NAMES } from './highlight.js';
|
|
64
|
+
export { mermaidPlugin } from './mermaid.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Mermaid diagram support (PLUGINS.md §6.1).
|
|
3
|
+
*
|
|
4
|
+
* The **native** plugin from `@jarenjs/mermaid`: a self-frozen
|
|
5
|
+
* `MdPlugin`-shaped object whose `render` parses the fence source and
|
|
6
|
+
* emits pure-vnode SVG synchronously (SSR-safe, no injected `mermaid`
|
|
7
|
+
* instance, no `innerHTML`). The dependency arrow points md → mermaid
|
|
8
|
+
*, and `@jarenjs/mermaid/plugin` does not import
|
|
9
|
+
* `definePlugin`, so there is no cycle. Consumers who never use it
|
|
10
|
+
* tree-shake it away (`sideEffects:false`).
|
|
11
|
+
*/
|
|
12
|
+
export { mermaidPlugin, refreshMermaidFence } from '@jarenjs/mermaid/plugin';
|