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
JavaScript
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
};