UNPKG

@vuux/editor

Version:

Vue Nuxt 富文本编辑器

250 lines (248 loc) 6.73 kB
import { ref as w, watch as D, onMounted as C, nextTick as K } from "vue"; import { generateTable as b } from "../utils.mjs"; import { markdownToHtml as H } from "./useParse.mjs"; import { Utils as x } from "@vuux/utils"; const R = (o) => { const p = w(""), i = w(""), m = [], v = [], S = x.debounce(async () => { const t = await H(i.value); t !== p.value && (p.value = t); }, 50); D(i, () => S()); const T = () => o.value ? { start: o.value.selectionStart, end: o.value.selectionEnd, value: o.value.value } : { start: 0, end: 0, value: "" }, _ = (t, n) => { o.value && (o.value.setSelectionRange(t, n), o.value.focus()); }, E = () => { m[m.length - 1] !== i.value && (m.push(i.value), m.length > 100 && m.shift(), v.length = 0); }, f = () => { m.length && (v.push(i.value), i.value = m.pop(), o.value && (o.value.value = i.value)); }, k = () => { v.length && (m.push(i.value), i.value = v.pop(), o.value && (o.value.value = i.value)); }, c = (t) => { if (!o.value) return; E(); const { start: n, end: e, value: r } = T(), s = r.slice(n, e), { text: l } = t(s); i.value = r.slice(0, n) + l + r.slice(e), K(() => _(n, n + l.length)); }, h = (t, n = "", e = "") => { c((r) => { let s = r || e; return s.startsWith(t) && s.endsWith(n) ? s = s.slice(t.length, s.length - n.length) : s = `${t}${s}${n}`, { text: s }; }); }, L = (t) => { (t.ctrlKey || t.metaKey) && t.key.toLowerCase() === "z" && (t.preventDefault(), f()), (t.ctrlKey || t.metaKey) && t.key.toLowerCase() === "y" && (t.preventDefault(), k()); }, { addManaged: y, removeAllManaged: M } = x.useManagedEvents(), I = () => { o.value && y(o.value, "keydown", L); }, A = { /** * 加粗 */ insertBold: () => { h("**", "**", "加粗"); }, /** * 下划线 */ insertUnderline: () => { h("!u", "!", "下划线"); }, /** * 斜体 */ insertItalic: () => { h("*", "*", "斜体"); }, /** * 删除线 */ insertStrike: () => { h("~~", "~~", "删除线"); }, /** * 行内代码 */ insertInlineCode: () => { h("`", "`", "代码"); }, /** * 标题 */ insertHeading: (t) => { c((n) => { let e = n || "标题"; const r = /^(#{1,6})\s+(.*)$/, s = e.match(r); if (s) { const l = s[1].length, a = s[2]; l === t ? e = a : e = "#".repeat(t) + " " + a; } else e = "#".repeat(t) + " " + e; return { text: e }; }); }, /** * 链接 */ insertLink: () => { h("[", "](https://www.usuuu.com)", "链接文字"); }, /** * 图片 */ insertImage: (t = "https://example.com/image.png", n = "图片文字") => { c((e) => { let r = e || n; const s = /^!\[(.*?)\]\(.*?\)$/, l = r.match(s); return l && (r = l[1]), { text: `![${r}](${t})` }; }); }, /** * 列表切换(有序|无序|任务列表)支持多行互斥 */ insertList: (t) => { c((n) => { let e; n ? e = n.split(` `) : e = ["列表项", "列表项"]; const r = /^- \[[ xX]?\] /, s = /^\d+\. /, l = /^[-*] /, a = e.every((d) => { const u = d.trim(); return t === "task" ? r.test(u) : t === "ordered" ? s.test(u) : l.test(u); }); return { text: e.map((d, u) => { let $ = d.trim(); return $ = $.replace(r, "").replace(s, "").replace(l, ""), a ? $ : t === "task" ? n ? `- [ ] ${$}` : u === 0 ? `- [ ] ${$}` : `- [x] ${$}` : t === "ordered" ? `${u + 1}. ${$}` : `- ${$}`; }).join(` `) }; }); }, /** * 引用块 */ insertBlockquote: () => { h("> ", "", "引用内容"); }, /** * 代码块 */ insertCodeBlock: (t = "javascript") => { c((n) => { let e = n || "console.log(123)"; const r = new RegExp(`^\\\`\\\`\\\`${t}\\n([\\s\\S]*?)\\n\\\`\\\`\\\`$`), s = e.match(r); return s ? e = s[1] : e = `\`\`\`${t} ${e} \`\`\``, { text: e }; }); }, /** * 分割线 */ insertHr: () => { c((t) => t.trim() === "---" ? { text: "" } : { text: `--- ` }); }, /** * 表格 */ insertTable: (t = 4, n = 4) => { c(() => ({ text: b(t, n) })); }, /** * 内嵌HTML */ insertHtmlBlock: (t = 1) => { c((n) => { let e = n || "点击展开更多内容"; const r = /^:::details\s+.*\n([\s\S]*?)\n:::$/m, s = e.match(r); return s ? e = s[1] : e = Array.from({ length: t }, (a, g) => `:::details 点击展开更多 ${e} :::`).join(` `), { text: e }; }); }, /** * 颜色选择 */ insertColor: (t) => { c((n) => { let e = n || "文字颜色"; const r = /^==(.+)==\{color:[^}]+\}$/, s = e.match(r); return s ? e = s[0].includes(t) ? s[1] : `==${s[1]}=={color:${t}}` : e = `==${e}=={color:${t}}`, { text: e }; }); }, /** * 字体大小 */ insertFontSize: (t) => { c((n) => { let e = n || "字体大小"; const r = /^==(.+)==\{size:(\d+)px\}$/, s = e.match(r); if (s) { const l = s[1]; parseInt(s[2], 10) === t ? e = l : e = `==${l}=={size:${t}px}`; } else e = `==${e}=={size:${t}px}`; return { text: e }; }); }, /** * 对齐 */ insertAlignBlock: (t) => { c((n) => { let e = n || "内容"; const r = /^:::align\s+(\w+)\n([\s\S]*?)\n:::$/m, s = e.match(r); if (s) { const l = s[1], a = s[2]; e = l === t ? a : `:::align ${t} ${a} :::`; } else e = `:::align ${t} ${e} :::`; return { text: e }; }); }, /** * 面板 */ insertPanel: (t, n = "标题") => { c((e) => { let r = e || "内容"; const s = /^:::panel\s+(\w+)\s+(.+)\n([\s\S]*?)\n:::$/m, l = r.match(s); if (l) { const [a, g, d, u] = l; r = g === t && d === n ? u : `:::panel ${t} ${n} ${u} :::`; } else r = `:::panel ${t} ${n} ${r} :::`; return { text: r }; }); }, /** * 拼音 */ insertPinyin: () => { c((t) => { let n = t || "拼音"; const e = /^\{(.+?)\s*\|\s*(.+?)\}$/, r = n.match(e); return r ? n = r[1] : n = `{${n} | pīn yīn}`, { text: n }; }); } }; return C(() => { I(), E(); }), { textareaRef: o, markdown: i, html: p, undo: f, redo: k, markdownActions: A }; }; export { R as useMarkdown };