comark 0.3.2 → 0.5.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 +25 -1
- package/dist/context.d.ts +78 -0
- package/dist/context.js +127 -0
- package/dist/devtools/bridge.d.ts +1 -0
- package/dist/devtools/bridge.js +1 -0
- package/dist/devtools/constants.d.ts +1 -0
- package/dist/devtools/constants.js +1 -0
- package/dist/devtools/renderer/dom.d.ts +1 -0
- package/dist/devtools/renderer/dom.js +1 -0
- package/dist/devtools/renderer/index.d.ts +2 -0
- package/dist/devtools/renderer/index.js +2 -0
- package/dist/devtools/renderer/output.d.ts +1 -0
- package/dist/devtools/renderer/output.js +1 -0
- package/dist/devtools/renderer/panel.d.ts +1 -0
- package/dist/devtools/renderer/panel.js +1 -0
- package/dist/devtools/renderer/styles.d.ts +1 -0
- package/dist/devtools/renderer/styles.js +1 -0
- package/dist/devtools/renderer/theme.d.ts +1 -0
- package/dist/devtools/renderer/theme.js +1 -0
- package/dist/devtools/types.d.ts +1 -0
- package/dist/devtools/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal/parse/auto-close/index.js +96 -31
- 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 +8 -1
- package/dist/internal/stringify/attributes.js +53 -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 +19 -9
- package/dist/internal/stringify/handlers/mdc.js +1 -1
- package/dist/internal/stringify/handlers/ol.js +15 -2
- 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/internal/stringify/state.js +13 -1
- package/dist/parse.d.ts +4 -4
- package/dist/parse.js +7 -3
- 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 -2
- 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 +12 -1
- package/dist/plugins/security.js +13 -6
- package/dist/plugins/summary.d.ts +4 -1
- package/dist/plugins/syntax.d.ts +1 -1
- package/dist/plugins/syntax.js +95 -36
- package/dist/plugins/task-list.d.ts +1 -1
- package/dist/plugins/toc.d.ts +3 -1
- package/dist/render.d.ts +6 -2
- package/dist/render.js +2 -2
- package/dist/types.d.ts +61 -12
- package/dist/utils/helpers.d.ts +16 -4
- package/dist/utils/helpers.js +15 -3
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.js +30 -14
- package/package.json +6 -3
- package/skills/comark/references/rendering-svelte.md +51 -7
- 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
package/dist/parse.js
CHANGED
|
@@ -44,13 +44,16 @@ 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());
|
|
51
54
|
const parser = new MarkdownExit({
|
|
52
55
|
html: false,
|
|
53
|
-
linkify: true,
|
|
56
|
+
linkify: options.linkify ?? true,
|
|
54
57
|
}).enable(['table', 'strikethrough']);
|
|
55
58
|
if (options.html !== false) {
|
|
56
59
|
parser.inline.ruler.before('text', 'comark_html_inline', html_inline);
|
|
@@ -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,10 +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,
|
|
284
|
-
tabindex: '0',
|
|
286
|
+
class: userClass ? `${classStr} . ${userClass}` : classStr,
|
|
285
287
|
};
|
|
286
288
|
if (options.preStyles) {
|
|
287
289
|
const lightTheme = options.themes?.light;
|
|
@@ -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;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ComarkElement, ComarkNode } from 'comark';
|
|
1
2
|
import type { PropsValidationOptions } from '../internal/props-validation.ts';
|
|
2
3
|
interface SecurityOptions extends PropsValidationOptions {
|
|
3
4
|
/**
|
|
@@ -5,6 +6,16 @@ interface SecurityOptions extends PropsValidationOptions {
|
|
|
5
6
|
* @default []
|
|
6
7
|
*/
|
|
7
8
|
blockedTags?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Tags to allow only in the output tree.
|
|
11
|
+
* @default []
|
|
12
|
+
*/
|
|
13
|
+
allowedTags?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Behavior when encountering an unallowed or blocked tag.
|
|
16
|
+
* @default undefined
|
|
17
|
+
*/
|
|
18
|
+
tagFallback?: (element: ComarkElement) => false | ComarkNode | Promise<false | ComarkNode>;
|
|
8
19
|
}
|
|
9
|
-
declare const _default: import("comark").ComarkPluginFactory<SecurityOptions>;
|
|
20
|
+
declare const _default: import("comark").ComarkPluginFactory<SecurityOptions, {}, {}>;
|
|
10
21
|
export default _default;
|
package/dist/plugins/security.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineComarkPlugin } from "../utils/helpers.js";
|
|
2
|
-
import {
|
|
2
|
+
import { visitAsync } from "../utils/index.js";
|
|
3
3
|
import { validateProps } from "../internal/props-validation.js";
|
|
4
4
|
export default defineComarkPlugin((options = {}) => {
|
|
5
|
-
const { blockedTags = [], allowedLinkPrefixes, allowedImagePrefixes, allowedProtocols, defaultOrigin, allowDataImages, } = options;
|
|
5
|
+
const { blockedTags = [], allowedTags = [], tagFallback = undefined, allowedLinkPrefixes, allowedImagePrefixes, allowedProtocols, defaultOrigin, allowDataImages, } = options;
|
|
6
6
|
const dropSet = new Set(blockedTags.map((t) => t.toLowerCase()));
|
|
7
|
+
const allowSet = new Set(allowedTags.map((t) => t.toLowerCase()));
|
|
7
8
|
const propsOptions = {
|
|
8
9
|
allowedLinkPrefixes,
|
|
9
10
|
allowedImagePrefixes,
|
|
@@ -13,11 +14,17 @@ export default defineComarkPlugin((options = {}) => {
|
|
|
13
14
|
};
|
|
14
15
|
return {
|
|
15
16
|
name: 'security',
|
|
16
|
-
post(state) {
|
|
17
|
-
|
|
17
|
+
async post(state) {
|
|
18
|
+
await visitAsync(state.tree, (node) => typeof node !== 'string' && node[0] !== null, async (node) => {
|
|
18
19
|
const element = node;
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const tagName = element[0].toLowerCase();
|
|
21
|
+
const isBlocked = dropSet.has(tagName);
|
|
22
|
+
const isNotAllowed = allowSet.size > 0 && !allowSet.has(tagName);
|
|
23
|
+
if (isNotAllowed || isBlocked) {
|
|
24
|
+
if (typeof tagFallback === 'function') {
|
|
25
|
+
return await tagFallback(element);
|
|
26
|
+
}
|
|
27
|
+
// return false to remove the node from the tree
|
|
21
28
|
return false;
|
|
22
29
|
}
|
|
23
30
|
const keys = Object.keys(element[1]);
|
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
|
@@ -4,6 +4,22 @@ import { parseBracketContent } from "../internal/parse/syntax/brackets.js";
|
|
|
4
4
|
import { searchProps } from "../internal/parse/syntax/props.js";
|
|
5
5
|
import { parseBlockParams } from "../internal/parse/syntax/block-params.js";
|
|
6
6
|
import { parseYaml } from "../internal/yaml.js";
|
|
7
|
+
/**
|
|
8
|
+
* A component name must start with a letter or `$`, followed by word chars,
|
|
9
|
+
* `$` or `-`. Mirrors the block name grammar (`RE_BLOCK_NAME = /^[a-z$]/i`).
|
|
10
|
+
*/
|
|
11
|
+
const RE_COMPONENT_NAME = /^[a-z$][\w$-]*/i;
|
|
12
|
+
/**
|
|
13
|
+
* Whether `name` begins with a syntactically valid component name.
|
|
14
|
+
*
|
|
15
|
+
* This prevents sequences such as `:8100` or `::30` from being treated as
|
|
16
|
+
* components — a purely numeric name is not a valid component and would
|
|
17
|
+
* otherwise produce invalid output like `createElement('8100')` (inline) or
|
|
18
|
+
* throw `Invalid block params` (block).
|
|
19
|
+
*/
|
|
20
|
+
function isValidComponentName(name) {
|
|
21
|
+
return RE_COMPONENT_NAME.test(name);
|
|
22
|
+
}
|
|
7
23
|
// #region Block component plugin (`::name` and `::name ... ::`)
|
|
8
24
|
const blockYamlLines = {
|
|
9
25
|
'---': '---',
|
|
@@ -18,7 +34,7 @@ const markdownItComarkBlock = (md) => {
|
|
|
18
34
|
const marker_char = marker_str.charCodeAt(0);
|
|
19
35
|
md.block.ruler.before('fence', 'comark_block_shorthand', function comark_block_shorthand(state, startLine, _endLine, silent) {
|
|
20
36
|
const line = state.src.slice(state.bMarks[startLine] + state.tShift[startLine], state.eMarks[startLine]);
|
|
21
|
-
if (
|
|
37
|
+
if (line[0] !== ':' || !isValidComponentName(line.slice(1)))
|
|
22
38
|
return false;
|
|
23
39
|
const { name, content, props, remaining } = parseBlockParams(line.slice(1));
|
|
24
40
|
// If there's unparsed remaining content, treat it as inline component in a paragraph
|
|
@@ -78,6 +94,11 @@ const markdownItComarkBlock = (md) => {
|
|
|
78
94
|
if (marker_count < min_markers)
|
|
79
95
|
return false;
|
|
80
96
|
const markup = state.src.slice(start, pos);
|
|
97
|
+
// Bail out (plain text) on an invalid name instead of letting
|
|
98
|
+
// parseBlockParams throw on e.g. `::8100`.
|
|
99
|
+
const nameStart = state.skipSpaces(pos);
|
|
100
|
+
if (nameStart < max && !isValidComponentName(state.src.slice(nameStart, max)))
|
|
101
|
+
return false;
|
|
81
102
|
const params = parseBlockParams(state.src.slice(pos, max));
|
|
82
103
|
if (!params.name)
|
|
83
104
|
return false;
|
|
@@ -201,6 +222,12 @@ const markdownItComarkBlock = (md) => {
|
|
|
201
222
|
const blockAttributesClosingFence = blockYamlLines[line] || '';
|
|
202
223
|
if (!blockAttributesClosingFence)
|
|
203
224
|
return false;
|
|
225
|
+
// The `---` fence is only valid on the line immediately after the component opener. Any other `---` is a thematic break.
|
|
226
|
+
if (line === '---') {
|
|
227
|
+
const parentOpenLine = state.env.comarkBlockTokens[0].map?.[0];
|
|
228
|
+
if (parentOpenLine === undefined || startLine !== parentOpenLine + 1)
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
204
231
|
let lineEnd = startLine + 1;
|
|
205
232
|
let found = false;
|
|
206
233
|
while (lineEnd < endLine) {
|
|
@@ -237,8 +264,38 @@ const markdownItComarkBlock = (md) => {
|
|
|
237
264
|
const line = state.src.slice(start, state.eMarks[startLine]);
|
|
238
265
|
const { name, props } = parseBlockParams(line.slice(1));
|
|
239
266
|
let lineEnd = startLine + 1;
|
|
267
|
+
let inCodeFence = false;
|
|
268
|
+
let codeFenceChar = '';
|
|
269
|
+
let codeFenceCount = 0;
|
|
240
270
|
while (lineEnd < endLine) {
|
|
241
271
|
const inner = state.src.slice(state.bMarks[lineEnd] + state.tShift[startLine], state.eMarks[lineEnd]);
|
|
272
|
+
if (inCodeFence) {
|
|
273
|
+
// Look for matching closing fence (same char, >= opening count, nothing but spaces after)
|
|
274
|
+
if (inner[0] === codeFenceChar) {
|
|
275
|
+
let fencePos = 1;
|
|
276
|
+
while (fencePos < inner.length && inner[fencePos] === codeFenceChar)
|
|
277
|
+
fencePos++;
|
|
278
|
+
if (fencePos >= codeFenceCount && inner.slice(fencePos).trim() === '') {
|
|
279
|
+
inCodeFence = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
lineEnd += 1;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
// Detect opening code fence (``` or ~~~, length >= 3)
|
|
286
|
+
if (inner[0] === '`' || inner[0] === '~') {
|
|
287
|
+
const ch = inner[0];
|
|
288
|
+
let fencePos = 1;
|
|
289
|
+
while (fencePos < inner.length && inner[fencePos] === ch)
|
|
290
|
+
fencePos++;
|
|
291
|
+
if (fencePos >= 3) {
|
|
292
|
+
inCodeFence = true;
|
|
293
|
+
codeFenceChar = ch;
|
|
294
|
+
codeFenceCount = fencePos;
|
|
295
|
+
lineEnd += 1;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
242
299
|
if (/^#\w+/.test(inner) || inner.startsWith('::'))
|
|
243
300
|
break;
|
|
244
301
|
lineEnd += 1;
|
|
@@ -347,10 +404,12 @@ const markdownItInlineComponent = (md) => {
|
|
|
347
404
|
// Empty name
|
|
348
405
|
if (nameEnd <= start + 1)
|
|
349
406
|
return false;
|
|
407
|
+
const name = state.src.slice(start + 1, nameEnd);
|
|
408
|
+
if (!isValidComponentName(name))
|
|
409
|
+
return false;
|
|
350
410
|
state.pos = index;
|
|
351
411
|
if (silent)
|
|
352
412
|
return true;
|
|
353
|
-
const name = state.src.slice(start + 1, nameEnd);
|
|
354
413
|
if (contentStart !== -1) {
|
|
355
414
|
state.push('mdc_inline_component', name, 1);
|
|
356
415
|
const oldPos = state.pos;
|
|
@@ -397,52 +456,52 @@ const markdownItInlineProps = (md) => {
|
|
|
397
456
|
const _parse = md.parse;
|
|
398
457
|
md.parse = function (src, env) {
|
|
399
458
|
const tokens = _parse.call(this, src, env);
|
|
400
|
-
// When the
|
|
401
|
-
//
|
|
459
|
+
// When the trailing inline child is a props token directly after a text
|
|
460
|
+
// node, lift the props onto the surrounding heading/paragraph/list_item.
|
|
461
|
+
// (If the props follow a closing tag, they belong to that inline tag, not
|
|
462
|
+
// the parent — leave them alone.)
|
|
402
463
|
tokens.forEach((token, index) => {
|
|
403
464
|
const prev = tokens[index - 1];
|
|
404
465
|
const next = tokens[index + 1];
|
|
405
466
|
if (!prev || !['heading_open', 'paragraph_open', 'list_item_open'].includes(prev.type) || prev.hidden)
|
|
406
467
|
return;
|
|
407
|
-
// list
|
|
468
|
+
// Tight-list paragraph: the inline lives one slot ahead
|
|
408
469
|
if (token.hidden && next?.type === 'inline')
|
|
409
470
|
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')
|
|
471
|
+
if (token.type !== 'inline' || !token.children?.length)
|
|
427
472
|
return;
|
|
428
|
-
const
|
|
429
|
-
if (
|
|
473
|
+
const last = token.children[token.children.length - 1];
|
|
474
|
+
if (last.type !== 'mdc_inline_props')
|
|
430
475
|
return;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
476
|
+
// Find the previous non-empty child. Markdown-it's emphasis tokenizer
|
|
477
|
+
// can leave an empty text token between the closing delimiter and the
|
|
478
|
+
// props — skipping it lets us distinguish "props on the parent" from
|
|
479
|
+
// "props on the preceding inline tag".
|
|
480
|
+
let beforeIdx = token.children.length - 2;
|
|
481
|
+
while (beforeIdx >= 0) {
|
|
482
|
+
const child = token.children[beforeIdx];
|
|
483
|
+
if (child.type === 'text' && !child.content) {
|
|
484
|
+
beforeIdx--;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
437
488
|
}
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
489
|
+
const beforeProps = beforeIdx >= 0 ? token.children[beforeIdx] : undefined;
|
|
490
|
+
if (!beforeProps || beforeProps.type !== 'text')
|
|
440
491
|
return;
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
tokenClose.hidden = true;
|
|
492
|
+
// Strip the trailing space the text picked up before the `{...}` token.
|
|
493
|
+
if (typeof beforeProps.content === 'string') {
|
|
494
|
+
beforeProps.content = beforeProps.content.replace(/[ \t]+$/, '');
|
|
445
495
|
}
|
|
496
|
+
const props = last.attrs;
|
|
497
|
+
// Drop the props token (last) plus any empty text tokens it left behind.
|
|
498
|
+
token.children.length = beforeProps.content ? beforeIdx + 1 : beforeIdx;
|
|
499
|
+
props?.forEach(([key, value]) => {
|
|
500
|
+
if (key === 'class')
|
|
501
|
+
prev.attrJoin('class', value);
|
|
502
|
+
else
|
|
503
|
+
prev.attrSet(key, value);
|
|
504
|
+
});
|
|
446
505
|
});
|
|
447
506
|
return tokens;
|
|
448
507
|
};
|
|
@@ -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/render.d.ts
CHANGED
|
@@ -8,7 +8,9 @@ export { resolveAttributes, resolveAttribute } from './internal/stringify/attrib
|
|
|
8
8
|
* @param context - The context of the renderer
|
|
9
9
|
* @returns The string representation of the Comark tree
|
|
10
10
|
*/
|
|
11
|
-
export declare function render(tree: ComarkTree
|
|
11
|
+
export declare function render(tree: ComarkTree | {
|
|
12
|
+
nodes: ComarkTree['nodes'];
|
|
13
|
+
}, context?: RenderOptions): Promise<string>;
|
|
12
14
|
/**
|
|
13
15
|
* Render Comark tree to markdown
|
|
14
16
|
*
|
|
@@ -16,4 +18,6 @@ export declare function render(tree: ComarkTree, context?: RenderOptions): Promi
|
|
|
16
18
|
* @param options - Optional rendering options
|
|
17
19
|
* @returns The markdown string with optional frontmatter
|
|
18
20
|
*/
|
|
19
|
-
export declare function renderMarkdown(tree: ComarkTree
|
|
21
|
+
export declare function renderMarkdown(tree: ComarkTree | {
|
|
22
|
+
nodes: ComarkTree['nodes'];
|
|
23
|
+
}, options?: RenderMarkdownOptions): Promise<string>;
|
package/dist/render.js
CHANGED
|
@@ -11,7 +11,7 @@ export { resolveAttributes, resolveAttribute } from "./internal/stringify/attrib
|
|
|
11
11
|
* @returns The string representation of the Comark tree
|
|
12
12
|
*/
|
|
13
13
|
export async function render(tree, context = {}) {
|
|
14
|
-
const state = createState({ ...context, tree, handlers: context.components });
|
|
14
|
+
const state = createState({ ...context, tree: tree, handlers: context.components });
|
|
15
15
|
let result = '';
|
|
16
16
|
for (const child of tree.nodes) {
|
|
17
17
|
result += await one(child, state);
|
|
@@ -27,5 +27,5 @@ export async function render(tree, context = {}) {
|
|
|
27
27
|
*/
|
|
28
28
|
export async function renderMarkdown(tree, options) {
|
|
29
29
|
const content = await render(tree, { format: 'markdown/comark', ...options });
|
|
30
|
-
return renderFrontmatter(tree.frontmatter, content, options?.frontmatterOptions);
|
|
30
|
+
return renderFrontmatter(tree.frontmatter || {}, content, options?.frontmatterOptions);
|
|
31
31
|
}
|