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