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

474 lines (473 loc) 14.2 kB
import { defineComponent as z, inject as q, ref as D, 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 O } from "./config.mjs"; import { b as $, 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 ce = `.${O}-preview > [data-line]`, w = (e, l) => +getComputedStyle(e).getPropertyValue(l).replace("px", ""), ve = (e, l) => { const p = ie(() => { e.removeEventListener("scroll", n), e.addEventListener("scroll", n), l.removeEventListener("scroll", n), l.addEventListener("scroll", n); }, 50), n = (E) => { const m = e.clientHeight, y = l.clientHeight, f = e.scrollHeight, s = l.scrollHeight, r = (f - m) / (s - y); E.target === e ? (l.removeEventListener("scroll", n), l.scrollTo({ top: e.scrollTop / r // behavior: 'smooth' }), p()) : (e.removeEventListener("scroll", n), e.scrollTo({ top: l.scrollTop * r // behavior: 'smooth' }), p()); }; return [ () => { p().finally(() => { e.dispatchEvent(new Event("scroll")); }); }, () => { e.removeEventListener("scroll", n), l.removeEventListener("scroll", n); } ]; }, he = (e, l, p) => { const { view: n } = p, E = ne(), m = (c) => n.lineBlockAt(n.state.doc.line(c + 1).from).top, y = (c) => n.lineBlockAt(n.state.doc.line(c + 1).from).bottom; let f = [], s = [], r = []; const L = () => { f = [], s = Array.from( l.querySelectorAll(ce) ), r = s.map((o) => Number(o.dataset.line)); const c = [...r], { lines: h } = n.state.doc; let H = c.shift() || 0, t = c.shift() || h; for (let o = 0; o < h; o++) o === t && (H = o, t = c.shift() || h), f.push({ start: H, end: t - 1 }); }, R = (c, h) => { let H = 1; for (let t = s.length - 1; t - 1 >= 0; t--) { const o = s[t], a = s[t - 1]; if (o.offsetTop + o.offsetHeight > h && a.offsetTop < h) { H = Number(a.dataset.line); break; } } for (let t = f.length - 1; t >= 0; t--) { const o = y(f[t].end), a = m(f[t].start); if (o > c && a <= c) { H = H < f[t].start ? H : f[t].start; break; } } return H; }; let x = 0, B = 0; const _ = () => { if (B !== 0) return !1; x++; const { scrollDOM: c, contentHeight: h } = n; let H = w(l, "padding-block-start"); const t = n.lineBlockAtHeight(c.scrollTop), { number: o } = n.state.doc.lineAt(t.from), a = f[o - 1]; if (!a) return !1; let u = 1; const k = l.querySelector(`[data-line="${a.start}"]`) || l.firstElementChild?.firstElementChild, d = l.querySelector(`[data-line="${a.end + 1}"]`) || l.lastElementChild?.lastElementChild, i = c.scrollHeight - c.clientHeight, I = l.scrollHeight - l.clientHeight; let g = m(a.start), v = y(a.end), C = k.offsetTop, S = d.offsetTop - C; g === 0 && (C = 0, k === d ? (H = 0, v = h - c.offsetHeight, S = I) : S = d.offsetTop), u = (c.scrollTop - g) / (v - g); const A = d == l.lastElementChild?.lastElementChild ? d.offsetTop + d.clientHeight : d.offsetTop; if (v >= i || A > I) { const b = R(i, I); g = m(b), u = (c.scrollTop - g) / (i - g); const P = l.querySelector(`[data-line="${b}"]`); g > 0 && P && (C = P.offsetTop), S = I - C + w(l, "padding-block-start"); } const T = C - H + S * u; E(l, T, () => { x--; }); }, F = () => { if (x !== 0) return; B++; const { scrollDOM: c } = n, h = l.scrollTop, H = l.scrollHeight, t = c.scrollHeight - c.clientHeight, o = l.scrollHeight - l.clientHeight; let a = l.firstElementChild?.firstElementChild, u = l.firstElementChild?.lastElementChild; if (r.length > 0) { let A = Math.ceil( r[r.length - 1] * (h / H) ), T = r.findLastIndex((b) => b <= A); T = T === -1 ? 0 : T, A = r[T]; for (let b = T; b >= 0 && b < r.length; ) if (s[b].offsetTop > h) { if (b - 1 >= 0) { b--; continue; } A = -1, T = b; break; } else { if (b + 1 < r.length && s[b + 1].offsetTop < h) { b++; continue; } A = r[b], T = b; break; } switch (T) { case -1: { a = l.firstElementChild?.firstElementChild, u = s[T]; break; } case r.length - 1: { a = s[T], u = l.firstElementChild?.lastElementChild; break; } default: a = s[T], u = s[T + 1 === s.length ? T : T + 1]; } } let k = a === l.firstElementChild?.firstElementChild ? 0 : a.offsetTop - w(a, "margin-block-start"), d = u.offsetTop, i = 0; const { start: I, end: g } = f[Number(a.dataset.line || 0)]; let v = m(I); const C = m( g + 1 === n.state.doc.lines ? g : g + 1 ); let S = 0; if (C > t || u.offsetTop + u.offsetHeight > o) { const A = R(t, o), T = l.querySelector(`[data-line="${A}"]`); k = T ? T.offsetTop - w(T, "margin-block-start") : k, v = m(A), i = (h - k) / (o - k), S = t - v; } else a === l.firstElementChild?.firstElementChild ? (a === u && (d = u.offsetTop + u.offsetHeight + w(u, "margin-block-end")), S = C, i = Math.max(h / d, 0)) : (i = Math.max( (h - k) / (d - k), 0 ), S = C - v); E(e, v + S * i, () => { B--; }); }, M = (c) => { const { scrollDOM: h, contentHeight: H } = n, t = h.clientHeight; if (H <= t || l.firstElementChild.clientHeight <= l.clientHeight || n.state.doc.lines <= f[f.length - 1]?.end) return !1; c.target === e ? _() : F(); }; return [ () => { L(), e.addEventListener("scroll", M), l.addEventListener("scroll", M), e.dispatchEvent(new Event("scroll")); }, () => { e.removeEventListener("scroll", M), l.removeEventListener("scroll", M); } ]; }, se = { 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: se, setup(e) { const l = q("scrollElementRef"), p = q("roorNodeRef"), n = D(); J(() => e.tocItem.active, (m) => { m && e.onActive(e.tocItem, n.value); }), K(() => { e.tocItem.active && e.onActive(e.tocItem, n.value); }); const E = (m) => { if (m.stopPropagation(), e.onClick(m, e.tocItem), m.defaultPrevented) return; const y = e.mdHeadingId({ text: e.tocItem.text, level: e.tocItem.level, index: e.tocItem.index, currentToken: e.tocItem.currentToken, nextToken: e.tocItem.nextToken }), f = p.value.getElementById(y), s = l.value; if (f && s) { let r = f.offsetParent, L = f.offsetTop; if (s.contains(r)) for (; r && s != r; ) L += r?.offsetTop, r = r?.offsetParent; const R = f.previousElementSibling; let x = 0; R || (x = w(f, "margin-block-start")), s?.scrollTo({ top: L - e.scrollElementOffsetTop - x, behavior: "smooth" }); } }; return () => N("div", { ref: n, class: [`${O}-catalog-link`, e.tocItem.active && `${O}-catalog-active`], onClick: E }, [N("span", { title: e.tocItem.text }, [e.tocItem.text]), e.tocItem.children && e.tocItem.children.length > 0 && N("div", { class: `${O}-catalog-wrapper` }, [e.tocItem.children.map((m) => N(Q, { mdHeadingId: e.mdHeadingId, key: `${e.tocItem.text}-link-${m.level}-${m.text}`, tocItem: m, onActive: e.onActive, onClick: e.onClick, scrollElementOffsetTop: e.scrollElementOffsetTop }, null))])]); } }), re = { /** * 编辑器的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: re, emits: ["onClick", "onActive"], setup(e, l) { const p = e.editorId, n = `#${p}-preview-wrapper`, E = X({ list: [], show: !1, scrollElement: e.scrollElement || n }), m = G(), y = D(), f = D(), s = D(), r = D(), L = G(), R = D({}); V("scrollElementRef", f), V("roorNodeRef", r); const x = Y(() => { const t = []; return E.list.forEach((o, a) => { if (e.catalogMaxDepth && o.level > e.catalogMaxDepth) return; const { text: u, level: k, line: d } = o, i = { level: k, text: u, line: d, index: a + 1, active: m.value === o }; if (t.length === 0) t.push(i); else { let I = t[t.length - 1]; if (i.level > I.level) for (let g = I.level + 1; g <= 6; g++) { const { children: v } = I; if (!v) { I.children = [i]; break; } if (I = v[v.length - 1], i.level <= I.level) { v.push(i); break; } } else t.push(i); } }), t; }), B = () => { if (E.scrollElement instanceof HTMLElement) return E.scrollElement; let t = document; return (E.scrollElement === n || e.isScrollElementInShadow) && (t = y.value?.getRootNode()), t.querySelector(E.scrollElement); }, _ = (t) => { if (t.length === 0) return m.value = void 0, E.list = [], !1; const { activeHead: o, activeIndex: a } = t.reduce((d, i, I) => { let g = 0; if (e.syncWith === "preview") { const v = r.value?.getElementById(e.mdHeadingId({ text: i.text, level: i.level, index: I + 1, currentToken: i.currentToken, nextToken: i.nextToken })); v instanceof HTMLElement && (g = oe(v, f.value)); } else { const v = L.value; if (v) { const C = v.lineBlockAt(v.state.doc.line(i.line + 1).from).top, S = v.scrollDOM.scrollTop; g = C - S; } } return g < e.offsetTop && g > d.minTop ? { activeHead: i, activeIndex: I, minTop: g } : d; }, { activeHead: t[0], activeIndex: 0, minTop: Number.MIN_SAFE_INTEGER }); let u = o; const { catalogMaxDepth: k } = e; if (k && u.level > k) { for (let d = a; d >= 0; d--) { const i = t[d]; if (i.level <= k) { u = i; break; } } if (u.level > k) { const d = t.find((i) => i.level <= k); d && (u = d); } } m.value = u, E.list = t; }, F = (t, o) => { R.value.top = o.offsetTop + w(o, "padding-block-start") + "px", e.onActive?.(t, o), l.emit("onActive", t, o); }, M = () => { _(E.list); }, c = (t) => { if (s.value?.removeEventListener("scroll", M), e.syncWith === "editor") s.value = L.value?.scrollDOM; else { const o = B(); f.value = o, s.value = o === document.documentElement ? document : o; } _(t), s.value?.addEventListener("scroll", M); }, h = (t) => { L.value = t; }; J([() => e.syncWith, L, () => e.catalogMaxDepth], () => { c(E.list); }), K(() => { r.value = y.value.getRootNode(), $.on(p, { name: j, callback: c }), $.on(p, { name: U, callback: h }), $.emit(p, te), $.emit(p, le); }), Z(() => { $.remove(p, j, c), $.remove(p, U, h), s.value?.removeEventListener("scroll", M); }); const H = (t, o) => { e.onClick?.(t, o), l.emit("onClick", t, o); }; return () => N("div", { class: [`${O}-catalog`, e.theme === "dark" && `${O}-catalog-dark`, e.class || ""], ref: y }, [x.value.length > 0 && N(ee, null, [N("div", { class: `${O}-catalog-indicator`, style: R.value }, null), N("div", { class: `${O}-catalog-container` }, [x.value.map((t) => N(Q, { mdHeadingId: e.mdHeadingId, tocItem: t, key: `link-${t.level}-${t.text}`, onActive: F, onClick: H, scrollElementOffsetTop: e.scrollElementOffsetTop }, null))])])]); } }); W.install = (e) => (e.component(W.name, W), e); export { W as M, ve as a, he as s };