@vuux/editor
Version:
Vue Nuxt 富文本编辑器
250 lines (248 loc) • 6.73 kB
JavaScript
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: `` };
});
},
/**
* 列表切换(有序|无序|任务列表)支持多行互斥
*/
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
};