@antdv-next/x-markdown 0.0.2 → 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -1
- package/dist/XMarkdown/components/AnimationText.js +5 -0
- package/dist/XMarkdown/components/AnimationText.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/XMarkdown/components/DebugPanel.js +7 -0
- package/dist/XMarkdown/components/DebugPanel.vue_vue_type_script_setup_true_lang.js +128 -0
- package/dist/XMarkdown/components/DebugPanel.vue_vue_type_style_index_0_scoped_d6923a13_lang.css +39 -0
- package/dist/XMarkdown/components/TailIndicator.js +7 -0
- package/dist/XMarkdown/components/TailIndicator.vue_vue_type_script_setup_true_lang.js +14 -0
- package/dist/XMarkdown/components/TailIndicator.vue_vue_type_style_index_0_scoped_802ace82_lang.css +6 -0
- package/dist/XMarkdown/components/index.js +4 -0
- package/dist/XMarkdown/composables/index.d.ts +4 -0
- package/dist/XMarkdown/composables/index.js +5 -0
- package/dist/XMarkdown/composables/useParser.js +24 -0
- package/dist/XMarkdown/composables/useRenderer.js +24 -0
- package/dist/XMarkdown/composables/useStreaming.js +215 -0
- package/dist/XMarkdown/composables/useTail.js +25 -0
- package/dist/XMarkdown/core/Parser.js +162 -0
- package/dist/XMarkdown/core/VueRenderer.js +223 -0
- package/dist/XMarkdown/core/detectUnclosedComponentTags.js +141 -0
- package/dist/XMarkdown/core/index.d.ts +3 -0
- package/dist/XMarkdown/core/index.js +4 -0
- package/dist/XMarkdown/index.d.ts +14 -0
- package/dist/XMarkdown/index.js +13 -0
- package/dist/XMarkdown/index.vue_vue_type_script_setup_true_lang.js +135 -0
- package/dist/XMarkdown/index2.js +5 -0
- package/dist/XMarkdown/interface.js +14 -0
- package/dist/XMarkdown/utils/tail.js +9 -0
- package/dist/_virtual/_plugin-vue_export-helper.js +8 -0
- package/dist/env.d.js +0 -0
- package/dist/index.js +12 -1137
- package/package.json +9 -8
- package/{dist → themes}/index.css +0 -45
- /package/{dist/plugins → plugins}/Latex/index.d.ts +0 -0
- /package/{dist/plugins → plugins}/Latex/index.js +0 -0
- /package/{dist/themes → themes}/dark.css +0 -0
- /package/{dist/themes → themes}/light.css +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Marked } from "marked";
|
|
2
|
+
//#region src/XMarkdown/core/Parser.ts
|
|
3
|
+
var escapeReplacements = {
|
|
4
|
+
"&": "&",
|
|
5
|
+
"<": "<",
|
|
6
|
+
">": ">",
|
|
7
|
+
"\"": """,
|
|
8
|
+
"'": "'"
|
|
9
|
+
};
|
|
10
|
+
function escapeHtml(html) {
|
|
11
|
+
if (/[&<>"']/.test(html)) return html.replace(/[&<>"']/g, (ch) => escapeReplacements[ch] || ch);
|
|
12
|
+
return html;
|
|
13
|
+
}
|
|
14
|
+
var Parser = class Parser {
|
|
15
|
+
static COMPLETE_FENCED_CODE = /^ {0,3}(`{3,}|~{3,})([\s\S]*?)\n {0,3}\1[ \n\t]*$/;
|
|
16
|
+
options;
|
|
17
|
+
markdownInstance;
|
|
18
|
+
injectTail = false;
|
|
19
|
+
codeBlockStates = [];
|
|
20
|
+
codeBlockStateIndex = 0;
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.options = {
|
|
23
|
+
openLinksInNewTab: options.openLinksInNewTab ?? true,
|
|
24
|
+
paragraphTag: options.paragraphTag ?? "p",
|
|
25
|
+
injectTail: options.injectTail ?? false,
|
|
26
|
+
protectCustomTags: options.protectCustomTags ?? true,
|
|
27
|
+
escapeRawHtml: options.escapeRawHtml ?? false,
|
|
28
|
+
config: {
|
|
29
|
+
gfm: true,
|
|
30
|
+
...options.config
|
|
31
|
+
},
|
|
32
|
+
components: options.components ?? {},
|
|
33
|
+
streamStatus: options.streamStatus ?? "done",
|
|
34
|
+
codeBlockStatus: options.codeBlockStatus ?? {}
|
|
35
|
+
};
|
|
36
|
+
this.markdownInstance = new Marked(this.options.config);
|
|
37
|
+
this.configureRenderers();
|
|
38
|
+
}
|
|
39
|
+
updateMarkedConfig(config) {
|
|
40
|
+
this.options.config = {
|
|
41
|
+
...this.options.config,
|
|
42
|
+
...config
|
|
43
|
+
};
|
|
44
|
+
this.markdownInstance = new Marked(this.options.config);
|
|
45
|
+
this.configureRenderers();
|
|
46
|
+
}
|
|
47
|
+
configureRenderers() {
|
|
48
|
+
this.markdownInstance.use({ renderer: {
|
|
49
|
+
html: (html) => {
|
|
50
|
+
if (this.options.escapeRawHtml) return escapeHtml(html);
|
|
51
|
+
return html;
|
|
52
|
+
},
|
|
53
|
+
link: (href, title, text) => {
|
|
54
|
+
return `<a href="${href}"${title ? ` title="${title}"` : ""}${this.options.openLinksInNewTab ? ` target="_blank" rel="noopener noreferrer"` : ""}>${text}</a>`;
|
|
55
|
+
},
|
|
56
|
+
paragraph: (text) => {
|
|
57
|
+
if (this.options.paragraphTag === "p") return `<p>${text}</p>`;
|
|
58
|
+
return `<${this.options.paragraphTag}>${text}</${this.options.paragraphTag}>`;
|
|
59
|
+
},
|
|
60
|
+
code: (code, infostring, escaped) => {
|
|
61
|
+
const lang = infostring || "";
|
|
62
|
+
return `<pre><code class="language-${lang}"${lang ? ` data-lang="${lang}"` : ""} data-block="true"${` data-state="${this.consumeCodeBlockState(lang)}"`}>${escaped ? code : escapeHtml(code)}</code></pre>`;
|
|
63
|
+
}
|
|
64
|
+
} });
|
|
65
|
+
}
|
|
66
|
+
prepareCodeBlockStates(markdown) {
|
|
67
|
+
this.codeBlockStates = [];
|
|
68
|
+
this.codeBlockStateIndex = 0;
|
|
69
|
+
const tokens = this.markdownInstance.lexer(markdown);
|
|
70
|
+
const walkTokens = (nodes) => {
|
|
71
|
+
for (const node of nodes) {
|
|
72
|
+
if (!node || typeof node !== "object") continue;
|
|
73
|
+
const token = node;
|
|
74
|
+
if (token.type === "code") {
|
|
75
|
+
const lang = typeof token.lang === "string" ? token.lang : "";
|
|
76
|
+
const codeBlockStyle = typeof token.codeBlockStyle === "string" ? token.codeBlockStyle : "";
|
|
77
|
+
const raw = typeof token.raw === "string" ? token.raw : "";
|
|
78
|
+
const inferredState = codeBlockStyle === "indented" || Parser.COMPLETE_FENCED_CODE.test(raw) ? "done" : "loading";
|
|
79
|
+
this.codeBlockStates.push(this.options.codeBlockStatus[lang] ?? inferredState);
|
|
80
|
+
}
|
|
81
|
+
const childTokens = token.tokens;
|
|
82
|
+
if (Array.isArray(childTokens)) walkTokens(childTokens);
|
|
83
|
+
const items = token.items;
|
|
84
|
+
if (Array.isArray(items)) for (const item of items) {
|
|
85
|
+
if (!item || typeof item !== "object") continue;
|
|
86
|
+
const itemTokens = item.tokens;
|
|
87
|
+
if (Array.isArray(itemTokens)) walkTokens(itemTokens);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
walkTokens(tokens);
|
|
92
|
+
}
|
|
93
|
+
consumeCodeBlockState(lang) {
|
|
94
|
+
const nextState = this.codeBlockStates[this.codeBlockStateIndex];
|
|
95
|
+
this.codeBlockStateIndex += 1;
|
|
96
|
+
return nextState ?? this.getCodeBlockState(lang);
|
|
97
|
+
}
|
|
98
|
+
getCodeBlockState(lang) {
|
|
99
|
+
return this.options.codeBlockStatus[lang] ?? this.options.streamStatus;
|
|
100
|
+
}
|
|
101
|
+
parse(markdown, parseOptions) {
|
|
102
|
+
this.injectTail = parseOptions?.injectTail ?? false;
|
|
103
|
+
let processed = markdown;
|
|
104
|
+
if (this.options.protectCustomTags) processed = this.protectCustomTags(processed);
|
|
105
|
+
if (this.options.escapeRawHtml) processed = this.escapeRawHtml(processed);
|
|
106
|
+
this.prepareCodeBlockStates(processed);
|
|
107
|
+
const html = this.markedParse(processed);
|
|
108
|
+
if (this.injectTail) return this.injectTailMarker(html);
|
|
109
|
+
return html;
|
|
110
|
+
}
|
|
111
|
+
markedParse(markdown) {
|
|
112
|
+
return this.markdownInstance.parse(markdown);
|
|
113
|
+
}
|
|
114
|
+
protectCustomTags(markdown) {
|
|
115
|
+
const customTagPattern = /<[A-Z][a-zA-Z0-9]*[^>]*>[\s\S]*?<\/[A-Z][a-zA-Z0-9]*>/g;
|
|
116
|
+
let result = markdown;
|
|
117
|
+
let placeholderIndex = 0;
|
|
118
|
+
result = result.replace(customTagPattern, () => {
|
|
119
|
+
const placeholder = `__CUSTOM_TAG_${placeholderIndex}__`;
|
|
120
|
+
placeholderIndex++;
|
|
121
|
+
return placeholder;
|
|
122
|
+
});
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
escapeRawHtml(markdown) {
|
|
126
|
+
return markdown.replace(/<[^>]+>/g, (match) => {
|
|
127
|
+
return escapeHtml(match);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
injectTailMarker(html) {
|
|
131
|
+
if (html.includes("<xmd-tail></xmd-tail>")) return html;
|
|
132
|
+
const container = document.createElement("div");
|
|
133
|
+
container.innerHTML = html;
|
|
134
|
+
const findLastMeaningfulNode = (root) => {
|
|
135
|
+
const children = Array.from(root.childNodes);
|
|
136
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
137
|
+
const node = children[i];
|
|
138
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
139
|
+
if ((node.textContent || "").trim()) return node;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
143
|
+
const nested = findLastMeaningfulNode(node);
|
|
144
|
+
if (nested) return nested;
|
|
145
|
+
return node;
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
};
|
|
149
|
+
const lastMeaningfulNode = findLastMeaningfulNode(container);
|
|
150
|
+
if (!lastMeaningfulNode || lastMeaningfulNode.nodeType !== Node.TEXT_NODE) return html;
|
|
151
|
+
const marker = document.createElement("xmd-tail");
|
|
152
|
+
lastMeaningfulNode.parentNode?.insertBefore(marker, lastMeaningfulNode.nextSibling);
|
|
153
|
+
return container.innerHTML;
|
|
154
|
+
}
|
|
155
|
+
setOptions(options) {
|
|
156
|
+
const { config, ...rest } = options;
|
|
157
|
+
Object.assign(this.options, rest);
|
|
158
|
+
if (config) this.updateMarkedConfig(config);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { Parser, escapeHtml };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import AnimationText_default from "../components/AnimationText.js";
|
|
2
|
+
import { detectUnclosedComponentTags, getTagInstanceId } from "./detectUnclosedComponentTags.js";
|
|
3
|
+
import { Fragment, createTextVNode, h } from "vue";
|
|
4
|
+
import DOMPurify from "dompurify";
|
|
5
|
+
//#region src/XMarkdown/core/VueRenderer.ts
|
|
6
|
+
var DEFAULT_ANIMATION_DURATION = 200;
|
|
7
|
+
var NON_WHITESPACE_REGEX = /[^\r\n\s]+/;
|
|
8
|
+
var MATHML_TAGS = [
|
|
9
|
+
"math",
|
|
10
|
+
"maction",
|
|
11
|
+
"maligngroup",
|
|
12
|
+
"malignmark",
|
|
13
|
+
"menclose",
|
|
14
|
+
"merror",
|
|
15
|
+
"mfenced",
|
|
16
|
+
"mfrac",
|
|
17
|
+
"mglyph",
|
|
18
|
+
"mi",
|
|
19
|
+
"mlabeledtr",
|
|
20
|
+
"mlongdiv",
|
|
21
|
+
"mmultiscripts",
|
|
22
|
+
"mn",
|
|
23
|
+
"mo",
|
|
24
|
+
"mover",
|
|
25
|
+
"mpadded",
|
|
26
|
+
"mphantom",
|
|
27
|
+
"mprescripts",
|
|
28
|
+
"mroot",
|
|
29
|
+
"mrow",
|
|
30
|
+
"ms",
|
|
31
|
+
"mscarries",
|
|
32
|
+
"mscarry",
|
|
33
|
+
"msgroup",
|
|
34
|
+
"msline",
|
|
35
|
+
"mspace",
|
|
36
|
+
"msqrt",
|
|
37
|
+
"msrow",
|
|
38
|
+
"mstack",
|
|
39
|
+
"mstyle",
|
|
40
|
+
"msub",
|
|
41
|
+
"msup",
|
|
42
|
+
"msubsup",
|
|
43
|
+
"mtable",
|
|
44
|
+
"mtd",
|
|
45
|
+
"mtext",
|
|
46
|
+
"mtr",
|
|
47
|
+
"munder",
|
|
48
|
+
"munderover",
|
|
49
|
+
"semantics",
|
|
50
|
+
"annotation",
|
|
51
|
+
"annotation-xml"
|
|
52
|
+
];
|
|
53
|
+
var VueRenderer = class {
|
|
54
|
+
options;
|
|
55
|
+
constructor(options = {}) {
|
|
56
|
+
this.options = {
|
|
57
|
+
components: options.components ?? {},
|
|
58
|
+
enableAnimation: options.enableAnimation ?? true,
|
|
59
|
+
animationConfig: {
|
|
60
|
+
fadeDuration: options.animationConfig?.fadeDuration ?? DEFAULT_ANIMATION_DURATION,
|
|
61
|
+
easing: options.animationConfig?.easing ?? "ease-in-out"
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
render(html) {
|
|
66
|
+
const unclosedTags = detectUnclosedComponentTags(html, Object.keys(this.options.components));
|
|
67
|
+
const sanitized = this.sanitize(html);
|
|
68
|
+
const nodes = this.parseToVNodes(sanitized, unclosedTags);
|
|
69
|
+
if (nodes.length === 0) return h("span", "");
|
|
70
|
+
if (nodes.length === 1) return nodes[0];
|
|
71
|
+
return h(Fragment, nodes);
|
|
72
|
+
}
|
|
73
|
+
sanitize(html) {
|
|
74
|
+
const allowedTags = [...new Set([
|
|
75
|
+
...Object.keys(this.options.components),
|
|
76
|
+
"p",
|
|
77
|
+
"br",
|
|
78
|
+
"span",
|
|
79
|
+
"div",
|
|
80
|
+
"a",
|
|
81
|
+
"img",
|
|
82
|
+
"ul",
|
|
83
|
+
"ol",
|
|
84
|
+
"li",
|
|
85
|
+
"h1",
|
|
86
|
+
"h2",
|
|
87
|
+
"h3",
|
|
88
|
+
"h4",
|
|
89
|
+
"h5",
|
|
90
|
+
"h6",
|
|
91
|
+
"blockquote",
|
|
92
|
+
"pre",
|
|
93
|
+
"code",
|
|
94
|
+
"em",
|
|
95
|
+
"strong",
|
|
96
|
+
"del",
|
|
97
|
+
"ins",
|
|
98
|
+
"table",
|
|
99
|
+
"thead",
|
|
100
|
+
"tbody",
|
|
101
|
+
"tr",
|
|
102
|
+
"th",
|
|
103
|
+
"td",
|
|
104
|
+
"hr",
|
|
105
|
+
"xmd-tail",
|
|
106
|
+
...MATHML_TAGS
|
|
107
|
+
])];
|
|
108
|
+
return DOMPurify.sanitize(html, {
|
|
109
|
+
ALLOWED_TAGS: allowedTags,
|
|
110
|
+
ALLOWED_ATTR: [
|
|
111
|
+
"class",
|
|
112
|
+
"style",
|
|
113
|
+
"aria-hidden",
|
|
114
|
+
"aria-label",
|
|
115
|
+
"role",
|
|
116
|
+
"xmlns",
|
|
117
|
+
"encoding",
|
|
118
|
+
"href",
|
|
119
|
+
"src",
|
|
120
|
+
"title",
|
|
121
|
+
"alt",
|
|
122
|
+
"target",
|
|
123
|
+
"rel",
|
|
124
|
+
"data-lang",
|
|
125
|
+
"data-block",
|
|
126
|
+
"data-state",
|
|
127
|
+
"data-raw",
|
|
128
|
+
"data-icon",
|
|
129
|
+
"data-description",
|
|
130
|
+
"icon",
|
|
131
|
+
"description"
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
parseToVNodes(html, unclosedTags) {
|
|
136
|
+
const template = `<div>${html}</div>`;
|
|
137
|
+
const container = document.createElement("div");
|
|
138
|
+
container.innerHTML = template;
|
|
139
|
+
const cidRef = { tagIndexes: {} };
|
|
140
|
+
const nodes = [];
|
|
141
|
+
Array.from(container.childNodes).forEach((node) => {
|
|
142
|
+
const vnode = this.convertNode(node, unclosedTags, cidRef);
|
|
143
|
+
if (vnode) nodes.push(vnode);
|
|
144
|
+
});
|
|
145
|
+
return nodes;
|
|
146
|
+
}
|
|
147
|
+
convertNode(domNode, unclosedTags, cidRef) {
|
|
148
|
+
if (domNode.nodeType === Node.TEXT_NODE) {
|
|
149
|
+
const text = domNode.textContent || "";
|
|
150
|
+
const parentTagName = domNode.parentNode instanceof Element ? domNode.parentNode.tagName.toLowerCase() : "";
|
|
151
|
+
const isParentCustomComponent = Boolean(parentTagName && this.options.components[parentTagName]);
|
|
152
|
+
if (this.options.enableAnimation && NON_WHITESPACE_REGEX.test(text) && !isParentCustomComponent) return this.wrapWithAnimation(text);
|
|
153
|
+
return createTextVNode(text);
|
|
154
|
+
}
|
|
155
|
+
if (domNode.nodeType !== Node.ELEMENT_NODE) return null;
|
|
156
|
+
const element = domNode;
|
|
157
|
+
const tagName = element.tagName.toLowerCase();
|
|
158
|
+
const customComponent = this.options.components[tagName];
|
|
159
|
+
if (customComponent) {
|
|
160
|
+
const componentProps = this.extractComponentProps(element, tagName, unclosedTags, cidRef);
|
|
161
|
+
const children = [];
|
|
162
|
+
Array.from(element.childNodes).forEach((child) => {
|
|
163
|
+
const childVNode = this.convertNode(child, unclosedTags, cidRef);
|
|
164
|
+
if (childVNode) children.push(childVNode);
|
|
165
|
+
});
|
|
166
|
+
if (children.length === 0) return h(customComponent, componentProps);
|
|
167
|
+
return h(customComponent, componentProps, { default: () => children });
|
|
168
|
+
}
|
|
169
|
+
if (tagName === "xmd-tail") return h("span", { class: "xmd-tail" }, "▋");
|
|
170
|
+
return this.convertNativeElement(element, tagName, unclosedTags, cidRef);
|
|
171
|
+
}
|
|
172
|
+
convertNativeElement(element, tagName, unclosedTags, cidRef) {
|
|
173
|
+
const children = [];
|
|
174
|
+
const props = {};
|
|
175
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
176
|
+
props[attr.name] = attr.value;
|
|
177
|
+
});
|
|
178
|
+
Array.from(element.childNodes).forEach((child) => {
|
|
179
|
+
const childVNode = this.convertNode(child, unclosedTags, cidRef);
|
|
180
|
+
if (childVNode) children.push(childVNode);
|
|
181
|
+
});
|
|
182
|
+
return h(tagName, props, children);
|
|
183
|
+
}
|
|
184
|
+
extractComponentProps(element, tagName, unclosedTags, cidRef) {
|
|
185
|
+
const props = {};
|
|
186
|
+
cidRef.tagIndexes[tagName] = (cidRef.tagIndexes[tagName] ?? 0) + 1;
|
|
187
|
+
const instanceId = getTagInstanceId(tagName, cidRef.tagIndexes[tagName]);
|
|
188
|
+
props.streamStatus = unclosedTags.has(instanceId) ? "loading" : "done";
|
|
189
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
190
|
+
props[attr.name] = attr.value;
|
|
191
|
+
});
|
|
192
|
+
if (tagName === "code") {
|
|
193
|
+
props.block = element.getAttribute("data-block") === "true";
|
|
194
|
+
props.streamStatus = element.getAttribute("data-state") === "loading" ? "loading" : "done";
|
|
195
|
+
const langFromData = element.getAttribute("data-lang") || "";
|
|
196
|
+
const className = element.getAttribute("class") || "";
|
|
197
|
+
const langFromClass = className.match(/(?:^|\s)language-([^\s]+)/)?.[1] ?? className.match(/(?:^|\s)lang-([^\s]+)/)?.[1] ?? "";
|
|
198
|
+
const lang = langFromData || langFromClass;
|
|
199
|
+
if (lang) props.lang = lang;
|
|
200
|
+
}
|
|
201
|
+
return props;
|
|
202
|
+
}
|
|
203
|
+
wrapWithAnimation(text) {
|
|
204
|
+
return h(AnimationText_default, {
|
|
205
|
+
text,
|
|
206
|
+
fadeDuration: this.options.animationConfig.fadeDuration,
|
|
207
|
+
easing: this.options.animationConfig.easing
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
setOptions(options) {
|
|
211
|
+
if (options.components) this.options.components = {
|
|
212
|
+
...this.options.components,
|
|
213
|
+
...options.components
|
|
214
|
+
};
|
|
215
|
+
if (options.enableAnimation !== void 0) this.options.enableAnimation = options.enableAnimation;
|
|
216
|
+
if (options.animationConfig) this.options.animationConfig = {
|
|
217
|
+
...this.options.animationConfig,
|
|
218
|
+
...options.animationConfig
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
//#endregion
|
|
223
|
+
export { VueRenderer };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
//#region src/XMarkdown/core/detectUnclosedComponentTags.ts
|
|
2
|
+
var WHITESPACE_REGEX = /\s/;
|
|
3
|
+
var TAG_NAME_CHAR_REGEX = /[a-zA-Z0-9-]/;
|
|
4
|
+
var VOID_ELEMENTS = new Set([
|
|
5
|
+
"area",
|
|
6
|
+
"base",
|
|
7
|
+
"br",
|
|
8
|
+
"col",
|
|
9
|
+
"embed",
|
|
10
|
+
"hr",
|
|
11
|
+
"img",
|
|
12
|
+
"input",
|
|
13
|
+
"link",
|
|
14
|
+
"meta",
|
|
15
|
+
"param",
|
|
16
|
+
"source",
|
|
17
|
+
"track",
|
|
18
|
+
"wbr"
|
|
19
|
+
]);
|
|
20
|
+
var COMMENT_START = "<!--";
|
|
21
|
+
var COMMENT_END = "-->";
|
|
22
|
+
var CDATA_START = "<![CDATA[";
|
|
23
|
+
var CDATA_END = "]]>";
|
|
24
|
+
var getTagInstanceId = (tagName, instance) => `${tagName}-${instance}`;
|
|
25
|
+
var skipComment = (html, pos) => {
|
|
26
|
+
if (!html.startsWith(COMMENT_START, pos)) return pos;
|
|
27
|
+
const endPos = html.indexOf(COMMENT_END, pos + 4);
|
|
28
|
+
return endPos === -1 ? html.length : endPos + 3;
|
|
29
|
+
};
|
|
30
|
+
var skipCDATA = (html, pos) => {
|
|
31
|
+
if (!html.startsWith(CDATA_START, pos)) return pos;
|
|
32
|
+
const endPos = html.indexOf(CDATA_END, pos + 9);
|
|
33
|
+
return endPos === -1 ? html.length : endPos + 3;
|
|
34
|
+
};
|
|
35
|
+
var parseClosingTag = (html, pos) => {
|
|
36
|
+
if (html[pos + 1] !== "/") return null;
|
|
37
|
+
let scanPos = pos + 2;
|
|
38
|
+
let tagName = "";
|
|
39
|
+
while (scanPos < html.length && WHITESPACE_REGEX.test(html[scanPos])) scanPos++;
|
|
40
|
+
while (scanPos < html.length && TAG_NAME_CHAR_REGEX.test(html[scanPos])) {
|
|
41
|
+
tagName += html[scanPos];
|
|
42
|
+
scanPos++;
|
|
43
|
+
}
|
|
44
|
+
while (scanPos < html.length && WHITESPACE_REGEX.test(html[scanPos])) scanPos++;
|
|
45
|
+
if (!tagName || html[scanPos] !== ">") return null;
|
|
46
|
+
return {
|
|
47
|
+
tagName: tagName.toLowerCase(),
|
|
48
|
+
endPos: scanPos + 1
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
var parseOpeningTag = (html, pos) => {
|
|
52
|
+
let scanPos = pos + 1;
|
|
53
|
+
let tagName = "";
|
|
54
|
+
while (scanPos < html.length && TAG_NAME_CHAR_REGEX.test(html[scanPos])) {
|
|
55
|
+
tagName += html[scanPos];
|
|
56
|
+
scanPos++;
|
|
57
|
+
}
|
|
58
|
+
if (!tagName) return null;
|
|
59
|
+
let foundEnd = false;
|
|
60
|
+
let isSelfClosing = false;
|
|
61
|
+
while (scanPos < html.length) {
|
|
62
|
+
if (html[scanPos] === ">") {
|
|
63
|
+
foundEnd = true;
|
|
64
|
+
isSelfClosing = html[scanPos - 1] === "/";
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (html[scanPos] === "\"" || html[scanPos] === "'") {
|
|
68
|
+
const quoteChar = html[scanPos];
|
|
69
|
+
scanPos++;
|
|
70
|
+
while (scanPos < html.length) {
|
|
71
|
+
if (html[scanPos] === "\\" && scanPos + 1 < html.length) {
|
|
72
|
+
scanPos += 2;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (html[scanPos] === quoteChar) {
|
|
76
|
+
scanPos++;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
scanPos++;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
scanPos++;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
tagName: tagName.toLowerCase(),
|
|
87
|
+
endPos: foundEnd ? scanPos + 1 : html.length,
|
|
88
|
+
foundEnd,
|
|
89
|
+
isSelfClosing
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
function detectUnclosedComponentTags(html, componentNames) {
|
|
93
|
+
const trackedTags = new Set(Array.from(componentNames, (tagName) => tagName.toLowerCase()));
|
|
94
|
+
if (trackedTags.size === 0 || html.length === 0) return /* @__PURE__ */ new Set();
|
|
95
|
+
const unclosedTags = /* @__PURE__ */ new Set();
|
|
96
|
+
const tagCounts = {};
|
|
97
|
+
const openTagIndexes = {};
|
|
98
|
+
let pos = 0;
|
|
99
|
+
while (pos < html.length) {
|
|
100
|
+
const afterComment = skipComment(html, pos);
|
|
101
|
+
if (afterComment !== pos) {
|
|
102
|
+
pos = afterComment;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const afterCDATA = skipCDATA(html, pos);
|
|
106
|
+
if (afterCDATA !== pos) {
|
|
107
|
+
pos = afterCDATA;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (html[pos] !== "<") {
|
|
111
|
+
pos++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const closingTag = parseClosingTag(html, pos);
|
|
115
|
+
if (closingTag) {
|
|
116
|
+
const pendingIndexes = openTagIndexes[closingTag.tagName];
|
|
117
|
+
if (trackedTags.has(closingTag.tagName) && pendingIndexes?.length) pendingIndexes.pop();
|
|
118
|
+
pos = closingTag.endPos;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const openingTag = parseOpeningTag(html, pos);
|
|
122
|
+
if (!openingTag || !trackedTags.has(openingTag.tagName)) {
|
|
123
|
+
pos++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
tagCounts[openingTag.tagName] = (tagCounts[openingTag.tagName] ?? 0) + 1;
|
|
127
|
+
const instance = tagCounts[openingTag.tagName];
|
|
128
|
+
if (!openingTag.foundEnd) unclosedTags.add(getTagInstanceId(openingTag.tagName, instance));
|
|
129
|
+
else if (!openingTag.isSelfClosing && !VOID_ELEMENTS.has(openingTag.tagName)) {
|
|
130
|
+
openTagIndexes[openingTag.tagName] ??= [];
|
|
131
|
+
openTagIndexes[openingTag.tagName].push(instance);
|
|
132
|
+
}
|
|
133
|
+
pos = openingTag.endPos;
|
|
134
|
+
}
|
|
135
|
+
for (const [tagName, pendingIndexes] of Object.entries(openTagIndexes)) pendingIndexes.forEach((instance) => {
|
|
136
|
+
unclosedTags.add(getTagInstanceId(tagName, instance));
|
|
137
|
+
});
|
|
138
|
+
return unclosedTags;
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { detectUnclosedComponentTags, getTagInstanceId };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { default as XMarkdown } from './index.vue';
|
|
2
|
+
export type { XMarkdownProps } from './interface';
|
|
3
|
+
export type { StreamingOption } from './interface';
|
|
4
|
+
export type { MarkedConfig } from './interface';
|
|
5
|
+
export type { ComponentProps } from './interface';
|
|
6
|
+
export { Parser } from './core/Parser';
|
|
7
|
+
export { VueRenderer } from './core/VueRenderer';
|
|
8
|
+
export { detectUnclosedComponentTags } from './core/detectUnclosedComponentTags';
|
|
9
|
+
export { useStreaming } from './composables/useStreaming';
|
|
10
|
+
export { useParser } from './composables/useParser';
|
|
11
|
+
export { useRenderer } from './composables/useRenderer';
|
|
12
|
+
export { useTail } from './composables/useTail';
|
|
13
|
+
export { AnimationText, DebugPanel, TailIndicator } from './components';
|
|
14
|
+
export { resolveTailContent } from './utils/tail';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import AnimationText_default from "./components/AnimationText.js";
|
|
2
|
+
import DebugPanel_default from "./components/DebugPanel.js";
|
|
3
|
+
import TailIndicator_default from "./components/TailIndicator.js";
|
|
4
|
+
import { useStreaming } from "./composables/useStreaming.js";
|
|
5
|
+
import { Parser } from "./core/Parser.js";
|
|
6
|
+
import { useParser } from "./composables/useParser.js";
|
|
7
|
+
import { detectUnclosedComponentTags } from "./core/detectUnclosedComponentTags.js";
|
|
8
|
+
import { VueRenderer } from "./core/VueRenderer.js";
|
|
9
|
+
import { useRenderer } from "./composables/useRenderer.js";
|
|
10
|
+
import { useTail } from "./composables/useTail.js";
|
|
11
|
+
import XMarkdown_default from "./index2.js";
|
|
12
|
+
import { resolveTailContent } from "./utils/tail.js";
|
|
13
|
+
export { AnimationText_default as AnimationText, DebugPanel_default as DebugPanel, Parser, TailIndicator_default as TailIndicator, VueRenderer, XMarkdown_default as XMarkdown, detectUnclosedComponentTags, resolveTailContent, useParser, useRenderer, useStreaming, useTail };
|