@powerduck/md-editor 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/Editor.d.ts +19 -0
- package/dist/Renderer.d.ts +22 -0
- package/dist/StatusBar.d.ts +28 -0
- package/dist/Toolbar.d.ts +20 -0
- package/dist/icons.d.ts +15 -0
- package/dist/index.cjs +14 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.mjs +482 -0
- package/dist/perf/IncrementalRenderer.d.ts +41 -0
- package/dist/perf/blocks.d.ts +23 -0
- package/dist/perf/schedule.d.ts +21 -0
- package/dist/plugins/math.d.ts +7 -0
- package/dist/plugins/mindmap.d.ts +24 -0
- package/dist/react/MarkdownEditor.d.ts +32 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react.cjs +1 -0
- package/dist/react.mjs +80 -0
- package/dist/style.css +1 -0
- package/package.json +94 -0
- package/src/styles/tokens.css +202 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { Compartment as w, EditorState as k } from "@codemirror/state";
|
|
2
|
+
import { EditorView as m, keymap as b, lineNumbers as p } from "@codemirror/view";
|
|
3
|
+
import { history as v, defaultKeymap as g, historyKeymap as y } from "@codemirror/commands";
|
|
4
|
+
import { markdown as C } from "@codemirror/lang-markdown";
|
|
5
|
+
import { languages as B } from "@codemirror/language-data";
|
|
6
|
+
import M from "markdown-it";
|
|
7
|
+
import E from "markdown-it-texmath";
|
|
8
|
+
import P from "katex";
|
|
9
|
+
class x {
|
|
10
|
+
constructor(e, t) {
|
|
11
|
+
this.lineNumbersCompartment = new w();
|
|
12
|
+
const r = [
|
|
13
|
+
v(),
|
|
14
|
+
b.of([...g, ...y]),
|
|
15
|
+
C({ codeLanguages: B }),
|
|
16
|
+
m.lineWrapping,
|
|
17
|
+
this.lineNumbersCompartment.of(t.lineNumbers ? p() : []),
|
|
18
|
+
m.updateListener.of((i) => {
|
|
19
|
+
var s;
|
|
20
|
+
i.docChanged && ((s = t.onChange) == null || s.call(t, i.state.doc.toString()));
|
|
21
|
+
})
|
|
22
|
+
];
|
|
23
|
+
this.view = new m({
|
|
24
|
+
state: k.create({ doc: t.value ?? "", extensions: r }),
|
|
25
|
+
parent: e
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
getValue() {
|
|
29
|
+
return this.view.state.doc.toString();
|
|
30
|
+
}
|
|
31
|
+
setValue(e) {
|
|
32
|
+
this.view.dispatch({
|
|
33
|
+
changes: { from: 0, to: this.view.state.doc.length, insert: e }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
setLineNumbers(e) {
|
|
37
|
+
this.view.dispatch({
|
|
38
|
+
effects: this.lineNumbersCompartment.reconfigure(e ? p() : [])
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
insertAtCursor(e) {
|
|
42
|
+
const { from: t, to: r } = this.view.state.selection.main;
|
|
43
|
+
this.view.dispatch({
|
|
44
|
+
changes: { from: t, to: r, insert: e },
|
|
45
|
+
selection: { anchor: t + e.length }
|
|
46
|
+
}), this.view.focus();
|
|
47
|
+
}
|
|
48
|
+
focus() {
|
|
49
|
+
this.view.focus();
|
|
50
|
+
}
|
|
51
|
+
destroy() {
|
|
52
|
+
this.view.destroy();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function S(n) {
|
|
56
|
+
n.use(E, {
|
|
57
|
+
engine: P,
|
|
58
|
+
delimiters: "dollars",
|
|
59
|
+
katexOptions: {
|
|
60
|
+
throwOnError: !1,
|
|
61
|
+
strict: !1
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
let T = 0;
|
|
66
|
+
function L(n) {
|
|
67
|
+
const e = n.renderer.rules.fence ? n.renderer.rules.fence.bind(n.renderer.rules) : (t, r, i, s, o) => o.renderToken(t, r, i);
|
|
68
|
+
n.renderer.rules.fence = (t, r, i, s, o) => {
|
|
69
|
+
const a = t[r];
|
|
70
|
+
if (!a) return e(t, r, i, s, o);
|
|
71
|
+
if (a.info.trim().toLowerCase() === "mindmap") {
|
|
72
|
+
const h = `md-editor-mindmap-${++T}`, u = encodeURIComponent(a.content);
|
|
73
|
+
return `<div class="md-editor-mindmap" id="${h}" data-source="${u}"></div>`;
|
|
74
|
+
}
|
|
75
|
+
return e(t, r, i, s, o);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function N(n) {
|
|
79
|
+
const e = n.querySelectorAll(
|
|
80
|
+
".md-editor-mindmap:not([data-hydrated])"
|
|
81
|
+
);
|
|
82
|
+
if (e.length === 0) return;
|
|
83
|
+
const { Transformer: t } = await import("markmap-lib"), r = await import("markmap-view"), i = r.Markmap ?? r.default;
|
|
84
|
+
if (!i) return;
|
|
85
|
+
const s = new t();
|
|
86
|
+
for (const o of Array.from(e)) {
|
|
87
|
+
o.setAttribute("data-hydrated", "1");
|
|
88
|
+
const a = decodeURIComponent(o.dataset.source ?? "") || "- Empty mindmap", { root: d } = s.transform(a);
|
|
89
|
+
o.innerHTML = "";
|
|
90
|
+
const h = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
91
|
+
h.setAttribute("style", "width:100%;height:100%;min-height:280px;"), o.appendChild(h), i.create(h, { autoFit: !0 }, d);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
class R {
|
|
95
|
+
constructor(e = {}) {
|
|
96
|
+
this.md = new M({
|
|
97
|
+
html: e.html ?? !1,
|
|
98
|
+
linkify: !0,
|
|
99
|
+
breaks: e.breaks ?? !1,
|
|
100
|
+
typographer: !0
|
|
101
|
+
}), e.math !== !1 && S(this.md), this.mindmapEnabled = e.mindmap !== !1, this.mindmapEnabled && L(this.md);
|
|
102
|
+
}
|
|
103
|
+
render(e) {
|
|
104
|
+
return this.md.render(e || "");
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Call after the rendered HTML is inserted into the DOM to hydrate
|
|
108
|
+
* mindmap placeholders into real SVG diagrams.
|
|
109
|
+
*/
|
|
110
|
+
async hydrate(e) {
|
|
111
|
+
this.mindmapEnabled && await N(e);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
class H {
|
|
115
|
+
constructor(e) {
|
|
116
|
+
this.buttons = /* @__PURE__ */ new Map(), this.el = document.createElement("div"), this.el.className = "md-editor-toolbar", this.el.setAttribute("role", "toolbar");
|
|
117
|
+
for (const t of e) {
|
|
118
|
+
const r = document.createElement("button");
|
|
119
|
+
if (r.type = "button", r.className = "md-editor-toolbar-btn", t.toggle && r.classList.add("is-toggle"), t.active && r.classList.add("is-active"), r.title = t.title, r.setAttribute("aria-label", t.title), r.setAttribute("data-action", t.name), r.innerHTML = t.icon, r.addEventListener("click", t.handler), this.buttons.set(t.name, r), this.el.appendChild(r), t.groupEnd) {
|
|
120
|
+
const i = document.createElement("span");
|
|
121
|
+
i.className = "md-editor-toolbar-sep", this.el.appendChild(i);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
setActive(e, t) {
|
|
126
|
+
var r;
|
|
127
|
+
(r = this.buttons.get(e)) == null || r.classList.toggle("is-active", t);
|
|
128
|
+
}
|
|
129
|
+
setIcon(e, t) {
|
|
130
|
+
const r = this.buttons.get(e);
|
|
131
|
+
r && (r.innerHTML = t);
|
|
132
|
+
}
|
|
133
|
+
destroy() {
|
|
134
|
+
for (const e of this.buttons.values())
|
|
135
|
+
e.onclick = null;
|
|
136
|
+
this.buttons.clear(), this.el.remove();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function f(n) {
|
|
140
|
+
const e = n.trim();
|
|
141
|
+
return {
|
|
142
|
+
charCount: n.length,
|
|
143
|
+
wordCount: e ? e.split(/\s+/).length : 0
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
class A {
|
|
147
|
+
constructor(e) {
|
|
148
|
+
this.el = document.createElement("div"), this.el.className = "md-editor-statusbar", this.countEl = document.createElement("span"), this.blockEl = document.createElement("span"), this.blockEl.className = "md-editor-statusbar-muted", this.refreshBtn = document.createElement("button"), this.refreshBtn.type = "button", this.refreshBtn.className = "md-editor-statusbar-refresh", this.refreshBtn.textContent = "Refresh preview", this.refreshBtn.style.display = "none", this.refreshBtn.addEventListener("click", e.onRefreshPreview), this.el.appendChild(this.countEl), this.el.appendChild(this.blockEl), this.el.appendChild(this.refreshBtn);
|
|
149
|
+
}
|
|
150
|
+
update(e) {
|
|
151
|
+
this.countEl.textContent = `${e.charCount} chars · ${e.wordCount} words`, this.blockEl.textContent = `${e.blockCount} render blocks`, this.refreshBtn.style.display = e.autoPreview ? "none" : "";
|
|
152
|
+
}
|
|
153
|
+
destroy() {
|
|
154
|
+
this.refreshBtn.onclick = null, this.el.remove();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const I = /^ {0,3}#{1,6}\s/, V = /^ {0,3}(`{3,}|~{3,})/;
|
|
158
|
+
function $(n) {
|
|
159
|
+
let e = 5381;
|
|
160
|
+
for (let t = 0; t < n.length; t++)
|
|
161
|
+
e = e * 33 ^ n.charCodeAt(t);
|
|
162
|
+
return (e >>> 0).toString(36) + ":" + n.length;
|
|
163
|
+
}
|
|
164
|
+
function j(n) {
|
|
165
|
+
var a;
|
|
166
|
+
const e = n.split(`
|
|
167
|
+
`), t = [];
|
|
168
|
+
let r = [], i = !1, s = "";
|
|
169
|
+
for (const d of e) {
|
|
170
|
+
const h = d.match(V);
|
|
171
|
+
if (h) {
|
|
172
|
+
const u = ((a = h[1]) == null ? void 0 : a[0]) ?? "";
|
|
173
|
+
i ? u === s && (i = !1) : (i = !0, s = u), r.push(d);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!i && I.test(d) && r.length > 0) {
|
|
177
|
+
t.push(r.join(`
|
|
178
|
+
`)), r = [d];
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
r.push(d);
|
|
182
|
+
}
|
|
183
|
+
r.length > 0 && t.push(r.join(`
|
|
184
|
+
`)), t.length === 0 && t.push("");
|
|
185
|
+
const o = /* @__PURE__ */ new Map();
|
|
186
|
+
return t.map((d) => {
|
|
187
|
+
const h = $(d), u = (o.get(h) ?? 0) + 1;
|
|
188
|
+
return o.set(h, u), { key: `${h}#${u}`, hash: h, source: d };
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
class K {
|
|
192
|
+
constructor(e, t, r = 800) {
|
|
193
|
+
this.container = e, this.renderBlock = t, this.maxCacheEntries = r, this.nodeByKey = /* @__PURE__ */ new Map(), this.htmlByHash = /* @__PURE__ */ new Map(), this.prevBlocks = [];
|
|
194
|
+
}
|
|
195
|
+
update(e) {
|
|
196
|
+
const t = j(e), r = document.createDocumentFragment(), i = /* @__PURE__ */ new Map();
|
|
197
|
+
let s = 0;
|
|
198
|
+
for (const o of t) {
|
|
199
|
+
let a = this.nodeByKey.get(o.key);
|
|
200
|
+
if (!a) {
|
|
201
|
+
let d = this.htmlByHash.get(o.hash);
|
|
202
|
+
d === void 0 && (d = this.renderBlock(o.source), this.htmlByHash.set(o.hash, d), s++), a = document.createElement("div"), a.className = "md-editor-block", a.dataset.blockKey = o.key, a.innerHTML = d;
|
|
203
|
+
}
|
|
204
|
+
i.set(o.key, a), r.appendChild(a);
|
|
205
|
+
}
|
|
206
|
+
return this.container.innerHTML = "", this.container.appendChild(r), this.nodeByKey = i, this.prevBlocks = t, this.evictCacheIfNeeded(t), { rerenderedCount: s, totalCount: t.length };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* When the cache exceeds the limit, evict hashes that no longer appear in
|
|
210
|
+
* the current document to prevent unbounded memory growth during long
|
|
211
|
+
* editing sessions.
|
|
212
|
+
*/
|
|
213
|
+
evictCacheIfNeeded(e) {
|
|
214
|
+
if (this.htmlByHash.size <= this.maxCacheEntries) return;
|
|
215
|
+
const t = new Set(e.map((r) => r.hash));
|
|
216
|
+
for (const r of Array.from(this.htmlByHash.keys()))
|
|
217
|
+
t.has(r) || this.htmlByHash.delete(r);
|
|
218
|
+
}
|
|
219
|
+
getBlockCount() {
|
|
220
|
+
return this.prevBlocks.length;
|
|
221
|
+
}
|
|
222
|
+
destroy() {
|
|
223
|
+
this.container.innerHTML = "", this.nodeByKey.clear(), this.htmlByHash.clear(), this.prevBlocks = [];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function q(n, e) {
|
|
227
|
+
let t, r;
|
|
228
|
+
const i = (...s) => {
|
|
229
|
+
r = s, t !== void 0 && clearTimeout(t), t = setTimeout(() => {
|
|
230
|
+
t = void 0, r && n(...r);
|
|
231
|
+
}, e);
|
|
232
|
+
};
|
|
233
|
+
return i.cancel = () => {
|
|
234
|
+
t !== void 0 && clearTimeout(t), t = void 0;
|
|
235
|
+
}, i.flush = () => {
|
|
236
|
+
t !== void 0 && (clearTimeout(t), t = void 0, r && n(...r));
|
|
237
|
+
}, i;
|
|
238
|
+
}
|
|
239
|
+
function D(n, e = 120, t = 600) {
|
|
240
|
+
const r = Math.max(0, n), i = Math.floor(r / 2e4) * 60;
|
|
241
|
+
return Math.min(t, e + i);
|
|
242
|
+
}
|
|
243
|
+
function Q(n, e = 300) {
|
|
244
|
+
const t = window;
|
|
245
|
+
return typeof t.requestIdleCallback == "function" ? t.requestIdleCallback(n, { timeout: e }) : window.setTimeout(n, 0);
|
|
246
|
+
}
|
|
247
|
+
function X(n) {
|
|
248
|
+
const e = window;
|
|
249
|
+
typeof e.cancelIdleCallback == "function" ? e.cancelIdleCallback(n) : clearTimeout(n);
|
|
250
|
+
}
|
|
251
|
+
const c = (n) => `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">${n}</svg>`, l = {
|
|
252
|
+
bold: c(
|
|
253
|
+
'<path d="M4 3h4.2a2.4 2.4 0 0 1 0 4.8H4V3Z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M4 7.8h4.8a2.4 2.4 0 1 1 0 4.8H4V7.8Z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>'
|
|
254
|
+
),
|
|
255
|
+
italic: c(
|
|
256
|
+
'<path d="M9.5 3h-3M6.5 13h-3M8.2 3 5.8 13" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>'
|
|
257
|
+
),
|
|
258
|
+
heading: c(
|
|
259
|
+
'<path d="M3.5 3v10M11.5 3v10M3.5 8h8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>'
|
|
260
|
+
),
|
|
261
|
+
link: c(
|
|
262
|
+
'<path d="M6.8 9.2 9.2 6.8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><path d="M7.6 4.4 8.4 3.6a2.6 2.6 0 1 1 3.7 3.7l-.9.9M8.4 11.6l-.8.8a2.6 2.6 0 1 1-3.7-3.7l.9-.9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>'
|
|
263
|
+
),
|
|
264
|
+
code: c(
|
|
265
|
+
'<path d="M5.5 4.5 2 8l3.5 3.5M10.5 4.5 14 8l-3.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>'
|
|
266
|
+
),
|
|
267
|
+
table: c(
|
|
268
|
+
'<rect x="2.5" y="3" width="11" height="10" rx="1.2" stroke="currentColor" stroke-width="1.3"/><path d="M2.5 6.6h11M6.5 3v10" stroke="currentColor" stroke-width="1.3"/>'
|
|
269
|
+
),
|
|
270
|
+
sigma: c(
|
|
271
|
+
'<path d="M11.5 3.5h-7l3.5 4.5-3.5 4.5h7" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round" stroke-linecap="round"/>'
|
|
272
|
+
),
|
|
273
|
+
mindmap: c(
|
|
274
|
+
'<circle cx="3.2" cy="8" r="1.6" stroke="currentColor" stroke-width="1.3"/><circle cx="12.2" cy="3.6" r="1.6" stroke="currentColor" stroke-width="1.3"/><circle cx="12.2" cy="12.4" r="1.6" stroke="currentColor" stroke-width="1.3"/><path d="M4.6 7.3 10.7 4.2M4.6 8.7l6.1 3.1" stroke="currentColor" stroke-width="1.3"/>'
|
|
275
|
+
),
|
|
276
|
+
eye: c(
|
|
277
|
+
'<path d="M1.5 8S4 3.5 8 3.5 14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8Z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><circle cx="8" cy="8" r="1.8" stroke="currentColor" stroke-width="1.3"/>'
|
|
278
|
+
),
|
|
279
|
+
refresh: c(
|
|
280
|
+
'<path d="M13 8a5 5 0 1 1-1.6-3.7" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><path d="M13 3v3.2H9.8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>'
|
|
281
|
+
),
|
|
282
|
+
moon: c(
|
|
283
|
+
'<path d="M13 9.6A5.4 5.4 0 1 1 6.4 3a4.3 4.3 0 0 0 6.6 6.6Z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>'
|
|
284
|
+
),
|
|
285
|
+
sun: c(
|
|
286
|
+
'<circle cx="8" cy="8" r="3" stroke="currentColor" stroke-width="1.3"/><path d="M8 1.8v1.4M8 12.8v1.4M14.2 8h-1.4M3.2 8H1.8M12.4 3.6l-1 1M4.6 11.4l-1 1M12.4 12.4l-1-1M4.6 4.6l-1-1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>'
|
|
287
|
+
)
|
|
288
|
+
}, F = {
|
|
289
|
+
bold: ["**", "**", "bold text"],
|
|
290
|
+
italic: ["_", "_", "italic text"],
|
|
291
|
+
heading: ["## ", "", "Heading"],
|
|
292
|
+
link: ["[", "](https://)", "link text"],
|
|
293
|
+
code: ["```\n", "\n```", "code"],
|
|
294
|
+
math: ["$", "$", "E=mc^2"],
|
|
295
|
+
mindmapBlock: [
|
|
296
|
+
`\`\`\`mindmap
|
|
297
|
+
# Root topic
|
|
298
|
+
## Branch one
|
|
299
|
+
- Child A
|
|
300
|
+
- Child B
|
|
301
|
+
## Branch two
|
|
302
|
+
\`\`\`
|
|
303
|
+
`,
|
|
304
|
+
"",
|
|
305
|
+
""
|
|
306
|
+
],
|
|
307
|
+
table: [`| Col 1 | Col 2 |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| `, ` | content |
|
|
310
|
+
`, "content"]
|
|
311
|
+
};
|
|
312
|
+
class Y {
|
|
313
|
+
constructor(e, t = {}) {
|
|
314
|
+
this.previewPane = null, this.incremental = null, this.toolbar = null, this.statusBar = null, this.scheduledRender = null, this.scheduledWait = 0;
|
|
315
|
+
const r = typeof e == "string" ? document.querySelector(e) : e;
|
|
316
|
+
if (!r) throw new Error("MarkdownEditor: mount container not found");
|
|
317
|
+
this.container = r, this.mode = t.mode ?? "simple", this.theme = t.theme ?? "light", this.showPreview = t.preview ?? !0, this.autoPreview = t.autoPreview ?? !0, this.renderDebounceOpt = t.renderDebounce, this.onChange = t.onChange, this.container.classList.add("md-editor-root", `md-editor-mode-${this.mode}`), this.container.setAttribute("data-theme", this.theme), this.renderer = new R({
|
|
318
|
+
math: t.math ?? !0,
|
|
319
|
+
mindmap: t.mindmap ?? !0
|
|
320
|
+
}), this.buildLayout(), this.codeEditor = new x(this.editorPane, {
|
|
321
|
+
value: t.value ?? "",
|
|
322
|
+
lineNumbers: this.mode === "complex",
|
|
323
|
+
onChange: (i) => {
|
|
324
|
+
var s;
|
|
325
|
+
(s = this.onChange) == null || s.call(this, i), this.autoPreview && this.requestPreviewRender(i), this.updateStatusBar(i);
|
|
326
|
+
}
|
|
327
|
+
}), this.mode === "complex" && (this.buildToolbar(), this.buildStatusBar()), this.previewPane && (this.incremental = new K(
|
|
328
|
+
this.previewPane,
|
|
329
|
+
(i) => this.renderer.render(i)
|
|
330
|
+
)), this.renderPreview(t.value ?? ""), this.updateStatusBar(t.value ?? "");
|
|
331
|
+
}
|
|
332
|
+
// ---------- Layout ----------
|
|
333
|
+
buildLayout() {
|
|
334
|
+
this.container.innerHTML = "", this.body = document.createElement("div"), this.body.className = "md-editor-body", this.editorPane = document.createElement("div"), this.editorPane.className = "md-editor-pane md-editor-source", this.body.appendChild(this.editorPane), this.showPreview && (this.previewPane = document.createElement("div"), this.previewPane.className = "md-editor-pane md-editor-preview", this.body.appendChild(this.previewPane)), this.container.appendChild(this.body);
|
|
335
|
+
}
|
|
336
|
+
buildToolbar() {
|
|
337
|
+
const e = [
|
|
338
|
+
{ name: "heading", title: "Heading", icon: l.heading, handler: () => this.insertSnippet("heading") },
|
|
339
|
+
{ name: "bold", title: "Bold", icon: l.bold, handler: () => this.insertSnippet("bold") },
|
|
340
|
+
{
|
|
341
|
+
name: "italic",
|
|
342
|
+
title: "Italic",
|
|
343
|
+
icon: l.italic,
|
|
344
|
+
handler: () => this.insertSnippet("italic"),
|
|
345
|
+
groupEnd: !0
|
|
346
|
+
},
|
|
347
|
+
{ name: "link", title: "Link", icon: l.link, handler: () => this.insertSnippet("link") },
|
|
348
|
+
{ name: "code", title: "Code block", icon: l.code, handler: () => this.insertSnippet("code") },
|
|
349
|
+
{
|
|
350
|
+
name: "table",
|
|
351
|
+
title: "Table",
|
|
352
|
+
icon: l.table,
|
|
353
|
+
handler: () => this.insertSnippet("table"),
|
|
354
|
+
groupEnd: !0
|
|
355
|
+
},
|
|
356
|
+
{ name: "math", title: "Math formula", icon: l.sigma, handler: () => this.insertSnippet("math") },
|
|
357
|
+
{
|
|
358
|
+
name: "mindmap",
|
|
359
|
+
title: "Insert mindmap",
|
|
360
|
+
icon: l.mindmap,
|
|
361
|
+
handler: () => this.insertSnippet("mindmapBlock"),
|
|
362
|
+
groupEnd: !0
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "preview",
|
|
366
|
+
title: "Toggle preview",
|
|
367
|
+
icon: l.eye,
|
|
368
|
+
toggle: !0,
|
|
369
|
+
active: this.showPreview,
|
|
370
|
+
handler: () => this.togglePreview()
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "theme",
|
|
374
|
+
title: "Toggle theme",
|
|
375
|
+
icon: this.theme === "dark" ? l.sun : l.moon,
|
|
376
|
+
handler: () => this.setTheme(this.theme === "dark" ? "light" : "dark")
|
|
377
|
+
}
|
|
378
|
+
];
|
|
379
|
+
this.toolbar = new H(e), this.container.insertBefore(this.toolbar.el, this.body);
|
|
380
|
+
}
|
|
381
|
+
buildStatusBar() {
|
|
382
|
+
this.statusBar = new A({
|
|
383
|
+
onRefreshPreview: () => this.renderPreview(this.codeEditor.getValue())
|
|
384
|
+
}), this.container.appendChild(this.statusBar.el);
|
|
385
|
+
}
|
|
386
|
+
insertSnippet(e) {
|
|
387
|
+
const [t, r, i] = F[e];
|
|
388
|
+
this.codeEditor.insertAtCursor(`${t}${i}${r}`);
|
|
389
|
+
}
|
|
390
|
+
togglePreview() {
|
|
391
|
+
var t;
|
|
392
|
+
if (!this.previewPane) return;
|
|
393
|
+
const e = this.previewPane.style.display !== "none";
|
|
394
|
+
this.previewPane.style.display = e ? "none" : "", (t = this.toolbar) == null || t.setActive("preview", !e);
|
|
395
|
+
}
|
|
396
|
+
// ---------- Render scheduling (performance core) ----------
|
|
397
|
+
requestPreviewRender(e) {
|
|
398
|
+
var r, i;
|
|
399
|
+
const t = this.renderDebounceOpt === !1 ? 0 : this.renderDebounceOpt ?? D(e.length);
|
|
400
|
+
if (t === 0) {
|
|
401
|
+
(r = this.scheduledRender) == null || r.cancel(), this.scheduledRender = null, this.renderPreview(e);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
(!this.scheduledRender || this.scheduledWait !== t) && ((i = this.scheduledRender) == null || i.cancel(), this.scheduledRender = q((s) => this.renderPreview(s), t), this.scheduledWait = t), this.scheduledRender(e);
|
|
405
|
+
}
|
|
406
|
+
renderPreview(e) {
|
|
407
|
+
if (!this.previewPane || !this.incremental) return;
|
|
408
|
+
const { totalCount: t } = this.incremental.update(e);
|
|
409
|
+
if (this.renderer.hydrate(this.previewPane), this.statusBar) {
|
|
410
|
+
const { charCount: r, wordCount: i } = f(e);
|
|
411
|
+
this.statusBar.update({
|
|
412
|
+
charCount: r,
|
|
413
|
+
wordCount: i,
|
|
414
|
+
blockCount: t,
|
|
415
|
+
mode: this.mode,
|
|
416
|
+
autoPreview: this.autoPreview
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
updateStatusBar(e) {
|
|
421
|
+
var i;
|
|
422
|
+
if (!this.statusBar) return;
|
|
423
|
+
const { charCount: t, wordCount: r } = f(e);
|
|
424
|
+
this.statusBar.update({
|
|
425
|
+
charCount: t,
|
|
426
|
+
wordCount: r,
|
|
427
|
+
blockCount: ((i = this.incremental) == null ? void 0 : i.getBlockCount()) ?? 0,
|
|
428
|
+
mode: this.mode,
|
|
429
|
+
autoPreview: this.autoPreview
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
// ---------- Public API ----------
|
|
433
|
+
getValue() {
|
|
434
|
+
return this.codeEditor.getValue();
|
|
435
|
+
}
|
|
436
|
+
setValue(e) {
|
|
437
|
+
var t;
|
|
438
|
+
this.codeEditor.setValue(e), (t = this.scheduledRender) == null || t.cancel(), this.renderPreview(e), this.updateStatusBar(e);
|
|
439
|
+
}
|
|
440
|
+
getHtml() {
|
|
441
|
+
return this.renderer.render(this.getValue());
|
|
442
|
+
}
|
|
443
|
+
/** Manually trigger a preview render (use when autoPreview is false). */
|
|
444
|
+
renderNow() {
|
|
445
|
+
var e;
|
|
446
|
+
(e = this.scheduledRender) == null || e.cancel(), this.renderPreview(this.codeEditor.getValue());
|
|
447
|
+
}
|
|
448
|
+
setAutoPreview(e) {
|
|
449
|
+
this.autoPreview = e, e && this.renderPreview(this.codeEditor.getValue()), this.updateStatusBar(this.codeEditor.getValue());
|
|
450
|
+
}
|
|
451
|
+
/** Switch between simple and complex modes at runtime. */
|
|
452
|
+
setMode(e) {
|
|
453
|
+
var t, r;
|
|
454
|
+
e !== this.mode && (this.mode = e, this.container.classList.remove("md-editor-mode-simple", "md-editor-mode-complex"), this.container.classList.add(`md-editor-mode-${e}`), this.codeEditor.setLineNumbers(e === "complex"), (t = this.toolbar) == null || t.destroy(), this.toolbar = null, (r = this.statusBar) == null || r.destroy(), this.statusBar = null, e === "complex" && (this.buildToolbar(), this.buildStatusBar(), this.updateStatusBar(this.codeEditor.getValue())));
|
|
455
|
+
}
|
|
456
|
+
setTheme(e) {
|
|
457
|
+
var t;
|
|
458
|
+
this.theme = e, this.container.setAttribute("data-theme", e), (t = this.toolbar) == null || t.setIcon("theme", e === "dark" ? l.sun : l.moon);
|
|
459
|
+
}
|
|
460
|
+
focus() {
|
|
461
|
+
this.codeEditor.focus();
|
|
462
|
+
}
|
|
463
|
+
destroy() {
|
|
464
|
+
var e, t, r, i;
|
|
465
|
+
(e = this.scheduledRender) == null || e.cancel(), this.scheduledRender = null, this.codeEditor.destroy(), (t = this.toolbar) == null || t.destroy(), this.toolbar = null, (r = this.statusBar) == null || r.destroy(), this.statusBar = null, (i = this.incremental) == null || i.destroy(), this.incremental = null, this.container.innerHTML = "", this.container.classList.remove("md-editor-root", `md-editor-mode-${this.mode}`), this.container.removeAttribute("data-theme");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
export {
|
|
469
|
+
x as CodeEditor,
|
|
470
|
+
K as IncrementalRenderer,
|
|
471
|
+
Y as MarkdownEditor,
|
|
472
|
+
R as Renderer,
|
|
473
|
+
A as StatusBar,
|
|
474
|
+
H as Toolbar,
|
|
475
|
+
D as adaptiveDebounceMs,
|
|
476
|
+
X as cancelIdle,
|
|
477
|
+
f as countText,
|
|
478
|
+
q as debounce,
|
|
479
|
+
l as icons,
|
|
480
|
+
Q as scheduleIdle,
|
|
481
|
+
j as splitIntoBlocks
|
|
482
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface IncrementalRenderResult {
|
|
2
|
+
/** Number of blocks actually re-rendered (cache miss) this update */
|
|
3
|
+
rerenderedCount: number;
|
|
4
|
+
/** Total number of blocks in this update */
|
|
5
|
+
totalCount: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Core performance strategy:
|
|
9
|
+
*
|
|
10
|
+
* 1. Source is split into blocks (see blocks.ts). Only changed blocks are
|
|
11
|
+
* re-run through markdown-it / KaTeX.
|
|
12
|
+
* 2. Unchanged blocks reuse their existing DOM nodes directly (including
|
|
13
|
+
* already-hydrated mindmap SVGs), with no string concatenation, innerHTML
|
|
14
|
+
* assignment, or KaTeX computation.
|
|
15
|
+
* 3. Reordering is handled by appending existing nodes to a DocumentFragment
|
|
16
|
+
* -- the browser moves them natively (auto-detaching from the old parent),
|
|
17
|
+
* so no manual node-shuttling code is needed.
|
|
18
|
+
* 4. The result cache is keyed by content hash (not position), so undo/redo
|
|
19
|
+
* and copy-paste of identical paragraphs also hit the cache.
|
|
20
|
+
* 5. Each block is wrapped in content-visibility: auto (see editor.css), so
|
|
21
|
+
* off-screen blocks skip layout and paint entirely -- a decisive win for
|
|
22
|
+
* multi-thousand-line documents.
|
|
23
|
+
*/
|
|
24
|
+
export declare class IncrementalRenderer {
|
|
25
|
+
private readonly container;
|
|
26
|
+
private readonly renderBlock;
|
|
27
|
+
private readonly maxCacheEntries;
|
|
28
|
+
private nodeByKey;
|
|
29
|
+
private htmlByHash;
|
|
30
|
+
private prevBlocks;
|
|
31
|
+
constructor(container: HTMLElement, renderBlock: (source: string) => string, maxCacheEntries?: number);
|
|
32
|
+
update(source: string): IncrementalRenderResult;
|
|
33
|
+
/**
|
|
34
|
+
* When the cache exceeds the limit, evict hashes that no longer appear in
|
|
35
|
+
* the current document to prevent unbounded memory growth during long
|
|
36
|
+
* editing sessions.
|
|
37
|
+
*/
|
|
38
|
+
private evictCacheIfNeeded;
|
|
39
|
+
getBlockCount(): number;
|
|
40
|
+
destroy(): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface SourceBlock {
|
|
2
|
+
/** Stable key for DOM node reuse (content hash + occurrence index) */
|
|
3
|
+
key: string;
|
|
4
|
+
/** Content hash used to detect whether this block changed */
|
|
5
|
+
hash: string;
|
|
6
|
+
/** Original markdown source for this block */
|
|
7
|
+
source: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Split a markdown document into blocks at ATX heading lines (# through ######).
|
|
11
|
+
*
|
|
12
|
+
* This split is safe under CommonMark semantics: a heading line is always the
|
|
13
|
+
* start of a new block, so unlike splitting on blank lines we never break
|
|
14
|
+
* list or blockquote structures that span blank lines.
|
|
15
|
+
*
|
|
16
|
+
* Content inside code fences (``` or ~~~) is never treated as a heading,
|
|
17
|
+
* preventing "# comment" in example code from being misread as a split point.
|
|
18
|
+
*
|
|
19
|
+
* Documents with no headings degrade to a single block. In that case
|
|
20
|
+
* incremental rendering falls back to full-document rendering, but the
|
|
21
|
+
* cross-edit result cache (htmlByHash) is still effective.
|
|
22
|
+
*/
|
|
23
|
+
export declare function splitIntoBlocks(source: string): SourceBlock[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface DebouncedFn<A extends unknown[]> {
|
|
2
|
+
(...args: A): void;
|
|
3
|
+
cancel: () => void;
|
|
4
|
+
flush: () => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Trailing-edge debounce. Editor input events fire on every keystroke; the
|
|
8
|
+
* preview does not need to re-render on every one. Rendering after a pause
|
|
9
|
+
* is the first gatekeeping mechanism for keeping large documents responsive.
|
|
10
|
+
*/
|
|
11
|
+
export declare function debounce<A extends unknown[]>(fn: (...args: A) => void, wait: number): DebouncedFn<A>;
|
|
12
|
+
/**
|
|
13
|
+
* Adaptive debounce interval based on document length. Larger documents cost
|
|
14
|
+
* more to render, so the pause should be longer to avoid redundant renders.
|
|
15
|
+
* Capped at max so small edits in large docs do not wait excessively.
|
|
16
|
+
*/
|
|
17
|
+
export declare function adaptiveDebounceMs(docLength: number, base?: number, max?: number): number;
|
|
18
|
+
type IdleHandle = number;
|
|
19
|
+
export declare function scheduleIdle(callback: () => void, timeout?: number): IdleHandle;
|
|
20
|
+
export declare function cancelIdle(handle: IdleHandle): void;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type MarkdownIt from 'markdown-it';
|
|
2
|
+
/**
|
|
3
|
+
* Intercept ```mindmap code fences and convert their content (a markdown
|
|
4
|
+
* outline) into a placeholder div. The actual SVG rendering happens
|
|
5
|
+
* asynchronously in hydrateMindmaps() because markdown-it renders strings
|
|
6
|
+
* synchronously while markmap requires real DOM nodes.
|
|
7
|
+
*
|
|
8
|
+
* Usage (in the editor markdown source):
|
|
9
|
+
*
|
|
10
|
+
* ```mindmap
|
|
11
|
+
* # Root topic
|
|
12
|
+
* ## Branch one
|
|
13
|
+
* - Child A
|
|
14
|
+
* - Child B
|
|
15
|
+
* ## Branch two
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function useMindmap(md: MarkdownIt): void;
|
|
19
|
+
/**
|
|
20
|
+
* After the preview DOM is updated, render all non-hydrated mindmap nodes.
|
|
21
|
+
* markmap-lib / markmap-view are dynamically imported so they do not
|
|
22
|
+
* increase the initial bundle size when mindmaps are absent.
|
|
23
|
+
*/
|
|
24
|
+
export declare function hydrateMindmaps(root: HTMLElement): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
import { type EditorMode, type EditorTheme, type MarkdownEditorOptions } from "../index.js";
|
|
3
|
+
export interface MarkdownEditorProps extends Omit<MarkdownEditorOptions, "value" | "onChange"> {
|
|
4
|
+
/** Controlled value. When provided and different from internal state, syncs the editor. */
|
|
5
|
+
value?: string;
|
|
6
|
+
/** Uncontrolled initial value. Only applied on mount. */
|
|
7
|
+
defaultValue?: string;
|
|
8
|
+
onChange?: (value: string) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
style?: CSSProperties;
|
|
11
|
+
}
|
|
12
|
+
export interface MarkdownEditorHandle {
|
|
13
|
+
getValue: () => string;
|
|
14
|
+
setValue: (value: string) => void;
|
|
15
|
+
getHtml: () => string;
|
|
16
|
+
focus: () => void;
|
|
17
|
+
setMode: (mode: EditorMode) => void;
|
|
18
|
+
setTheme: (theme: EditorTheme) => void;
|
|
19
|
+
/** Manually trigger a preview render when autoPreview is false. */
|
|
20
|
+
renderNow: () => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Only "structural" options (mode, math, mindmap, preview, autoPreview,
|
|
24
|
+
* renderDebounce) trigger a destroy-and-recreate of the underlying instance,
|
|
25
|
+
* because they change DOM structure or the rendering pipeline.
|
|
26
|
+
*
|
|
27
|
+
* theme and value use their own lightweight sync paths and never recreate
|
|
28
|
+
* the instance -- critical for large documents where rebuilding CodeMirror
|
|
29
|
+
* and clearing the incremental render cache on every React re-render would
|
|
30
|
+
* be prohibitively expensive.
|
|
31
|
+
*/
|
|
32
|
+
export declare const MarkdownEditorReact: import("react").ForwardRefExoticComponent<MarkdownEditorProps & import("react").RefAttributes<MarkdownEditorHandle>>;
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("react/jsx-runtime"),n=require("react"),y=require("./index.cjs"),k=n.forwardRef(function(g,v){const{value:u,defaultValue:E,onChange:a,className:M,style:V,mode:i,theme:c,math:d,mindmap:l,preview:f,autoPreview:m,renderDebounce:w}=g,s=n.useRef(null),t=n.useRef(null),o=n.useRef(a);return o.current=a,n.useLayoutEffect(()=>{if(!s.current)return;const e=new y.MarkdownEditor(s.current,{value:u??E??"",mode:i,theme:c,math:d,mindmap:l,preview:f,autoPreview:m,renderDebounce:w,onChange:r=>{var R;return(R=o.current)==null?void 0:R.call(o,r)}});return t.current=e,()=>{e.destroy(),t.current=null}},[i,d,l,f,m,w]),n.useEffect(()=>{const e=t.current;!e||u===void 0||e.getValue()!==u&&e.setValue(u)},[u]),n.useEffect(()=>{t.current&&c&&t.current.setTheme(c)},[c]),n.useImperativeHandle(v,()=>({getValue:()=>{var e;return((e=t.current)==null?void 0:e.getValue())??""},setValue:e=>{var r;return(r=t.current)==null?void 0:r.setValue(e)},getHtml:()=>{var e;return((e=t.current)==null?void 0:e.getHtml())??""},focus:()=>{var e;return(e=t.current)==null?void 0:e.focus()},setMode:e=>{var r;return(r=t.current)==null?void 0:r.setMode(e)},setTheme:e=>{var r;return(r=t.current)==null?void 0:r.setTheme(e)},renderNow:()=>{var e;return(e=t.current)==null?void 0:e.renderNow()}}),[]),h.jsx("div",{ref:s,className:M,style:V})});exports.MarkdownEditorReact=k;
|