comark 0.0.1 → 0.1.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 +104 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/internal/frontmatter.d.ts +16 -0
- package/dist/internal/frontmatter.js +43 -0
- package/dist/internal/parse/auto-close/index.d.ts +12 -0
- package/dist/internal/parse/auto-close/index.js +457 -0
- package/dist/internal/parse/auto-close/table.d.ts +4 -0
- package/dist/internal/parse/auto-close/table.js +161 -0
- package/dist/internal/parse/auto-unwrap.d.ts +20 -0
- package/dist/internal/parse/auto-unwrap.js +42 -0
- package/dist/internal/parse/html/html_block_rule.d.ts +2 -0
- package/dist/internal/parse/html/html_block_rule.js +60 -0
- package/dist/internal/parse/html/html_blocks.d.ts +2 -0
- package/dist/internal/parse/html/html_blocks.js +66 -0
- package/dist/internal/parse/html/html_inline_rule.d.ts +2 -0
- package/dist/internal/parse/html/html_inline_rule.js +43 -0
- package/dist/internal/parse/html/html_re.d.ts +3 -0
- package/dist/internal/parse/html/html_re.js +18 -0
- package/dist/internal/parse/html/index.d.ts +18 -0
- package/dist/internal/parse/html/index.js +122 -0
- package/dist/internal/parse/incremental.d.ts +12 -0
- package/dist/internal/parse/incremental.js +39 -0
- package/dist/internal/parse/token-processor.d.ts +9 -0
- package/dist/internal/parse/token-processor.js +803 -0
- package/dist/internal/props-validation.d.ts +12 -0
- package/dist/internal/props-validation.js +112 -0
- package/dist/internal/stringify/attributes.d.ts +21 -0
- package/dist/internal/stringify/attributes.js +67 -0
- package/dist/internal/stringify/handlers/a.d.ts +3 -0
- package/dist/internal/stringify/handlers/a.js +11 -0
- package/dist/internal/stringify/handlers/blockquote.d.ts +3 -0
- package/dist/internal/stringify/handlers/blockquote.js +18 -0
- package/dist/internal/stringify/handlers/br.d.ts +3 -0
- package/dist/internal/stringify/handlers/br.js +3 -0
- package/dist/internal/stringify/handlers/code.d.ts +3 -0
- package/dist/internal/stringify/handlers/code.js +11 -0
- package/dist/internal/stringify/handlers/comment.d.ts +3 -0
- package/dist/internal/stringify/handlers/comment.js +6 -0
- package/dist/internal/stringify/handlers/del.d.ts +3 -0
- package/dist/internal/stringify/handlers/del.js +4 -0
- package/dist/internal/stringify/handlers/emphesis.d.ts +3 -0
- package/dist/internal/stringify/handlers/emphesis.js +13 -0
- package/dist/internal/stringify/handlers/heading.d.ts +3 -0
- package/dist/internal/stringify/handlers/heading.js +7 -0
- package/dist/internal/stringify/handlers/hr.d.ts +3 -0
- package/dist/internal/stringify/handlers/hr.js +3 -0
- package/dist/internal/stringify/handlers/html.d.ts +3 -0
- package/dist/internal/stringify/handlers/html.js +73 -0
- package/dist/internal/stringify/handlers/img.d.ts +3 -0
- package/dist/internal/stringify/handlers/img.js +9 -0
- package/dist/internal/stringify/handlers/index.d.ts +2 -0
- package/dist/internal/stringify/handlers/index.js +56 -0
- package/dist/internal/stringify/handlers/li.d.ts +3 -0
- package/dist/internal/stringify/handlers/li.js +43 -0
- package/dist/internal/stringify/handlers/math.d.ts +3 -0
- package/dist/internal/stringify/handlers/math.js +8 -0
- package/dist/internal/stringify/handlers/mdc.d.ts +3 -0
- package/dist/internal/stringify/handlers/mdc.js +47 -0
- package/dist/internal/stringify/handlers/mermaid.d.ts +3 -0
- package/dist/internal/stringify/handlers/mermaid.js +8 -0
- package/dist/internal/stringify/handlers/ol.d.ts +3 -0
- package/dist/internal/stringify/handlers/ol.js +18 -0
- package/dist/internal/stringify/handlers/p.d.ts +3 -0
- package/dist/internal/stringify/handlers/p.js +8 -0
- package/dist/internal/stringify/handlers/pre.d.ts +3 -0
- package/dist/internal/stringify/handlers/pre.js +60 -0
- package/dist/internal/stringify/handlers/strong.d.ts +3 -0
- package/dist/internal/stringify/handlers/strong.js +13 -0
- package/dist/internal/stringify/handlers/table.d.ts +8 -0
- package/dist/internal/stringify/handlers/table.js +180 -0
- package/dist/internal/stringify/handlers/template.d.ts +3 -0
- package/dist/internal/stringify/handlers/template.js +14 -0
- package/dist/internal/stringify/handlers/ul.d.ts +3 -0
- package/dist/internal/stringify/handlers/ul.js +18 -0
- package/dist/internal/stringify/indent.d.ts +4 -0
- package/dist/internal/stringify/indent.js +8 -0
- package/dist/internal/stringify/state.d.ts +13 -0
- package/dist/internal/stringify/state.js +121 -0
- package/dist/internal/yaml.d.ts +12 -0
- package/dist/internal/yaml.js +51 -0
- package/dist/parse.d.ts +66 -0
- package/dist/parse.js +163 -0
- package/dist/plugins/alert.d.ts +2 -0
- package/dist/plugins/alert.js +66 -0
- package/dist/plugins/emoji.d.ts +3 -0
- package/dist/plugins/emoji.js +438 -0
- package/dist/plugins/headings.d.ts +48 -0
- package/dist/plugins/headings.js +85 -0
- package/dist/plugins/highlight.d.ts +63 -0
- package/dist/plugins/highlight.js +235 -0
- package/dist/plugins/math.d.ts +59 -0
- package/dist/plugins/math.js +263 -0
- package/dist/plugins/mermaid.d.ts +38 -0
- package/dist/plugins/mermaid.js +185 -0
- package/dist/plugins/security.d.ts +11 -0
- package/dist/plugins/security.js +32 -0
- package/dist/plugins/summary.d.ts +2 -0
- package/dist/plugins/summary.js +22 -0
- package/dist/plugins/task-list.d.ts +8 -0
- package/dist/plugins/task-list.js +117 -0
- package/dist/plugins/toc.d.ts +15 -0
- package/dist/plugins/toc.js +118 -0
- package/dist/render.d.ts +18 -0
- package/dist/render.js +29 -0
- package/dist/types.d.ts +258 -0
- package/dist/types.js +1 -0
- package/dist/utils/caret.d.ts +7 -0
- package/dist/utils/caret.js +36 -0
- package/dist/utils/index.d.ts +38 -0
- package/dist/utils/index.js +149 -0
- package/package.json +73 -9
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const unsafeTags: string[];
|
|
2
|
+
export declare const unsafeAttributes: string[];
|
|
3
|
+
export declare const unsafeLinkPrefix: string[];
|
|
4
|
+
export interface PropsValidationOptions {
|
|
5
|
+
allowedLinkPrefixes?: string[];
|
|
6
|
+
allowedImagePrefixes?: string[];
|
|
7
|
+
allowedProtocols?: string[];
|
|
8
|
+
defaultOrigin?: string;
|
|
9
|
+
allowDataImages?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function validateProp(attribute: string, value: string, options?: PropsValidationOptions): string | false;
|
|
12
|
+
export declare function validateProps(type: string, props?: Record<string, any>, options?: PropsValidationOptions): Record<string, any>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export const unsafeTags = [
|
|
2
|
+
'object',
|
|
3
|
+
];
|
|
4
|
+
export const unsafeAttributes = [
|
|
5
|
+
'srcdoc',
|
|
6
|
+
'formaction',
|
|
7
|
+
];
|
|
8
|
+
export const unsafeLinkPrefix = [
|
|
9
|
+
'javascript:',
|
|
10
|
+
'data:text/html',
|
|
11
|
+
'vbscript:',
|
|
12
|
+
'data:text/javascript',
|
|
13
|
+
'data:text/vbscript',
|
|
14
|
+
'data:text/css',
|
|
15
|
+
'data:text/plain',
|
|
16
|
+
'data:text/xml',
|
|
17
|
+
];
|
|
18
|
+
function rewriteToDefaultOrigin(urlStr, defaultOrigin) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(urlStr);
|
|
21
|
+
const origin = new URL(defaultOrigin);
|
|
22
|
+
parsed.protocol = origin.protocol;
|
|
23
|
+
parsed.host = origin.host;
|
|
24
|
+
return parsed.href;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return defaultOrigin;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function validateUrl(value, mode, options) {
|
|
31
|
+
const { allowedLinkPrefixes = ['*'], allowedImagePrefixes = ['*'], allowedProtocols = ['*'], defaultOrigin, allowDataImages = true, } = options;
|
|
32
|
+
const decodedUrl = decodeURIComponent(value);
|
|
33
|
+
const urlSanitized = decodedUrl
|
|
34
|
+
.replace(/&#x([0-9a-f]+);?/gi, '')
|
|
35
|
+
.replace(/&#(\d+);?/g, '')
|
|
36
|
+
.replace(/&[a-z]+;?/gi, '');
|
|
37
|
+
let url;
|
|
38
|
+
try {
|
|
39
|
+
// Parse without a base — throws for relative URLs, succeeds for absolute
|
|
40
|
+
url = new URL(urlSanitized);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Relative URLs are always allowed
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
// Block known-unsafe protocols — hard floor, not overrideable by options
|
|
47
|
+
if (unsafeLinkPrefix.some(prefix => url.href.toLowerCase().startsWith(prefix))) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
// Block data: images when allowDataImages is false
|
|
51
|
+
if (mode === 'image' && !allowDataImages && url.protocol === 'data:') {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
// Check allowed protocols
|
|
55
|
+
if (!allowedProtocols.includes('*')) {
|
|
56
|
+
const protocol = url.protocol.replace(':', '');
|
|
57
|
+
if (!allowedProtocols.includes(protocol)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Check allowed URL prefixes
|
|
62
|
+
const allowedPrefixes = mode === 'link' ? allowedLinkPrefixes : allowedImagePrefixes;
|
|
63
|
+
if (!allowedPrefixes.includes('*')) {
|
|
64
|
+
const href = url.href.toLowerCase();
|
|
65
|
+
const matchesPrefix = allowedPrefixes.some(prefix => href.startsWith(prefix.toLowerCase()));
|
|
66
|
+
if (!matchesPrefix) {
|
|
67
|
+
if (defaultOrigin) {
|
|
68
|
+
return rewriteToDefaultOrigin(urlSanitized, defaultOrigin);
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
export function validateProp(attribute, value, options = {}) {
|
|
76
|
+
attribute = attribute.toLowerCase();
|
|
77
|
+
if (attribute.startsWith('on') || unsafeAttributes.includes(attribute)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (attribute === 'href') {
|
|
81
|
+
return validateUrl(value, 'link', options);
|
|
82
|
+
}
|
|
83
|
+
if (attribute === 'src') {
|
|
84
|
+
return validateUrl(value, 'image', options);
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
export function validateProps(type, props, options = {}) {
|
|
89
|
+
/**
|
|
90
|
+
* If the tag is marked as unsafe, drop all props
|
|
91
|
+
*/
|
|
92
|
+
if (unsafeTags.includes(type.toLowerCase())) {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
if (!props)
|
|
96
|
+
return {};
|
|
97
|
+
const entries = Object.entries(props);
|
|
98
|
+
if (entries.length === 0)
|
|
99
|
+
return {};
|
|
100
|
+
props = Object.fromEntries(entries.flatMap(([name, value]) => {
|
|
101
|
+
if (name === 'id' && !value) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const result = validateProp(name, value, options);
|
|
105
|
+
if (result === false) {
|
|
106
|
+
console.warn(`[comark/plugins/security] removing unsafe attribute: ${name}="${value}"`);
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
return [[name, result]];
|
|
110
|
+
}));
|
|
111
|
+
return props;
|
|
112
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert attributes to a string of Comark attributes
|
|
3
|
+
*
|
|
4
|
+
* @param attributes - The attributes to stringify
|
|
5
|
+
* @returns The stringified attributes
|
|
6
|
+
*/
|
|
7
|
+
export declare function comarkAttributes(attributes: Record<string, unknown>): string;
|
|
8
|
+
/**
|
|
9
|
+
* Convert attributes to a string of HTML attributes
|
|
10
|
+
*
|
|
11
|
+
* @param attributes - The attributes to stringify
|
|
12
|
+
* @returns The stringified attributes
|
|
13
|
+
*/
|
|
14
|
+
export declare function htmlAttributes(attributes: Record<string, unknown>): string;
|
|
15
|
+
/**
|
|
16
|
+
* Convert attributes to a string of YAML attributes
|
|
17
|
+
*
|
|
18
|
+
* @param attributes - The attributes to stringify
|
|
19
|
+
* @returns The stringified attributes
|
|
20
|
+
*/
|
|
21
|
+
export declare function comarkYamlAttributes(attributes: Record<string, unknown>): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { stringifyYaml } from "../yaml.js";
|
|
2
|
+
/**
|
|
3
|
+
* Convert attributes to a string of Comark attributes
|
|
4
|
+
*
|
|
5
|
+
* @param attributes - The attributes to stringify
|
|
6
|
+
* @returns The stringified attributes
|
|
7
|
+
*/
|
|
8
|
+
export function comarkAttributes(attributes) {
|
|
9
|
+
const attrs = Object.entries(attributes)
|
|
10
|
+
.map(([key, value]) => {
|
|
11
|
+
if (key.startsWith(':') && value === 'true') {
|
|
12
|
+
return key.slice(1);
|
|
13
|
+
}
|
|
14
|
+
if (key === 'id') {
|
|
15
|
+
return `#${value}`;
|
|
16
|
+
}
|
|
17
|
+
if (key === 'class') {
|
|
18
|
+
return value.split(' ').map(c => `.${c}`).join('');
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'object') {
|
|
21
|
+
return `${key}="${JSON.stringify(value).replace(/"/g, '\\"')}"`;
|
|
22
|
+
}
|
|
23
|
+
return `${key}="${value}"`;
|
|
24
|
+
})
|
|
25
|
+
.join(' ');
|
|
26
|
+
return attrs.length > 0 ? `{${attrs}}` : '';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert attributes to a string of HTML attributes
|
|
30
|
+
*
|
|
31
|
+
* @param attributes - The attributes to stringify
|
|
32
|
+
* @returns The stringified attributes
|
|
33
|
+
*/
|
|
34
|
+
export function htmlAttributes(attributes) {
|
|
35
|
+
return Object.entries(attributes)
|
|
36
|
+
.map(([key, value]) => {
|
|
37
|
+
if (key.startsWith(':')) {
|
|
38
|
+
if (value === 'true') {
|
|
39
|
+
return key.slice(1);
|
|
40
|
+
}
|
|
41
|
+
return `${key.slice(1)}="${value}"`;
|
|
42
|
+
}
|
|
43
|
+
if (value === 'true')
|
|
44
|
+
return key;
|
|
45
|
+
if (typeof value === 'object') {
|
|
46
|
+
return `${key}="${JSON.stringify(value).replace(/"/g, '\\"')}"`;
|
|
47
|
+
}
|
|
48
|
+
return `${key}="${value}"`;
|
|
49
|
+
})
|
|
50
|
+
.join(' ');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert attributes to a string of YAML attributes
|
|
54
|
+
*
|
|
55
|
+
* @param attributes - The attributes to stringify
|
|
56
|
+
* @returns The stringified attributes
|
|
57
|
+
*/
|
|
58
|
+
export function comarkYamlAttributes(attributes) {
|
|
59
|
+
// Normalize boolean attributes to remove the colon prefix
|
|
60
|
+
const normalized = Object.fromEntries(Object.entries(attributes).map(([key, value]) => {
|
|
61
|
+
if (key.startsWith(':') && (value === 'true' || value === 'false')) {
|
|
62
|
+
return [key.slice(1), value];
|
|
63
|
+
}
|
|
64
|
+
return [key, value];
|
|
65
|
+
}));
|
|
66
|
+
return `---\n${stringifyYaml(normalized).trim()}\n---`;
|
|
67
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { comarkAttributes } from '../attributes';
|
|
2
|
+
// TODO: support title & attributes
|
|
3
|
+
export async function a(node, state) {
|
|
4
|
+
const [_, attrs] = node;
|
|
5
|
+
const { href, ...rest } = attrs;
|
|
6
|
+
const attrsString = Object.keys(rest).length > 0
|
|
7
|
+
? comarkAttributes(rest)
|
|
8
|
+
: '';
|
|
9
|
+
const content = await state.flow(node, state);
|
|
10
|
+
return `[${content}](${href})${attrsString}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function blockquote(node, state) {
|
|
2
|
+
const children = node.slice(2);
|
|
3
|
+
let childResult = '';
|
|
4
|
+
for (const child of children) {
|
|
5
|
+
childResult += await state.one(child, state, node);
|
|
6
|
+
}
|
|
7
|
+
const content = childResult
|
|
8
|
+
.trim()
|
|
9
|
+
.split('\n')
|
|
10
|
+
.map(line => line ? `> ${line}` : '>')
|
|
11
|
+
.join('\n');
|
|
12
|
+
if (node[1].as) {
|
|
13
|
+
return `> [!${String(node[1].as).toUpperCase()}]\n`
|
|
14
|
+
+ content
|
|
15
|
+
+ state.context.blockSeparator;
|
|
16
|
+
}
|
|
17
|
+
return content + state.context.blockSeparator;
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { comarkAttributes } from '../attributes';
|
|
2
|
+
import { textContent } from 'comark/utils';
|
|
3
|
+
export function code(node, _state) {
|
|
4
|
+
const [_, attrs] = node;
|
|
5
|
+
const attrsString = Object.keys(attrs).length > 0
|
|
6
|
+
? comarkAttributes(attrs)
|
|
7
|
+
: '';
|
|
8
|
+
const content = textContent(node);
|
|
9
|
+
const fence = content.includes('`') ? '``' : '`';
|
|
10
|
+
return `${fence}${content}${fence}${attrsString}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { comarkAttributes } from '../attributes';
|
|
2
|
+
export async function emphesis(node, state) {
|
|
3
|
+
const [_, attrs, ...children] = node;
|
|
4
|
+
let content = '';
|
|
5
|
+
for (const child of children) {
|
|
6
|
+
content += await state.one(child, state, node);
|
|
7
|
+
}
|
|
8
|
+
content = content.trim();
|
|
9
|
+
const attrsString = Object.keys(attrs).length > 0
|
|
10
|
+
? comarkAttributes(attrs)
|
|
11
|
+
: '';
|
|
12
|
+
return `*${content}*${attrsString}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { htmlAttributes } from '../attributes';
|
|
2
|
+
import { indent } from '../indent';
|
|
3
|
+
const textBlocks = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'th']);
|
|
4
|
+
const selfCloseTags = new Set(['br', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
|
|
5
|
+
const inlineTags = new Set(['strong', 'em', 'code', 'a', 'br', 'span', 'img']);
|
|
6
|
+
const blockTags = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'ul', 'ol', 'blockquote', 'hr', 'table', 'td', 'th']);
|
|
7
|
+
export async function html(node, state, parent) {
|
|
8
|
+
const [tag, attr, ...children] = node;
|
|
9
|
+
const { $ = {}, ...attributes } = attr;
|
|
10
|
+
const hasOnlyTextChildren = children.every(child => typeof child === 'string' || inlineTags.has(String(child?.[0])));
|
|
11
|
+
const hasTextSibling = children.some(child => typeof child === 'string');
|
|
12
|
+
const isBlock = textBlocks.has(String(tag));
|
|
13
|
+
const isInline = inlineTags.has(String(tag)) && $.block === 0;
|
|
14
|
+
let oneLiner = isBlock && hasOnlyTextChildren;
|
|
15
|
+
if (!oneLiner && inlineTags.has(String(tag)) && hasOnlyTextChildren) {
|
|
16
|
+
oneLiner = true;
|
|
17
|
+
}
|
|
18
|
+
if (tag === 'pre') {
|
|
19
|
+
oneLiner = true;
|
|
20
|
+
}
|
|
21
|
+
// If parent is a paragraph, it is inline
|
|
22
|
+
if (parent?.[0] === 'p' || state.context.inline) {
|
|
23
|
+
oneLiner = true;
|
|
24
|
+
}
|
|
25
|
+
if ($.block === 0) {
|
|
26
|
+
oneLiner = true;
|
|
27
|
+
}
|
|
28
|
+
const isSelfClose = selfCloseTags.has(String(tag));
|
|
29
|
+
// Do not modify context if we are already in html mode
|
|
30
|
+
const revert = state.applyContext({ inline: oneLiner });
|
|
31
|
+
const childrenContent = [];
|
|
32
|
+
for (const child of children) {
|
|
33
|
+
childrenContent.push(await state.one(child, state, node));
|
|
34
|
+
}
|
|
35
|
+
let content = '';
|
|
36
|
+
let isPrevBlock = true;
|
|
37
|
+
for (let i = 0; i < children.length; i++) {
|
|
38
|
+
const childContent = childrenContent[i];
|
|
39
|
+
const child = children[i];
|
|
40
|
+
const isBlock = blockTags.has(String(child?.[0])) || (!inlineTags.has(String(child?.[0])) && !hasTextSibling);
|
|
41
|
+
if (i > 0 && !isPrevBlock && isBlock) {
|
|
42
|
+
content += state.context.blockSeparator;
|
|
43
|
+
}
|
|
44
|
+
content += childContent;
|
|
45
|
+
isPrevBlock = isBlock;
|
|
46
|
+
if (isBlock && i < children.length - 1) {
|
|
47
|
+
content += state.context.blockSeparator;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Revert, only if we modified the context
|
|
51
|
+
if (revert) {
|
|
52
|
+
state.applyContext(revert);
|
|
53
|
+
}
|
|
54
|
+
const attrs = Object.keys(attributes).length > 0
|
|
55
|
+
? ` ${htmlAttributes(attributes)}`
|
|
56
|
+
: '';
|
|
57
|
+
if (isSelfClose) {
|
|
58
|
+
return `<${tag}${attrs} />` + (!parent && !isInline ? state.context.blockSeparator : '');
|
|
59
|
+
}
|
|
60
|
+
if (!oneLiner && content) {
|
|
61
|
+
content = '\n' + paddNoneHtmlContent(content, state).trimEnd() + '\n';
|
|
62
|
+
}
|
|
63
|
+
return `<${tag}${attrs}>${content}</${tag}>`
|
|
64
|
+
+ (!parent && !isInline ? state.context.blockSeparator : '');
|
|
65
|
+
}
|
|
66
|
+
function paddNoneHtmlContent(content, state) {
|
|
67
|
+
if (state.context.html) {
|
|
68
|
+
return indent(content);
|
|
69
|
+
}
|
|
70
|
+
return ((content.trim().startsWith('<') ? '' : '')
|
|
71
|
+
+ content
|
|
72
|
+
+ (content.trim().endsWith('>') ? '' : ''));
|
|
73
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { comarkAttributes } from '../attributes';
|
|
2
|
+
export function img(node, _state) {
|
|
3
|
+
const [_, attrs] = node;
|
|
4
|
+
const { title, src, alt, ...rest } = attrs;
|
|
5
|
+
const attrsString = Object.keys(rest).length > 0
|
|
6
|
+
? comarkAttributes(rest)
|
|
7
|
+
: '';
|
|
8
|
+
return title ? `` : `${attrsString}`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { code } from "./code.js";
|
|
2
|
+
import { pre } from "./pre.js";
|
|
3
|
+
import { hr } from "./hr.js";
|
|
4
|
+
import { heading } from "./heading.js";
|
|
5
|
+
import { p } from "./p.js";
|
|
6
|
+
import { a } from "./a.js";
|
|
7
|
+
import { ul } from "./ul.js";
|
|
8
|
+
import { ol } from "./ol.js";
|
|
9
|
+
import { li } from "./li.js";
|
|
10
|
+
import { html } from "./html.js";
|
|
11
|
+
import { strong } from "./strong.js";
|
|
12
|
+
import { emphesis } from "./emphesis.js";
|
|
13
|
+
import { blockquote } from "./blockquote.js";
|
|
14
|
+
import { img } from "./img.js";
|
|
15
|
+
import { del } from "./del.js";
|
|
16
|
+
import { mdc } from "./mdc.js";
|
|
17
|
+
import { br } from "./br.js";
|
|
18
|
+
import { template } from "./template.js";
|
|
19
|
+
import { table, thead, tbody, tr, th, td } from "./table.js";
|
|
20
|
+
import { comment } from "./comment.js";
|
|
21
|
+
import { math } from "./math.js";
|
|
22
|
+
import { mermaid } from "./mermaid.js";
|
|
23
|
+
export const handlers = {
|
|
24
|
+
code,
|
|
25
|
+
pre,
|
|
26
|
+
hr,
|
|
27
|
+
br,
|
|
28
|
+
h1: heading,
|
|
29
|
+
h2: heading,
|
|
30
|
+
h3: heading,
|
|
31
|
+
h4: heading,
|
|
32
|
+
h5: heading,
|
|
33
|
+
h6: heading,
|
|
34
|
+
p,
|
|
35
|
+
a,
|
|
36
|
+
ul,
|
|
37
|
+
ol,
|
|
38
|
+
li,
|
|
39
|
+
html,
|
|
40
|
+
strong,
|
|
41
|
+
em: emphesis,
|
|
42
|
+
blockquote,
|
|
43
|
+
img,
|
|
44
|
+
del,
|
|
45
|
+
mdc,
|
|
46
|
+
template,
|
|
47
|
+
table,
|
|
48
|
+
thead,
|
|
49
|
+
tbody,
|
|
50
|
+
tr,
|
|
51
|
+
th,
|
|
52
|
+
td,
|
|
53
|
+
comment,
|
|
54
|
+
math,
|
|
55
|
+
mermaid,
|
|
56
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export async function li(node, state) {
|
|
2
|
+
const children = node.slice(2);
|
|
3
|
+
const order = state.context.order;
|
|
4
|
+
let prefix = order ? `${order}. ` : '- ';
|
|
5
|
+
const className = node[1].className && Array.isArray(node[1].className)
|
|
6
|
+
? node[1].className.join(' ')
|
|
7
|
+
: String(node[1].className || node[1].class);
|
|
8
|
+
const taskList = className.includes('task-list-item');
|
|
9
|
+
if (taskList) {
|
|
10
|
+
const input = children.shift();
|
|
11
|
+
prefix += input[1].checked || input[1][':checked'] ? '[x] ' : '[ ] ';
|
|
12
|
+
}
|
|
13
|
+
let result = '';
|
|
14
|
+
for (const child of children) {
|
|
15
|
+
result += await state.one(child, state, node);
|
|
16
|
+
}
|
|
17
|
+
result = result.trim();
|
|
18
|
+
if (!order) {
|
|
19
|
+
result = escapeLeadingNumberDot(result);
|
|
20
|
+
}
|
|
21
|
+
if (order) {
|
|
22
|
+
state.applyContext({ order: order + 1 });
|
|
23
|
+
}
|
|
24
|
+
return `${prefix}${result}\n`;
|
|
25
|
+
}
|
|
26
|
+
function escapeLeadingNumberDot(str) {
|
|
27
|
+
if (str.length === 0)
|
|
28
|
+
return str;
|
|
29
|
+
const len = str.length;
|
|
30
|
+
const firstChar = str.charCodeAt(0);
|
|
31
|
+
if (firstChar < 48 || firstChar > 57)
|
|
32
|
+
return str; // Not a digit
|
|
33
|
+
let i = 1;
|
|
34
|
+
for (; i < len; i++) {
|
|
35
|
+
const code = str.charCodeAt(i);
|
|
36
|
+
if (code < 48 || code > 57)
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (i < len && str[i] === '.') {
|
|
40
|
+
return str.slice(0, i) + '\\.' + str.slice(i + 1);
|
|
41
|
+
}
|
|
42
|
+
return str;
|
|
43
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { textContent } from 'comark/utils';
|
|
2
|
+
export function math(node, state, parent) {
|
|
3
|
+
const content = textContent(node);
|
|
4
|
+
if (parent?.some((child, index) => index > 1 && typeof child === 'string')) {
|
|
5
|
+
return `$$${content}$$`;
|
|
6
|
+
}
|
|
7
|
+
return `$$\n${content}\n$$${state.context.blockSeparator}`;
|
|
8
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { indent } from "../indent.js";
|
|
2
|
+
import { comarkAttributes, comarkYamlAttributes } from "../attributes.js";
|
|
3
|
+
import { html } from "./html.js";
|
|
4
|
+
// HTML elements that always create an inline context for their children
|
|
5
|
+
const INLINE_HTML_ELEMENTS = new Set(['a', 'strong', 'em', 'span']);
|
|
6
|
+
export async function mdc(node, state, parent) {
|
|
7
|
+
const [tag, attr, ...children] = node;
|
|
8
|
+
const { $, ...attributes } = attr;
|
|
9
|
+
if (tag === 'table') {
|
|
10
|
+
return html(node, state);
|
|
11
|
+
}
|
|
12
|
+
const attributeEntries = Object.entries(attributes);
|
|
13
|
+
const hasObjectAttributes = attributeEntries.some(([, value]) => typeof value === 'object');
|
|
14
|
+
// Component is inline if it has text siblings in parent
|
|
15
|
+
// or is inside an inline HTML element
|
|
16
|
+
const hasTextSiblings = parent?.some((child, index) => index > 1 && typeof child === 'string') ?? false;
|
|
17
|
+
const insideInlineElement = parent !== undefined && INLINE_HTML_ELEMENTS.has(String(parent[0]));
|
|
18
|
+
let inline = hasTextSiblings || insideInlineElement;
|
|
19
|
+
// if component has object attributes, it cannot be inline
|
|
20
|
+
if (hasObjectAttributes) {
|
|
21
|
+
inline = false;
|
|
22
|
+
}
|
|
23
|
+
let content = '';
|
|
24
|
+
const childState = { ...state, nodeDepthInTree: (state.nodeDepthInTree || 0) + 1 };
|
|
25
|
+
for (const child of children) {
|
|
26
|
+
content += await state.one(child, childState, node);
|
|
27
|
+
}
|
|
28
|
+
content = content.trimEnd();
|
|
29
|
+
const attrs = attributeEntries.length > 0 ? comarkAttributes(attributes) : '';
|
|
30
|
+
if (tag === 'span') {
|
|
31
|
+
return `[${content}]${attrs}`;
|
|
32
|
+
}
|
|
33
|
+
const fence = ':'.repeat((state.nodeDepthInTree || 0) + 2);
|
|
34
|
+
let result = `:${tag}${content && `[${content}]`}${attrs}` + (!parent ? state.context.blockSeparator : '');
|
|
35
|
+
if (!inline) {
|
|
36
|
+
const maxInlineAttributes = state.context.maxInlineAttributes ?? 3;
|
|
37
|
+
const useYaml = hasObjectAttributes || maxInlineAttributes === 0 || attributeEntries.length > maxInlineAttributes;
|
|
38
|
+
if (useYaml) {
|
|
39
|
+
const yamlAttrs = comarkYamlAttributes(attributes);
|
|
40
|
+
result = `${fence}${tag}\n${yamlAttrs}${content ? `\n${content}` : ''}\n${fence}` + state.context.blockSeparator;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
result = `${fence}${tag}${attrs}${content ? `\n${content}` : ''}\n${fence}` + state.context.blockSeparator;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return inline ? result : indent(result, { level: parent ? 1 : 0 });
|
|
47
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { comarkAttributes } from '../attributes';
|
|
2
|
+
const fence = '```';
|
|
3
|
+
export function mermaid(node, state) {
|
|
4
|
+
const [_, attributes] = node;
|
|
5
|
+
const { content, ...rest } = attributes;
|
|
6
|
+
const attrs = comarkAttributes(rest);
|
|
7
|
+
return `${fence}mermaid${attrs ? ` ${attrs}` : ''}\n${content}\n${fence}${state.context.blockSeparator}`;
|
|
8
|
+
}
|