UNPKG

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...

458 lines (457 loc) 13.8 kB
import { defineComponent as z, inject as q, ref as _, watch as J, onMounted as K, createVNode as N, reactive as X, shallowRef as G, provide as V, computed as Y, onBeforeUnmount as Z, Fragment as ee } from "vue"; import { p as w } from "./config.mjs"; import { b as B, j, G as U, k as te, S as le } from "./event-bus.mjs"; import { a as oe } from "./index5.mjs"; import { createSmoothScroll as ne, debounce as ie } from "@vavt/util"; const se = `.${w}-preview > [data-line]`, F = (e, t) => +getComputedStyle(e).getPropertyValue(t).replace("px", ""), ve = (e, t) => { const E = ie(() => { e.removeEventListener("scroll", s), e.addEventListener("scroll", s), t.removeEventListener("scroll", s), t.addEventListener("scroll", s); }, 50), s = (n) => { const k = e.clientHeight, y = t.clientHeight, d = e.scrollHeight, r = t.scrollHeight, m = (d - k) / (r - y); n.target === e ? (t.removeEventListener("scroll", s), t.scrollTo({ top: e.scrollTop / m // behavior: 'smooth' }), E()) : (e.removeEventListener("scroll", s), e.scrollTo({ top: t.scrollTop * m // behavior: 'smooth' }), E()); }; return [ () => { E().finally(() => { e.dispatchEvent(new Event("scroll")); }); }, () => { e.removeEventListener("scroll", s), t.removeEventListener("scroll", s); } ]; }, ge = (e, t, E) => { const { view: s } = E, n = ne(), k = (c) => s.lineBlockAt(s.state.doc.line(c + 1).from).top, y = (c) => s.lineBlockAt(s.state.doc.line(c + 1).from).bottom; let d = [], r = [], m = []; const S = () => { d = [], r = Array.from( t.querySelectorAll(se) ), m = r.map((f) => Number(f.dataset.line)); const c = [...m], { lines: u } = s.state.doc; let l = c.shift() || 0, o = c.shift() || u; for (let f = 0; f < u; f++) f === o && (l = f, o = c.shift() || u), d.push({ start: l, end: o - 1 }); }, b = (c, u) => { let l = 1; for (let o = r.length - 1; o - 1 >= 0; o--) { const f = r[o], i = r[o - 1]; if (f.offsetTop + f.offsetHeight > u && i.offsetTop < u) { l = Number(i.dataset.line); break; } } for (let o = d.length - 1; o >= 0; o--) { const f = y(d[o].end), i = k(d[o].start); if (f > c && i <= c) { l = l < d[o].start ? l : d[o].start; break; } } return l; }; let C = 0, M = 0; const $ = () => { if (M !== 0) return !1; C++; const { scrollDOM: c, contentHeight: u } = s; let l = F(t, "padding-top"); const o = s.lineBlockAtHeight(c.scrollTop), { number: f } = s.state.doc.lineAt(o.from), i = d[f - 1]; if (!i) return !1; let g = 1; const h = t.querySelector(`[data-line="${i.start}"]`) || t.firstElementChild?.firstElementChild, a = t.querySelector(`[data-line="${i.end + 1}"]`) || t.lastElementChild?.lastElementChild, p = c.scrollHeight - c.clientHeight, L = t.scrollHeight - t.clientHeight; let T = k(i.start), R = y(i.end), x = h.offsetTop, I = a.offsetTop - x; T === 0 && (x = 0, h === a ? (l = 0, R = u - c.offsetHeight, I = L) : I = a.offsetTop), g = (c.scrollTop - T) / (R - T); const A = a == t.lastElementChild?.lastElementChild ? a.offsetTop + a.clientHeight : a.offsetTop; if (R >= p || A > L) { const H = b(p, L); T = k(H), g = (c.scrollTop - T) / (p - T); const P = t.querySelector(`[data-line="${H}"]`); T > 0 && P && (x = P.offsetTop), I = L - x + F(t, "padding-top"); } const v = x - l + I * g; n(t, v, () => { C--; }); }, D = () => { if (C !== 0) return; M++; const { scrollDOM: c } = s, u = t.scrollTop, l = t.scrollHeight, o = c.scrollHeight - c.clientHeight, f = t.scrollHeight - t.clientHeight; let i = t.firstElementChild?.firstElementChild, g = t.firstElementChild?.lastElementChild; if (m.length > 0) { let A = Math.ceil( m[m.length - 1] * (u / l) ), v = m.findLastIndex((H) => H <= A); v = v === -1 ? 0 : v, A = m[v]; for (let H = v; H >= 0 && H < m.length; ) if (r[H].offsetTop > u) { if (H - 1 >= 0) { H--; continue; } A = -1, v = H; break; } else { if (H + 1 < m.length && r[H + 1].offsetTop < u) { H++; continue; } A = m[H], v = H; break; } switch (v) { case -1: { i = t.firstElementChild?.firstElementChild, g = r[v]; break; } case m.length - 1: { i = r[v], g = t.firstElementChild?.lastElementChild; break; } default: i = r[v], g = r[v + 1 === r.length ? v : v + 1]; } } let h = i === t.firstElementChild?.firstElementChild ? 0 : i.offsetTop - F(i, "margin-top"), a = g.offsetTop, p = 0; const { start: L, end: T } = d[Number(i.dataset.line || 0)]; let R = k(L); const x = k( T + 1 === s.state.doc.lines ? T : T + 1 ); let I = 0; if (x > o || g.offsetTop + g.offsetHeight > f) { const A = b(o, f), v = t.querySelector(`[data-line="${A}"]`); h = v ? v.offsetTop - F(v, "margin-top") : h, R = k(A), p = (u - h) / (f - h), I = o - R; } else i === t.firstElementChild?.firstElementChild ? (i === g && (a = g.offsetTop + g.offsetHeight + +getComputedStyle(g).marginBottom.replace("px", "")), I = x, p = Math.max(u / a, 0)) : (p = Math.max( (u - h) / (a - h), 0 ), I = x - R); n(e, R + I * p, () => { M--; }); }, O = (c) => { const { scrollDOM: u, contentHeight: l } = s, o = u.clientHeight; if (l <= o || t.firstElementChild.clientHeight <= t.clientHeight || s.state.doc.lines <= d[d.length - 1]?.end) return !1; c.target === e ? $() : D(); }; return [ () => { S(), e.addEventListener("scroll", O), t.addEventListener("scroll", O), e.dispatchEvent(new Event("scroll")); }, () => { e.removeEventListener("scroll", O), t.removeEventListener("scroll", O); } ]; }, re = { tocItem: { type: Object, default: () => ({}) }, mdHeadingId: { type: Function, default: () => { } }, onActive: { type: Function, default: () => { } }, onClick: { type: Function, default: () => { } }, scrollElementOffsetTop: { type: Number, default: 0 } }, Q = /* @__PURE__ */ z({ props: re, setup(e) { const t = q("scrollElementRef"), E = q("roorNodeRef"), s = _(); return J(() => e.tocItem.active, (n) => { n && e.onActive(e.tocItem, s.value); }), K(() => { e.tocItem.active && e.onActive(e.tocItem, s.value); }), () => { const { tocItem: n, mdHeadingId: k, onClick: y, scrollElementOffsetTop: d } = e; return N("div", { ref: s, class: [`${w}-catalog-link`, n.active && `${w}-catalog-active`], onClick: (r) => { if (r.stopPropagation(), y(r, n), r.defaultPrevented) return; const m = k({ text: n.text, level: n.level, index: n.index, currentToken: n.currentToken, nextToken: n.nextToken }), S = E.value.getElementById(m), b = t.value; if (S && b) { let C = S.offsetParent, M = S.offsetTop; if (b.contains(C)) for (; C && b != C; ) M += C?.offsetTop, C = C?.offsetParent; const $ = S.previousElementSibling; let D = 0; $ || (D = F(S, "margin-top")), b?.scrollTo({ top: M - d - D, behavior: "smooth" }); } } }, [N("span", { title: n.text }, [n.text]), n.children && n.children.length > 0 && N("div", { class: `${w}-catalog-wrapper` }, [n.children.map((r) => N(Q, { mdHeadingId: k, key: `${n.text}-link-${r.level}-${r.text}`, tocItem: r, onActive: e.onActive, onClick: y, scrollElementOffsetTop: d }, null))])]); }; } }), ce = { /** * 编辑器的Id,务必与需要绑定的编辑器Id相同 */ editorId: { type: String, default: void 0 }, class: { type: String, default: "" }, mdHeadingId: { type: Function, default: ({ text: e }) => e }, /** * 指定滚动的容器,选择器需带上对应的符号,默认预览框 * 元素必须定位!!!!!! * * 默认:#md-editor-preview-wrapper */ scrollElement: { type: [String, Object], default: void 0 }, theme: { type: String, default: "light" }, /** * 高亮标题相对滚动容器顶部偏移量,即距离该值时,高亮当前目录菜单项 * * 默认:20px */ offsetTop: { type: Number, default: 20 }, /** * 滚动区域的固定顶部高度 * * 默认:0 */ scrollElementOffsetTop: { type: Number, default: 0 }, onClick: { type: Function, default: void 0 }, onActive: { type: Function, default: void 0 }, /** * 滚动容器是否在web component中,默认不在 * * 在其中的话通过document查询不到 */ isScrollElementInShadow: { type: Boolean, default: !1 }, /** * 设置与哪个区域同步,默认与内容区域同步 * * >= v5.3.0 */ syncWith: { type: String, default: "preview" }, /** * 控制最大显示的目录层级 */ catalogMaxDepth: { type: Number, default: void 0 } }, W = /* @__PURE__ */ z({ name: "MdCatalog", props: ce, emits: ["onClick", "onActive"], setup(e, t) { const E = e.editorId, s = `#${E}-preview-wrapper`, n = X({ list: [], show: !1, scrollElement: e.scrollElement || s }), k = G(), y = _(), d = _(), r = _(), m = _(), S = G(), b = _({}); V("scrollElementRef", d), V("roorNodeRef", m); const C = Y(() => { const l = []; return n.list.forEach((o, f) => { if (e.catalogMaxDepth && o.level > e.catalogMaxDepth) return; const { text: i, level: g, line: h } = o, a = { level: g, text: i, line: h, index: f + 1, active: k.value === o }; if (l.length === 0) l.push(a); else { let p = l[l.length - 1]; if (a.level > p.level) for (let L = p.level + 1; L <= 6; L++) { const { children: T } = p; if (!T) { p.children = [a]; break; } if (p = T[T.length - 1], a.level <= p.level) { T.push(a); break; } } else l.push(a); } }), l; }), M = () => { if (n.scrollElement instanceof HTMLElement) return n.scrollElement; let l = document; return (n.scrollElement === s || e.isScrollElementInShadow) && (l = y.value?.getRootNode()), l.querySelector(n.scrollElement); }, $ = (l) => { if (l.length === 0) return k.value = void 0, n.list = [], !1; const { activeHead: o } = l.reduce((f, i, g) => { let h = 0; if (e.syncWith === "preview") { const a = m.value?.getElementById(e.mdHeadingId({ text: i.text, level: i.level, index: g + 1, currentToken: i.currentToken, nextToken: i.nextToken })); a instanceof HTMLElement && (h = oe(a, d.value)); } else { const a = S.value; if (a) { const p = a.lineBlockAt(a.state.doc.line(i.line + 1).from).top, L = a.scrollDOM.scrollTop; h = p - L; } } return h < e.offsetTop && h > f.minTop ? { activeHead: i, minTop: h } : f; }, { activeHead: l[0], minTop: Number.MIN_SAFE_INTEGER }); k.value = o, n.list = l; }, D = (l, o) => { b.value.top = o.offsetTop + F(o, "padding-top") + "px", e.onActive?.(l, o), t.emit("onActive", l, o); }, O = () => { $(n.list); }, c = (l) => { if (r.value?.removeEventListener("scroll", O), e.syncWith === "editor") r.value = S.value?.scrollDOM; else { const o = M(); d.value = o, r.value = o === document.documentElement ? document : o; } $(l), r.value?.addEventListener("scroll", O); }, u = (l) => { S.value = l; }; return J([() => e.syncWith, S, () => e.catalogMaxDepth], () => { c(n.list); }), K(() => { m.value = y.value.getRootNode(), B.on(E, { name: j, callback: c }), B.on(E, { name: U, callback: u }), B.emit(E, te), B.emit(E, le); }), Z(() => { B.remove(E, j, c), B.remove(E, U, u), r.value?.removeEventListener("scroll", O); }), () => N("div", { class: [`${w}-catalog`, e.theme === "dark" && `${w}-catalog-dark`, e.class || ""], ref: y }, [C.value.length > 0 && N(ee, null, [N("div", { class: `${w}-catalog-indicator`, style: b.value }, null), N("div", { class: `${w}-catalog-container` }, [C.value.map((l) => N(Q, { mdHeadingId: e.mdHeadingId, tocItem: l, key: `link-${l.level}-${l.text}`, onActive: D, onClick: (o, f) => { e.onClick?.(o, f), t.emit("onClick", o, f); }, scrollElementOffsetTop: e.scrollElementOffsetTop }, null))])])]); } }); W.install = (e) => (e.component(W.name, W), e); export { W as M, ve as a, ge as s };