comark 0.3.2 → 0.4.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/dist/internal/parse/auto-close/index.js +44 -18
- package/dist/internal/parse/auto-unwrap.js +5 -1
- package/dist/internal/parse/html/html_block_rule.js +9 -15
- package/dist/internal/parse/html/index.d.ts +1 -0
- package/dist/internal/parse/html/index.js +1 -1
- package/dist/internal/parse/token-processor.js +70 -32
- package/dist/internal/stringify/attributes.d.ts +7 -0
- package/dist/internal/stringify/attributes.js +52 -0
- package/dist/internal/stringify/handlers/blockquote.js +17 -0
- package/dist/internal/stringify/handlers/heading.js +6 -1
- package/dist/internal/stringify/handlers/html.js +8 -2
- package/dist/internal/stringify/handlers/li.js +4 -1
- package/dist/internal/stringify/handlers/mdc.js +1 -1
- package/dist/internal/stringify/handlers/ol.js +11 -1
- package/dist/internal/stringify/handlers/p.js +4 -0
- package/dist/internal/stringify/handlers/pre.js +11 -2
- package/dist/internal/stringify/handlers/table.js +7 -0
- package/dist/internal/stringify/handlers/template.js +4 -1
- package/dist/internal/stringify/handlers/ul.js +11 -1
- package/dist/parse.d.ts +4 -4
- package/dist/parse.js +6 -2
- package/dist/plugins/alert.d.ts +1 -1
- package/dist/plugins/binding.d.ts +1 -1
- package/dist/plugins/breaks.d.ts +1 -1
- package/dist/plugins/emoji.d.ts +1 -1
- package/dist/plugins/footnotes.d.ts +1 -1
- package/dist/plugins/headings.d.ts +19 -8
- package/dist/plugins/headings.js +25 -15
- package/dist/plugins/highlight.d.ts +1 -1
- package/dist/plugins/highlight.js +4 -1
- package/dist/plugins/json-render.d.ts +1 -1
- package/dist/plugins/math.d.ts +1 -1
- package/dist/plugins/mermaid.d.ts +1 -1
- package/dist/plugins/punctuation.d.ts +1 -1
- package/dist/plugins/security.d.ts +1 -1
- package/dist/plugins/summary.d.ts +4 -1
- package/dist/plugins/syntax.d.ts +1 -1
- package/dist/plugins/syntax.js +70 -34
- package/dist/plugins/task-list.d.ts +1 -1
- package/dist/plugins/toc.d.ts +3 -1
- package/dist/types.d.ts +56 -12
- package/dist/utils/helpers.d.ts +16 -4
- package/dist/utils/helpers.js +15 -3
- package/package.json +3 -2
- package/skills/comark/references/rendering-svelte.md +51 -7
- package/dist/devtools/index.d.ts +0 -1
- package/dist/devtools/index.js +0 -1
- package/dist/devtools/register.d.ts +0 -1
- package/dist/devtools/register.js +0 -1
- package/dist/devtools/registry.d.ts +0 -1
- package/dist/devtools/registry.js +0 -1
- package/dist/devtools/vite.d.ts +0 -1
- package/dist/devtools/vite.js +0 -1
- package/dist/internal/stringify/indent.d.ts +0 -1
- package/dist/internal/stringify/indent.js +0 -1
- package/dist/vite.d.ts +0 -1
- package/dist/vite.js +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { comarkAttributes } from "../attributes.js";
|
|
1
2
|
// slot template
|
|
2
3
|
export async function template(node, state, parent) {
|
|
3
4
|
const [_, attrs] = node;
|
|
@@ -10,5 +11,7 @@ export async function template(node, state, parent) {
|
|
|
10
11
|
return content + state.context.blockSeparator;
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
const { name: _name, $: _$, ...rest } = attrs;
|
|
15
|
+
const extraAttrs = comarkAttributes(rest);
|
|
16
|
+
return `#${attrs.name}${extraAttrs}\n${content}` + state.context.blockSeparator;
|
|
14
17
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { indent } from "../../../utils/index.js";
|
|
2
|
+
import { comarkAttributes, userBlockAttrs } from "../attributes.js";
|
|
2
3
|
export async function ul(node, state) {
|
|
3
4
|
const children = node.slice(2);
|
|
4
5
|
const revert = state.applyContext({ list: true, order: false, listIndent: 2 });
|
|
@@ -7,12 +8,21 @@ export async function ul(node, state) {
|
|
|
7
8
|
result += await state.one(child, state);
|
|
8
9
|
}
|
|
9
10
|
result = result.trim();
|
|
11
|
+
state.applyContext(revert);
|
|
12
|
+
// ul with user attrs round-trips via `::ul{attrs}\n- …\n::` — the native
|
|
13
|
+
// markdown list syntax has no slot for list-level attrs.
|
|
14
|
+
const attrs = comarkAttributes(userBlockAttrs('ul', node[1]));
|
|
15
|
+
if (attrs) {
|
|
16
|
+
if (revert.list) {
|
|
17
|
+
return '\n' + indent(`::ul${attrs}\n${result}\n::`, { width: revert.listIndent || 2 });
|
|
18
|
+
}
|
|
19
|
+
return `::ul${attrs}\n${result}\n::` + state.context.blockSeparator;
|
|
20
|
+
}
|
|
10
21
|
if (revert.list) {
|
|
11
22
|
result = '\n' + indent(result, { width: revert.listIndent || 2 });
|
|
12
23
|
}
|
|
13
24
|
else {
|
|
14
25
|
result = result + state.context.blockSeparator;
|
|
15
26
|
}
|
|
16
|
-
state.applyContext(revert);
|
|
17
27
|
return result;
|
|
18
28
|
}
|
package/dist/parse.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComarkParseFn, ParseOptions, ComarkTree } from './types.ts';
|
|
1
|
+
import type { ComarkParseFn, ComarkPlugin, MergePluginFrontmatter, MergePluginMeta, ParseOptions, ResolvedFrontmatter, ResolvedMeta, ComarkTree } from './types.ts';
|
|
2
2
|
export { parseFrontmatter } from './internal/frontmatter.ts';
|
|
3
3
|
export { defineComarkPlugin } from './utils/helpers.ts';
|
|
4
4
|
/**
|
|
@@ -30,7 +30,7 @@ export { defineComarkPlugin } from './utils/helpers.ts';
|
|
|
30
30
|
* const parseNoHtml = createParse({ html: false })
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
|
-
export declare function createParse(options?: ParseOptions): ComarkParseFn
|
|
33
|
+
export declare function createParse<const TPlugins extends readonly ComarkPlugin<any, any>[] = []>(options?: ParseOptions<TPlugins>): ComarkParseFn<ResolvedMeta<MergePluginMeta<TPlugins>>, ResolvedFrontmatter<MergePluginFrontmatter<TPlugins>>>;
|
|
34
34
|
/**
|
|
35
35
|
* Parse Comark content from a string
|
|
36
36
|
*
|
|
@@ -64,7 +64,7 @@ export declare function createParse(options?: ParseOptions): ComarkParseFn;
|
|
|
64
64
|
* const tree2 = await parse(content, { autoUnwrap: false })
|
|
65
65
|
* ```
|
|
66
66
|
*/
|
|
67
|
-
export declare function parse(markdown: string, options?: ParseOptions): Promise<ComarkTree
|
|
67
|
+
export declare function parse<const TPlugins extends readonly ComarkPlugin<any, any>[] = []>(markdown: string, options?: ParseOptions<TPlugins>): Promise<ComarkTree<ResolvedMeta<MergePluginMeta<TPlugins>>, ResolvedFrontmatter<MergePluginFrontmatter<TPlugins>>>>;
|
|
68
68
|
/**
|
|
69
69
|
* Creates a serialized parser function for Comark content.
|
|
70
70
|
* This is useful for parsing large files in a streaming manner.
|
|
@@ -80,4 +80,4 @@ export declare function parse(markdown: string, options?: ParseOptions): Promise
|
|
|
80
80
|
* const tree = await parse(content)
|
|
81
81
|
* console.log(tree.nodes)
|
|
82
82
|
*/
|
|
83
|
-
export declare function createSerializedParse(options?: ParseOptions): ComarkParseFn
|
|
83
|
+
export declare function createSerializedParse<const TPlugins extends readonly ComarkPlugin<any, any>[] = []>(options?: ParseOptions<TPlugins>): ComarkParseFn<ResolvedMeta<MergePluginMeta<TPlugins>>, ResolvedFrontmatter<MergePluginFrontmatter<TPlugins>>>;
|
package/dist/parse.js
CHANGED
|
@@ -44,7 +44,10 @@ export { defineComarkPlugin } from "./utils/helpers.js";
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
export function createParse(options = {}) {
|
|
47
|
-
const { autoUnwrap = true, autoClose = true
|
|
47
|
+
const { autoUnwrap = true, autoClose = true } = options;
|
|
48
|
+
// Make a mutable working copy so the inferred (possibly readonly) user tuple
|
|
49
|
+
// isn't mutated by the unshift calls below.
|
|
50
|
+
const plugins = options.plugins ? [...options.plugins] : [];
|
|
48
51
|
plugins.unshift(syntax());
|
|
49
52
|
plugins.unshift(taskList());
|
|
50
53
|
plugins.unshift(alert());
|
|
@@ -65,7 +68,7 @@ export function createParse(options = {}) {
|
|
|
65
68
|
}
|
|
66
69
|
let lastOutput = null;
|
|
67
70
|
let lastInput = null;
|
|
68
|
-
|
|
71
|
+
const parseFn = async (markdown, opts = {}) => {
|
|
69
72
|
const state = {
|
|
70
73
|
options,
|
|
71
74
|
tokens: [],
|
|
@@ -143,6 +146,7 @@ export function createParse(options = {}) {
|
|
|
143
146
|
}
|
|
144
147
|
return state.tree;
|
|
145
148
|
};
|
|
149
|
+
return parseFn;
|
|
146
150
|
}
|
|
147
151
|
/**
|
|
148
152
|
* Parse Comark content from a string
|
package/dist/plugins/alert.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("comark").ComarkPluginFactory<unknown>;
|
|
1
|
+
declare const _default: import("comark").ComarkPluginFactory<unknown, {}, {}>;
|
|
2
2
|
export default _default;
|
|
@@ -7,7 +7,7 @@ export interface MdcInlineBindingOptions {
|
|
|
7
7
|
*/
|
|
8
8
|
tag?: string;
|
|
9
9
|
}
|
|
10
|
-
declare const _default: import("../types").ComarkPluginFactory<MdcInlineBindingOptions>;
|
|
10
|
+
declare const _default: import("../types").ComarkPluginFactory<MdcInlineBindingOptions, {}, {}>;
|
|
11
11
|
export default _default;
|
|
12
12
|
/**
|
|
13
13
|
* Markdown-format handler that renders a `binding` node back to the
|
package/dist/plugins/breaks.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("../types.ts").ComarkPluginFactory<unknown>;
|
|
1
|
+
declare const _default: import("../types.ts").ComarkPluginFactory<unknown, {}, {}>;
|
|
2
2
|
export default _default;
|
package/dist/plugins/emoji.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { MarkdownItPlugin } from 'comark';
|
|
2
2
|
export declare const markdownItEmoji: MarkdownItPlugin;
|
|
3
|
-
declare const _default: import("comark").ComarkPluginFactory<unknown>;
|
|
3
|
+
declare const _default: import("comark").ComarkPluginFactory<unknown, {}, {}>;
|
|
4
4
|
export default _default;
|
|
@@ -35,7 +35,7 @@ export interface FootnotesConfig {
|
|
|
35
35
|
* })
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
|
-
declare const _default: import("comark").ComarkPluginFactory<FootnotesConfig>;
|
|
38
|
+
declare const _default: import("comark").ComarkPluginFactory<FootnotesConfig, {}, {}>;
|
|
39
39
|
export default _default;
|
|
40
40
|
/**
|
|
41
41
|
* Conditional stringify handler for footnotes.
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
export interface HeadingsOptions {
|
|
2
2
|
/**
|
|
3
3
|
* Tag to extract as title and set to `tree.meta.title`.
|
|
4
|
+
* Set to `false` to disable title extraction.
|
|
4
5
|
* @default 'h1'
|
|
5
6
|
*/
|
|
6
|
-
titleTag?: string;
|
|
7
|
+
titleTag?: string | false;
|
|
7
8
|
/**
|
|
8
9
|
* Tag to extract as description and set to `tree.meta.description`.
|
|
9
10
|
* Useful alternatives: `'blockquote'`
|
|
11
|
+
* Set to `false` to disable description extraction.
|
|
10
12
|
* @default 'p'
|
|
11
13
|
*/
|
|
12
|
-
descriptionTag?: string;
|
|
14
|
+
descriptionTag?: string | false;
|
|
13
15
|
/**
|
|
14
16
|
* Whether to remove the extracted nodes from the tree.
|
|
15
17
|
* @default false
|
|
@@ -29,20 +31,29 @@ export interface HeadingsOptions {
|
|
|
29
31
|
* content is written to `tree.meta.description`. When no title was found,
|
|
30
32
|
* this check starts from the very first content node.
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
+
* By default the extracted nodes are kept in the tree. Set `remove: true`
|
|
35
|
+
* to strip them so they are not rendered twice.
|
|
34
36
|
*
|
|
35
37
|
* @example
|
|
36
38
|
* ```ts
|
|
37
|
-
* // Default — h1 as title, first paragraph as description
|
|
39
|
+
* // Default — h1 as title, first paragraph as description, nodes kept in tree
|
|
38
40
|
* headings()
|
|
39
41
|
*
|
|
40
42
|
* // Use a blockquote as the description instead of a paragraph
|
|
41
43
|
* headings({ descriptionTag: 'blockquote' })
|
|
42
44
|
*
|
|
43
|
-
* // Extract metadata
|
|
44
|
-
* headings({ remove:
|
|
45
|
+
* // Extract metadata and remove the matched nodes from the tree
|
|
46
|
+
* headings({ remove: true })
|
|
47
|
+
*
|
|
48
|
+
* // Disable title extraction, only extract description
|
|
49
|
+
* headings({ titleTag: false })
|
|
50
|
+
*
|
|
51
|
+
* // Disable description extraction, only extract title
|
|
52
|
+
* headings({ descriptionTag: false })
|
|
45
53
|
* ```
|
|
46
54
|
*/
|
|
47
|
-
declare const _default: import("comark").ComarkPluginFactory<HeadingsOptions
|
|
55
|
+
declare const _default: import("comark").ComarkPluginFactory<HeadingsOptions, {
|
|
56
|
+
title?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
}, {}>;
|
|
48
59
|
export default _default;
|
package/dist/plugins/headings.js
CHANGED
|
@@ -35,19 +35,25 @@ function flattenNodeText(node) {
|
|
|
35
35
|
* content is written to `tree.meta.description`. When no title was found,
|
|
36
36
|
* this check starts from the very first content node.
|
|
37
37
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* By default the extracted nodes are kept in the tree. Set `remove: true`
|
|
39
|
+
* to strip them so they are not rendered twice.
|
|
40
40
|
*
|
|
41
41
|
* @example
|
|
42
42
|
* ```ts
|
|
43
|
-
* // Default — h1 as title, first paragraph as description
|
|
43
|
+
* // Default — h1 as title, first paragraph as description, nodes kept in tree
|
|
44
44
|
* headings()
|
|
45
45
|
*
|
|
46
46
|
* // Use a blockquote as the description instead of a paragraph
|
|
47
47
|
* headings({ descriptionTag: 'blockquote' })
|
|
48
48
|
*
|
|
49
|
-
* // Extract metadata
|
|
50
|
-
* headings({ remove:
|
|
49
|
+
* // Extract metadata and remove the matched nodes from the tree
|
|
50
|
+
* headings({ remove: true })
|
|
51
|
+
*
|
|
52
|
+
* // Disable title extraction, only extract description
|
|
53
|
+
* headings({ titleTag: false })
|
|
54
|
+
*
|
|
55
|
+
* // Disable description extraction, only extract title
|
|
56
|
+
* headings({ descriptionTag: false })
|
|
51
57
|
* ```
|
|
52
58
|
*/
|
|
53
59
|
export default defineComarkPlugin((options = {}) => {
|
|
@@ -60,17 +66,21 @@ export default defineComarkPlugin((options = {}) => {
|
|
|
60
66
|
const contentNodes = nodes.filter((node) => Array.isArray(node) && getTag(node) !== 'hr');
|
|
61
67
|
let titleNodeIndex = -1;
|
|
62
68
|
let descriptionNodeIndex = -1;
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
let nextContentIndex = 0;
|
|
70
|
+
if (titleTag !== false) {
|
|
71
|
+
const first = contentNodes[0];
|
|
72
|
+
if (first && getTag(first) === titleTag) {
|
|
73
|
+
titleNodeIndex = nodes.indexOf(first);
|
|
74
|
+
state.tree.meta.title = flattenNodeText(first);
|
|
75
|
+
nextContentIndex = 1;
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
if (descriptionTag !== false) {
|
|
79
|
+
const candidate = contentNodes[nextContentIndex];
|
|
80
|
+
if (candidate && getTag(candidate) === descriptionTag) {
|
|
81
|
+
descriptionNodeIndex = nodes.indexOf(candidate);
|
|
82
|
+
state.tree.meta.description = flattenNodeText(candidate);
|
|
83
|
+
}
|
|
74
84
|
}
|
|
75
85
|
if (remove) {
|
|
76
86
|
// Remove in reverse order to preserve indices
|
|
@@ -56,5 +56,5 @@ export declare function highlightCodeBlocks(tree: ComarkTree, options?: Highligh
|
|
|
56
56
|
* Useful for testing or when you want to reconfigure
|
|
57
57
|
*/
|
|
58
58
|
export declare function resetHighlighter(): void;
|
|
59
|
-
declare const _default: import("comark").ComarkPluginFactory<HighlightOptions>;
|
|
59
|
+
declare const _default: import("comark").ComarkPluginFactory<HighlightOptions, {}, {}>;
|
|
60
60
|
export default _default;
|
|
@@ -278,9 +278,12 @@ export async function highlightCodeBlocks(tree, options = {}) {
|
|
|
278
278
|
line += 1;
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
|
+
// Merge highlighter class with any user-supplied class (e.g. from
|
|
282
|
+
// `::pre{.user-class}`) so the wrapper's class isn't lost.
|
|
283
|
+
const userClass = typeof preAttrs.class === 'string' ? preAttrs.class.trim() : '';
|
|
281
284
|
const newPreAttrs = {
|
|
282
285
|
...preAttrs,
|
|
283
|
-
class: classStr,
|
|
286
|
+
class: userClass ? `${classStr} . ${userClass}` : classStr,
|
|
284
287
|
tabindex: '0',
|
|
285
288
|
};
|
|
286
289
|
if (options.preStyles) {
|
|
@@ -49,5 +49,5 @@ interface JsonRenderConfig {
|
|
|
49
49
|
* </template>
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
|
-
declare const _default: import("../types").ComarkPluginFactory<JsonRenderConfig>;
|
|
52
|
+
declare const _default: import("../types").ComarkPluginFactory<JsonRenderConfig, {}, {}>;
|
|
53
53
|
export default _default;
|
package/dist/plugins/math.d.ts
CHANGED
|
@@ -55,5 +55,5 @@ export declare function validateMath(code: string): boolean;
|
|
|
55
55
|
* })
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
|
-
declare const _default: import("comark").ComarkPluginFactory<MathConfig>;
|
|
58
|
+
declare const _default: import("comark").ComarkPluginFactory<MathConfig, {}, {}>;
|
|
59
59
|
export default _default;
|
|
@@ -34,5 +34,5 @@ export declare function searchProps(content: string, index?: number): {
|
|
|
34
34
|
* })
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
-
declare const _default: import("comark").ComarkPluginFactory<MermaidConfig>;
|
|
37
|
+
declare const _default: import("comark").ComarkPluginFactory<MermaidConfig, {}, {}>;
|
|
38
38
|
export default _default;
|
|
@@ -63,5 +63,5 @@ export interface PunctuationOptions {
|
|
|
63
63
|
* })
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
|
-
declare const _default: import("comark").ComarkPluginFactory<PunctuationOptions>;
|
|
66
|
+
declare const _default: import("comark").ComarkPluginFactory<PunctuationOptions, {}, {}>;
|
|
67
67
|
export default _default;
|
|
@@ -6,5 +6,5 @@ interface SecurityOptions extends PropsValidationOptions {
|
|
|
6
6
|
*/
|
|
7
7
|
blockedTags?: string[];
|
|
8
8
|
}
|
|
9
|
-
declare const _default: import("comark").ComarkPluginFactory<SecurityOptions>;
|
|
9
|
+
declare const _default: import("comark").ComarkPluginFactory<SecurityOptions, {}, {}>;
|
|
10
10
|
export default _default;
|
package/dist/plugins/syntax.d.ts
CHANGED
|
@@ -44,6 +44,6 @@ export interface SyntaxOptions {
|
|
|
44
44
|
*/
|
|
45
45
|
bindingTag?: string;
|
|
46
46
|
}
|
|
47
|
-
declare const _default: import("../types.ts").ComarkPluginFactory<SyntaxOptions>;
|
|
47
|
+
declare const _default: import("../types.ts").ComarkPluginFactory<SyntaxOptions, {}, {}>;
|
|
48
48
|
export default _default;
|
|
49
49
|
export declare const markdownItComark: MarkdownItPluginWithOptions<SyntaxOptions>;
|
package/dist/plugins/syntax.js
CHANGED
|
@@ -201,6 +201,12 @@ const markdownItComarkBlock = (md) => {
|
|
|
201
201
|
const blockAttributesClosingFence = blockYamlLines[line] || '';
|
|
202
202
|
if (!blockAttributesClosingFence)
|
|
203
203
|
return false;
|
|
204
|
+
// The `---` fence is only valid on the line immediately after the component opener. Any other `---` is a thematic break.
|
|
205
|
+
if (line === '---') {
|
|
206
|
+
const parentOpenLine = state.env.comarkBlockTokens[0].map?.[0];
|
|
207
|
+
if (parentOpenLine === undefined || startLine !== parentOpenLine + 1)
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
204
210
|
let lineEnd = startLine + 1;
|
|
205
211
|
let found = false;
|
|
206
212
|
while (lineEnd < endLine) {
|
|
@@ -237,8 +243,38 @@ const markdownItComarkBlock = (md) => {
|
|
|
237
243
|
const line = state.src.slice(start, state.eMarks[startLine]);
|
|
238
244
|
const { name, props } = parseBlockParams(line.slice(1));
|
|
239
245
|
let lineEnd = startLine + 1;
|
|
246
|
+
let inCodeFence = false;
|
|
247
|
+
let codeFenceChar = '';
|
|
248
|
+
let codeFenceCount = 0;
|
|
240
249
|
while (lineEnd < endLine) {
|
|
241
250
|
const inner = state.src.slice(state.bMarks[lineEnd] + state.tShift[startLine], state.eMarks[lineEnd]);
|
|
251
|
+
if (inCodeFence) {
|
|
252
|
+
// Look for matching closing fence (same char, >= opening count, nothing but spaces after)
|
|
253
|
+
if (inner[0] === codeFenceChar) {
|
|
254
|
+
let fencePos = 1;
|
|
255
|
+
while (fencePos < inner.length && inner[fencePos] === codeFenceChar)
|
|
256
|
+
fencePos++;
|
|
257
|
+
if (fencePos >= codeFenceCount && inner.slice(fencePos).trim() === '') {
|
|
258
|
+
inCodeFence = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
lineEnd += 1;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
// Detect opening code fence (``` or ~~~, length >= 3)
|
|
265
|
+
if (inner[0] === '`' || inner[0] === '~') {
|
|
266
|
+
const ch = inner[0];
|
|
267
|
+
let fencePos = 1;
|
|
268
|
+
while (fencePos < inner.length && inner[fencePos] === ch)
|
|
269
|
+
fencePos++;
|
|
270
|
+
if (fencePos >= 3) {
|
|
271
|
+
inCodeFence = true;
|
|
272
|
+
codeFenceChar = ch;
|
|
273
|
+
codeFenceCount = fencePos;
|
|
274
|
+
lineEnd += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
242
278
|
if (/^#\w+/.test(inner) || inner.startsWith('::'))
|
|
243
279
|
break;
|
|
244
280
|
lineEnd += 1;
|
|
@@ -397,52 +433,52 @@ const markdownItInlineProps = (md) => {
|
|
|
397
433
|
const _parse = md.parse;
|
|
398
434
|
md.parse = function (src, env) {
|
|
399
435
|
const tokens = _parse.call(this, src, env);
|
|
400
|
-
// When the
|
|
401
|
-
//
|
|
436
|
+
// When the trailing inline child is a props token directly after a text
|
|
437
|
+
// node, lift the props onto the surrounding heading/paragraph/list_item.
|
|
438
|
+
// (If the props follow a closing tag, they belong to that inline tag, not
|
|
439
|
+
// the parent — leave them alone.)
|
|
402
440
|
tokens.forEach((token, index) => {
|
|
403
441
|
const prev = tokens[index - 1];
|
|
404
442
|
const next = tokens[index + 1];
|
|
405
443
|
if (!prev || !['heading_open', 'paragraph_open', 'list_item_open'].includes(prev.type) || prev.hidden)
|
|
406
444
|
return;
|
|
407
|
-
// list
|
|
445
|
+
// Tight-list paragraph: the inline lives one slot ahead
|
|
408
446
|
if (token.hidden && next?.type === 'inline')
|
|
409
447
|
token = next;
|
|
410
|
-
if (token.type
|
|
411
|
-
token.children?.length === 2 &&
|
|
412
|
-
token.children[0].type === 'text' &&
|
|
413
|
-
token.children[1].type === 'mdc_inline_props') {
|
|
414
|
-
const props = token.children[1].attrs;
|
|
415
|
-
token.children.splice(1, 1);
|
|
416
|
-
props?.forEach(([key, value]) => {
|
|
417
|
-
if (key === 'class')
|
|
418
|
-
prev.attrJoin('class', value);
|
|
419
|
-
else
|
|
420
|
-
prev.attrSet(key, value);
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
// Deduplicate `ul` wrapping when `::ul` is used and contains exactly one bullet list
|
|
425
|
-
tokens.forEach((tokenOpen, index) => {
|
|
426
|
-
if (tokenOpen.type !== 'bullet_list_open')
|
|
448
|
+
if (token.type !== 'inline' || !token.children?.length)
|
|
427
449
|
return;
|
|
428
|
-
const
|
|
429
|
-
if (
|
|
450
|
+
const last = token.children[token.children.length - 1];
|
|
451
|
+
if (last.type !== 'mdc_inline_props')
|
|
430
452
|
return;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
453
|
+
// Find the previous non-empty child. Markdown-it's emphasis tokenizer
|
|
454
|
+
// can leave an empty text token between the closing delimiter and the
|
|
455
|
+
// props — skipping it lets us distinguish "props on the parent" from
|
|
456
|
+
// "props on the preceding inline tag".
|
|
457
|
+
let beforeIdx = token.children.length - 2;
|
|
458
|
+
while (beforeIdx >= 0) {
|
|
459
|
+
const child = token.children[beforeIdx];
|
|
460
|
+
if (child.type === 'text' && !child.content) {
|
|
461
|
+
beforeIdx--;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
break;
|
|
437
465
|
}
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
466
|
+
const beforeProps = beforeIdx >= 0 ? token.children[beforeIdx] : undefined;
|
|
467
|
+
if (!beforeProps || beforeProps.type !== 'text')
|
|
440
468
|
return;
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
tokenClose.hidden = true;
|
|
469
|
+
// Strip the trailing space the text picked up before the `{...}` token.
|
|
470
|
+
if (typeof beforeProps.content === 'string') {
|
|
471
|
+
beforeProps.content = beforeProps.content.replace(/[ \t]+$/, '');
|
|
445
472
|
}
|
|
473
|
+
const props = last.attrs;
|
|
474
|
+
// Drop the props token (last) plus any empty text tokens it left behind.
|
|
475
|
+
token.children.length = beforeProps.content ? beforeIdx + 1 : beforeIdx;
|
|
476
|
+
props?.forEach(([key, value]) => {
|
|
477
|
+
if (key === 'class')
|
|
478
|
+
prev.attrJoin('class', value);
|
|
479
|
+
else
|
|
480
|
+
prev.attrSet(key, value);
|
|
481
|
+
});
|
|
446
482
|
});
|
|
447
483
|
return tokens;
|
|
448
484
|
};
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* This plugin runs before inline parsing to prevent Comark from interpreting
|
|
5
5
|
* task list markers [X] and [ ] as Comark inline span syntax.
|
|
6
6
|
*/
|
|
7
|
-
declare const _default: import("../types.ts").ComarkPluginFactory<unknown>;
|
|
7
|
+
declare const _default: import("../types.ts").ComarkPluginFactory<unknown, {}, {}>;
|
|
8
8
|
export default _default;
|
package/dist/plugins/toc.d.ts
CHANGED
|
@@ -12,5 +12,7 @@ export interface Toc {
|
|
|
12
12
|
links: TocLink[];
|
|
13
13
|
}
|
|
14
14
|
export declare function generateFlatToc(body: ComarkTree, options: Toc): Toc;
|
|
15
|
-
declare const _default: import("comark").ComarkPluginFactory<Partial<Toc
|
|
15
|
+
declare const _default: import("comark").ComarkPluginFactory<Partial<Toc>, {
|
|
16
|
+
toc: Toc;
|
|
17
|
+
}, {}>;
|
|
16
18
|
export default _default;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { DumpOptions } from 'js-yaml';
|
|
2
2
|
import type MarkdownExit from 'markdown-exit';
|
|
3
3
|
import type MarkdownIt from 'markdown-it';
|
|
4
|
+
/**
|
|
5
|
+
* The `[keyof T] extends [never]` form (rather than `keyof T extends never`)
|
|
6
|
+
* is the standard trick to prevent TS from distributing the check over a
|
|
7
|
+
* union — we want to test "is T's keyset empty?" as one yes/no question.
|
|
8
|
+
*/
|
|
9
|
+
type Writable<T> = [keyof T] extends [never] ? Record<string, any> : T;
|
|
4
10
|
/**
|
|
5
11
|
* The Comark text
|
|
6
12
|
* @param string - The text content
|
|
@@ -43,11 +49,14 @@ export type ComarkNode = ComarkElement | ComarkText | ComarkComment;
|
|
|
43
49
|
* @param nodes - The nodes of the tree
|
|
44
50
|
* @param frontmatter - The frontmatter data which is the data at the top of the file
|
|
45
51
|
* @param meta - The meta data of tree, it can be used to store additional data for the tree
|
|
52
|
+
*
|
|
53
|
+
* The `TMeta` and `TFrontmatter` type parameters allow `parse` / `createParse`
|
|
54
|
+
* to surface plugin-contributed keys with narrow types (see `MergePluginMeta`).
|
|
46
55
|
*/
|
|
47
|
-
export interface ComarkTree {
|
|
56
|
+
export interface ComarkTree<TMeta = Record<string, any>, TFrontmatter = Record<string, any>> {
|
|
48
57
|
nodes: ComarkNode[];
|
|
49
|
-
frontmatter:
|
|
50
|
-
meta:
|
|
58
|
+
frontmatter: TFrontmatter;
|
|
59
|
+
meta: TMeta;
|
|
51
60
|
}
|
|
52
61
|
export interface ContextBase {
|
|
53
62
|
/**
|
|
@@ -234,26 +243,60 @@ export type ComarkParsePreState = {
|
|
|
234
243
|
options: ParseOptions;
|
|
235
244
|
[key: string]: any;
|
|
236
245
|
};
|
|
237
|
-
export type ComarkParsePostState = {
|
|
246
|
+
export type ComarkParsePostState<TMeta = Record<string, any>, TFrontmatter = Record<string, any>> = {
|
|
238
247
|
markdown: string;
|
|
239
|
-
tree: ComarkTree
|
|
248
|
+
tree: ComarkTree<TMeta, TFrontmatter>;
|
|
240
249
|
options: ParseOptions;
|
|
241
250
|
tokens: unknown[];
|
|
242
251
|
[key: string]: any;
|
|
243
252
|
};
|
|
244
|
-
|
|
253
|
+
/**
|
|
254
|
+
* A Comark plugin.
|
|
255
|
+
*
|
|
256
|
+
* `TMeta` / `TFrontmatter` are phantom type parameters that record what this
|
|
257
|
+
* plugin contributes to `tree.meta` / `tree.frontmatter`. They are surfaced
|
|
258
|
+
* only via the optional `__meta` / `__frontmatter` markers — implementations
|
|
259
|
+
* never set these at runtime; they exist purely so the contribution survives
|
|
260
|
+
* `ReturnType<typeof factory>` inference and can be merged in `createParse`.
|
|
261
|
+
*/
|
|
262
|
+
export type ComarkPlugin<TMeta = {}, TFrontmatter = {}> = {
|
|
245
263
|
name: string;
|
|
246
264
|
markdownItPlugins?: MarkdownItPlugin[];
|
|
247
265
|
pre?: (state: ComarkParsePreState) => Promise<void> | void;
|
|
248
|
-
post?: (state: ComarkParsePostState) => Promise<void> | void;
|
|
266
|
+
post?: (state: ComarkParsePostState<Writable<TMeta>, Writable<TFrontmatter>>) => Promise<void> | void;
|
|
267
|
+
/** Phantom — used for type inference only. Never set at runtime. */
|
|
268
|
+
__meta?: TMeta;
|
|
269
|
+
/** Phantom — used for type inference only. Never set at runtime. */
|
|
270
|
+
__frontmatter?: TFrontmatter;
|
|
249
271
|
};
|
|
250
|
-
export type ComarkPluginFactory<Options> = (opts?: Options) => ComarkPlugin
|
|
251
|
-
|
|
272
|
+
export type ComarkPluginFactory<Options, TMeta = {}, TFrontmatter = {}> = (opts?: Options) => ComarkPlugin<TMeta, TFrontmatter>;
|
|
273
|
+
type PluginMetaOf<P> = P extends ComarkPlugin<infer M, any> ? M : {};
|
|
274
|
+
type PluginFrontmatterOf<P> = P extends ComarkPlugin<any, infer F> ? F : {};
|
|
275
|
+
/**
|
|
276
|
+
* Walk a tuple of plugins and intersect their meta contributions.
|
|
277
|
+
* Returns `{}` when the tuple is empty or when nothing was contributed.
|
|
278
|
+
*/
|
|
279
|
+
export type MergePluginMeta<TPlugins extends readonly unknown[]> = TPlugins extends readonly [infer Head, ...infer Rest] ? PluginMetaOf<Head> & MergePluginMeta<Rest extends readonly unknown[] ? Rest : []> : {};
|
|
280
|
+
/**
|
|
281
|
+
* Walk a tuple of plugins and intersect their frontmatter contributions.
|
|
282
|
+
*/
|
|
283
|
+
export type MergePluginFrontmatter<TPlugins extends readonly unknown[]> = TPlugins extends readonly [
|
|
284
|
+
infer Head,
|
|
285
|
+
...infer Rest
|
|
286
|
+
] ? PluginFrontmatterOf<Head> & MergePluginFrontmatter<Rest extends readonly unknown[] ? Rest : []> : {};
|
|
287
|
+
/**
|
|
288
|
+
* When no plugin contributed meta keys, fall back to the permissive
|
|
289
|
+
* `Record<string, any>` (backwards-compatible). Otherwise, preserve narrow
|
|
290
|
+
* keys and type unknown accesses as `unknown` (safer than `any`).
|
|
291
|
+
*/
|
|
292
|
+
export type ResolvedMeta<T> = [keyof T] extends [never] ? Record<string, any> : T & Record<string, unknown>;
|
|
293
|
+
export type ResolvedFrontmatter<T> = [keyof T] extends [never] ? Record<string, any> : T & Record<string, unknown>;
|
|
294
|
+
export type ComponentManifest = (name: string) => unknown | Promise<unknown> | undefined | null;
|
|
252
295
|
export interface ComarkContextProvider {
|
|
253
296
|
components: Record<string, any>;
|
|
254
297
|
componentManifest: ComponentManifest;
|
|
255
298
|
}
|
|
256
|
-
export interface ParseOptions {
|
|
299
|
+
export interface ParseOptions<TPlugins extends readonly ComarkPlugin<any, any>[] = readonly ComarkPlugin<any, any>[]> {
|
|
257
300
|
/**
|
|
258
301
|
* Whether to automatically unwrap single paragraphs in container components.
|
|
259
302
|
* When enabled, if a container component (alert, card, callout, note, warning, tip, info)
|
|
@@ -300,7 +343,7 @@ export interface ParseOptions {
|
|
|
300
343
|
* Additional plugins to use
|
|
301
344
|
* @default []
|
|
302
345
|
*/
|
|
303
|
-
plugins?:
|
|
346
|
+
plugins?: TPlugins;
|
|
304
347
|
}
|
|
305
348
|
/**
|
|
306
349
|
* Type signature for the options object passed to the Comark parser function returned by createParse().
|
|
@@ -312,4 +355,5 @@ export type ComarkParseFnOptions = {
|
|
|
312
355
|
* Type signature for the async Comark parser function returned by createParse().
|
|
313
356
|
* Accepts a markdown string and optional parsing options, and returns a Promise of ComarkTree.
|
|
314
357
|
*/
|
|
315
|
-
export type ComarkParseFn = (markdown: string, opts?: ComarkParseFnOptions) => Promise<ComarkTree
|
|
358
|
+
export type ComarkParseFn<TMeta = Record<string, any>, TFrontmatter = Record<string, any>> = (markdown: string, opts?: ComarkParseFnOptions) => Promise<ComarkTree<TMeta, TFrontmatter>>;
|
|
359
|
+
export {};
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -5,8 +5,20 @@ import type { ComarkPluginFactory } from '../types.ts';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function createSerializedTask<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>): (...args: TArgs) => Promise<TResult>;
|
|
7
7
|
/**
|
|
8
|
-
* Define a Comark plugin
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Define a Comark plugin.
|
|
9
|
+
*
|
|
10
|
+
* `TMeta` and `TFrontmatter` declare what the plugin contributes to
|
|
11
|
+
* `tree.meta` / `tree.frontmatter`. They are inferred from the factory's
|
|
12
|
+
* return type when set via the `__meta` / `__frontmatter` phantom markers,
|
|
13
|
+
* or can be passed explicitly. Plugins that don't contribute typed keys can
|
|
14
|
+
* omit them entirely.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* defineComarkPlugin<Options, { toc: Toc }>((opts) => ({
|
|
19
|
+
* name: 'toc',
|
|
20
|
+
* post(state) { state.tree.meta.toc = ... },
|
|
21
|
+
* }))
|
|
22
|
+
* ```
|
|
11
23
|
*/
|
|
12
|
-
export declare function defineComarkPlugin<Options>(fn: ComarkPluginFactory<Options>): ComarkPluginFactory<Options>;
|
|
24
|
+
export declare function defineComarkPlugin<Options, TMeta = {}, TFrontmatter = {}>(fn: ComarkPluginFactory<Options, TMeta, TFrontmatter>): ComarkPluginFactory<Options, TMeta, TFrontmatter>;
|