@hejiayue/x-markdown-test 0.0.1-beta.107
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 +1090 -0
- package/dist/index-BjeepIV6.js +529 -0
- package/dist/index-BjeepIV6.js.map +1 -0
- package/dist/index-CgG50XSZ.cjs +2 -0
- package/dist/index-CgG50XSZ.cjs.map +1 -0
- package/dist/index-DtKeGkdv.cjs +2 -0
- package/dist/index-DtKeGkdv.cjs.map +1 -0
- package/dist/index-Ys7-7uFi.js +1856 -0
- package/dist/index-Ys7-7uFi.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/types/MarkdownRender/index.d.ts +441 -0
- package/dist/types/core/components.d.ts +4 -0
- package/dist/types/core/hast-to-vnode.d.ts +13 -0
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/core/useProcessor.d.ts +23 -0
- package/dist/types/hooks/index.d.ts +6 -0
- package/dist/types/hooks/useComponents.d.ts +21 -0
- package/dist/types/hooks/useHighlight.d.ts +26 -0
- package/dist/types/hooks/useMarkdown.d.ts +2 -0
- package/dist/types/hooks/useMermaid.d.ts +22 -0
- package/dist/types/hooks/usePlugins.d.ts +8 -0
- package/dist/types/hooks/useTheme.d.ts +25 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/plugins/rehypePlugin.d.ts +2 -0
- package/dist/x-markdown.cjs.js +2 -0
- package/dist/x-markdown.cjs.js.map +1 -0
- package/dist/x-markdown.es.js +20 -0
- package/dist/x-markdown.es.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,1856 @@
|
|
|
1
|
+
import { h, toValue, computed, defineComponent, toRefs, shallowRef, watch, shallowReadonly, getCurrentScope, onScopeDispose, readonly, getCurrentInstance, onMounted, unref, toRaw, ref, isRef, onUnmounted, createElementBlock, openBlock, normalizeStyle, createElementVNode, toDisplayString, normalizeClass, createTextVNode, Fragment, renderList, createCommentVNode, renderSlot, createBlock, resolveDynamicComponent, createVNode, defineAsyncComponent } from "vue";
|
|
2
|
+
import { find, svg, html } from "property-information";
|
|
3
|
+
import deepmerge from "deepmerge";
|
|
4
|
+
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
|
|
5
|
+
import remarkParse from "remark-parse";
|
|
6
|
+
import remarkRehype from "remark-rehype";
|
|
7
|
+
import { unified } from "unified";
|
|
8
|
+
import rehypeKatex from "rehype-katex";
|
|
9
|
+
import rehypeRaw from "rehype-raw";
|
|
10
|
+
import remarkBreaks from "remark-breaks";
|
|
11
|
+
import remarkGfm from "remark-gfm";
|
|
12
|
+
import remarkMath from "remark-math";
|
|
13
|
+
import { visit } from "unist-util-visit";
|
|
14
|
+
import { flow, throttle } from "lodash-es";
|
|
15
|
+
function render(hast, attrs, slots, customAttrs) {
|
|
16
|
+
const keyCounter = {};
|
|
17
|
+
return h(
|
|
18
|
+
"div",
|
|
19
|
+
attrs,
|
|
20
|
+
renderChildren(
|
|
21
|
+
hast.children,
|
|
22
|
+
{ listDepth: -1, listOrdered: false, listItemIndex: -1, svg: false },
|
|
23
|
+
hast,
|
|
24
|
+
slots ?? {},
|
|
25
|
+
toValue(customAttrs) ?? {},
|
|
26
|
+
keyCounter
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
function renderChildren(nodeList, ctx, parent, slots, customAttrs, keyCounter) {
|
|
31
|
+
return nodeList.map((node) => {
|
|
32
|
+
switch (node.type) {
|
|
33
|
+
case "text":
|
|
34
|
+
return node.value;
|
|
35
|
+
case "raw":
|
|
36
|
+
return node.value;
|
|
37
|
+
case "root":
|
|
38
|
+
return renderChildren(node.children, ctx, parent, slots, customAttrs, keyCounter);
|
|
39
|
+
case "element": {
|
|
40
|
+
const { attrs, context, aliasList, vnodeProps } = getVNodeInfos(node, parent, ctx, keyCounter, customAttrs);
|
|
41
|
+
for (let i = aliasList.length - 1; i >= 0; i--) {
|
|
42
|
+
const targetSlot = slots[aliasList[i]];
|
|
43
|
+
if (typeof targetSlot === "function") {
|
|
44
|
+
return targetSlot({
|
|
45
|
+
...vnodeProps,
|
|
46
|
+
...attrs,
|
|
47
|
+
children: () => renderChildren(node.children, context, node, slots, customAttrs, keyCounter)
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return h(node.tagName, attrs, renderChildren(node.children, context, node, slots, customAttrs, keyCounter));
|
|
52
|
+
}
|
|
53
|
+
default:
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function getVNodeInfos(node, parent, context, keyCounter, customAttrs) {
|
|
59
|
+
const aliasList = [];
|
|
60
|
+
let attrs = {};
|
|
61
|
+
const vnodeProps = {};
|
|
62
|
+
const ctx = { ...context };
|
|
63
|
+
if (node.type === "element") {
|
|
64
|
+
aliasList.push(node.tagName);
|
|
65
|
+
keyCounter[node.tagName] = node.tagName in keyCounter ? keyCounter[node.tagName] + 1 : 0;
|
|
66
|
+
vnodeProps.key = `${node.tagName}-${keyCounter[node.tagName]}`;
|
|
67
|
+
node.properties = node.properties || {};
|
|
68
|
+
if (node.tagName === "svg") {
|
|
69
|
+
ctx.svg = true;
|
|
70
|
+
}
|
|
71
|
+
attrs = Object.entries(node.properties).reduce((acc, [hastKey, value]) => {
|
|
72
|
+
const attrInfo = find(ctx.svg ? svg : html, hastKey);
|
|
73
|
+
acc[attrInfo.attribute] = value;
|
|
74
|
+
return acc;
|
|
75
|
+
}, {});
|
|
76
|
+
switch (node.tagName) {
|
|
77
|
+
case "h1":
|
|
78
|
+
case "h2":
|
|
79
|
+
case "h3":
|
|
80
|
+
case "h4":
|
|
81
|
+
case "h5":
|
|
82
|
+
case "h6":
|
|
83
|
+
vnodeProps.level = Number.parseFloat(node.tagName.slice(1));
|
|
84
|
+
aliasList.push("heading");
|
|
85
|
+
break;
|
|
86
|
+
case "code":
|
|
87
|
+
vnodeProps.languageOriginal = Array.isArray(attrs.class) ? attrs.class.find((cls) => cls.startsWith("language-")) : "";
|
|
88
|
+
vnodeProps.language = vnodeProps.languageOriginal ? vnodeProps.languageOriginal.replace("language-", "") : "";
|
|
89
|
+
vnodeProps.inline = "tagName" in parent && parent.tagName !== "pre";
|
|
90
|
+
vnodeProps.content = node.children[0]?.value ?? "";
|
|
91
|
+
aliasList.push(vnodeProps.inline ? "inline-code" : "block-code");
|
|
92
|
+
break;
|
|
93
|
+
case "thead":
|
|
94
|
+
case "tbody":
|
|
95
|
+
ctx.currentContext = node.tagName;
|
|
96
|
+
break;
|
|
97
|
+
case "td":
|
|
98
|
+
case "th":
|
|
99
|
+
case "tr":
|
|
100
|
+
vnodeProps.isHead = context.currentContext === "thead";
|
|
101
|
+
break;
|
|
102
|
+
case "ul":
|
|
103
|
+
case "ol":
|
|
104
|
+
ctx.listDepth = context.listDepth + 1;
|
|
105
|
+
ctx.listOrdered = node.tagName === "ol";
|
|
106
|
+
ctx.listItemIndex = -1;
|
|
107
|
+
vnodeProps.ordered = ctx.listOrdered;
|
|
108
|
+
vnodeProps.depth = ctx.listDepth;
|
|
109
|
+
aliasList.push("list");
|
|
110
|
+
break;
|
|
111
|
+
case "li":
|
|
112
|
+
ctx.listItemIndex++;
|
|
113
|
+
vnodeProps.ordered = ctx.listOrdered;
|
|
114
|
+
vnodeProps.depth = ctx.listDepth;
|
|
115
|
+
vnodeProps.index = ctx.listItemIndex;
|
|
116
|
+
aliasList.push("list-item");
|
|
117
|
+
break;
|
|
118
|
+
case "slot":
|
|
119
|
+
if (typeof node.properties["slot-name"] === "string") {
|
|
120
|
+
aliasList.push(`${node.properties["slot-name"]}`);
|
|
121
|
+
delete node.properties["slot-name"];
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
attrs = computeAttrs(
|
|
126
|
+
node,
|
|
127
|
+
aliasList,
|
|
128
|
+
vnodeProps,
|
|
129
|
+
{ ...attrs },
|
|
130
|
+
// TODO: fix this
|
|
131
|
+
customAttrs
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
attrs,
|
|
136
|
+
context: ctx,
|
|
137
|
+
aliasList,
|
|
138
|
+
vnodeProps
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function computeAttrs(node, aliasList, vnodeProps, attrs, customAttrs) {
|
|
142
|
+
const result = {
|
|
143
|
+
...attrs
|
|
144
|
+
};
|
|
145
|
+
for (let i = aliasList.length - 1; i >= 0; i--) {
|
|
146
|
+
const name = aliasList[i];
|
|
147
|
+
if (name in customAttrs) {
|
|
148
|
+
const customAttr = customAttrs[name];
|
|
149
|
+
return {
|
|
150
|
+
...result,
|
|
151
|
+
...typeof customAttr === "function" ? customAttr(node, { ...attrs, ...vnodeProps }) : customAttr
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
function useMarkdownProcessor(options) {
|
|
158
|
+
const processor = computed(() => {
|
|
159
|
+
return createProcessor({
|
|
160
|
+
prePlugins: [remarkParse, ...toValue(options?.remarkPlugins) ?? []],
|
|
161
|
+
rehypePlugins: toValue(options?.rehypePlugins),
|
|
162
|
+
rehypeOptions: toValue(options?.rehypeOptions),
|
|
163
|
+
sanitize: toValue(options?.sanitize),
|
|
164
|
+
sanitizeOptions: toValue(options?.sanitizeOptions)
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
return { processor };
|
|
168
|
+
}
|
|
169
|
+
function createProcessor(options) {
|
|
170
|
+
return unified().use(options?.prePlugins ?? []).use(remarkRehype, {
|
|
171
|
+
allowDangerousHtml: true,
|
|
172
|
+
...options?.rehypeOptions || {}
|
|
173
|
+
}).use(options?.rehypePlugins ?? []).use(
|
|
174
|
+
options?.sanitize ? [
|
|
175
|
+
[
|
|
176
|
+
rehypeSanitize,
|
|
177
|
+
deepmerge(
|
|
178
|
+
defaultSchema,
|
|
179
|
+
options?.sanitizeOptions?.sanitizeOptions || {},
|
|
180
|
+
options?.sanitizeOptions?.mergeOptions || {}
|
|
181
|
+
)
|
|
182
|
+
]
|
|
183
|
+
] : []
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const sharedProps = {
|
|
187
|
+
markdown: {
|
|
188
|
+
type: String,
|
|
189
|
+
default: ""
|
|
190
|
+
},
|
|
191
|
+
customAttrs: {
|
|
192
|
+
type: Object,
|
|
193
|
+
default: () => ({})
|
|
194
|
+
},
|
|
195
|
+
remarkPlugins: {
|
|
196
|
+
type: Array,
|
|
197
|
+
default: () => []
|
|
198
|
+
},
|
|
199
|
+
rehypePlugins: {
|
|
200
|
+
type: Array,
|
|
201
|
+
default: () => []
|
|
202
|
+
},
|
|
203
|
+
rehypeOptions: {
|
|
204
|
+
type: Object,
|
|
205
|
+
default: () => ({})
|
|
206
|
+
},
|
|
207
|
+
sanitize: {
|
|
208
|
+
type: Boolean,
|
|
209
|
+
default: false
|
|
210
|
+
},
|
|
211
|
+
sanitizeOptions: {
|
|
212
|
+
type: Object,
|
|
213
|
+
default: () => ({})
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const vueMarkdownImpl = defineComponent({
|
|
217
|
+
name: "VueMarkdown",
|
|
218
|
+
props: sharedProps,
|
|
219
|
+
setup(props, { slots, attrs }) {
|
|
220
|
+
const { markdown, remarkPlugins, rehypePlugins, rehypeOptions, sanitize, sanitizeOptions, customAttrs } = toRefs(props);
|
|
221
|
+
const { processor } = useMarkdownProcessor({
|
|
222
|
+
remarkPlugins,
|
|
223
|
+
rehypePlugins,
|
|
224
|
+
rehypeOptions,
|
|
225
|
+
sanitize,
|
|
226
|
+
sanitizeOptions
|
|
227
|
+
});
|
|
228
|
+
return () => {
|
|
229
|
+
const mdast = processor.value.parse(markdown.value);
|
|
230
|
+
const hast = processor.value.runSync(mdast);
|
|
231
|
+
return render(hast, attrs, slots, customAttrs.value);
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
const vueMarkdownAsyncImpl = defineComponent({
|
|
236
|
+
name: "VueMarkdownAsync",
|
|
237
|
+
props: sharedProps,
|
|
238
|
+
async setup(props, { slots, attrs }) {
|
|
239
|
+
const { markdown, remarkPlugins, rehypePlugins, rehypeOptions, sanitize, sanitizeOptions, customAttrs } = toRefs(props);
|
|
240
|
+
const { processor } = useMarkdownProcessor({
|
|
241
|
+
remarkPlugins,
|
|
242
|
+
rehypePlugins,
|
|
243
|
+
rehypeOptions,
|
|
244
|
+
sanitize,
|
|
245
|
+
sanitizeOptions
|
|
246
|
+
});
|
|
247
|
+
const hast = shallowRef(null);
|
|
248
|
+
const process = async () => {
|
|
249
|
+
const mdast = processor.value.parse(markdown.value);
|
|
250
|
+
hast.value = await processor.value.run(mdast);
|
|
251
|
+
};
|
|
252
|
+
watch(() => [markdown.value, processor.value], process, { flush: "sync" });
|
|
253
|
+
await process();
|
|
254
|
+
return () => {
|
|
255
|
+
return hast.value ? render(hast.value, attrs, slots, customAttrs.value) : null;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const VueMarkdown = vueMarkdownImpl;
|
|
260
|
+
const VueMarkdownAsync = vueMarkdownAsyncImpl;
|
|
261
|
+
function tryOnScopeDispose(fn, failSilently) {
|
|
262
|
+
if (getCurrentScope()) {
|
|
263
|
+
onScopeDispose(fn, failSilently);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
const isClient = typeof window !== "undefined" && typeof document !== "undefined";
|
|
269
|
+
const toString = Object.prototype.toString;
|
|
270
|
+
const isObject = (val) => toString.call(val) === "[object Object]";
|
|
271
|
+
function createSingletonPromise(fn) {
|
|
272
|
+
let _promise;
|
|
273
|
+
function wrapper() {
|
|
274
|
+
if (!_promise) _promise = fn();
|
|
275
|
+
return _promise;
|
|
276
|
+
}
|
|
277
|
+
wrapper.reset = async () => {
|
|
278
|
+
const _prev = _promise;
|
|
279
|
+
_promise = void 0;
|
|
280
|
+
if (_prev) await _prev;
|
|
281
|
+
};
|
|
282
|
+
return wrapper;
|
|
283
|
+
}
|
|
284
|
+
function toArray(value) {
|
|
285
|
+
return Array.isArray(value) ? value : [value];
|
|
286
|
+
}
|
|
287
|
+
function useTimeoutFn(cb, interval, options = {}) {
|
|
288
|
+
const { immediate = true, immediateCallback = false } = options;
|
|
289
|
+
const isPending = shallowRef(false);
|
|
290
|
+
let timer;
|
|
291
|
+
function clear() {
|
|
292
|
+
if (timer) {
|
|
293
|
+
clearTimeout(timer);
|
|
294
|
+
timer = void 0;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function stop() {
|
|
298
|
+
isPending.value = false;
|
|
299
|
+
clear();
|
|
300
|
+
}
|
|
301
|
+
function start(...args) {
|
|
302
|
+
if (immediateCallback) cb();
|
|
303
|
+
clear();
|
|
304
|
+
isPending.value = true;
|
|
305
|
+
timer = setTimeout(() => {
|
|
306
|
+
isPending.value = false;
|
|
307
|
+
timer = void 0;
|
|
308
|
+
cb(...args);
|
|
309
|
+
}, toValue(interval));
|
|
310
|
+
}
|
|
311
|
+
if (immediate) {
|
|
312
|
+
isPending.value = true;
|
|
313
|
+
if (isClient) start();
|
|
314
|
+
}
|
|
315
|
+
tryOnScopeDispose(stop);
|
|
316
|
+
return {
|
|
317
|
+
isPending: shallowReadonly(isPending),
|
|
318
|
+
start,
|
|
319
|
+
stop
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function watchImmediate(source, cb, options) {
|
|
323
|
+
return watch(source, cb, {
|
|
324
|
+
...options,
|
|
325
|
+
immediate: true
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const defaultWindow = isClient ? window : void 0;
|
|
329
|
+
const defaultNavigator = isClient ? window.navigator : void 0;
|
|
330
|
+
function unrefElement(elRef) {
|
|
331
|
+
var _$el;
|
|
332
|
+
const plain = toValue(elRef);
|
|
333
|
+
return (_$el = plain === null || plain === void 0 ? void 0 : plain.$el) !== null && _$el !== void 0 ? _$el : plain;
|
|
334
|
+
}
|
|
335
|
+
function useEventListener(...args) {
|
|
336
|
+
const register = (el, event, listener, options) => {
|
|
337
|
+
el.addEventListener(event, listener, options);
|
|
338
|
+
return () => el.removeEventListener(event, listener, options);
|
|
339
|
+
};
|
|
340
|
+
const firstParamTargets = computed(() => {
|
|
341
|
+
const test = toArray(toValue(args[0])).filter((e) => e != null);
|
|
342
|
+
return test.every((e) => typeof e !== "string") ? test : void 0;
|
|
343
|
+
});
|
|
344
|
+
return watchImmediate(() => {
|
|
345
|
+
var _firstParamTargets$va, _firstParamTargets$va2;
|
|
346
|
+
return [
|
|
347
|
+
(_firstParamTargets$va = (_firstParamTargets$va2 = firstParamTargets.value) === null || _firstParamTargets$va2 === void 0 ? void 0 : _firstParamTargets$va2.map((e) => unrefElement(e))) !== null && _firstParamTargets$va !== void 0 ? _firstParamTargets$va : [defaultWindow].filter((e) => e != null),
|
|
348
|
+
toArray(toValue(firstParamTargets.value ? args[1] : args[0])),
|
|
349
|
+
toArray(unref(firstParamTargets.value ? args[2] : args[1])),
|
|
350
|
+
toValue(firstParamTargets.value ? args[3] : args[2])
|
|
351
|
+
];
|
|
352
|
+
}, ([raw_targets, raw_events, raw_listeners, raw_options], _, onCleanup) => {
|
|
353
|
+
if (!(raw_targets === null || raw_targets === void 0 ? void 0 : raw_targets.length) || !(raw_events === null || raw_events === void 0 ? void 0 : raw_events.length) || !(raw_listeners === null || raw_listeners === void 0 ? void 0 : raw_listeners.length)) return;
|
|
354
|
+
const optionsClone = isObject(raw_options) ? { ...raw_options } : raw_options;
|
|
355
|
+
const cleanups = raw_targets.flatMap((el) => raw_events.flatMap((event) => raw_listeners.map((listener) => register(el, event, listener, optionsClone))));
|
|
356
|
+
onCleanup(() => {
|
|
357
|
+
cleanups.forEach((fn) => fn());
|
|
358
|
+
});
|
|
359
|
+
}, { flush: "post" });
|
|
360
|
+
}
|
|
361
|
+
// @__NO_SIDE_EFFECTS__
|
|
362
|
+
function useMounted() {
|
|
363
|
+
const isMounted = shallowRef(false);
|
|
364
|
+
const instance = getCurrentInstance();
|
|
365
|
+
if (instance) onMounted(() => {
|
|
366
|
+
isMounted.value = true;
|
|
367
|
+
}, instance);
|
|
368
|
+
return isMounted;
|
|
369
|
+
}
|
|
370
|
+
// @__NO_SIDE_EFFECTS__
|
|
371
|
+
function useSupported(callback) {
|
|
372
|
+
const isMounted = /* @__PURE__ */ useMounted();
|
|
373
|
+
return computed(() => {
|
|
374
|
+
isMounted.value;
|
|
375
|
+
return Boolean(callback());
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
// @__NO_SIDE_EFFECTS__
|
|
379
|
+
function usePermission(permissionDesc, options = {}) {
|
|
380
|
+
const { controls = false, navigator: navigator$1 = defaultNavigator } = options;
|
|
381
|
+
const isSupported = /* @__PURE__ */ useSupported(() => navigator$1 && "permissions" in navigator$1);
|
|
382
|
+
const permissionStatus = shallowRef();
|
|
383
|
+
const desc = typeof permissionDesc === "string" ? { name: permissionDesc } : permissionDesc;
|
|
384
|
+
const state = shallowRef();
|
|
385
|
+
const update = () => {
|
|
386
|
+
var _permissionStatus$val, _permissionStatus$val2;
|
|
387
|
+
state.value = (_permissionStatus$val = (_permissionStatus$val2 = permissionStatus.value) === null || _permissionStatus$val2 === void 0 ? void 0 : _permissionStatus$val2.state) !== null && _permissionStatus$val !== void 0 ? _permissionStatus$val : "prompt";
|
|
388
|
+
};
|
|
389
|
+
useEventListener(permissionStatus, "change", update, { passive: true });
|
|
390
|
+
const query = createSingletonPromise(async () => {
|
|
391
|
+
if (!isSupported.value) return;
|
|
392
|
+
if (!permissionStatus.value) try {
|
|
393
|
+
permissionStatus.value = await navigator$1.permissions.query(desc);
|
|
394
|
+
} catch (_unused) {
|
|
395
|
+
permissionStatus.value = void 0;
|
|
396
|
+
} finally {
|
|
397
|
+
update();
|
|
398
|
+
}
|
|
399
|
+
if (controls) return toRaw(permissionStatus.value);
|
|
400
|
+
});
|
|
401
|
+
query();
|
|
402
|
+
if (controls) return {
|
|
403
|
+
state,
|
|
404
|
+
isSupported,
|
|
405
|
+
query
|
|
406
|
+
};
|
|
407
|
+
else return state;
|
|
408
|
+
}
|
|
409
|
+
function useClipboard(options = {}) {
|
|
410
|
+
const { navigator: navigator$1 = defaultNavigator, read = false, source, copiedDuring = 1500, legacy = false } = options;
|
|
411
|
+
const isClipboardApiSupported = /* @__PURE__ */ useSupported(() => navigator$1 && "clipboard" in navigator$1);
|
|
412
|
+
const permissionRead = /* @__PURE__ */ usePermission("clipboard-read");
|
|
413
|
+
const permissionWrite = /* @__PURE__ */ usePermission("clipboard-write");
|
|
414
|
+
const isSupported = computed(() => isClipboardApiSupported.value || legacy);
|
|
415
|
+
const text = shallowRef("");
|
|
416
|
+
const copied = shallowRef(false);
|
|
417
|
+
const timeout = useTimeoutFn(() => copied.value = false, copiedDuring, { immediate: false });
|
|
418
|
+
async function updateText() {
|
|
419
|
+
let useLegacy = !(isClipboardApiSupported.value && isAllowed(permissionRead.value));
|
|
420
|
+
if (!useLegacy) try {
|
|
421
|
+
text.value = await navigator$1.clipboard.readText();
|
|
422
|
+
} catch (_unused) {
|
|
423
|
+
useLegacy = true;
|
|
424
|
+
}
|
|
425
|
+
if (useLegacy) text.value = legacyRead();
|
|
426
|
+
}
|
|
427
|
+
if (isSupported.value && read) useEventListener(["copy", "cut"], updateText, { passive: true });
|
|
428
|
+
async function copy(value = toValue(source)) {
|
|
429
|
+
if (isSupported.value && value != null) {
|
|
430
|
+
let useLegacy = !(isClipboardApiSupported.value && isAllowed(permissionWrite.value));
|
|
431
|
+
if (!useLegacy) try {
|
|
432
|
+
await navigator$1.clipboard.writeText(value);
|
|
433
|
+
} catch (_unused2) {
|
|
434
|
+
useLegacy = true;
|
|
435
|
+
}
|
|
436
|
+
if (useLegacy) legacyCopy(value);
|
|
437
|
+
text.value = value;
|
|
438
|
+
copied.value = true;
|
|
439
|
+
timeout.start();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function legacyCopy(value) {
|
|
443
|
+
const ta = document.createElement("textarea");
|
|
444
|
+
ta.value = value;
|
|
445
|
+
ta.style.position = "absolute";
|
|
446
|
+
ta.style.opacity = "0";
|
|
447
|
+
ta.setAttribute("readonly", "");
|
|
448
|
+
document.body.appendChild(ta);
|
|
449
|
+
ta.select();
|
|
450
|
+
document.execCommand("copy");
|
|
451
|
+
ta.remove();
|
|
452
|
+
}
|
|
453
|
+
function legacyRead() {
|
|
454
|
+
var _document$getSelectio, _document, _document$getSelectio2;
|
|
455
|
+
return (_document$getSelectio = (_document = document) === null || _document === void 0 || (_document$getSelectio2 = _document.getSelection) === null || _document$getSelectio2 === void 0 || (_document$getSelectio2 = _document$getSelectio2.call(_document)) === null || _document$getSelectio2 === void 0 ? void 0 : _document$getSelectio2.toString()) !== null && _document$getSelectio !== void 0 ? _document$getSelectio : "";
|
|
456
|
+
}
|
|
457
|
+
function isAllowed(status) {
|
|
458
|
+
return status === "granted" || status === "prompt";
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
isSupported,
|
|
462
|
+
text: readonly(text),
|
|
463
|
+
copied: readonly(copied),
|
|
464
|
+
copy
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const SHIKI_PKG = "shiki";
|
|
468
|
+
const SHIKI_STREAM_PKG = "shiki-stream";
|
|
469
|
+
let shikiModulePromise = null;
|
|
470
|
+
let shikiStreamModulePromise = null;
|
|
471
|
+
let hasShownDependencyHint = false;
|
|
472
|
+
const showDependencyHint = () => {
|
|
473
|
+
if (hasShownDependencyHint) return;
|
|
474
|
+
hasShownDependencyHint = true;
|
|
475
|
+
console.log(
|
|
476
|
+
"%c[x-markdown]%c 代码高亮功能已降级为纯文本模式",
|
|
477
|
+
"font-weight: bold; color: #0066cc;",
|
|
478
|
+
"color: #666;"
|
|
479
|
+
);
|
|
480
|
+
console.log(
|
|
481
|
+
"%c如需语法高亮功能,请安装以下依赖:",
|
|
482
|
+
"color: #666; font-weight: bold;"
|
|
483
|
+
);
|
|
484
|
+
console.log(
|
|
485
|
+
"%c pnpm add shiki shiki-stream",
|
|
486
|
+
"color: #00aa00; font-family: monospace;"
|
|
487
|
+
);
|
|
488
|
+
console.log(
|
|
489
|
+
"%c安装后请重启开发服务器",
|
|
490
|
+
"color: #999; font-size: 12px;"
|
|
491
|
+
);
|
|
492
|
+
};
|
|
493
|
+
const loadShiki = () => {
|
|
494
|
+
if (!shikiModulePromise) {
|
|
495
|
+
shikiModulePromise = import(SHIKI_PKG).catch(() => {
|
|
496
|
+
showDependencyHint();
|
|
497
|
+
return null;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return shikiModulePromise;
|
|
501
|
+
};
|
|
502
|
+
const loadShikiStream = () => {
|
|
503
|
+
if (!shikiStreamModulePromise) {
|
|
504
|
+
shikiStreamModulePromise = import(SHIKI_STREAM_PKG).catch(() => {
|
|
505
|
+
showDependencyHint();
|
|
506
|
+
return null;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return shikiStreamModulePromise;
|
|
510
|
+
};
|
|
511
|
+
const tokensToLineTokens = (tokens) => {
|
|
512
|
+
if (!tokens.length) return [[]];
|
|
513
|
+
const lines = [[]];
|
|
514
|
+
let currentLine = lines[0];
|
|
515
|
+
const startNewLine = () => {
|
|
516
|
+
currentLine = [];
|
|
517
|
+
lines.push(currentLine);
|
|
518
|
+
};
|
|
519
|
+
tokens.forEach((token) => {
|
|
520
|
+
const content = token.content ?? "";
|
|
521
|
+
if (content === "\n") {
|
|
522
|
+
startNewLine();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (!content.includes("\n")) {
|
|
526
|
+
currentLine.push(token);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const segments = content.split("\n");
|
|
530
|
+
segments.forEach((segment, index) => {
|
|
531
|
+
if (segment) {
|
|
532
|
+
currentLine.push({
|
|
533
|
+
...token,
|
|
534
|
+
content: segment
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
if (index < segments.length - 1) {
|
|
538
|
+
startNewLine();
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
return lines.length === 0 ? [[]] : lines;
|
|
543
|
+
};
|
|
544
|
+
const createPreStyle = (bg, fg) => {
|
|
545
|
+
if (!bg && !fg) return void 0;
|
|
546
|
+
return {
|
|
547
|
+
backgroundColor: bg,
|
|
548
|
+
color: fg
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
function useHighlight(text, options) {
|
|
552
|
+
const streaming = ref();
|
|
553
|
+
const isLoading = ref(false);
|
|
554
|
+
const error = ref(null);
|
|
555
|
+
let tokenizer = null;
|
|
556
|
+
let previousText = "";
|
|
557
|
+
let highlighter = null;
|
|
558
|
+
let currentUsedLang = "";
|
|
559
|
+
let lastRequestedLang = "";
|
|
560
|
+
const effectiveTheme = computed(() => {
|
|
561
|
+
const theme = isRef(options.theme) ? options.theme.value : options.theme;
|
|
562
|
+
return theme || "slack-dark";
|
|
563
|
+
});
|
|
564
|
+
const effectiveLanguage = computed(() => {
|
|
565
|
+
return toValue(options.language) || "text";
|
|
566
|
+
});
|
|
567
|
+
const lines = computed(() => streaming.value?.lines || [[]]);
|
|
568
|
+
const preStyle = computed(() => streaming.value?.preStyle);
|
|
569
|
+
const updateTokens = async (nextText, forceReset = false) => {
|
|
570
|
+
if (!tokenizer) return;
|
|
571
|
+
if (forceReset) {
|
|
572
|
+
tokenizer.clear();
|
|
573
|
+
previousText = "";
|
|
574
|
+
}
|
|
575
|
+
const canAppend = !forceReset && nextText.startsWith(previousText);
|
|
576
|
+
let chunk = nextText;
|
|
577
|
+
if (canAppend) {
|
|
578
|
+
chunk = nextText.slice(previousText.length);
|
|
579
|
+
} else if (!forceReset) {
|
|
580
|
+
tokenizer.clear();
|
|
581
|
+
}
|
|
582
|
+
previousText = nextText;
|
|
583
|
+
if (!chunk) {
|
|
584
|
+
const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable];
|
|
585
|
+
streaming.value = {
|
|
586
|
+
colorReplacements: options.colorReplacements,
|
|
587
|
+
lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],
|
|
588
|
+
preStyle: streaming.value?.preStyle
|
|
589
|
+
};
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
await tokenizer.enqueue(chunk);
|
|
594
|
+
const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable];
|
|
595
|
+
streaming.value = {
|
|
596
|
+
colorReplacements: options.colorReplacements,
|
|
597
|
+
lines: tokensToLineTokens(mergedTokens),
|
|
598
|
+
preStyle: streaming.value?.preStyle
|
|
599
|
+
};
|
|
600
|
+
} catch (err) {
|
|
601
|
+
console.error("[x-markdown] Streaming highlighting failed:", err);
|
|
602
|
+
error.value = err;
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const initHighlighter = async () => {
|
|
606
|
+
isLoading.value = true;
|
|
607
|
+
error.value = null;
|
|
608
|
+
let currentLang = effectiveLanguage.value;
|
|
609
|
+
const currentTheme = effectiveTheme.value;
|
|
610
|
+
try {
|
|
611
|
+
const mod = await loadShiki();
|
|
612
|
+
if (!mod) {
|
|
613
|
+
console.warn("[x-markdown] Shiki not available, falling back to plain text mode");
|
|
614
|
+
streaming.value = {
|
|
615
|
+
colorReplacements: options.colorReplacements,
|
|
616
|
+
lines: [[{ content: text.value }]],
|
|
617
|
+
preStyle: void 0
|
|
618
|
+
};
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
highlighter = await mod.getSingletonHighlighter({
|
|
622
|
+
langs: [],
|
|
623
|
+
themes: [currentTheme]
|
|
624
|
+
});
|
|
625
|
+
lastRequestedLang = currentLang;
|
|
626
|
+
try {
|
|
627
|
+
await highlighter.loadLanguage(currentLang);
|
|
628
|
+
currentUsedLang = currentLang;
|
|
629
|
+
} catch {
|
|
630
|
+
console.warn(`[x-markdown] Failed to load language: ${currentLang}, falling back to plaintext`);
|
|
631
|
+
currentLang = "plaintext";
|
|
632
|
+
currentUsedLang = "plaintext";
|
|
633
|
+
}
|
|
634
|
+
const StreamMod = await loadShikiStream();
|
|
635
|
+
if (!StreamMod) {
|
|
636
|
+
console.warn("[x-markdown] shiki-stream not available, using non-streaming mode");
|
|
637
|
+
const tokens = highlighter.codeToThemedTokens(text.value, currentLang, currentTheme);
|
|
638
|
+
streaming.value = {
|
|
639
|
+
colorReplacements: options.colorReplacements,
|
|
640
|
+
lines: tokensToLineTokens(tokens),
|
|
641
|
+
preStyle: createPreStyle(
|
|
642
|
+
highlighter.getTheme(currentTheme)?.bg,
|
|
643
|
+
highlighter.getTheme(currentTheme)?.fg
|
|
644
|
+
)
|
|
645
|
+
};
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const ShikiStreamTokenizer = StreamMod.ShikiStreamTokenizer || StreamMod.default;
|
|
649
|
+
tokenizer = new ShikiStreamTokenizer({
|
|
650
|
+
highlighter,
|
|
651
|
+
lang: currentLang,
|
|
652
|
+
theme: currentTheme
|
|
653
|
+
});
|
|
654
|
+
previousText = "";
|
|
655
|
+
const themeInfo = highlighter.getTheme(currentTheme);
|
|
656
|
+
const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg);
|
|
657
|
+
if (text.value) {
|
|
658
|
+
await updateTokens(text.value, true);
|
|
659
|
+
if (streaming.value) {
|
|
660
|
+
streaming.value.preStyle = preStyleValue;
|
|
661
|
+
}
|
|
662
|
+
} else {
|
|
663
|
+
streaming.value = {
|
|
664
|
+
colorReplacements: options.colorReplacements,
|
|
665
|
+
lines: [[]],
|
|
666
|
+
preStyle: preStyleValue
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
} catch (err) {
|
|
670
|
+
console.error("[x-markdown] Highlighter initialization failed:", err);
|
|
671
|
+
error.value = err;
|
|
672
|
+
streaming.value = {
|
|
673
|
+
colorReplacements: options.colorReplacements,
|
|
674
|
+
lines: [[{ content: text.value }]],
|
|
675
|
+
preStyle: void 0
|
|
676
|
+
};
|
|
677
|
+
} finally {
|
|
678
|
+
isLoading.value = false;
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
watch(
|
|
682
|
+
() => [effectiveLanguage.value, effectiveTheme.value],
|
|
683
|
+
async ([newLang]) => {
|
|
684
|
+
const requestedLang = newLang;
|
|
685
|
+
if (highlighter && currentUsedLang === "plaintext" && requestedLang !== lastRequestedLang && requestedLang !== "plaintext") {
|
|
686
|
+
try {
|
|
687
|
+
await highlighter.loadLanguage(requestedLang);
|
|
688
|
+
initHighlighter();
|
|
689
|
+
return;
|
|
690
|
+
} catch {
|
|
691
|
+
lastRequestedLang = requestedLang;
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
initHighlighter();
|
|
696
|
+
},
|
|
697
|
+
{ immediate: true }
|
|
698
|
+
);
|
|
699
|
+
watch(text, async (newText) => {
|
|
700
|
+
const requestedLang = effectiveLanguage.value;
|
|
701
|
+
if (highlighter && currentUsedLang === "plaintext" && requestedLang !== lastRequestedLang && requestedLang !== "plaintext") {
|
|
702
|
+
try {
|
|
703
|
+
await highlighter.loadLanguage(requestedLang);
|
|
704
|
+
await initHighlighter();
|
|
705
|
+
return;
|
|
706
|
+
} catch {
|
|
707
|
+
lastRequestedLang = requestedLang;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (tokenizer) {
|
|
711
|
+
updateTokens(newText);
|
|
712
|
+
} else if (!highlighter) {
|
|
713
|
+
streaming.value = {
|
|
714
|
+
colorReplacements: options.colorReplacements,
|
|
715
|
+
lines: [[{ content: newText }]],
|
|
716
|
+
preStyle: streaming.value?.preStyle
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
onUnmounted(() => {
|
|
721
|
+
tokenizer?.clear();
|
|
722
|
+
tokenizer = null;
|
|
723
|
+
previousText = "";
|
|
724
|
+
});
|
|
725
|
+
return {
|
|
726
|
+
streaming,
|
|
727
|
+
lines,
|
|
728
|
+
preStyle,
|
|
729
|
+
isLoading,
|
|
730
|
+
error
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const _hoisted_1$1 = { class: "x-md-syntax-code-block" };
|
|
734
|
+
const _hoisted_2$1 = { class: "x-md-code-content" };
|
|
735
|
+
const _hoisted_3$1 = { key: 0 };
|
|
736
|
+
const SHIKI_CORE_PKG$1 = "@shikijs/core";
|
|
737
|
+
const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
|
738
|
+
...{
|
|
739
|
+
name: "SyntaxCodeBlock"
|
|
740
|
+
},
|
|
741
|
+
__name: "SyntaxCodeBlock",
|
|
742
|
+
props: {
|
|
743
|
+
code: {},
|
|
744
|
+
language: {},
|
|
745
|
+
lightTheme: { default: "vitesse-light" },
|
|
746
|
+
darkTheme: { default: "vitesse-dark" },
|
|
747
|
+
isDark: { type: Boolean, default: false },
|
|
748
|
+
colorReplacements: {},
|
|
749
|
+
codeMaxHeight: {},
|
|
750
|
+
enableAnimate: { type: Boolean, default: false }
|
|
751
|
+
},
|
|
752
|
+
setup(__props, { expose: __expose }) {
|
|
753
|
+
let getTokenStyleObjectFn = null;
|
|
754
|
+
onMounted(async () => {
|
|
755
|
+
const mod = await Function(`return import('${SHIKI_CORE_PKG$1}')`)().catch(() => {
|
|
756
|
+
return { getTokenStyleObject: () => ({}) };
|
|
757
|
+
});
|
|
758
|
+
getTokenStyleObjectFn = mod.getTokenStyleObject;
|
|
759
|
+
});
|
|
760
|
+
const props = __props;
|
|
761
|
+
const code = computed(() => props.code.trim());
|
|
762
|
+
const language = computed(() => props.language || "text");
|
|
763
|
+
const actualTheme = computed(() => props.isDark ? props.darkTheme : props.lightTheme);
|
|
764
|
+
const { lines, preStyle } = useHighlight(code, {
|
|
765
|
+
language,
|
|
766
|
+
theme: actualTheme,
|
|
767
|
+
colorReplacements: props.colorReplacements
|
|
768
|
+
});
|
|
769
|
+
const applyColorReplacement = (color, replacements) => {
|
|
770
|
+
if (!replacements) return color;
|
|
771
|
+
return replacements[color.toLowerCase()] || color;
|
|
772
|
+
};
|
|
773
|
+
const normalizeStyleKeys = (style) => {
|
|
774
|
+
const normalized = {};
|
|
775
|
+
Object.entries(style).forEach(([key, value]) => {
|
|
776
|
+
const camelKey = key.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
777
|
+
normalized[camelKey] = value;
|
|
778
|
+
});
|
|
779
|
+
return normalized;
|
|
780
|
+
};
|
|
781
|
+
const getTokenStyle = (token) => {
|
|
782
|
+
if (token.htmlStyle) {
|
|
783
|
+
const baseStyle2 = normalizeStyleKeys(token.htmlStyle);
|
|
784
|
+
if (!props.colorReplacements) return baseStyle2;
|
|
785
|
+
const style2 = { ...baseStyle2 };
|
|
786
|
+
if (style2.color && typeof style2.color === "string") {
|
|
787
|
+
style2.color = applyColorReplacement(style2.color, props.colorReplacements);
|
|
788
|
+
}
|
|
789
|
+
if (style2.backgroundColor && typeof style2.backgroundColor === "string") {
|
|
790
|
+
style2.backgroundColor = applyColorReplacement(style2.backgroundColor, props.colorReplacements);
|
|
791
|
+
}
|
|
792
|
+
return style2;
|
|
793
|
+
}
|
|
794
|
+
if (!getTokenStyleObjectFn) {
|
|
795
|
+
return {};
|
|
796
|
+
}
|
|
797
|
+
const rawStyle = getTokenStyleObjectFn(token);
|
|
798
|
+
const baseStyle = normalizeStyleKeys(rawStyle);
|
|
799
|
+
if (!props.colorReplacements) return baseStyle;
|
|
800
|
+
const style = { ...baseStyle };
|
|
801
|
+
if (style.color && typeof style.color === "string") {
|
|
802
|
+
style.color = applyColorReplacement(style.color, props.colorReplacements);
|
|
803
|
+
}
|
|
804
|
+
if (style.backgroundColor && typeof style.backgroundColor === "string") {
|
|
805
|
+
style.backgroundColor = applyColorReplacement(style.backgroundColor, props.colorReplacements);
|
|
806
|
+
}
|
|
807
|
+
return style;
|
|
808
|
+
};
|
|
809
|
+
const showFallback = computed(() => !lines.value?.length);
|
|
810
|
+
const codeContainerStyle = computed(() => ({
|
|
811
|
+
...preStyle.value,
|
|
812
|
+
maxHeight: props.codeMaxHeight
|
|
813
|
+
}));
|
|
814
|
+
__expose({
|
|
815
|
+
lines,
|
|
816
|
+
code,
|
|
817
|
+
language,
|
|
818
|
+
actualTheme
|
|
819
|
+
});
|
|
820
|
+
return (_ctx, _cache) => {
|
|
821
|
+
return openBlock(), createElementBlock("div", _hoisted_1$1, [
|
|
822
|
+
showFallback.value ? (openBlock(), createElementBlock("pre", {
|
|
823
|
+
key: 0,
|
|
824
|
+
style: normalizeStyle(codeContainerStyle.value)
|
|
825
|
+
}, [
|
|
826
|
+
createElementVNode("code", null, toDisplayString(code.value), 1)
|
|
827
|
+
], 4)) : (openBlock(), createElementBlock("pre", {
|
|
828
|
+
key: 1,
|
|
829
|
+
class: normalizeClass(["shiki", actualTheme.value]),
|
|
830
|
+
style: normalizeStyle(codeContainerStyle.value),
|
|
831
|
+
tabindex: "0"
|
|
832
|
+
}, [
|
|
833
|
+
_cache[4] || (_cache[4] = createTextVNode(" ", -1)),
|
|
834
|
+
createElementVNode("code", _hoisted_2$1, [
|
|
835
|
+
_cache[2] || (_cache[2] = createTextVNode("\n ", -1)),
|
|
836
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(lines), (line, i) => {
|
|
837
|
+
return openBlock(), createElementBlock("span", {
|
|
838
|
+
key: i,
|
|
839
|
+
class: "x-md-code-line"
|
|
840
|
+
}, [
|
|
841
|
+
_cache[0] || (_cache[0] = createTextVNode("\n ", -1)),
|
|
842
|
+
!line.length ? (openBlock(), createElementBlock("span", _hoisted_3$1, " ")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(line, (token, j) => {
|
|
843
|
+
return openBlock(), createElementBlock("span", {
|
|
844
|
+
key: j,
|
|
845
|
+
style: normalizeStyle(getTokenStyle(token)),
|
|
846
|
+
class: normalizeClass({ "x-md-animated-word": props.enableAnimate })
|
|
847
|
+
}, toDisplayString(token.content), 7);
|
|
848
|
+
}), 128)),
|
|
849
|
+
_cache[1] || (_cache[1] = createTextVNode("\n ", -1))
|
|
850
|
+
]);
|
|
851
|
+
}), 128)),
|
|
852
|
+
_cache[3] || (_cache[3] = createTextVNode("\n ", -1))
|
|
853
|
+
]),
|
|
854
|
+
_cache[5] || (_cache[5] = createTextVNode("\n ", -1))
|
|
855
|
+
], 6))
|
|
856
|
+
]);
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
const _export_sfc = (sfc, props) => {
|
|
861
|
+
const target = sfc.__vccOpts || sfc;
|
|
862
|
+
for (const [key, val] of props) {
|
|
863
|
+
target[key] = val;
|
|
864
|
+
}
|
|
865
|
+
return target;
|
|
866
|
+
};
|
|
867
|
+
const SyntaxCodeBlock = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-7cbaa1d1"]]);
|
|
868
|
+
const _hoisted_1 = { class: "x-md-code-header" };
|
|
869
|
+
const _hoisted_2 = { class: "x-md-code-header__left" };
|
|
870
|
+
const _hoisted_3 = ["title"];
|
|
871
|
+
const _hoisted_4 = { class: "x-md-code-lang" };
|
|
872
|
+
const _hoisted_5 = { class: "x-md-code-header__right" };
|
|
873
|
+
const _hoisted_6 = ["title", "disabled", "onClick"];
|
|
874
|
+
const _hoisted_7 = {
|
|
875
|
+
key: 0,
|
|
876
|
+
class: "x-md-copy-icon",
|
|
877
|
+
width: "16",
|
|
878
|
+
height: "16",
|
|
879
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
880
|
+
viewBox: "0 0 1024 1024"
|
|
881
|
+
};
|
|
882
|
+
const _hoisted_8 = {
|
|
883
|
+
key: 1,
|
|
884
|
+
class: "x-md-copy-icon",
|
|
885
|
+
width: "16",
|
|
886
|
+
height: "16",
|
|
887
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
888
|
+
viewBox: "0 0 1024 1024"
|
|
889
|
+
};
|
|
890
|
+
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
891
|
+
...{
|
|
892
|
+
name: "CodeBlock"
|
|
893
|
+
},
|
|
894
|
+
__name: "index",
|
|
895
|
+
props: {
|
|
896
|
+
code: {},
|
|
897
|
+
language: {},
|
|
898
|
+
lightTheme: { default: "vitesse-light" },
|
|
899
|
+
darkTheme: { default: "vitesse-dark" },
|
|
900
|
+
isDark: { type: Boolean, default: false },
|
|
901
|
+
colorReplacements: {},
|
|
902
|
+
codeMaxHeight: {},
|
|
903
|
+
showCodeBlockHeader: { type: Boolean, default: true },
|
|
904
|
+
enableAnimate: { type: Boolean, default: false },
|
|
905
|
+
codeBlockActions: { default: void 0 },
|
|
906
|
+
stickyCodeBlockHeader: { type: Boolean, default: true }
|
|
907
|
+
},
|
|
908
|
+
setup(__props, { expose: __expose }) {
|
|
909
|
+
const { copy, copied } = useClipboard({ copiedDuring: 2e3 });
|
|
910
|
+
const collapsed = ref(false);
|
|
911
|
+
const syntaxCodeBlockRef = ref(null);
|
|
912
|
+
const toggleCollapse = () => {
|
|
913
|
+
collapsed.value = !collapsed.value;
|
|
914
|
+
};
|
|
915
|
+
const props = __props;
|
|
916
|
+
const code = computed(() => props.code.trim());
|
|
917
|
+
const language = computed(() => props.language || "text");
|
|
918
|
+
const normalizedActions = computed(() => {
|
|
919
|
+
return props.codeBlockActions || [];
|
|
920
|
+
});
|
|
921
|
+
const filteredActions = computed(() => {
|
|
922
|
+
return normalizedActions.value.filter((action) => {
|
|
923
|
+
if (!action.show) return true;
|
|
924
|
+
return action.show(slotProps.value);
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
const slotProps = computed(() => ({
|
|
928
|
+
language: language.value,
|
|
929
|
+
code: code.value,
|
|
930
|
+
copy,
|
|
931
|
+
copied: copied.value,
|
|
932
|
+
collapsed: collapsed.value,
|
|
933
|
+
toggleCollapse
|
|
934
|
+
}));
|
|
935
|
+
function renderActionIcon(action) {
|
|
936
|
+
if (!action.icon) return null;
|
|
937
|
+
if (typeof action.icon === "string") {
|
|
938
|
+
return h("span", {
|
|
939
|
+
class: "x-md-action-icon",
|
|
940
|
+
innerHTML: action.icon
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
if (typeof action.icon === "function") {
|
|
944
|
+
try {
|
|
945
|
+
const result = action.icon(slotProps.value);
|
|
946
|
+
if (result && typeof result === "object" && "__v_isVNode" in result) {
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
} catch {
|
|
950
|
+
}
|
|
951
|
+
return h(action.icon);
|
|
952
|
+
}
|
|
953
|
+
return h(action.icon);
|
|
954
|
+
}
|
|
955
|
+
function handleActionClick(action) {
|
|
956
|
+
if (action.disabled) return;
|
|
957
|
+
action.onClick?.(slotProps.value);
|
|
958
|
+
}
|
|
959
|
+
__expose({
|
|
960
|
+
copy,
|
|
961
|
+
copied,
|
|
962
|
+
collapsed,
|
|
963
|
+
toggleCollapse,
|
|
964
|
+
syntaxCodeBlockRef
|
|
965
|
+
});
|
|
966
|
+
return (_ctx, _cache) => {
|
|
967
|
+
return openBlock(), createElementBlock("div", {
|
|
968
|
+
class: normalizeClass(["x-md-code-block", { "x-md-code-block--dark": props.isDark }])
|
|
969
|
+
}, [
|
|
970
|
+
__props.showCodeBlockHeader ? (openBlock(), createElementBlock("div", {
|
|
971
|
+
key: 0,
|
|
972
|
+
class: normalizeClass(["x-md-code-header-wrapper", [{ "x-md-code-header-wrapper--sticky": props.stickyCodeBlockHeader }, { "x-md-code-header-wrapper--collapsed": collapsed.value }]])
|
|
973
|
+
}, [
|
|
974
|
+
createElementVNode("div", _hoisted_1, [
|
|
975
|
+
renderSlot(_ctx.$slots, "codeHeader", {
|
|
976
|
+
language: language.value,
|
|
977
|
+
code: code.value,
|
|
978
|
+
copy: unref(copy),
|
|
979
|
+
copied: unref(copied),
|
|
980
|
+
collapsed: collapsed.value,
|
|
981
|
+
toggleCollapse
|
|
982
|
+
}, () => [
|
|
983
|
+
createElementVNode("div", _hoisted_2, [
|
|
984
|
+
createElementVNode("button", {
|
|
985
|
+
class: normalizeClass(["x-md-collapse-btn", { "x-md-collapse-btn--collapsed": collapsed.value }]),
|
|
986
|
+
onClick: toggleCollapse,
|
|
987
|
+
title: collapsed.value ? "展开代码" : "折叠代码"
|
|
988
|
+
}, [..._cache[1] || (_cache[1] = [
|
|
989
|
+
createElementVNode("svg", {
|
|
990
|
+
class: "x-md-collapse-icon",
|
|
991
|
+
viewBox: "0 0 24 24",
|
|
992
|
+
width: "14",
|
|
993
|
+
height: "14",
|
|
994
|
+
fill: "none",
|
|
995
|
+
stroke: "currentColor",
|
|
996
|
+
"stroke-width": "2",
|
|
997
|
+
"stroke-linecap": "round",
|
|
998
|
+
"stroke-linejoin": "round"
|
|
999
|
+
}, [
|
|
1000
|
+
createElementVNode("polyline", { points: "6 9 12 15 18 9" })
|
|
1001
|
+
], -1)
|
|
1002
|
+
])], 10, _hoisted_3),
|
|
1003
|
+
createElementVNode("span", _hoisted_4, toDisplayString(language.value), 1)
|
|
1004
|
+
]),
|
|
1005
|
+
createElementVNode("div", _hoisted_5, [
|
|
1006
|
+
renderSlot(_ctx.$slots, "codeActions", {
|
|
1007
|
+
code: code.value,
|
|
1008
|
+
copy: unref(copy),
|
|
1009
|
+
copied: unref(copied)
|
|
1010
|
+
}, () => [
|
|
1011
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(filteredActions.value, (action) => {
|
|
1012
|
+
return openBlock(), createElementBlock("button", {
|
|
1013
|
+
key: action.key,
|
|
1014
|
+
class: normalizeClass(["x-md-action-btn", [action.class, { "x-md-action-btn--disabled": action.disabled }]]),
|
|
1015
|
+
style: normalizeStyle(action.style),
|
|
1016
|
+
title: action.title,
|
|
1017
|
+
disabled: action.disabled,
|
|
1018
|
+
onClick: ($event) => handleActionClick(action)
|
|
1019
|
+
}, [
|
|
1020
|
+
action.icon ? (openBlock(), createBlock(resolveDynamicComponent(renderActionIcon(action)), { key: 0 })) : createCommentVNode("", true)
|
|
1021
|
+
], 14, _hoisted_6);
|
|
1022
|
+
}), 128)),
|
|
1023
|
+
createElementVNode("button", {
|
|
1024
|
+
class: normalizeClass(["x-md-copy-btn", { "x-md-copy-btn--copied": unref(copied) }]),
|
|
1025
|
+
onClick: _cache[0] || (_cache[0] = ($event) => unref(copy)(code.value))
|
|
1026
|
+
}, [
|
|
1027
|
+
unref(copied) ? (openBlock(), createElementBlock("svg", _hoisted_7, [..._cache[2] || (_cache[2] = [
|
|
1028
|
+
createElementVNode("path", {
|
|
1029
|
+
fill: "currentColor",
|
|
1030
|
+
d: "M406.656 706.944 195.84 496.256a32 32 0 1 0-45.248 45.248l256 256 512-512a32 32 0 0 0-45.248-45.248L406.592 706.944z"
|
|
1031
|
+
}, null, -1)
|
|
1032
|
+
])])) : (openBlock(), createElementBlock("svg", _hoisted_8, [..._cache[3] || (_cache[3] = [
|
|
1033
|
+
createElementVNode("path", {
|
|
1034
|
+
fill: "currentColor",
|
|
1035
|
+
d: "M768 832a128 128 0 0 1-128 128H192A128 128 0 0 1 64 832V384a128 128 0 0 1 128-128v64a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64z"
|
|
1036
|
+
}, null, -1),
|
|
1037
|
+
createElementVNode("path", {
|
|
1038
|
+
fill: "currentColor",
|
|
1039
|
+
d: "M384 128a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64V192a64 64 0 0 0-64-64zm0-64h448a128 128 0 0 1 128 128v448a128 128 0 0 1-128 128H384a128 128 0 0 1-128-128V192A128 128 0 0 1 384 64"
|
|
1040
|
+
}, null, -1)
|
|
1041
|
+
])]))
|
|
1042
|
+
], 2)
|
|
1043
|
+
], true)
|
|
1044
|
+
])
|
|
1045
|
+
], true)
|
|
1046
|
+
])
|
|
1047
|
+
], 2)) : createCommentVNode("", true),
|
|
1048
|
+
createElementVNode("div", {
|
|
1049
|
+
class: normalizeClass(["x-md-code-body", { "x-md-code-body--collapsed": collapsed.value }])
|
|
1050
|
+
}, [
|
|
1051
|
+
createVNode(SyntaxCodeBlock, {
|
|
1052
|
+
ref_key: "syntaxCodeBlockRef",
|
|
1053
|
+
ref: syntaxCodeBlockRef,
|
|
1054
|
+
code: code.value,
|
|
1055
|
+
language: language.value,
|
|
1056
|
+
"light-theme": props.lightTheme,
|
|
1057
|
+
"dark-theme": props.darkTheme,
|
|
1058
|
+
"is-dark": props.isDark,
|
|
1059
|
+
"color-replacements": props.colorReplacements,
|
|
1060
|
+
"code-max-height": props.codeMaxHeight,
|
|
1061
|
+
"enable-animate": props.enableAnimate
|
|
1062
|
+
}, null, 8, ["code", "language", "light-theme", "dark-theme", "is-dark", "color-replacements", "code-max-height", "enable-animate"])
|
|
1063
|
+
], 2)
|
|
1064
|
+
], 2);
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
const CodeBlock = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-8b0fb0b9"]]);
|
|
1069
|
+
const SHIKI_CORE_PKG = "@shikijs/core";
|
|
1070
|
+
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
1071
|
+
__name: "index",
|
|
1072
|
+
props: {
|
|
1073
|
+
raw: { default: () => ({}) },
|
|
1074
|
+
isDark: { type: Boolean, default: false },
|
|
1075
|
+
shikiTheme: { default: () => ["vitesse-light", "vitesse-dark"] },
|
|
1076
|
+
enableAnimate: { type: Boolean, default: false }
|
|
1077
|
+
},
|
|
1078
|
+
setup(__props) {
|
|
1079
|
+
let getTokenStyleObjectFn = null;
|
|
1080
|
+
onMounted(async () => {
|
|
1081
|
+
const mod = await Function(`return import('${SHIKI_CORE_PKG}')`)().catch(() => {
|
|
1082
|
+
return { getTokenStyleObject: () => ({}) };
|
|
1083
|
+
});
|
|
1084
|
+
getTokenStyleObjectFn = mod.getTokenStyleObject;
|
|
1085
|
+
});
|
|
1086
|
+
const props = __props;
|
|
1087
|
+
const content = computed(() => props.raw?.content ?? "");
|
|
1088
|
+
const language = computed(() => props.raw?.language || "ts");
|
|
1089
|
+
const actualTheme = computed(() => props.isDark ? props.shikiTheme[1] : props.shikiTheme[0]);
|
|
1090
|
+
const { lines, preStyle } = useHighlight(content, {
|
|
1091
|
+
language,
|
|
1092
|
+
theme: actualTheme
|
|
1093
|
+
});
|
|
1094
|
+
const flatTokens = computed(() => lines.value.flat());
|
|
1095
|
+
const codeStyle = computed(() => preStyle.value || {});
|
|
1096
|
+
const normalizeStyleKeys = (style) => {
|
|
1097
|
+
const normalized = {};
|
|
1098
|
+
Object.entries(style).forEach(([key, value]) => {
|
|
1099
|
+
const camelKey = key.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
1100
|
+
normalized[camelKey] = value;
|
|
1101
|
+
});
|
|
1102
|
+
return normalized;
|
|
1103
|
+
};
|
|
1104
|
+
const getTokenStyle = (token) => {
|
|
1105
|
+
if (token.htmlStyle) {
|
|
1106
|
+
return normalizeStyleKeys(token.htmlStyle);
|
|
1107
|
+
}
|
|
1108
|
+
if (!getTokenStyleObjectFn) {
|
|
1109
|
+
return {};
|
|
1110
|
+
}
|
|
1111
|
+
const rawStyle = getTokenStyleObjectFn(token);
|
|
1112
|
+
return normalizeStyleKeys(rawStyle);
|
|
1113
|
+
};
|
|
1114
|
+
return (_ctx, _cache) => {
|
|
1115
|
+
return openBlock(), createElementBlock("div", {
|
|
1116
|
+
class: normalizeClass(["x-md-inline-code", {
|
|
1117
|
+
"x-md-inline-code--dark": props.isDark,
|
|
1118
|
+
"x-md-animated-word": props.enableAnimate
|
|
1119
|
+
}])
|
|
1120
|
+
}, [
|
|
1121
|
+
createElementVNode("code", {
|
|
1122
|
+
style: normalizeStyle(codeStyle.value)
|
|
1123
|
+
}, [
|
|
1124
|
+
!flatTokens.value.length ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
1125
|
+
createTextVNode(toDisplayString(content.value), 1)
|
|
1126
|
+
], 64)) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(flatTokens.value, (token, i) => {
|
|
1127
|
+
return openBlock(), createElementBlock("span", {
|
|
1128
|
+
key: i,
|
|
1129
|
+
style: normalizeStyle(getTokenStyle(token)),
|
|
1130
|
+
class: normalizeClass({ "x-md-animated-word": props.enableAnimate })
|
|
1131
|
+
}, toDisplayString(token.content), 7);
|
|
1132
|
+
}), 128))
|
|
1133
|
+
], 4)
|
|
1134
|
+
], 2);
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
const CodeLine = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-97166d5b"]]);
|
|
1139
|
+
const Mermaid = defineAsyncComponent(() => import("./index-BjeepIV6.js"));
|
|
1140
|
+
const _sfc_main = defineComponent({
|
|
1141
|
+
props: {
|
|
1142
|
+
raw: { type: Object, default: () => ({}) },
|
|
1143
|
+
codeXRender: { type: Object, default: () => ({}) },
|
|
1144
|
+
isDark: { type: Boolean, default: false },
|
|
1145
|
+
shikiTheme: {
|
|
1146
|
+
type: Array,
|
|
1147
|
+
default: () => ["vitesse-light", "vitesse-dark"]
|
|
1148
|
+
},
|
|
1149
|
+
showCodeBlockHeader: { type: Boolean, default: true },
|
|
1150
|
+
stickyCodeBlockHeader: { type: Boolean, default: true },
|
|
1151
|
+
codeMaxHeight: { type: String, default: void 0 },
|
|
1152
|
+
enableAnimate: { type: Boolean, default: false },
|
|
1153
|
+
codeBlockActions: { type: Array, default: void 0 },
|
|
1154
|
+
mermaidActions: { type: Array, default: void 0 },
|
|
1155
|
+
mermaidConfig: { type: Object, default: void 0 }
|
|
1156
|
+
},
|
|
1157
|
+
setup(props, { slots }) {
|
|
1158
|
+
const { codeXRender } = props;
|
|
1159
|
+
return () => {
|
|
1160
|
+
if (props.raw.inline) {
|
|
1161
|
+
if (codeXRender && codeXRender.inline) {
|
|
1162
|
+
const renderer = codeXRender.inline;
|
|
1163
|
+
if (typeof renderer === "function") {
|
|
1164
|
+
return renderer(props);
|
|
1165
|
+
}
|
|
1166
|
+
return h(renderer, props);
|
|
1167
|
+
}
|
|
1168
|
+
return h(CodeLine, {
|
|
1169
|
+
raw: props.raw,
|
|
1170
|
+
isDark: props.isDark,
|
|
1171
|
+
shikiTheme: props.shikiTheme,
|
|
1172
|
+
enableAnimate: props.enableAnimate
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
const { language } = props.raw;
|
|
1176
|
+
if (codeXRender && codeXRender[language]) {
|
|
1177
|
+
const renderer = codeXRender[language];
|
|
1178
|
+
if (typeof renderer === "function") {
|
|
1179
|
+
return renderer(props);
|
|
1180
|
+
}
|
|
1181
|
+
return h(renderer, props);
|
|
1182
|
+
}
|
|
1183
|
+
if (language === "mermaid") {
|
|
1184
|
+
const mermaidSlots = {};
|
|
1185
|
+
Object.keys(slots).forEach((key) => {
|
|
1186
|
+
if (key.startsWith("mermaid")) {
|
|
1187
|
+
mermaidSlots[key] = slots[key];
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
return h(
|
|
1191
|
+
Mermaid,
|
|
1192
|
+
{
|
|
1193
|
+
raw: props.raw,
|
|
1194
|
+
isDark: props.isDark,
|
|
1195
|
+
shikiTheme: props.shikiTheme,
|
|
1196
|
+
mermaidActions: props.mermaidActions,
|
|
1197
|
+
config: props.mermaidConfig
|
|
1198
|
+
},
|
|
1199
|
+
mermaidSlots
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
return h(
|
|
1203
|
+
CodeBlock,
|
|
1204
|
+
{
|
|
1205
|
+
code: props.raw.content || "",
|
|
1206
|
+
language: props.raw.language || "text",
|
|
1207
|
+
isDark: props.isDark,
|
|
1208
|
+
lightTheme: props.shikiTheme[0],
|
|
1209
|
+
darkTheme: props.shikiTheme[1],
|
|
1210
|
+
showCodeBlockHeader: props.showCodeBlockHeader,
|
|
1211
|
+
stickyCodeBlockHeader: props.stickyCodeBlockHeader,
|
|
1212
|
+
codeMaxHeight: props.codeMaxHeight,
|
|
1213
|
+
enableAnimate: props.enableAnimate,
|
|
1214
|
+
codeBlockActions: props.codeBlockActions
|
|
1215
|
+
},
|
|
1216
|
+
slots
|
|
1217
|
+
);
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
function useComponents(props) {
|
|
1222
|
+
const components = {
|
|
1223
|
+
code: (raw) => h(_sfc_main, {
|
|
1224
|
+
raw,
|
|
1225
|
+
codeXRender: props?.codeXRender,
|
|
1226
|
+
isDark: props?.isDark,
|
|
1227
|
+
shikiTheme: props?.shikiTheme,
|
|
1228
|
+
enableAnimate: props?.enableAnimate,
|
|
1229
|
+
showCodeBlockHeader: props?.showCodeBlockHeader,
|
|
1230
|
+
stickyCodeBlockHeader: props?.stickyCodeBlockHeader,
|
|
1231
|
+
codeMaxHeight: props?.codeMaxHeight,
|
|
1232
|
+
codeBlockActions: props?.codeBlockActions,
|
|
1233
|
+
mermaidActions: props?.mermaidActions,
|
|
1234
|
+
mermaidConfig: props?.mermaidConfig
|
|
1235
|
+
})
|
|
1236
|
+
};
|
|
1237
|
+
return components;
|
|
1238
|
+
}
|
|
1239
|
+
function useProcessMarkdown(markdown) {
|
|
1240
|
+
return preprocessLaTeX(markdown);
|
|
1241
|
+
}
|
|
1242
|
+
function preprocessLaTeX(markdown) {
|
|
1243
|
+
if (typeof markdown !== "string") return markdown;
|
|
1244
|
+
const codeBlockRegex = /```[\s\S]*?```/g;
|
|
1245
|
+
const codeBlocks = markdown.match(codeBlockRegex) || [];
|
|
1246
|
+
const escapeReplacement = (str) => str.replace(/\$/g, "_ELX_DOLLAR_");
|
|
1247
|
+
let processedMarkdown = markdown.replace(codeBlockRegex, "ELX_CODE_BLOCK_PLACEHOLDER");
|
|
1248
|
+
processedMarkdown = flow([
|
|
1249
|
+
(str) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`),
|
|
1250
|
+
(str) => str.replace(/\\\[([\s\S]*?)\\\]/g, (_, equation) => `$$${equation}$$`),
|
|
1251
|
+
(str) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`),
|
|
1252
|
+
(str) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`)
|
|
1253
|
+
])(processedMarkdown);
|
|
1254
|
+
codeBlocks.forEach((block) => {
|
|
1255
|
+
processedMarkdown = processedMarkdown.replace("ELX_CODE_BLOCK_PLACEHOLDER", escapeReplacement(block));
|
|
1256
|
+
});
|
|
1257
|
+
processedMarkdown = processedMarkdown.replace(/_ELX_DOLLAR_/g, "$");
|
|
1258
|
+
return processedMarkdown;
|
|
1259
|
+
}
|
|
1260
|
+
function downloadSvgAsPng(svg2) {
|
|
1261
|
+
if (!svg2) return;
|
|
1262
|
+
try {
|
|
1263
|
+
const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg2)}`;
|
|
1264
|
+
const img = new Image();
|
|
1265
|
+
img.onload = () => {
|
|
1266
|
+
try {
|
|
1267
|
+
const canvas = document.createElement("canvas");
|
|
1268
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: false });
|
|
1269
|
+
if (!ctx) return;
|
|
1270
|
+
const scale = 2;
|
|
1271
|
+
canvas.width = img.width * scale;
|
|
1272
|
+
canvas.height = img.height * scale;
|
|
1273
|
+
ctx.imageSmoothingEnabled = true;
|
|
1274
|
+
ctx.imageSmoothingQuality = "high";
|
|
1275
|
+
ctx.fillStyle = "#ffffff";
|
|
1276
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1277
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
1278
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-");
|
|
1279
|
+
try {
|
|
1280
|
+
canvas.toBlob(
|
|
1281
|
+
(blob) => {
|
|
1282
|
+
if (!blob) return;
|
|
1283
|
+
const url = URL.createObjectURL(blob);
|
|
1284
|
+
const link = document.createElement("a");
|
|
1285
|
+
link.href = url;
|
|
1286
|
+
link.download = `mermaid-diagram-${timestamp}.png`;
|
|
1287
|
+
document.body.appendChild(link);
|
|
1288
|
+
link.click();
|
|
1289
|
+
document.body.removeChild(link);
|
|
1290
|
+
URL.revokeObjectURL(url);
|
|
1291
|
+
},
|
|
1292
|
+
"image/png",
|
|
1293
|
+
0.95
|
|
1294
|
+
);
|
|
1295
|
+
} catch (toBlobError) {
|
|
1296
|
+
console.error("Failed to convert canvas to blob:", toBlobError);
|
|
1297
|
+
try {
|
|
1298
|
+
const dataUrl = canvas.toDataURL("image/png", 0.95);
|
|
1299
|
+
const link = document.createElement("a");
|
|
1300
|
+
link.href = dataUrl;
|
|
1301
|
+
link.download = `mermaid-diagram-${timestamp}.png`;
|
|
1302
|
+
document.body.appendChild(link);
|
|
1303
|
+
link.click();
|
|
1304
|
+
document.body.removeChild(link);
|
|
1305
|
+
} catch (dataUrlError) {
|
|
1306
|
+
console.error("Failed to convert canvas to data URL:", dataUrlError);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
} catch (canvasError) {
|
|
1310
|
+
console.error("Canvas operation failed:", canvasError);
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
img.onerror = (error) => {
|
|
1314
|
+
console.error("Failed to load image:", error);
|
|
1315
|
+
};
|
|
1316
|
+
img.src = svgDataUrl;
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
console.error("Failed to download SVG:", error);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const MERMAID_PKG = "mermaid";
|
|
1322
|
+
let mermaidPromise = null;
|
|
1323
|
+
let hasShownMermaidHint = false;
|
|
1324
|
+
const showMermaidHint = () => {
|
|
1325
|
+
if (hasShownMermaidHint) return;
|
|
1326
|
+
hasShownMermaidHint = true;
|
|
1327
|
+
console.log(
|
|
1328
|
+
"%c[x-markdown]%c Mermaid 图表功能已降级为代码块显示",
|
|
1329
|
+
"font-weight: bold; color: #9333ea;",
|
|
1330
|
+
"color: #666;"
|
|
1331
|
+
);
|
|
1332
|
+
console.log(
|
|
1333
|
+
"%c如需 Mermaid 图表渲染功能,请安装:",
|
|
1334
|
+
"color: #666; font-weight: bold;"
|
|
1335
|
+
);
|
|
1336
|
+
console.log(
|
|
1337
|
+
"%c pnpm add mermaid",
|
|
1338
|
+
"color: #9333ea; font-family: monospace;"
|
|
1339
|
+
);
|
|
1340
|
+
console.log(
|
|
1341
|
+
"%c安装后请重启开发服务器",
|
|
1342
|
+
"color: #999; font-size: 12px;"
|
|
1343
|
+
);
|
|
1344
|
+
};
|
|
1345
|
+
async function loadMermaid() {
|
|
1346
|
+
if (typeof window === "undefined") return null;
|
|
1347
|
+
if (!mermaidPromise) {
|
|
1348
|
+
mermaidPromise = import(MERMAID_PKG).then((m) => m.default).catch(() => {
|
|
1349
|
+
showMermaidHint();
|
|
1350
|
+
return null;
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
return mermaidPromise;
|
|
1354
|
+
}
|
|
1355
|
+
const renderQueue = [];
|
|
1356
|
+
let isProcessingQueue = false;
|
|
1357
|
+
async function processRenderQueue() {
|
|
1358
|
+
if (isProcessingQueue) return;
|
|
1359
|
+
isProcessingQueue = true;
|
|
1360
|
+
while (renderQueue.length > 0) {
|
|
1361
|
+
const task = renderQueue.shift();
|
|
1362
|
+
if (task) {
|
|
1363
|
+
try {
|
|
1364
|
+
await task();
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
console.error("Mermaid render queue error:", err);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
isProcessingQueue = false;
|
|
1371
|
+
}
|
|
1372
|
+
function addToRenderQueue(task) {
|
|
1373
|
+
renderQueue.push(task);
|
|
1374
|
+
processRenderQueue();
|
|
1375
|
+
}
|
|
1376
|
+
function useMermaid(content, options = {}) {
|
|
1377
|
+
const optionsRef = computed(() => typeof options === "object" && "value" in options ? options.value : options);
|
|
1378
|
+
const mermaidConfig = computed(() => ({
|
|
1379
|
+
suppressErrorRendering: true,
|
|
1380
|
+
startOnLoad: false,
|
|
1381
|
+
securityLevel: "loose",
|
|
1382
|
+
theme: optionsRef.value.theme || "default",
|
|
1383
|
+
...optionsRef.value.config || {}
|
|
1384
|
+
}));
|
|
1385
|
+
const data = ref("");
|
|
1386
|
+
const error = ref(null);
|
|
1387
|
+
const isLoading = ref(false);
|
|
1388
|
+
let isUnmounted = false;
|
|
1389
|
+
const getRenderContainer = () => {
|
|
1390
|
+
const containerOption = optionsRef.value.container;
|
|
1391
|
+
if (containerOption) {
|
|
1392
|
+
return typeof containerOption === "object" && "value" in containerOption ? containerOption.value : containerOption;
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
};
|
|
1396
|
+
const throttledRender = throttle(
|
|
1397
|
+
() => {
|
|
1398
|
+
const contentValue = typeof content === "string" ? content : content.value;
|
|
1399
|
+
if (!contentValue?.trim()) {
|
|
1400
|
+
data.value = "";
|
|
1401
|
+
error.value = null;
|
|
1402
|
+
isLoading.value = false;
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
isLoading.value = true;
|
|
1406
|
+
addToRenderQueue(async () => {
|
|
1407
|
+
if (isUnmounted) return;
|
|
1408
|
+
try {
|
|
1409
|
+
const mermaidInstance = await loadMermaid();
|
|
1410
|
+
if (!mermaidInstance) {
|
|
1411
|
+
data.value = contentValue;
|
|
1412
|
+
error.value = null;
|
|
1413
|
+
isLoading.value = false;
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
mermaidInstance.initialize(mermaidConfig.value);
|
|
1417
|
+
const isValid = await mermaidInstance.parse(contentValue.trim());
|
|
1418
|
+
if (!isValid) {
|
|
1419
|
+
data.value = "";
|
|
1420
|
+
error.value = new Error("Mermaid parse error: Invalid syntax");
|
|
1421
|
+
isLoading.value = false;
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
const renderId = `${optionsRef.value.id || "mermaid"}-${Math.random().toString(36).substring(2, 11)}`;
|
|
1425
|
+
const container = getRenderContainer();
|
|
1426
|
+
if (!container) {
|
|
1427
|
+
isLoading.value = false;
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
const { svg: svg2 } = await mermaidInstance.render(renderId, contentValue, container);
|
|
1431
|
+
data.value = svg2;
|
|
1432
|
+
error.value = null;
|
|
1433
|
+
isLoading.value = false;
|
|
1434
|
+
} catch (err) {
|
|
1435
|
+
data.value = "";
|
|
1436
|
+
error.value = err;
|
|
1437
|
+
isLoading.value = false;
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
},
|
|
1441
|
+
100,
|
|
1442
|
+
{ leading: false, trailing: true }
|
|
1443
|
+
);
|
|
1444
|
+
watch(
|
|
1445
|
+
[() => typeof content === "string" ? content : content.value, () => mermaidConfig.value],
|
|
1446
|
+
() => {
|
|
1447
|
+
throttledRender();
|
|
1448
|
+
},
|
|
1449
|
+
{ immediate: true }
|
|
1450
|
+
);
|
|
1451
|
+
onUnmounted(() => {
|
|
1452
|
+
isUnmounted = true;
|
|
1453
|
+
});
|
|
1454
|
+
return {
|
|
1455
|
+
data,
|
|
1456
|
+
error,
|
|
1457
|
+
isLoading
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
function useMermaidZoom(options) {
|
|
1461
|
+
const { container } = options;
|
|
1462
|
+
const scale = ref(1);
|
|
1463
|
+
const posX = ref(0);
|
|
1464
|
+
const posY = ref(0);
|
|
1465
|
+
const isDragging = ref(false);
|
|
1466
|
+
let removeEvents = null;
|
|
1467
|
+
const getSvg = () => container.value?.querySelector(".syntax-mermaid__content svg");
|
|
1468
|
+
const updateTransform = (svg2) => {
|
|
1469
|
+
svg2.style.transformOrigin = "center center";
|
|
1470
|
+
svg2.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`;
|
|
1471
|
+
};
|
|
1472
|
+
const resetState = () => {
|
|
1473
|
+
scale.value = 1;
|
|
1474
|
+
posX.value = 0;
|
|
1475
|
+
posY.value = 0;
|
|
1476
|
+
isDragging.value = false;
|
|
1477
|
+
};
|
|
1478
|
+
const addInteractionEvents = (containerEl) => {
|
|
1479
|
+
let startX = 0;
|
|
1480
|
+
let startY = 0;
|
|
1481
|
+
let isInteractingWithMermaid = false;
|
|
1482
|
+
const onStart = (clientX, clientY) => {
|
|
1483
|
+
isDragging.value = true;
|
|
1484
|
+
startX = clientX - posX.value;
|
|
1485
|
+
startY = clientY - posY.value;
|
|
1486
|
+
document.body.style.userSelect = "none";
|
|
1487
|
+
};
|
|
1488
|
+
const onMove = (clientX, clientY) => {
|
|
1489
|
+
if (isDragging.value && isInteractingWithMermaid) {
|
|
1490
|
+
posX.value = clientX - startX;
|
|
1491
|
+
posY.value = clientY - startY;
|
|
1492
|
+
const svg2 = getSvg();
|
|
1493
|
+
if (svg2) {
|
|
1494
|
+
updateTransform(svg2);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
const onEnd = () => {
|
|
1499
|
+
isDragging.value = false;
|
|
1500
|
+
isInteractingWithMermaid = false;
|
|
1501
|
+
document.body.style.userSelect = "";
|
|
1502
|
+
};
|
|
1503
|
+
const onMouseDown = (e) => {
|
|
1504
|
+
if (e.button !== 0) return;
|
|
1505
|
+
if (e.target === containerEl || containerEl.contains(e.target)) {
|
|
1506
|
+
e.preventDefault();
|
|
1507
|
+
isInteractingWithMermaid = true;
|
|
1508
|
+
onStart(e.clientX, e.clientY);
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
const onMouseMove = (e) => {
|
|
1512
|
+
if (isInteractingWithMermaid) {
|
|
1513
|
+
onMove(e.clientX, e.clientY);
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
const handleWheelZoom = (e) => {
|
|
1517
|
+
const svg2 = getSvg();
|
|
1518
|
+
if (!svg2) return;
|
|
1519
|
+
const containerRect = containerEl.getBoundingClientRect();
|
|
1520
|
+
const svgRect = svg2.getBoundingClientRect();
|
|
1521
|
+
const mouseX = e.clientX - containerRect.left;
|
|
1522
|
+
const mouseY = e.clientY - containerRect.top;
|
|
1523
|
+
const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2;
|
|
1524
|
+
const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2;
|
|
1525
|
+
const offsetX = (mouseX - svgCenterX - posX.value) / scale.value;
|
|
1526
|
+
const offsetY = (mouseY - svgCenterY - posY.value) / scale.value;
|
|
1527
|
+
const delta = e.deltaY > 0 ? -0.05 : 0.05;
|
|
1528
|
+
const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10);
|
|
1529
|
+
if (newScale === scale.value) return;
|
|
1530
|
+
scale.value = newScale;
|
|
1531
|
+
posX.value = mouseX - svgCenterX - offsetX * scale.value;
|
|
1532
|
+
posY.value = mouseY - svgCenterY - offsetY * scale.value;
|
|
1533
|
+
updateTransform(svg2);
|
|
1534
|
+
};
|
|
1535
|
+
const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true });
|
|
1536
|
+
const onWheel = (e) => {
|
|
1537
|
+
if (e.target === containerEl || containerEl.contains(e.target)) {
|
|
1538
|
+
e.preventDefault();
|
|
1539
|
+
throttledWheelZoom(e);
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
const onTouchStart = (e) => {
|
|
1543
|
+
if (e.target === containerEl || containerEl.contains(e.target)) {
|
|
1544
|
+
if (e.touches.length === 1) {
|
|
1545
|
+
e.preventDefault();
|
|
1546
|
+
isInteractingWithMermaid = true;
|
|
1547
|
+
onStart(e.touches[0].clientX, e.touches[0].clientY);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
const onTouchMove = (e) => {
|
|
1552
|
+
if (isInteractingWithMermaid) {
|
|
1553
|
+
e.preventDefault();
|
|
1554
|
+
onMove(e.touches[0].clientX, e.touches[0].clientY);
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
containerEl.addEventListener("mousedown", onMouseDown);
|
|
1558
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
1559
|
+
document.addEventListener("mouseup", onEnd);
|
|
1560
|
+
containerEl.addEventListener("wheel", onWheel, { passive: false });
|
|
1561
|
+
containerEl.addEventListener("touchstart", onTouchStart, { passive: false });
|
|
1562
|
+
containerEl.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
1563
|
+
document.addEventListener("touchend", onEnd);
|
|
1564
|
+
return () => {
|
|
1565
|
+
containerEl.removeEventListener("mousedown", onMouseDown);
|
|
1566
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
1567
|
+
document.removeEventListener("mouseup", onEnd);
|
|
1568
|
+
containerEl.removeEventListener("wheel", onWheel);
|
|
1569
|
+
containerEl.removeEventListener("touchstart", onTouchStart);
|
|
1570
|
+
containerEl.removeEventListener("touchmove", onTouchMove);
|
|
1571
|
+
document.removeEventListener("touchend", onEnd);
|
|
1572
|
+
document.body.style.userSelect = "";
|
|
1573
|
+
};
|
|
1574
|
+
};
|
|
1575
|
+
const zoomIn = () => {
|
|
1576
|
+
const svg2 = getSvg();
|
|
1577
|
+
if (svg2) {
|
|
1578
|
+
scale.value = Math.min(scale.value + 0.2, 10);
|
|
1579
|
+
updateTransform(svg2);
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
const zoomOut = () => {
|
|
1583
|
+
const svg2 = getSvg();
|
|
1584
|
+
if (svg2) {
|
|
1585
|
+
scale.value = Math.max(scale.value - 0.2, 0.1);
|
|
1586
|
+
updateTransform(svg2);
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
const reset = () => {
|
|
1590
|
+
const svg2 = getSvg();
|
|
1591
|
+
if (svg2) {
|
|
1592
|
+
resetState();
|
|
1593
|
+
updateTransform(svg2);
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
const fullscreen = () => {
|
|
1597
|
+
if (!container.value) return;
|
|
1598
|
+
if (document.fullscreenElement) {
|
|
1599
|
+
document.exitFullscreen();
|
|
1600
|
+
} else {
|
|
1601
|
+
container.value.requestFullscreen?.();
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
const initialize = () => {
|
|
1605
|
+
if (!container.value) return;
|
|
1606
|
+
resetState();
|
|
1607
|
+
removeEvents = addInteractionEvents(container.value);
|
|
1608
|
+
const svg2 = getSvg();
|
|
1609
|
+
if (svg2) {
|
|
1610
|
+
updateTransform(svg2);
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
const destroy = () => {
|
|
1614
|
+
removeEvents?.();
|
|
1615
|
+
removeEvents = null;
|
|
1616
|
+
resetState();
|
|
1617
|
+
};
|
|
1618
|
+
watch(
|
|
1619
|
+
() => container.value,
|
|
1620
|
+
() => {
|
|
1621
|
+
destroy();
|
|
1622
|
+
resetState();
|
|
1623
|
+
}
|
|
1624
|
+
);
|
|
1625
|
+
onUnmounted(destroy);
|
|
1626
|
+
return {
|
|
1627
|
+
zoomIn,
|
|
1628
|
+
zoomOut,
|
|
1629
|
+
reset,
|
|
1630
|
+
fullscreen,
|
|
1631
|
+
destroy,
|
|
1632
|
+
initialize
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function rehypeAnimatedPlugin() {
|
|
1636
|
+
return (tree) => {
|
|
1637
|
+
visit(tree, "element", (node) => {
|
|
1638
|
+
if (["p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "strong", "th", "td"].includes(node.tagName) && node.children) {
|
|
1639
|
+
const newChildren = [];
|
|
1640
|
+
for (const child of node.children) {
|
|
1641
|
+
if (child.type === "text") {
|
|
1642
|
+
const segmenter = new Intl.Segmenter("zh", { granularity: "word" });
|
|
1643
|
+
const segments = segmenter.segment(child.value);
|
|
1644
|
+
const words = [...segments].map((segment) => segment.segment).filter(Boolean);
|
|
1645
|
+
words.forEach((word) => {
|
|
1646
|
+
newChildren.push({
|
|
1647
|
+
children: [{ type: "text", value: word }],
|
|
1648
|
+
properties: {
|
|
1649
|
+
className: "x-md-animated-word"
|
|
1650
|
+
},
|
|
1651
|
+
tagName: "span",
|
|
1652
|
+
type: "element"
|
|
1653
|
+
});
|
|
1654
|
+
});
|
|
1655
|
+
} else {
|
|
1656
|
+
newChildren.push(child);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
node.children = newChildren;
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function usePlugins(props) {
|
|
1665
|
+
const {
|
|
1666
|
+
allowHtml,
|
|
1667
|
+
enableAnimate,
|
|
1668
|
+
enableLatex,
|
|
1669
|
+
enableBreaks,
|
|
1670
|
+
enableGfm,
|
|
1671
|
+
rehypePlugins,
|
|
1672
|
+
remarkPlugins,
|
|
1673
|
+
rehypePluginsAhead,
|
|
1674
|
+
remarkPluginsAhead
|
|
1675
|
+
} = toRefs(props);
|
|
1676
|
+
const rehype = computed(() => {
|
|
1677
|
+
return [
|
|
1678
|
+
...rehypePluginsAhead.value,
|
|
1679
|
+
allowHtml.value && rehypeRaw,
|
|
1680
|
+
enableLatex.value && rehypeKatex,
|
|
1681
|
+
enableAnimate.value && rehypeAnimatedPlugin,
|
|
1682
|
+
...rehypePlugins.value
|
|
1683
|
+
].filter(Boolean);
|
|
1684
|
+
});
|
|
1685
|
+
const remark = computed(() => {
|
|
1686
|
+
const base = [
|
|
1687
|
+
enableLatex.value && remarkMath,
|
|
1688
|
+
enableGfm.value !== false && [remarkGfm, { singleTilde: false }],
|
|
1689
|
+
enableBreaks.value && remarkBreaks
|
|
1690
|
+
].filter(Boolean);
|
|
1691
|
+
return [
|
|
1692
|
+
...remarkPluginsAhead.value,
|
|
1693
|
+
...base,
|
|
1694
|
+
...remarkPlugins.value
|
|
1695
|
+
];
|
|
1696
|
+
});
|
|
1697
|
+
return {
|
|
1698
|
+
rehypePlugins: rehype,
|
|
1699
|
+
remarkPlugins: remark
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
const themeMap = {
|
|
1703
|
+
light: "vitesse-light",
|
|
1704
|
+
dark: "vitesse-dark"
|
|
1705
|
+
};
|
|
1706
|
+
function useTheme(options = {}) {
|
|
1707
|
+
const { lightTheme = themeMap.light, darkTheme = themeMap.dark } = options;
|
|
1708
|
+
const internalMode = ref(isRef(options.mode) ? options.mode.value : options.mode || "auto");
|
|
1709
|
+
const mode = computed({
|
|
1710
|
+
get: () => isRef(options.mode) ? options.mode.value : internalMode.value,
|
|
1711
|
+
set: (val) => {
|
|
1712
|
+
internalMode.value = val;
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
const systemIsDark = ref(false);
|
|
1716
|
+
if (typeof window !== "undefined") {
|
|
1717
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
1718
|
+
systemIsDark.value = mediaQuery.matches;
|
|
1719
|
+
mediaQuery.addEventListener("change", (e) => {
|
|
1720
|
+
systemIsDark.value = e.matches;
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
const isDark = computed(() => {
|
|
1724
|
+
if (mode.value === "auto") {
|
|
1725
|
+
return systemIsDark.value;
|
|
1726
|
+
}
|
|
1727
|
+
return mode.value === "dark";
|
|
1728
|
+
});
|
|
1729
|
+
const actualTheme = computed(() => {
|
|
1730
|
+
const customTheme = isRef(options.theme) ? options.theme.value : options.theme;
|
|
1731
|
+
if (customTheme) return customTheme;
|
|
1732
|
+
return isDark.value ? darkTheme : lightTheme;
|
|
1733
|
+
});
|
|
1734
|
+
const setMode = (newMode) => {
|
|
1735
|
+
internalMode.value = newMode;
|
|
1736
|
+
};
|
|
1737
|
+
const toggleMode = () => {
|
|
1738
|
+
const modes = ["light", "dark", "auto"];
|
|
1739
|
+
const currentIndex = modes.indexOf(mode.value);
|
|
1740
|
+
internalMode.value = modes[(currentIndex + 1) % modes.length];
|
|
1741
|
+
};
|
|
1742
|
+
return {
|
|
1743
|
+
mode,
|
|
1744
|
+
isDark,
|
|
1745
|
+
actualTheme,
|
|
1746
|
+
setMode,
|
|
1747
|
+
toggleMode
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
const markdownRendererProps = {
|
|
1751
|
+
markdown: { type: String, default: "" },
|
|
1752
|
+
allowHtml: { type: Boolean, default: false },
|
|
1753
|
+
enableLatex: { type: Boolean, default: true },
|
|
1754
|
+
enableAnimate: { type: Boolean, default: false },
|
|
1755
|
+
enableBreaks: { type: Boolean, default: true },
|
|
1756
|
+
enableGfm: { type: Boolean, default: true },
|
|
1757
|
+
isDark: { type: Boolean, default: false },
|
|
1758
|
+
shikiTheme: {
|
|
1759
|
+
type: Array,
|
|
1760
|
+
default: () => ["vitesse-light", "vitesse-dark"]
|
|
1761
|
+
},
|
|
1762
|
+
showCodeBlockHeader: { type: Boolean, default: true },
|
|
1763
|
+
stickyCodeBlockHeader: { type: Boolean, default: false },
|
|
1764
|
+
codeMaxHeight: { type: String, default: void 0 },
|
|
1765
|
+
codeBlockActions: { type: Array, default: void 0 },
|
|
1766
|
+
mermaidActions: { type: Array, default: void 0 },
|
|
1767
|
+
mermaidConfig: { type: Object, default: void 0 },
|
|
1768
|
+
codeXRender: { type: Object, default: () => ({}) },
|
|
1769
|
+
customAttrs: { type: Object, default: () => ({}) },
|
|
1770
|
+
remarkPlugins: { type: Array, default: () => [] },
|
|
1771
|
+
remarkPluginsAhead: { type: Array, default: () => [] },
|
|
1772
|
+
rehypePlugins: { type: Array, default: () => [] },
|
|
1773
|
+
rehypePluginsAhead: { type: Array, default: () => [] },
|
|
1774
|
+
rehypeOptions: { type: Object, default: () => ({}) },
|
|
1775
|
+
sanitize: { type: Boolean, default: false },
|
|
1776
|
+
sanitizeOptions: { type: Object, default: () => ({}) }
|
|
1777
|
+
};
|
|
1778
|
+
function createMarkdownRenderer(name, coreComponent) {
|
|
1779
|
+
return defineComponent({
|
|
1780
|
+
name,
|
|
1781
|
+
props: markdownRendererProps,
|
|
1782
|
+
setup(props, { slots, attrs }) {
|
|
1783
|
+
const { rehypePlugins, remarkPlugins } = usePlugins(props);
|
|
1784
|
+
const components = useComponents(props);
|
|
1785
|
+
const markdown = computed(() => {
|
|
1786
|
+
return props.markdown;
|
|
1787
|
+
});
|
|
1788
|
+
const renderProps = computed(() => ({
|
|
1789
|
+
// markdown 内容
|
|
1790
|
+
markdown: markdown.value,
|
|
1791
|
+
// 自定义属性对象
|
|
1792
|
+
customAttrs: props.customAttrs,
|
|
1793
|
+
// remark 插件列表
|
|
1794
|
+
remarkPlugins: toValue(remarkPlugins),
|
|
1795
|
+
// rehype 插件列表
|
|
1796
|
+
rehypePlugins: toValue(rehypePlugins),
|
|
1797
|
+
// rehype 配置项
|
|
1798
|
+
rehypeOptions: props.rehypeOptions,
|
|
1799
|
+
// 是否启用内容清洗
|
|
1800
|
+
sanitize: props.sanitize,
|
|
1801
|
+
// 清洗选项
|
|
1802
|
+
sanitizeOptions: props.sanitizeOptions
|
|
1803
|
+
}));
|
|
1804
|
+
return () => h(
|
|
1805
|
+
"div",
|
|
1806
|
+
{
|
|
1807
|
+
// 外层容器 class,支持深色模式
|
|
1808
|
+
class: ["x-md-renderer", { "is-dark": props.isDark }],
|
|
1809
|
+
// 外层容器样式,根据深色模式设置背景色和文字颜色
|
|
1810
|
+
style: {
|
|
1811
|
+
backgroundColor: props.isDark ? "#1e1e1e" : "#ffffff",
|
|
1812
|
+
color: props.isDark ? "#e5e5e5" : "#333333",
|
|
1813
|
+
padding: "16px"
|
|
1814
|
+
},
|
|
1815
|
+
// 透传外部传入的 attrs
|
|
1816
|
+
...attrs
|
|
1817
|
+
},
|
|
1818
|
+
// 渲染核心组件
|
|
1819
|
+
h(
|
|
1820
|
+
coreComponent,
|
|
1821
|
+
{ ...renderProps.value, class: "x-md-core" },
|
|
1822
|
+
{
|
|
1823
|
+
// 合并自定义组件和外部插槽
|
|
1824
|
+
...components,
|
|
1825
|
+
...slots
|
|
1826
|
+
}
|
|
1827
|
+
)
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
const MarkdownRenderer = createMarkdownRenderer("MarkdownRenderer", VueMarkdown);
|
|
1833
|
+
const MarkdownRendererAsync = createMarkdownRenderer("MarkdownRendererAsync", VueMarkdownAsync);
|
|
1834
|
+
export {
|
|
1835
|
+
CodeBlock as C,
|
|
1836
|
+
MarkdownRenderer as M,
|
|
1837
|
+
VueMarkdown as V,
|
|
1838
|
+
useMermaidZoom as a,
|
|
1839
|
+
useClipboard as b,
|
|
1840
|
+
VueMarkdownAsync as c,
|
|
1841
|
+
downloadSvgAsPng as d,
|
|
1842
|
+
MarkdownRendererAsync as e,
|
|
1843
|
+
renderChildren as f,
|
|
1844
|
+
getVNodeInfos as g,
|
|
1845
|
+
createProcessor as h,
|
|
1846
|
+
useMarkdownProcessor as i,
|
|
1847
|
+
useComponents as j,
|
|
1848
|
+
useHighlight as k,
|
|
1849
|
+
useProcessMarkdown as l,
|
|
1850
|
+
usePlugins as m,
|
|
1851
|
+
useTheme as n,
|
|
1852
|
+
preprocessLaTeX as p,
|
|
1853
|
+
render as r,
|
|
1854
|
+
useMermaid as u
|
|
1855
|
+
};
|
|
1856
|
+
//# sourceMappingURL=index-Ys7-7uFi.js.map
|