md-editor-v3
Version:
Markdown editor for vue3, developed in jsx and typescript, dark theme、beautify content by prettier、render articles directly、paste or clip the picture and upload it...
1,634 lines (1,633 loc) • 66.5 kB
JavaScript
import { inject, onMounted, watch, toRef, nextTick, shallowRef, ref, computed, onBeforeUnmount, defineComponent, createVNode, Fragment, provide, useId, reactive } from "vue";
import { p as prefix, g as globalConfig, d as codeCss, s as staticTextDefault, b as allFooter, a as allToolbar } from "./config.mjs";
import { b as bus, E as ERROR_CATCHER, g as CATALOG_CHANGED, P as PUSH_CATALOG, h as RERENDER, B as BUILD_FINISHED, T as TASK_STATE_CHANGED, U as UPLOAD_IMAGE, a as CHANGE_CATALOG_VISIBLE, c as ON_SAVE, i as PAGE_FULL_SCREEN_CHANGED, F as FULL_SCREEN_CHANGED, j as PREVIEW_CHANGED, k as PREVIEW_ONLY_CHANGED, H as HTML_PREVIEW_CHANGED, l as CATALOG_VISIBLE_CHANGED, R as REPLACE, f as EVENT_LISTENER, C as CHANGE_FULL_SCREEN } from "./event-name.mjs";
import { debounce, randomId, deepMerge, deepClone } from "@vavt/util";
import { a as appendHandler, u as updateHandler, S as StrIcon, z as zoomMermaid } from "./dom.mjs";
import mediumZoom from "medium-zoom";
import copy2clipboard from "@vavt/copy2clipboard";
import mdit from "markdown-it";
import ImageFiguresPlugin from "markdown-it-image-figures";
import SubPlugin from "markdown-it-sub";
import SupPlugin from "markdown-it-sup";
import { g as generateCodeRowNumber } from "./index5.mjs";
import { LRUCache } from "lru-cache";
const CDN_IDS = {
hljs: `${prefix}-hljs`,
hlcss: `${prefix}-hlCss`,
prettier: `${prefix}-prettier`,
prettierMD: `${prefix}-prettierMD`,
cropperjs: `${prefix}-cropper`,
croppercss: `${prefix}-cropperCss`,
screenfull: `${prefix}-screenfull`,
mermaidM: `${prefix}-mermaid-m`,
mermaid: `${prefix}-mermaid`,
katexjs: `${prefix}-katex`,
katexcss: `${prefix}-katexCss`
};
const userZoom = (props, html) => {
const editorId = inject("editorId");
const { noImgZoomIn } = props;
const zoomHander = debounce(() => {
const imgs = document.querySelectorAll(
`#${editorId}-preview img:not(.not-zoom):not(.medium-zoom-image)`
);
if (imgs.length === 0) {
return;
}
mediumZoom(imgs, {
background: "#00000073"
});
});
onMounted(() => {
if (!noImgZoomIn && props.setting.preview) {
zoomHander();
}
});
watch([html, toRef(props.setting, "preview")], () => {
if (!noImgZoomIn && props.setting.preview) {
zoomHander();
}
});
};
const useCopyCode = (props, html, key) => {
const editorId = inject("editorId");
const rootRef = inject("rootRef");
const ult = inject("usedLanguageText");
const initCopyEntry = () => {
rootRef.value.querySelectorAll(`#${editorId} .${prefix}-preview .${prefix}-code`).forEach((codeBlock) => {
let clearTimer = -1;
const copyButton = codeBlock.querySelector(
`.${prefix}-copy-button`
);
if (copyButton)
copyButton.onclick = (e) => {
e.preventDefault();
clearTimeout(clearTimer);
const activeCode = codeBlock.querySelector("input:checked + pre code") || codeBlock.querySelector("pre code");
const codeText = activeCode.textContent;
const { text, successTips, failTips } = ult.value.copyCode;
let msg = successTips;
copy2clipboard(props.formatCopiedText(codeText)).catch(() => {
msg = failTips;
}).finally(() => {
if (copyButton.dataset.isIcon) {
copyButton.dataset.tips = msg;
} else {
copyButton.innerHTML = msg;
}
clearTimer = window.setTimeout(() => {
if (copyButton.dataset.isIcon) {
copyButton.dataset.tips = text;
} else {
copyButton.innerHTML = text;
}
}, 1500);
});
};
});
};
const htmlChanged = () => {
nextTick(initCopyEntry);
};
const settingPreviewChanged = (nVal) => {
if (nVal) {
nextTick(initCopyEntry);
}
};
watch([html, key], htmlChanged);
watch(() => props.setting.preview, settingPreviewChanged);
watch(() => props.setting.htmlPreview, settingPreviewChanged);
onMounted(initCopyEntry);
};
const useHighlight = (props) => {
const highlight = inject("highlight");
const hljsRef = shallowRef(globalConfig.editorExtensions.highlight.instance);
onMounted(() => {
if (props.noHighlight || hljsRef.value) {
return;
}
appendHandler("link", {
...highlight.value.css,
rel: "stylesheet",
id: CDN_IDS.hlcss
});
appendHandler(
"script",
{
...highlight.value.js,
id: CDN_IDS.hljs,
onload() {
hljsRef.value = window.hljs;
}
},
"hljs"
);
});
watch(
() => highlight.value.css,
() => {
if (props.noHighlight || globalConfig.editorExtensions.highlight.instance) {
return;
}
updateHandler("link", {
...highlight.value.css,
rel: "stylesheet",
id: CDN_IDS.hlcss
});
}
);
return hljsRef;
};
const mermaidCache = new LRUCache({
max: 1e3,
// 缓存10分钟
ttl: 6e5
});
const useMermaid = (props) => {
const editorId = inject("editorId");
const theme = inject("theme");
const rootRef = inject("rootRef");
const { editorExtensions, editorExtensionsAttrs, mermaidConfig } = globalConfig;
let mermaid = editorExtensions.mermaid.instance;
const reRenderRef = shallowRef(-1);
const configMermaid = () => {
if (!props.noMermaid && mermaid) {
mermaid.initialize(
mermaidConfig({
startOnLoad: false,
theme: theme.value === "dark" ? "dark" : "default"
})
);
reRenderRef.value = reRenderRef.value + 1;
}
};
watch(
() => theme.value,
() => {
mermaidCache.clear();
configMermaid();
}
);
onMounted(() => {
var _a, _b;
if (props.noMermaid || mermaid) {
return;
}
const jsSrc = editorExtensions.mermaid.js;
if (/\.mjs/.test(jsSrc)) {
appendHandler("link", {
...(_a = editorExtensionsAttrs.mermaid) == null ? void 0 : _a.js,
rel: "modulepreload",
href: jsSrc,
id: CDN_IDS.mermaidM
});
import(
/* @vite-ignore */
/* webpackIgnore: true */
jsSrc
).then((module) => {
mermaid = module.default;
configMermaid();
});
} else {
appendHandler(
"script",
{
...(_b = editorExtensionsAttrs.mermaid) == null ? void 0 : _b.js,
src: jsSrc,
id: CDN_IDS.mermaid,
onload() {
mermaid = window.mermaid;
configMermaid();
}
},
"mermaid"
);
}
});
const replaceMermaid = async () => {
if (!props.noMermaid && mermaid) {
const mermaidSourceEles = rootRef.value.querySelectorAll(
`div.${prefix}-mermaid`
);
const svgContainingElement = document.createElement("div");
const sceWidth = document.body.offsetWidth > 1366 ? document.body.offsetWidth : 1366;
const sceHeight = document.body.offsetHeight > 768 ? document.body.offsetHeight : 768;
svgContainingElement.style.width = sceWidth + "px";
svgContainingElement.style.height = sceHeight + "px";
svgContainingElement.style.position = "fixed";
svgContainingElement.style.zIndex = "-10000";
svgContainingElement.style.top = "-10000";
let count = mermaidSourceEles.length;
if (count > 0) {
document.body.appendChild(svgContainingElement);
}
await Promise.allSettled(
Array.from(mermaidSourceEles).map((ele) => {
const handler = async (item) => {
var _a;
if (item.dataset.closed === "false") {
return false;
}
const code = item.innerText;
let mermaidHtml = mermaidCache.get(code);
if (!mermaidHtml) {
const idRand = randomId();
let result = { svg: "" };
try {
result = await mermaid.render(idRand, code, svgContainingElement);
mermaidHtml = await props.sanitizeMermaid(result.svg);
const p = document.createElement("p");
p.className = `${prefix}-mermaid`;
p.setAttribute("data-processed", "");
p.innerHTML = mermaidHtml;
(_a = p.children[0]) == null ? void 0 : _a.removeAttribute("height");
mermaidCache.set(code, p.innerHTML);
if (item.dataset.line !== void 0) {
p.dataset.line = item.dataset.line;
}
item.replaceWith(p);
} catch (error) {
bus.emit(editorId, ERROR_CATCHER, {
name: "mermaid",
message: error.message,
error
});
}
if (--count === 0) {
svgContainingElement.remove();
}
}
};
return handler(ele);
})
);
}
};
return { reRenderRef, replaceMermaid };
};
const useKatex = (props) => {
const katex = shallowRef(globalConfig.editorExtensions.katex.instance);
onMounted(() => {
var _a, _b;
if (props.noKatex || katex.value) {
return;
}
const { editorExtensions, editorExtensionsAttrs } = globalConfig;
appendHandler(
"script",
{
...(_a = editorExtensionsAttrs.katex) == null ? void 0 : _a.js,
src: editorExtensions.katex.js,
id: CDN_IDS.katexjs,
onload() {
katex.value = window.katex;
}
},
"katex"
);
appendHandler("link", {
...(_b = editorExtensionsAttrs.katex) == null ? void 0 : _b.css,
rel: "stylesheet",
href: editorExtensions.katex.css,
id: CDN_IDS.katexcss
});
});
return katex;
};
const MermaidPlugin = (md, options) => {
const temp = md.renderer.rules.fence.bind(md.renderer.rules);
md.renderer.rules.fence = (tokens, idx, ops, env, slf) => {
var _a;
const token = tokens[idx];
const code = token.content.trim();
if (token.info === "mermaid") {
token.attrSet("class", `${prefix}-mermaid`);
token.attrSet("data-mermaid-theme", options.themeRef.value);
if (token.map && token.level === 0) {
const closeLine = token.map[1] - 1;
const closeLineText = (_a = env.srcLines[closeLine]) == null ? void 0 : _a.trim();
const isClosingFence = closeLineText == null ? void 0 : closeLineText.startsWith("```");
token.attrSet("data-closed", isClosingFence);
token.attrSet("data-line", String(token.map[0]));
}
const mermaidHtml = mermaidCache.get(code);
if (mermaidHtml) {
token.attrSet("data-processed", "");
return `<p ${slf.renderAttrs(token)}>${mermaidHtml}</p>`;
}
return `<div ${slf.renderAttrs(token)}>${md.utils.escapeHtml(code)}</div>`;
}
return temp(tokens, idx, ops, env, slf);
};
};
const mergeAttrs = (token, addAttrs) => {
const tmpAttrs = token.attrs ? token.attrs.slice() : [];
addAttrs.forEach((addAttr) => {
const i = token.attrIndex(addAttr[0]);
if (i < 0) {
tmpAttrs.push(addAttr);
} else {
tmpAttrs[i] = tmpAttrs[i].slice();
tmpAttrs[i][1] += ` ${addAttr[1]}`;
}
});
return tmpAttrs;
};
const delimiters = {
block: [
{ open: "$$", close: "$$" },
{ open: "\\[", close: "\\]" }
],
inline: [
{ open: "$$", close: "$$" },
{ open: "$", close: "$" },
{ open: "\\[", close: "\\]" },
{ open: "\\(", close: "\\)" }
]
};
const create_math_inline = (options) => (state, silent) => {
const delimiters2 = options.delimiters;
let match, token, pos;
for (const delim of delimiters2) {
if (state.src.startsWith(delim.open, state.pos)) {
const start = state.pos + delim.open.length;
match = start;
while ((match = state.src.indexOf(delim.close, match)) !== -1) {
pos = match - 1;
while (state.src[pos] === "\\") {
pos -= 1;
}
if ((match - pos) % 2 === 1) {
break;
}
match += delim.close.length;
}
if (match === -1) {
if (!silent) {
state.pending += delim.open;
}
state.pos = start;
return true;
}
if (match - start === 0) {
if (!silent) {
state.pending += delim.open + delim.close;
}
state.pos = start + delim.close.length;
return true;
}
if (!silent) {
const inlineContent = state.src.slice(start, match);
token = state.push("math_inline", "math", 0);
token.markup = delim.open;
token.content = inlineContent;
}
state.pos = match + delim.close.length;
return true;
}
}
return false;
};
const create_math_block = (options) => (state, start, end, silent) => {
const delimiters2 = options.delimiters;
let firstLine, lastLine, next, lastPos, found = false;
let pos = state.bMarks[start] + state.tShift[start];
let max = state.eMarks[start];
for (const delim of delimiters2) {
if (state.src.slice(pos, pos + delim.open.length) === delim.open && state.src.slice(max - delim.close.length, max) === delim.close) {
pos += delim.open.length;
firstLine = state.src.slice(pos, max);
if (silent) {
return true;
}
if (firstLine.trim().slice(-delim.close.length) === delim.close) {
firstLine = firstLine.trim().slice(0, -delim.close.length);
found = true;
}
for (next = start; !found; ) {
next++;
if (next >= end) {
break;
}
pos = state.bMarks[next] + state.tShift[next];
max = state.eMarks[next];
if (pos < max && state.tShift[next] < state.blkIndent) {
break;
}
if (state.src.slice(pos, max).trim().slice(-delim.close.length) === delim.close) {
lastPos = state.src.slice(0, max).lastIndexOf(delim.close);
lastLine = state.src.slice(pos, lastPos);
found = true;
}
}
state.line = next + 1;
const token = state.push("math_block", "math", 0);
token.block = true;
token.content = (firstLine && firstLine.trim() ? firstLine + "\n" : "") + state.getLines(start + 1, next, state.tShift[start], true) + (lastLine && lastLine.trim() ? lastLine : "");
token.map = [start, state.line];
token.markup = delim.open;
return true;
}
}
return false;
};
const KatexPlugin = (md, { katexRef, inlineDelimiters, blockDelimiters }) => {
const katexInline = (tokens, idx, options, env, slf) => {
const token = tokens[idx];
const tmpToken = {
attrs: mergeAttrs(token, [["class", `${prefix}-katex-inline`]])
};
if (katexRef.value) {
const html = katexRef.value.renderToString(
token.content,
globalConfig.katexConfig({
throwOnError: false
})
);
return `<span ${slf.renderAttrs(tmpToken)} data-processed>${html}</span>`;
} else {
return `<span ${slf.renderAttrs(tmpToken)}>${token.content}</span>`;
}
};
const katexBlock = (tokens, idx, options, env, slf) => {
const token = tokens[idx];
const tmpToken = {
attrs: mergeAttrs(token, [["class", `${prefix}-katex-block`]])
};
if (katexRef.value) {
const html = katexRef.value.renderToString(
token.content,
globalConfig.katexConfig({
throwOnError: false,
displayMode: true
})
);
return `<p ${slf.renderAttrs(tmpToken)} data-processed>${html}</p>`;
} else {
return `<p ${slf.renderAttrs(tmpToken)}>${token.content}</p>`;
}
};
md.inline.ruler.before(
"escape",
"math_inline",
create_math_inline({
delimiters: inlineDelimiters || delimiters.inline
})
);
md.block.ruler.after(
"blockquote",
"math_block",
create_math_block({
delimiters: blockDelimiters || delimiters.block
}),
{
alt: ["paragraph", "reference", "blockquote", "list"]
}
);
md.renderer.rules.math_inline = katexInline;
md.renderer.rules.math_block = katexBlock;
};
const AdmonitionPlugin = (md, options) => {
options = options || {};
const markers = 3, markerStr = options.marker || "!", markerChar = markerStr.charCodeAt(0), markerLen = markerStr.length;
let type = "", title = "";
const render = (tokens, idx, _options, _env, self) => {
const token = tokens[idx];
if (token.type === "admonition_open") {
tokens[idx].attrPush([
"class",
`${prefix}-admonition ${prefix}-admonition-${token.info}`
]);
} else if (token.type === "admonition_title_open") {
tokens[idx].attrPush(["class", `${prefix}-admonition-title`]);
}
return self.renderToken(tokens, idx, _options);
};
const validate = (params) => {
const array = params.trim().split(" ", 2);
title = "";
type = array[0];
if (array.length > 1) {
title = params.substring(type.length + 2);
}
};
md.block.ruler.before(
"code",
"admonition",
(state, startLine, endLine, silent) => {
let pos, nextLine, token, autoClosed = false, start = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine];
if (markerChar !== state.src.charCodeAt(start)) {
return false;
}
for (pos = start + 1; pos <= max; pos++) {
if (markerStr[(pos - start) % markerLen] !== state.src[pos]) {
break;
}
}
const markerCount = Math.floor((pos - start) / markerLen);
if (markerCount !== markers) {
return false;
}
pos -= (pos - start) % markerLen;
const markup = state.src.slice(start, pos);
const params = state.src.slice(pos, max);
validate(params);
if (silent) {
return true;
}
nextLine = startLine;
for (; ; ) {
nextLine++;
if (nextLine >= endLine) {
break;
}
start = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
if (start < max && state.sCount[nextLine] < state.blkIndent) {
break;
}
if (markerChar !== state.src.charCodeAt(start)) {
continue;
}
if (state.sCount[nextLine] - state.blkIndent >= 4) {
continue;
}
for (pos = start + 1; pos <= max; pos++) {
if (markerStr[(pos - start) % markerLen] !== state.src[pos]) {
break;
}
}
if (Math.floor((pos - start) / markerLen) < markerCount) {
continue;
}
pos -= (pos - start) % markerLen;
pos = state.skipSpaces(pos);
if (pos < max) {
continue;
}
autoClosed = true;
break;
}
const oldParent = state.parentType;
const oldLineMax = state.lineMax;
state.parentType = "root";
state.lineMax = nextLine;
token = state.push("admonition_open", "div", 1);
token.markup = markup;
token.block = true;
token.info = type;
token.map = [startLine, nextLine];
if (title) {
token = state.push("admonition_title_open", "p", 1);
token.markup = markup + " " + type;
token.map = [startLine, nextLine];
token = state.push("inline", "", 0);
token.content = title;
token.map = [startLine, state.line - 1];
token.children = [];
token = state.push("admonition_title_close", "p", -1);
token.markup = markup + " " + type;
}
state.md.block.tokenize(state, startLine + 1, nextLine);
token = state.push("admonition_close", "div", -1);
token.markup = state.src.slice(start, pos);
token.block = true;
state.parentType = oldParent;
state.lineMax = oldLineMax;
state.line = nextLine + (autoClosed ? 1 : 0);
return true;
},
{
alt: ["paragraph", "reference", "blockquote", "list"]
}
);
md.renderer.rules["admonition_open"] = render;
md.renderer.rules["admonition_title_open"] = render;
md.renderer.rules["admonition_title_close"] = render;
md.renderer.rules["admonition_close"] = render;
};
const HeadingPlugin = (md, options) => {
md.renderer.rules.heading_open = (tokens, idx) => {
var _a;
const token = tokens[idx];
const text = ((_a = tokens[idx + 1].children) == null ? void 0 : _a.reduce((p, c) => {
return p + (["text", "code_inline", "math_inline"].includes(c.type) ? c.content || "" : "");
}, "")) || "";
const level = token.markup.length;
options.headsRef.value.push({
text,
level,
line: token.map[0]
});
if (token.map && token.level === 0) {
token.attrSet(
"id",
options.mdHeadingId(text, level, options.headsRef.value.length)
);
}
return md.renderer.renderToken(tokens, idx, options);
};
md.renderer.rules.heading_close = (tokens, idx, opts, _env, self) => {
return self.renderToken(tokens, idx, opts);
};
};
const codetabs = (md, _opts) => {
const defaultRender = md.renderer.rules.fence, unescapeAll = md.utils.unescapeAll, re = /\[(\w*)(?::([\w ]*))?\]/, mandatoryRe = /::(open|close)/;
const getInfo = (token) => {
return token.info ? unescapeAll(token.info).trim() : "";
};
const getGroupAndTab = (token) => {
const info = getInfo(token), [group = null, tab = ""] = (re.exec(info) || []).slice(1);
return [group, tab];
};
const getLangName = (token) => {
const info = getInfo(token);
return info ? info.split(/(\s+)/g)[0] : "";
};
const getTagType = (token) => {
const mandatory = token.info.match(mandatoryRe) || [];
const open = mandatory[1] === "open" || mandatory[1] !== "close" && _opts.codeFoldable && token.content.trim().split("\n").length < _opts.autoFoldThreshold;
const tagContainer = mandatory[1] || _opts.codeFoldable ? "details" : "div", tagHeader = mandatory[1] || _opts.codeFoldable ? "summary" : "div";
return { open, tagContainer, tagHeader };
};
const fenceGroup = (tokens, idx, options, env, slf) => {
var _a;
if (tokens[idx].hidden) {
return "";
}
const codeCodeText = (_a = _opts.usedLanguageTextRef.value) == null ? void 0 : _a.copyCode.text;
const copyBtnHtml = _opts.customIconRef.value.copy || codeCodeText;
const isIcon = !!_opts.customIconRef.value.copy;
const collapseTips = `<span class="${prefix}-collapse-tips">${StrIcon("collapse-tips", _opts.customIconRef.value)}</span>`;
const [GROUP] = getGroupAndTab(tokens[idx]);
if (GROUP === null) {
const { open: open2, tagContainer: tagContainer2, tagHeader: tagHeader2 } = getTagType(tokens[idx]);
const addAttrs2 = [["class", `${prefix}-code`]];
if (open2) addAttrs2.push(["open", ""]);
const tmpToken2 = {
attrs: mergeAttrs(tokens[idx], addAttrs2)
};
tokens[idx].info = tokens[idx].info.replace(mandatoryRe, "");
const codeRendered = defaultRender(tokens, idx, options, env, slf);
return `
<${tagContainer2} ${slf.renderAttrs(tmpToken2)}>
<${tagHeader2} class="${prefix}-code-head">
<div class="${prefix}-code-flag"><span></span><span></span><span></span></div>
<div class="${prefix}-code-action">
<span class="${prefix}-code-lang">${md.utils.escapeHtml(tokens[idx].info.trim())}</span>
<span class="${prefix}-copy-button" data-tips="${codeCodeText}"${isIcon ? " data-is-icon=true" : ""}>${copyBtnHtml}</span>
${_opts.extraTools instanceof Function ? _opts.extraTools({ lang: tokens[idx].info.trim() }) : _opts.extraTools || ""}
${tagContainer2 === "details" ? collapseTips : ""}
</div>
</${tagHeader2}>
${codeRendered}
</${tagContainer2}>
`;
}
let token, group, tab, checked, labels = "", pres = "", langs = "";
const { open, tagContainer, tagHeader } = getTagType(tokens[idx]);
const addAttrs = [["class", `${prefix}-code`]];
if (open) addAttrs.push(["open", ""]);
const tmpToken = {
attrs: mergeAttrs(tokens[idx], addAttrs)
};
for (let i = idx; i < tokens.length; i++) {
token = tokens[i];
[group, tab] = getGroupAndTab(token);
if (group !== GROUP) {
break;
}
token.info = token.info.replace(re, "").replace(mandatoryRe, "");
token.hidden = true;
const className = `${prefix}-codetab-${_opts.editorId}-${idx}-${i - idx}`;
checked = i - idx > 0 ? "" : "checked";
labels += `
<li>
<input
type="radio"
id="label-${prefix}-codetab-label-1-${_opts.editorId}-${idx}-${i - idx}"
name="${prefix}-codetab-label-${_opts.editorId}-${idx}"
class="${className}"
${checked}
>
<label
for="label-${prefix}-codetab-label-1-${_opts.editorId}-${idx}-${i - idx}"
onclick="this.getRootNode().querySelectorAll('.${className}').forEach(e => e.click())"
>
${md.utils.escapeHtml(tab || getLangName(token))}
</label>
</li>`;
pres += `
<div role="tabpanel">
<input
type="radio"
name="${prefix}-codetab-pre-${_opts.editorId}-${idx}"
class="${className}"
${checked}
role="presentation">
${defaultRender(tokens, i, options, env, slf)}
</div>`;
langs += `
<input
type="radio"
name="${prefix}-codetab-lang-${_opts.editorId}-${idx}"
class="${className}"
${checked}
role="presentation">
<span class=${prefix}-code-lang role="note">${md.utils.escapeHtml(getLangName(token))}</span>`;
}
return `
<${tagContainer} ${slf.renderAttrs(tmpToken)}>
<${tagHeader} class="${prefix}-code-head">
<div class="${prefix}-code-flag">
<ul class="${prefix}-codetab-label" role="tablist">${labels}</ul>
</div>
<div class="${prefix}-code-action">
<span class="${prefix}-codetab-lang">${langs}</span>
<span class="${prefix}-copy-button" data-tips="${codeCodeText}"${isIcon ? " data-is-icon=true" : ""}>${copyBtnHtml}</span>
${_opts.extraTools instanceof Function ? _opts.extraTools({ lang: tokens[idx].info.trim() }) : _opts.extraTools || ""}
${tagContainer === "details" ? collapseTips : ""}
</div>
</${tagHeader}>
${pres}
</${tagContainer}>
`;
};
md.renderer.rules.fence = fenceGroup;
md.renderer.rules.code_block = fenceGroup;
};
const attrSet = (token, name, value) => {
const index = token.attrIndex(name);
const attr = [name, value];
if (index < 0) {
token.attrPush(attr);
} else {
token.attrs = token.attrs || [];
token.attrs[index] = attr;
}
};
const isInline = (token) => {
return token.type === "inline";
};
const isParagraph = (token) => {
return token.type === "paragraph_open";
};
const isListItem = (token) => {
return token.type === "list_item_open";
};
const startsWithTodoMarkdown = (token) => {
return token.content.indexOf("[ ] ") === 0 || token.content.indexOf("[x] ") === 0 || token.content.indexOf("[X] ") === 0;
};
const isTodoItem = (tokens, index) => {
return isInline(tokens[index]) && isParagraph(tokens[index - 1]) && isListItem(tokens[index - 2]) && startsWithTodoMarkdown(tokens[index]);
};
const parentToken = (tokens, index) => {
const targetLevel = tokens[index].level - 1;
for (let i = index - 1; i >= 0; i--) {
if (tokens[i].level === targetLevel) {
return i;
}
}
return -1;
};
const beginLabel = (TokenConstructor) => {
const token = new TokenConstructor("html_inline", "", 0);
token.content = "<label>";
return token;
};
const endLabel = (TokenConstructor) => {
const token = new TokenConstructor("html_inline", "", 0);
token.content = "</label>";
return token;
};
const afterLabel = (content, id, TokenConstructor) => {
const token = new TokenConstructor("html_inline", "", 0);
token.content = '<label class="task-list-item-label" for="' + id + '">' + content + "</label>";
token.attrs = [{ for: id }];
return token;
};
const makeCheckbox = (token, TokenConstructor, options) => {
const checkbox = new TokenConstructor("html_inline", "", 0);
const disabledAttr = !options.enabled ? ' disabled="" ' : " ";
if (token.content.indexOf("[ ] ") === 0) {
checkbox.content = '<input class="task-list-item-checkbox"' + disabledAttr + 'type="checkbox">';
} else if (token.content.indexOf("[x] ") === 0 || token.content.indexOf("[X] ") === 0) {
checkbox.content = '<input class="task-list-item-checkbox" checked=""' + disabledAttr + 'type="checkbox">';
}
return checkbox;
};
const todoify = (token, TokenConstructor, options) => {
token.children = token.children || [];
token.children.unshift(makeCheckbox(token, TokenConstructor, options));
token.children[1].content = token.children[1].content.slice(3);
token.content = token.content.slice(3);
if (options.label) {
if (options.labelAfter) {
token.children.pop();
const id = "task-item-" + Math.ceil(Math.random() * (1e4 * 1e3) - 1e3);
token.children[0].content = token.children[0].content.slice(0, -1) + ' id="' + id + '">';
token.children.push(afterLabel(token.content, id, TokenConstructor));
} else {
token.children.unshift(beginLabel(TokenConstructor));
token.children.push(endLabel(TokenConstructor));
}
}
};
const githubTaskLists = (md, options = {}) => {
md.core.ruler.after("inline", "github-task-lists", (state) => {
const tokens = state.tokens;
for (let i = 2; i < tokens.length; i++) {
if (isTodoItem(tokens, i)) {
todoify(tokens[i], state.Token, options);
attrSet(
tokens[i - 2],
"class",
"task-list-item" + (options.enabled ? " enabled" : " ")
);
attrSet(tokens[parentToken(tokens, i - 2)], "class", "contains-task-list");
}
}
});
};
const initLineNumber = (md) => {
md.core.ruler.push("init-line-number", (state) => {
state.tokens.forEach((token) => {
if (token.map) {
if (!token.attrs) {
token.attrs = [];
}
token.attrs.push(["data-line", token.map[0].toString()]);
}
});
return true;
});
};
const useMarkdownIt = (props, previewOnly) => {
const { editorConfig, markdownItConfig, markdownItPlugins, editorExtensions } = globalConfig;
const editorId = inject("editorId");
const languageRef = inject("language");
const usedLanguageTextRef = inject(
"usedLanguageText"
);
const showCodeRowNumber = inject("showCodeRowNumber");
const themeRef = inject("theme");
const customIconRef = inject("customIcon");
const rootRef = inject("rootRef");
const headsRef = ref([]);
let clearMermaidEvents = () => {
};
const hljsRef = useHighlight(props);
const katexRef = useKatex(props);
const { reRenderRef, replaceMermaid } = useMermaid(props);
const md = mdit({
html: true,
breaks: true,
linkify: true
});
markdownItConfig(md, {
editorId
});
const plugins = [
{
type: "image",
plugin: ImageFiguresPlugin,
options: { figcaption: true, classes: "md-zoom" }
},
{
type: "admonition",
plugin: AdmonitionPlugin,
options: {}
},
{
type: "taskList",
plugin: githubTaskLists,
options: {}
},
{
type: "heading",
plugin: HeadingPlugin,
options: { mdHeadingId: props.mdHeadingId, headsRef }
},
{
type: "code",
plugin: codetabs,
options: {
editorId,
usedLanguageTextRef,
// showCodeRowNumber,
codeFoldable: props.codeFoldable,
autoFoldThreshold: props.autoFoldThreshold,
customIconRef
}
},
{
type: "sub",
plugin: SubPlugin,
options: {}
},
{
type: "sup",
plugin: SupPlugin,
options: {}
}
];
if (!props.noKatex) {
plugins.push({
type: "katex",
plugin: KatexPlugin,
options: { katexRef }
});
}
if (!props.noMermaid) {
plugins.push({
type: "mermaid",
plugin: MermaidPlugin,
options: { themeRef }
});
}
markdownItPlugins(plugins, {
editorId
}).forEach((item) => {
md.use(item.plugin, item.options);
});
const userDefHighlight = md.options.highlight;
md.set({
highlight: (str, language, attrs) => {
if (userDefHighlight) {
const result = userDefHighlight(str, language, attrs);
if (result) {
return result;
}
}
let codeHtml;
if (!props.noHighlight && hljsRef.value) {
const hljsLang = hljsRef.value.getLanguage(language);
if (hljsLang) {
codeHtml = hljsRef.value.highlight(str, {
language,
ignoreIllegals: true
}).value;
} else {
codeHtml = hljsRef.value.highlightAuto(str).value;
}
} else {
codeHtml = md.utils.escapeHtml(str);
}
const codeSpan = showCodeRowNumber ? generateCodeRowNumber(
codeHtml.replace(/^\n+|\n+$/g, ""),
str.replace(/^\n+|\n+$/g, "")
) : `<span class="${prefix}-code-block">${codeHtml.replace(/^\n+|\n+$/g, "")}</span>`;
return `<pre><code class="language-${language}" language=${language}>${codeSpan}</code></pre>`;
}
});
initLineNumber(md);
const key = ref(`_article-key_${randomId()}`);
const html = ref(
props.sanitize(
md.render(props.modelValue, {
srcLines: props.modelValue.split("\n")
})
)
);
const updatedTodo = () => {
bus.emit(editorId, BUILD_FINISHED, html.value);
props.onHtmlChanged(html.value);
props.onGetCatalog(headsRef.value);
bus.emit(editorId, CATALOG_CHANGED, headsRef.value);
nextTick(() => {
replaceMermaid().then(() => {
var _a, _b;
if ((_a = editorExtensions.mermaid) == null ? void 0 : _a.enableZoom) {
clearMermaidEvents();
clearMermaidEvents = zoomMermaid(
(_b = rootRef.value) == null ? void 0 : _b.querySelectorAll(
`#${editorId} p.${prefix}-mermaid:not([data-closed=false])`
),
{
customIcon: customIconRef.value
}
);
}
});
});
};
const markHtml = () => {
headsRef.value = [];
html.value = props.sanitize(
md.render(props.modelValue, {
srcLines: props.modelValue.split("\n")
})
);
updatedTodo();
};
const needReRender = computed(() => {
return (props.noKatex || katexRef.value) && (props.noHighlight || hljsRef.value);
});
let timer = -1;
watch([toRef(props, "modelValue"), needReRender, reRenderRef, languageRef], () => {
timer = window.setTimeout(
() => {
markHtml();
},
previewOnly ? 0 : editorConfig.renderDelay
);
});
watch(
() => props.setting.preview,
() => {
if (props.setting.preview) {
nextTick(() => {
replaceMermaid().then(() => {
var _a, _b;
if ((_a = editorExtensions.mermaid) == null ? void 0 : _a.enableZoom) {
clearMermaidEvents();
clearMermaidEvents = zoomMermaid(
(_b = rootRef.value) == null ? void 0 : _b.querySelectorAll(
`#${editorId} p.${prefix}-mermaid:not([data-closed=false])`
),
{
customIcon: customIconRef.value
}
);
}
});
bus.emit(editorId, CATALOG_CHANGED, headsRef.value);
});
}
}
);
onMounted(updatedTodo);
onMounted(() => {
bus.on(editorId, {
name: PUSH_CATALOG,
callback() {
bus.emit(editorId, CATALOG_CHANGED, headsRef.value);
}
});
bus.on(editorId, {
name: RERENDER,
callback: () => {
key.value = `_article-key_${randomId()}`;
markHtml();
}
});
});
onBeforeUnmount(() => {
clearMermaidEvents();
clearTimeout(timer);
});
return { html, key };
};
const template = {
checked: {
regexp: /- \[x\]/,
value: "- [ ]"
},
unChecked: {
regexp: /- \[\s\]/,
value: "- [x]"
}
};
const useTaskState = (props, html) => {
const editorId = inject("editorId");
const rootRef = inject("rootRef");
let removeListener = () => {
};
const addListener = () => {
if (!rootRef.value) {
return false;
}
const tasks = rootRef.value.querySelectorAll(".task-list-item.enabled");
const listener = (e) => {
var _a;
e.preventDefault();
const nextValue = e.target.checked ? "unChecked" : "checked";
const line = (_a = e.target.parentElement) == null ? void 0 : _a.dataset.line;
if (!line) {
return;
}
const lineNumber = Number(line);
const lines = props.modelValue.split("\n");
const targetValue = lines[Number(lineNumber)].replace(
template[nextValue].regexp,
template[nextValue].value
);
if (props.previewOnly) {
lines[Number(lineNumber)] = targetValue;
props.onChange(lines.join("\n"));
} else {
bus.emit(editorId, TASK_STATE_CHANGED, lineNumber + 1, targetValue);
}
};
tasks.forEach((item) => {
item.addEventListener("click", listener);
});
removeListener = () => {
tasks.forEach((item) => {
item.removeEventListener("click", listener);
});
};
};
onBeforeUnmount(() => {
removeListener();
});
watch(
[html],
() => {
removeListener();
nextTick(addListener);
},
{
immediate: true
}
);
};
const useRemount = (props, html, key) => {
const handler = () => {
nextTick(() => {
var _a;
(_a = props.onRemount) == null ? void 0 : _a.call(props);
});
};
const settingPreviewChanged = (nVal) => {
if (nVal) {
handler();
}
};
watch([html, key], handler);
watch(() => props.setting.preview, settingPreviewChanged);
watch(() => props.setting.htmlPreview, settingPreviewChanged);
onMounted(handler);
};
const contentPreviewProps = {
modelValue: {
type: String,
default: ""
},
onChange: {
type: Function,
default: () => {
}
},
setting: {
type: Object,
default: () => ({ preview: true })
},
onHtmlChanged: {
type: Function,
default: () => {
}
},
onGetCatalog: {
type: Function,
default: () => {
}
},
mdHeadingId: {
type: Function,
default: () => ""
},
noMermaid: {
type: Boolean,
default: false
},
sanitize: {
type: Function,
default: (html) => html
},
// 不使用该函数功能
noKatex: {
type: Boolean,
default: false
},
formatCopiedText: {
type: Function,
default: (text) => text
},
noHighlight: {
type: Boolean,
default: false
},
previewOnly: {
type: Boolean,
default: false
},
noImgZoomIn: {
type: Boolean
},
sanitizeMermaid: {
type: Function
},
codeFoldable: {
type: Boolean
},
autoFoldThreshold: {
type: Number
},
onRemount: {
type: Function
}
};
const contentProps = {
...contentPreviewProps,
updateModelValue: {
type: Function,
default: () => {
}
},
placeholder: {
type: String,
default: ""
},
scrollAuto: {
type: Boolean
},
autofocus: {
type: Boolean
},
disabled: {
type: Boolean
},
readonly: {
type: Boolean
},
maxlength: {
type: Number
},
autoDetectCode: {
type: Boolean
},
/**
* 输入框失去焦点时触发事件
*/
onBlur: {
type: Function,
default: () => {
}
},
/**
* 输入框获得焦点时触发事件
*/
onFocus: {
type: Function,
default: () => {
}
},
noPrettier: {
type: Boolean
},
completions: {
type: Array
},
catalogVisible: {
type: Boolean
},
theme: {
type: String,
default: "light"
},
onInput: {
type: Function
},
onDrop: {
type: Function,
default: () => {
}
},
inputBoxWidth: {
type: String
},
oninputBoxWidthChange: {
type: Function
},
transformImgUrl: {
type: Function,
default: (t) => t
},
catalogLayout: {
type: String
},
catalogMaxDepth: {
type: Number
}
};
const splitNodes = (html) => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
return Array.from(doc.body.childNodes);
};
const compareHtml = (newNodes, currentNodes) => {
const updates = [];
const deletes = [];
newNodes.forEach((newNode, index) => {
const currentNode = currentNodes[index];
if (!currentNode) {
updates.push({
index,
newNode
});
return;
}
if (newNode.nodeType !== currentNode.nodeType || newNode.textContent !== currentNode.textContent || newNode.nodeType === 1 && newNode.outerHTML !== currentNode.outerHTML) {
updates.push({
index,
newNode
});
}
});
if (currentNodes.length > newNodes.length) {
for (let i = newNodes.length; i < currentNodes.length; i++) {
deletes.push(currentNodes[i]);
}
}
return {
updates,
deletes
};
};
const UpdateOnDemand = /* @__PURE__ */ defineComponent({
name: "UpdateOnDemand",
props: {
html: {
type: String,
required: true
}
},
setup(props) {
const editorId = inject("editorId");
const previewTheme = inject("previewTheme");
const showCodeRowNumber = inject("showCodeRowNumber");
const htmlContainer = ref();
const firstHtml = props.html;
const updateHtmlContent = (updates, deletes) => {
if (!htmlContainer.value) return;
deletes.forEach((node) => {
node.remove();
});
updates.forEach(({
index,
newNode
}) => {
var _a, _b, _c;
const targetNode = (_a = htmlContainer.value) == null ? void 0 : _a.childNodes[index];
if (!targetNode) {
(_b = htmlContainer.value) == null ? void 0 : _b.appendChild(newNode.cloneNode(true));
} else {
(_c = htmlContainer.value) == null ? void 0 : _c.replaceChild(newNode.cloneNode(true), targetNode);
}
});
};
watch(() => props.html, (newHtml) => {
var _a;
const newNodes = splitNodes(newHtml);
const currentNodes = Array.from(((_a = htmlContainer.value) == null ? void 0 : _a.childNodes) || []);
const {
updates,
deletes
} = compareHtml(newNodes, currentNodes);
updateHtmlContent(updates, deletes);
});
return () => createVNode("div", {
"id": `${editorId}-preview`,
"class": [`${prefix}-preview`, `${previewTheme == null ? void 0 : previewTheme.value}-theme`, showCodeRowNumber && `${prefix}-scrn`],
"innerHTML": firstHtml,
"ref": htmlContainer
}, null);
}
});
const ContentPreview = /* @__PURE__ */ defineComponent({
name: "ContentPreview",
props: contentPreviewProps,
setup(props) {
const editorId = inject("editorId");
const {
html,
key
} = useMarkdownIt(props, props.previewOnly);
useCopyCode(props, html, key);
userZoom(props, html);
useTaskState(props, html);
useRemount(props, html, key);
return () => {
return createVNode(Fragment, null, [props.setting.preview && createVNode("div", {
"id": `${editorId}-preview-wrapper`,
"class": `${prefix}-preview-wrapper`,
"key": "content-preview-wrapper"
}, [createVNode(UpdateOnDemand, {
"key": key.value,
"html": html.value
}, null)]), !props.previewOnly && props.setting.htmlPreview && createVNode("div", {
"id": `${editorId}-html-wrapper`,
"class": `${prefix}-preview-wrapper`,
"key": "html-preview-wrapper"
}, [createVNode("div", {
"class": `${prefix}-html`
}, [html.value])])]);
};
}
});
const useOnSave = (props, context, options) => {
const { editorId } = options;
const state = reactive({
// 是否已编译成html
buildFinished: false,
// 存储当前最新的html
html: ""
});
watch(
() => props.modelValue,
() => {
state.buildFinished = false;
}
);
onMounted(() => {
bus.on(editorId, {
name: BUILD_FINISHED,
callback(html) {
state.buildFinished = true;
state.html = html;
}
});
bus.on(editorId, {
name: ON_SAVE,
callback() {
const htmlPromise = new Promise((rev) => {
if (state.buildFinished) {
rev(state.html);
} else {
const buildFinishedCallback = (html) => {
rev(html);
bus.remove(editorId, BUILD_FINISHED, buildFinishedCallback);
};
bus.on(editorId, {
name: BUILD_FINISHED,
callback: buildFinishedCallback
});
}
});
if (props.onSave) {
props.onSave(props.modelValue, htmlPromise);
} else {
context.emit("onSave", props.modelValue, htmlPromise);
}
}
});
});
};
const useProvidePreview = (props, rootRef) => {
const hljsUrls = globalConfig.editorExtensions.highlight;
const hljsAttrs = globalConfig.editorExtensionsAttrs.highlight;
const editorId = useEditorId(props);
provide("editorId", editorId);
provide("rootRef", rootRef);
provide(
"theme",
computed(() => props.theme)
);
provide(
"language",
computed(() => props.language)
);
provide(
"highlight",
computed(() => {
const { js: jsUrl } = hljsUrls;
const cssList = {
...codeCss,
...hljsUrls.css
};
const { js: jsAttrs, css: cssAttrs = {} } = hljsAttrs || {};
const _theme = props.codeStyleReverse && props.codeStyleReverseList.includes(props.previewTheme) ? "dark" : props.theme;
const codeCssHref = cssList[props.codeTheme] ? cssList[props.codeTheme][_theme] : codeCss.atom[_theme];
const codeCssAttrs = cssList[props.codeTheme] && cssAttrs[props.codeTheme] ? cssAttrs[props.codeTheme][_theme] : cssAttrs["atom"] ? cssAttrs["atom"][_theme] : {};
return {
js: {
src: jsUrl,
...jsAttrs
},
css: {
href: codeCssHref,
...codeCssAttrs
}
};
})
);
provide("showCodeRowNumber", props.showCodeRowNumber);
const usedLanguageText = computed(() => {
const allText = {
...staticTextDefault,
...globalConfig.editorConfig.languageUserDefined
};
return deepMerge(
deepClone(staticTextDefault["en-US"]),
allText[props.language] || {}
);
});
provide("usedLanguageText", usedLanguageText);
provide(
"previewTheme",
computed(() => props.previewTheme)
);
provide(
"customIcon",
computed(() => props.customIcon)
);
return { editorId };
};
const useProvide = (props, rootRef) => {
provide("tabWidth", props.tabWidth);
provide(
"disabled",
computed(() => props.disabled)
);
return useProvidePreview(props, rootRef);
};
const useExpansion = (props) => {
const { noPrettier, noUploadImg } = props;
const { editorExtensions, editorExtensionsAttrs } = globalConfig;
const noPrettierScript = noPrettier || editorExtensions.prettier.prettierInstance;
const noParserMarkdownScript = noPrettier || editorExtensions.prettier.parserMarkdownInstance;
const noCropperScript = noUploadImg || editorExtensions.cropper.instance;
onMounted(() => {
if (!noCropperScript) {
const { js = {}, css = {} } = editorExtensionsAttrs.cropper || {};
appendHandler("link", {
...css,
rel: "stylesheet",
href: editorExtensions.cropper.css,
id: CDN_IDS.croppercss
});
appendHandler("script", {
...js,
src: editorExtensions.cropper.js,
id: CDN_IDS.cropperjs
});
}
if (!noPrettierScript) {
const { standaloneJs = {} } = editorExtensionsAttrs.prettier || {};
appendHandler("script", {
...standaloneJs,
src: editorExtensions.prettier.standaloneJs,
id: CDN_IDS.prettier
});
}
if (!noParserMarkdownScript) {
const { parserMarkdownJs = {} } = editorExtensionsAttrs.prettier || {};
appendHandler("script", {
...parserMarkdownJs,
src: editorExtensions.prettier.parserMarkdownJs,
id: CDN_IDS.prettierMD
});
}
});
};
const useErrorCatcher = (props, context, options) => {
const { editorId } = options;
onMounted(() => {
bus.on(editorId, {
name: ERROR_CATCHER,
callback: (err) => {
var _a;
(_a = props.onError) == null ? void 0 : _a.call(props, err);
context.emit("onError", err);
}
});
});
};
const useConfig = (props, context, options) => {
const { editorId } = options;
const setting = reactive({
pageFullscreen: props.pageFullscreen,
fullscreen: false,
preview: props.preview,
htmlPreview: props.preview ? false : props.htmlPreview,
previewOnly: false
});
const cacheSetting = reactive({ ...setting });
const updateSetting = (k, v) => {
const realValue = v === void 0 ? !setting[k] : v;
switch (k) {
case "preview": {
setting.htmlPreview = false;
setting.previewOnly = false;
break;
}
case "htmlPreview": {
setting.preview = false;
setting.previewOnly = false;
break;
}
case "previewOnly": {
if (realValue) {
if (!setting.preview && !setting.htmlPreview) {
setting.preview = true;
}
} else {
if (!cacheSetting.preview) {
setting.preview = false;
}
if (!cacheSetting.htmlPreview) {
setting.htmlPreview = false;
}
}