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