remons-markdown-editor 1.0.0-beta.1

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/dist/index.js ADDED
@@ -0,0 +1,1227 @@
1
+ // src/MarkdownEditor.tsx
2
+ import { useState as useState3, useRef as useRef4, useCallback as useCallback5, useEffect as useEffect3, forwardRef as forwardRef2, useImperativeHandle } from "react";
3
+
4
+ // src/components/Editor/index.tsx
5
+ import { useEffect, useRef } from "react";
6
+ import { EditorView as EditorView2, basicSetup } from "codemirror";
7
+ import { EditorState as EditorState2 } from "@codemirror/state";
8
+
9
+ // src/components/Editor/codemirrorExtensions.ts
10
+ import { EditorView, keymap } from "@codemirror/view";
11
+ import { defaultKeymap, indentWithTab } from "@codemirror/commands";
12
+ import { markdown } from "@codemirror/lang-markdown";
13
+ import { oneDark } from "@codemirror/theme-one-dark";
14
+
15
+ // src/components/Editor/containerWidget.ts
16
+ import { ViewPlugin, Decoration, WidgetType } from "@codemirror/view";
17
+ import { Facet } from "@codemirror/state";
18
+
19
+ // src/utils/parseContainer.ts
20
+ function parseContainerProps(jsonStr) {
21
+ var _a, _b, _c;
22
+ try {
23
+ const props = {};
24
+ const trimmed = jsonStr.trim();
25
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
26
+ const inner = trimmed.slice(1, -1).trim();
27
+ if (!inner) return props;
28
+ const matches = inner.matchAll(/(?:["']?)(\w+)(?:["']?)\s*:\s*(?:"([^"]*)"|'([^']*)'|(\d+))/g);
29
+ for (const m of matches) {
30
+ props[m[1]] = (_c = (_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : m[4]) != null ? _c : "";
31
+ }
32
+ }
33
+ return props;
34
+ } catch (e) {
35
+ return {};
36
+ }
37
+ }
38
+ function findContainers(docText) {
39
+ const containers = [];
40
+ const lines = docText.split("\n");
41
+ let i = 0;
42
+ while (i < lines.length) {
43
+ const line = lines[i];
44
+ const match = line.match(/^:::(\w+)$/);
45
+ if (match) {
46
+ const typeName = match[1];
47
+ let charPos = 0;
48
+ for (let l = 0; l < i; l++) {
49
+ charPos += lines[l].length + 1;
50
+ }
51
+ let props = {};
52
+ let propsLineFrom = 0;
53
+ let propsLineTo = 0;
54
+ let propsLineIndex = i + 1;
55
+ if (propsLineIndex < lines.length) {
56
+ const propsLine = lines[propsLineIndex].trim();
57
+ const propsMatch = propsLine.match(/^\{[\s\S]*\}$/);
58
+ if (propsMatch) {
59
+ props = parseContainerProps(propsLine);
60
+ propsLineFrom = charPos + line.length + 1;
61
+ propsLineTo = propsLineFrom + lines[propsLineIndex].length;
62
+ }
63
+ }
64
+ let endPos = 0;
65
+ for (let j = i + 1; j < lines.length; j++) {
66
+ if (lines[j].trim() === ":::") {
67
+ let endCharPos = 0;
68
+ for (let l = 0; l < j; l++) {
69
+ endCharPos += lines[l].length + 1;
70
+ }
71
+ endPos = endCharPos;
72
+ i = j + 1;
73
+ break;
74
+ }
75
+ }
76
+ containers.push({
77
+ startPos: charPos,
78
+ typeName,
79
+ propsLineFrom,
80
+ propsLineTo,
81
+ props,
82
+ endPos
83
+ });
84
+ }
85
+ i++;
86
+ }
87
+ return containers;
88
+ }
89
+
90
+ // src/registry/componentRegistry.tsx
91
+ import { Input, Select } from "antd";
92
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
93
+ var ComponentRegistry = class {
94
+ constructor() {
95
+ this.schemas = /* @__PURE__ */ new Map();
96
+ }
97
+ /**
98
+ * 注册组件 schema
99
+ */
100
+ register(name, schema) {
101
+ this.schemas.set(name, schema);
102
+ }
103
+ /**
104
+ * 根据组件名获取已注册的 schema
105
+ */
106
+ get(name) {
107
+ return this.schemas.get(name);
108
+ }
109
+ /**
110
+ * 检查组件类型是否已注册
111
+ */
112
+ has(name) {
113
+ return this.schemas.has(name);
114
+ }
115
+ /**
116
+ * 获取所有已注册的组件名
117
+ */
118
+ getAllNames() {
119
+ return Array.from(this.schemas.keys());
120
+ }
121
+ /**
122
+ * 获取所有已注册的 schema
123
+ */
124
+ getAll() {
125
+ return Array.from(this.schemas.values());
126
+ }
127
+ /**
128
+ * 移除组件注册
129
+ */
130
+ unregister(name) {
131
+ this.schemas.delete(name);
132
+ }
133
+ };
134
+ var componentRegistry = new ComponentRegistry();
135
+ componentRegistry.register("linkCard", {
136
+ name: "linkCard",
137
+ label: "\u94FE\u63A5\u5361\u7247",
138
+ fields: [
139
+ { key: "title", defaultValue: "\u94FE\u63A5\u6807\u9898" },
140
+ { key: "link", defaultValue: "https://example.com" },
141
+ { key: "description", defaultValue: "\u94FE\u63A5\u63CF\u8FF0" },
142
+ { key: "theme", defaultValue: "default" }
143
+ ],
144
+ renderDialog: (props, onChange) => /* @__PURE__ */ jsxs(Fragment, { children: [
145
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
146
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u6807\u9898" }),
147
+ /* @__PURE__ */ jsx(Input, { value: props.title, onChange: (e) => onChange("title", e.target.value) })
148
+ ] }),
149
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
150
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u94FE\u63A5" }),
151
+ /* @__PURE__ */ jsx(Input, { value: props.link, onChange: (e) => onChange("link", e.target.value) })
152
+ ] }),
153
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
154
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u63CF\u8FF0" }),
155
+ /* @__PURE__ */ jsx(Input.TextArea, { rows: 3, value: props.description, onChange: (e) => onChange("description", e.target.value) })
156
+ ] }),
157
+ /* @__PURE__ */ jsxs("div", { children: [
158
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u4E3B\u9898" }),
159
+ /* @__PURE__ */ jsx(
160
+ Select,
161
+ {
162
+ value: props.theme,
163
+ onChange: (v) => onChange("theme", v),
164
+ options: [
165
+ { label: "\u9ED8\u8BA4", value: "default" },
166
+ { label: "\u4E3B\u8981", value: "primary" },
167
+ { label: "\u8B66\u544A", value: "warning" }
168
+ ]
169
+ }
170
+ )
171
+ ] })
172
+ ] })
173
+ });
174
+ componentRegistry.register("note", {
175
+ name: "note",
176
+ label: "\u6CE8\u91CA",
177
+ fields: [
178
+ { key: "type", defaultValue: "info" },
179
+ { key: "title", defaultValue: "\u6CE8\u610F" },
180
+ { key: "content", defaultValue: "\u5907\u6CE8\u5185\u5BB9" }
181
+ ],
182
+ renderDialog: (props, onChange) => /* @__PURE__ */ jsxs(Fragment, { children: [
183
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
184
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u7C7B\u578B" }),
185
+ /* @__PURE__ */ jsx(
186
+ Select,
187
+ {
188
+ value: props.type,
189
+ onChange: (v) => onChange("type", v),
190
+ options: [
191
+ { label: "\u4FE1\u606F", value: "info" },
192
+ { label: "\u63D0\u793A", value: "tip" },
193
+ { label: "\u8B66\u544A", value: "warning" },
194
+ { label: "\u5371\u9669", value: "danger" }
195
+ ]
196
+ }
197
+ )
198
+ ] }),
199
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
200
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u6807\u9898" }),
201
+ /* @__PURE__ */ jsx(Input, { value: props.title, onChange: (e) => onChange("title", e.target.value) })
202
+ ] }),
203
+ /* @__PURE__ */ jsxs("div", { children: [
204
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u5185\u5BB9" }),
205
+ /* @__PURE__ */ jsx(Input.TextArea, { rows: 3, value: props.content, onChange: (e) => onChange("content", e.target.value) })
206
+ ] })
207
+ ] })
208
+ });
209
+ var componentRegistry_default = componentRegistry;
210
+
211
+ // src/components/Editor/containerWidget.ts
212
+ var containerEditCallback = Facet.define({
213
+ combine: (values) => values[0]
214
+ });
215
+ var ContainerHeaderWidget = class extends WidgetType {
216
+ constructor(typeName) {
217
+ super();
218
+ this.typeName = typeName;
219
+ }
220
+ toDOM() {
221
+ var _a, _b;
222
+ const container = document.createElement("span");
223
+ container.className = "container-widget-header";
224
+ const text = document.createElement("span");
225
+ text.className = "container-widget-text";
226
+ text.textContent = `:::${this.typeName}`;
227
+ const button = document.createElement("button");
228
+ button.className = "container-edit-btn";
229
+ button.title = `\u7F16\u8F91 ${(_b = (_a = componentRegistry_default.get(this.typeName)) == null ? void 0 : _a.label) != null ? _b : this.typeName}`;
230
+ button.textContent = "\u270E";
231
+ button.__typeName = this.typeName;
232
+ container.appendChild(text);
233
+ container.appendChild(button);
234
+ return container;
235
+ }
236
+ eq(other) {
237
+ return this.typeName === other.typeName;
238
+ }
239
+ ignoreEvent() {
240
+ return false;
241
+ }
242
+ };
243
+ var ContainerPropsWidget = class extends WidgetType {
244
+ constructor(propsText) {
245
+ super();
246
+ this.propsText = propsText;
247
+ }
248
+ toDOM() {
249
+ const span = document.createElement("span");
250
+ span.className = "container-widget-props";
251
+ span.textContent = this.propsText;
252
+ return span;
253
+ }
254
+ eq(other) {
255
+ return this.propsText === other.propsText;
256
+ }
257
+ ignoreEvent() {
258
+ return false;
259
+ }
260
+ };
261
+ var ContainerCloseWidget = class extends WidgetType {
262
+ toDOM() {
263
+ const span = document.createElement("span");
264
+ span.className = "container-widget-text";
265
+ span.textContent = ":::";
266
+ return span;
267
+ }
268
+ eq() {
269
+ return true;
270
+ }
271
+ ignoreEvent() {
272
+ return false;
273
+ }
274
+ };
275
+ function findContainerDecorations(state) {
276
+ const docText = state.doc.toString();
277
+ const containers = findContainers(docText);
278
+ const decorations = [];
279
+ for (const container of containers) {
280
+ const headerLine = state.doc.lineAt(container.startPos);
281
+ decorations.push({
282
+ from: headerLine.from,
283
+ to: headerLine.to,
284
+ value: Decoration.replace({
285
+ widget: new ContainerHeaderWidget(container.typeName)
286
+ })
287
+ });
288
+ if (container.propsLineFrom > 0 && container.propsLineTo > 0) {
289
+ const propsLine = state.doc.lineAt(container.propsLineFrom);
290
+ const propsText = propsLine.text;
291
+ decorations.push({
292
+ from: propsLine.from,
293
+ to: propsLine.to,
294
+ value: Decoration.replace({
295
+ widget: new ContainerPropsWidget(propsText)
296
+ })
297
+ });
298
+ }
299
+ if (container.endPos > 0) {
300
+ const endLine = state.doc.lineAt(container.endPos);
301
+ decorations.push({
302
+ from: endLine.from,
303
+ to: endLine.to,
304
+ value: Decoration.replace({
305
+ widget: new ContainerCloseWidget()
306
+ })
307
+ });
308
+ }
309
+ }
310
+ return decorations;
311
+ }
312
+ var containerWidgetPlugin = ViewPlugin.fromClass(
313
+ class {
314
+ constructor(view) {
315
+ this.decorations = Decoration.set(findContainerDecorations(view.state), true);
316
+ }
317
+ update(update) {
318
+ if (update.docChanged || update.selectionSet) {
319
+ this.decorations = Decoration.set(findContainerDecorations(update.state), true);
320
+ }
321
+ }
322
+ },
323
+ {
324
+ decorations: (value) => value.decorations,
325
+ eventHandlers: {
326
+ // 点击编辑按钮时触发回调
327
+ click: (event, view) => {
328
+ const target = event.target;
329
+ if (target.classList.contains("container-edit-btn")) {
330
+ const typeName = target.__typeName;
331
+ if (typeName) {
332
+ const docText = view.state.doc.toString();
333
+ const containers = findContainers(docText);
334
+ const buttonPos = view.posAtDOM(target);
335
+ for (const container of containers) {
336
+ const headerLine = view.state.doc.lineAt(container.startPos);
337
+ if (buttonPos >= headerLine.from - 5 && buttonPos <= headerLine.to + 5) {
338
+ const callback = view.state.facet(containerEditCallback);
339
+ if (callback) {
340
+ callback(view, container.typeName, container.propsLineFrom, container.propsLineTo, { ...container.props });
341
+ }
342
+ return true;
343
+ }
344
+ }
345
+ }
346
+ return false;
347
+ }
348
+ return false;
349
+ }
350
+ }
351
+ }
352
+ );
353
+
354
+ // src/components/Editor/codemirrorExtensions.ts
355
+ var getCodeMirrorExtensions = (opts) => {
356
+ const extensions = [
357
+ markdown(),
358
+ oneDark,
359
+ keymap.of([...defaultKeymap, indentWithTab]),
360
+ EditorView.lineWrapping,
361
+ EditorView.baseTheme({
362
+ ".cm-scroller": {
363
+ height: "100%",
364
+ overflow: "auto"
365
+ },
366
+ // 容器头行 Widget 样式
367
+ ".container-widget-header": {
368
+ display: "inline",
369
+ fontSize: "inherit",
370
+ fontFamily: "inherit"
371
+ },
372
+ ".container-widget-text": {
373
+ fontWeight: "bold",
374
+ color: "#79c0ff"
375
+ },
376
+ // 容器属性行 Widget 样式
377
+ ".container-widget-props": {
378
+ display: "inline",
379
+ fontSize: "inherit",
380
+ fontFamily: "inherit",
381
+ color: "#a5d6ff"
382
+ },
383
+ // 容器编辑按钮样式
384
+ ".container-edit-btn": {
385
+ display: "inline-block",
386
+ padding: "0 4px",
387
+ fontSize: "12px",
388
+ color: "#58a6ff",
389
+ background: "transparent",
390
+ border: "none",
391
+ borderRadius: "4px",
392
+ cursor: "pointer",
393
+ marginRight: "4px",
394
+ verticalAlign: "middle"
395
+ },
396
+ ".container-edit-btn:hover": {
397
+ background: "rgba(88, 166, 255, 0.16)"
398
+ }
399
+ })
400
+ ];
401
+ extensions.push(containerWidgetPlugin);
402
+ if (opts == null ? void 0 : opts.onEditContainer) {
403
+ extensions.push(containerEditCallback.of(opts.onEditContainer));
404
+ }
405
+ return extensions;
406
+ };
407
+
408
+ // src/components/Editor/index.tsx
409
+ import { jsx as jsx2 } from "react/jsx-runtime";
410
+ function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer }) {
411
+ const editorRef = useRef(null);
412
+ const viewRef = useRef(null);
413
+ useEffect(() => {
414
+ if (!editorRef.current) return;
415
+ const extensions = [
416
+ basicSetup,
417
+ ...getCodeMirrorExtensions({ onEditContainer }),
418
+ EditorView2.updateListener.of((update) => {
419
+ if (update.docChanged) {
420
+ onChange(update.state.doc.toString());
421
+ }
422
+ })
423
+ ];
424
+ const state = EditorState2.create({
425
+ doc: value,
426
+ extensions
427
+ });
428
+ viewRef.current = new EditorView2({
429
+ state,
430
+ parent: editorRef.current
431
+ });
432
+ if (onScrollReady && viewRef.current) {
433
+ onScrollReady(viewRef.current.scrollDOM);
434
+ }
435
+ if (onViewReady) {
436
+ onViewReady(viewRef.current);
437
+ }
438
+ return () => {
439
+ var _a;
440
+ (_a = viewRef.current) == null ? void 0 : _a.destroy();
441
+ };
442
+ }, []);
443
+ useEffect(() => {
444
+ if (viewRef.current && viewRef.current.state.doc.toString() !== value) {
445
+ viewRef.current.dispatch({
446
+ changes: { from: 0, to: viewRef.current.state.doc.length, insert: value }
447
+ });
448
+ }
449
+ }, [value]);
450
+ return /* @__PURE__ */ jsx2("div", { ref: editorRef, className: "editor" });
451
+ }
452
+
453
+ // src/components/Preview/index.tsx
454
+ import { forwardRef } from "react";
455
+ import { registerAll, excludedSelectors } from "remons-markdown-plugins";
456
+ import RenderMarkdown, { languagesCommon, initHighlighter } from "remons-render-markdown";
457
+ import "remons-markdown-plugins/style.css";
458
+ import "remons-render-markdown/dist/index.css";
459
+ import { jsx as jsx3 } from "react/jsx-runtime";
460
+ initHighlighter(languagesCommon);
461
+ var Preview = forwardRef(({ content }, ref) => {
462
+ return /* @__PURE__ */ jsx3("div", { ref, className: "preview", children: /* @__PURE__ */ jsx3(RenderMarkdown, { content, isSlotMermaid: true, excludedSelectors, customRenderers: [(md) => md.use(registerAll)] }) });
463
+ });
464
+ Preview.displayName = "Preview";
465
+ var Preview_default = Preview;
466
+
467
+ // src/components/Panel/SplitPanels.tsx
468
+ import { Splitter } from "antd";
469
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
470
+ function SplitPanels({ leftPanel, rightPanel }) {
471
+ return /* @__PURE__ */ jsxs2(Splitter, { className: "split-panels", children: [
472
+ /* @__PURE__ */ jsx4(Splitter.Panel, { defaultSize: "50%", min: "20%", max: "80%", children: leftPanel }),
473
+ /* @__PURE__ */ jsx4(Splitter.Panel, { children: rightPanel })
474
+ ] });
475
+ }
476
+
477
+ // src/components/Toolbar/index.tsx
478
+ import { useCallback as useCallback4 } from "react";
479
+ import { Button as Button3 } from "antd";
480
+ import { FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons";
481
+
482
+ // src/components/Toolbar/hooks/useToolbar.ts
483
+ import { useCallback, useMemo } from "react";
484
+
485
+ // src/components/Toolbar/config.ts
486
+ import {
487
+ BoldOutlined,
488
+ ItalicOutlined,
489
+ StrikethroughOutlined,
490
+ UnderlineOutlined,
491
+ UnorderedListOutlined,
492
+ OrderedListOutlined,
493
+ LinkOutlined,
494
+ PictureOutlined,
495
+ CodeOutlined,
496
+ BlockOutlined,
497
+ MinusOutlined,
498
+ FieldStringOutlined,
499
+ FontSizeOutlined,
500
+ TagsOutlined
501
+ } from "@ant-design/icons";
502
+
503
+ // src/components/Toolbar/buttons.tsx
504
+ function insertAtCursor(view, before, after = "", placeholder = "") {
505
+ const { from, to } = view.state.selection.main;
506
+ const selectedText = view.state.doc.sliceString(from, to);
507
+ const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
508
+ view.dispatch({ changes: { from, to, insert } });
509
+ if (!selectedText && placeholder) {
510
+ view.dispatch({ selection: { anchor: from + before.length } });
511
+ }
512
+ }
513
+ function insertAtLineStart(view, text) {
514
+ const { from } = view.state.selection.main;
515
+ const line = view.state.doc.lineAt(from);
516
+ view.dispatch({ changes: { from: line.from, to: line.from, insert: text } });
517
+ view.dispatch({ selection: { anchor: line.from + text.length } });
518
+ }
519
+ async function uploadFileToServer(file) {
520
+ var _a;
521
+ const formData = new FormData();
522
+ formData.append("file", file);
523
+ const response = await fetch("https://remons.cn:3008/content/uploadMarkdownImg", {
524
+ method: "POST",
525
+ body: formData
526
+ });
527
+ if (!response.ok) return null;
528
+ const result = await response.json();
529
+ if (result.code === 200 && result.success && ((_a = result.data) == null ? void 0 : _a.path)) {
530
+ return `https://remons.cn:3008${result.data.path}`;
531
+ }
532
+ return null;
533
+ }
534
+ function uploadImage(view) {
535
+ const input = document.createElement("input");
536
+ input.type = "file";
537
+ input.accept = "image/*";
538
+ input.style.display = "none";
539
+ input.addEventListener("change", async () => {
540
+ if (!input.files || input.files.length === 0) return;
541
+ const file = input.files[0];
542
+ const { from, to } = view.state.selection.main;
543
+ const selectedText = view.state.doc.sliceString(from, to) || "\u63CF\u8FF0";
544
+ const url = await uploadFileToServer(file);
545
+ if (url) {
546
+ const insert = `![${selectedText}](${url})`;
547
+ view.dispatch({ changes: { from, to, insert } });
548
+ }
549
+ input.remove();
550
+ });
551
+ document.body.appendChild(input);
552
+ input.click();
553
+ }
554
+ function wrapSelection(view, prefix, suffix) {
555
+ const { from, to, empty } = view.state.selection.main;
556
+ if (empty) return;
557
+ const selected = view.state.doc.sliceString(from, to);
558
+ view.dispatch({ changes: { from, to, insert: `${prefix}${selected}${suffix}` } });
559
+ }
560
+ function toggleWrap(view, openTag, closeTag) {
561
+ const { from, to, empty } = view.state.selection.main;
562
+ const doc = view.state.doc;
563
+ if (!empty) {
564
+ const selected = doc.sliceString(from, to);
565
+ if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
566
+ const inner = selected.slice(openTag.length, -closeTag.length);
567
+ view.dispatch({ changes: { from, to, insert: inner } });
568
+ return;
569
+ }
570
+ }
571
+ {
572
+ const cursorPos = empty ? from : to;
573
+ const line = doc.lineAt(cursorPos);
574
+ const lineText = doc.sliceString(line.from, line.to);
575
+ const openIdx = lineText.indexOf(openTag);
576
+ const closeIdx = lineText.lastIndexOf(closeTag);
577
+ if (openIdx >= 0 && closeIdx > openIdx) {
578
+ const absOpen = line.from + openIdx;
579
+ const absClose = line.from + closeIdx;
580
+ if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
581
+ const inner = doc.sliceString(absOpen + openTag.length, absClose);
582
+ view.dispatch({
583
+ changes: {
584
+ from: absOpen,
585
+ to: absClose + closeTag.length,
586
+ insert: inner
587
+ }
588
+ });
589
+ return;
590
+ }
591
+ }
592
+ }
593
+ wrapSelection(view, openTag, closeTag);
594
+ }
595
+
596
+ // src/components/Toolbar/config.ts
597
+ function createToolbarConfig() {
598
+ return [
599
+ {
600
+ id: "heading",
601
+ icon: FontSizeOutlined,
602
+ label: "\u6807\u9898",
603
+ children: [
604
+ { id: "heading1", label: "\u6807\u9898 1", icon: FontSizeOutlined, handler: (v) => insertAtLineStart(v, "# ") },
605
+ { id: "heading2", label: "\u6807\u9898 2", icon: FontSizeOutlined, handler: (v) => insertAtLineStart(v, "## ") },
606
+ { id: "heading3", label: "\u6807\u9898 3", icon: FontSizeOutlined, handler: (v) => insertAtLineStart(v, "### ") },
607
+ { id: "heading4", label: "\u6807\u9898 4", icon: FontSizeOutlined, handler: (v) => insertAtLineStart(v, "#### ") },
608
+ { id: "heading5", label: "\u6807\u9898 5", icon: FontSizeOutlined, handler: (v) => insertAtLineStart(v, "##### ") },
609
+ { id: "heading6", label: "\u6807\u9898 6", icon: FontSizeOutlined, handler: (v) => insertAtLineStart(v, "###### ") }
610
+ ]
611
+ },
612
+ { separatorBefore: true },
613
+ { id: "bold", label: "\u52A0\u7C97", icon: BoldOutlined, handler: (v) => wrapSelection(v, "**", "**") },
614
+ { id: "italic", label: "\u659C\u4F53", icon: ItalicOutlined, handler: (v) => wrapSelection(v, "*", "*") },
615
+ { id: "strikethrough", label: "\u5220\u9664\u7EBF", icon: StrikethroughOutlined, handler: (v) => wrapSelection(v, "~~", "~~") },
616
+ { id: "underline", label: "\u4E0B\u5212\u7EBF", icon: UnderlineOutlined, handler: (v) => wrapSelection(v, "<u>", "</u>") },
617
+ { separatorBefore: true },
618
+ { id: "ul", label: "\u65E0\u5E8F\u5217\u8868", icon: UnorderedListOutlined, handler: (v) => insertAtLineStart(v, "- ") },
619
+ { id: "ol", label: "\u6709\u5E8F\u5217\u8868", icon: OrderedListOutlined, handler: (v) => insertAtLineStart(v, "1. ") },
620
+ { separatorBefore: true },
621
+ { id: "link", label: "\u94FE\u63A5", icon: LinkOutlined, handler: (v) => insertAtCursor(v, "[", "](url)", "\u6587\u672C") },
622
+ { id: "image", label: "\u56FE\u7247", icon: PictureOutlined, handler: (v) => uploadImage(v) },
623
+ { separatorBefore: true },
624
+ { id: "code", label: "\u884C\u5185\u4EE3\u7801", icon: FieldStringOutlined, handler: (v) => wrapSelection(v, "`", "`") },
625
+ {
626
+ id: "codeblock",
627
+ label: "\u4EE3\u7801\u5757",
628
+ icon: CodeOutlined,
629
+ handler: (v) => {
630
+ const { from, to } = v.state.selection.main;
631
+ const selected = v.state.doc.sliceString(from, to);
632
+ const insert = selected ? "```\n" + selected + "\n```" : "```\n\n```";
633
+ v.dispatch({ changes: { from, to, insert } });
634
+ }
635
+ },
636
+ { id: "quote", label: "\u5F15\u7528", icon: BlockOutlined, handler: (v) => insertAtLineStart(v, "> ") },
637
+ { id: "textContainer", label: "\u6587\u5B57\u5BB9\u5668", icon: TagsOutlined, handler: (v) => toggleWrap(v, "<text-container>", "</text-container>") },
638
+ {
639
+ id: "hr",
640
+ label: "\u5206\u5272\u7EBF",
641
+ icon: MinusOutlined,
642
+ handler: (v) => {
643
+ const { from } = v.state.selection.main;
644
+ v.dispatch({ changes: { from, insert: "\n---\n" } });
645
+ }
646
+ }
647
+ ];
648
+ }
649
+
650
+ // src/components/Toolbar/hooks/useToolbar.ts
651
+ function useToolbar(editorView, extraItems) {
652
+ const config = useMemo(() => {
653
+ const base = createToolbarConfig();
654
+ return extraItems ? [...base, { separatorBefore: true }, ...extraItems] : base;
655
+ }, [extraItems]);
656
+ const handleClick = useCallback((button) => {
657
+ if (button.handler && editorView) {
658
+ editorView.focus();
659
+ button.handler(editorView, editorView.state);
660
+ }
661
+ }, [editorView]);
662
+ return { config, handleClick };
663
+ }
664
+
665
+ // src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
666
+ import { Button, Dropdown } from "antd";
667
+ import { DownOutlined } from "@ant-design/icons";
668
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
669
+ function ToolbarItemRenderer({ item, onClick }) {
670
+ const Icon = item.icon;
671
+ if (item.children && item.children.length > 0) {
672
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
673
+ item.separatorBefore && /* @__PURE__ */ jsx5("div", { className: "toolbar-separator" }),
674
+ /* @__PURE__ */ jsx5("div", { className: "toolbar-group", children: /* @__PURE__ */ jsx5(
675
+ Dropdown,
676
+ {
677
+ menu: {
678
+ items: item.children.map((child) => {
679
+ var _a;
680
+ return {
681
+ type: "item",
682
+ key: (_a = child.id) != null ? _a : "",
683
+ label: /* @__PURE__ */ jsxs3("span", { onClick: () => onClick(child), children: [
684
+ child.icon && (() => {
685
+ const ChildIcon = child.icon;
686
+ return /* @__PURE__ */ jsx5(ChildIcon, {});
687
+ })(),
688
+ " ",
689
+ child.label
690
+ ] })
691
+ };
692
+ })
693
+ },
694
+ placement: "bottomLeft",
695
+ children: /* @__PURE__ */ jsxs3(Button, { className: "toolbar-button", type: "text", size: "small", title: item.label, children: [
696
+ /* @__PURE__ */ jsx5(Icon, {}),
697
+ /* @__PURE__ */ jsx5(DownOutlined, { style: { marginLeft: 2, fontSize: 10 } })
698
+ ] })
699
+ }
700
+ ) })
701
+ ] });
702
+ }
703
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
704
+ item.separatorBefore && /* @__PURE__ */ jsx5("div", { className: "toolbar-separator" }),
705
+ /* @__PURE__ */ jsx5("div", { className: "toolbar-group", children: /* @__PURE__ */ jsx5(
706
+ Button,
707
+ {
708
+ id: item.id,
709
+ className: "toolbar-button",
710
+ type: "text",
711
+ size: "small",
712
+ title: item.label,
713
+ onClick: () => onClick(item),
714
+ children: /* @__PURE__ */ jsx5(Icon, {})
715
+ }
716
+ ) })
717
+ ] });
718
+ }
719
+
720
+ // src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
721
+ import React, { useCallback as useCallback3, useRef as useRef3 } from "react";
722
+ import { Button as Button2, Dropdown as Dropdown2 } from "antd";
723
+ import { TableOutlined } from "@ant-design/icons";
724
+
725
+ // src/components/Toolbar/renderers/TablePicker/index.tsx
726
+ import { useState, useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
727
+
728
+ // src/components/Toolbar/renderers/TablePicker/index.module.less
729
+ var index_module_default = {};
730
+
731
+ // src/components/Toolbar/renderers/TablePicker/index.tsx
732
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
733
+ var MAX_ROW = 12;
734
+ var MAX_COL = 12;
735
+ var REFRESH_TIME = 200;
736
+ function TablePicker({ onSelect }) {
737
+ const [hoverSize, setHoverSize] = useState({ row: 0, col: 0 });
738
+ const [prevHoverSize, setPrevHoverSize] = useState({ row: 0, col: 0 });
739
+ const [confirmedSize, setConfirmedSize] = useState({ row: 0, col: 0 });
740
+ const maxColRef = useRef2(0);
741
+ const timerIdRef = useRef2(null);
742
+ const refreshTimerRef = useRef2(null);
743
+ const commitRef = useRef2(null);
744
+ const clearTimer = useCallback2((flushFn) => {
745
+ if (timerIdRef.current) {
746
+ timerIdRef.current = null;
747
+ }
748
+ if (flushFn && maxColRef.current > 0) {
749
+ timerIdRef.current = setTimeout(flushFn, REFRESH_TIME);
750
+ }
751
+ }, []);
752
+ useEffect2(
753
+ () => () => {
754
+ if (timerIdRef.current) clearTimeout(timerIdRef.current);
755
+ if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
756
+ },
757
+ []
758
+ );
759
+ function extend(col, row) {
760
+ if (col > maxColRef.current) {
761
+ maxColRef.current = col;
762
+ setHoverSize({ row, col });
763
+ setConfirmedSize({ row, col });
764
+ } else {
765
+ setHoverSize({ row, col });
766
+ if (row !== confirmedSize.row) {
767
+ setConfirmedSize({ row, col: maxColRef.current });
768
+ }
769
+ if (row === 1) {
770
+ commitRef.current = { cols: col, rows: 1 };
771
+ setConfirmedSize({ row: 1, col });
772
+ }
773
+ }
774
+ }
775
+ function flash(col, dir, limit, finalCallback) {
776
+ let current = dir === 1 ? 1 : limit;
777
+ const step = dir === 1 ? 1 : -1;
778
+ let timer = 0;
779
+ refreshTimerRef.current = setInterval(() => {
780
+ setHoverSize({ row: current, col });
781
+ if (current > confirmedSize.row || dir === -1) {
782
+ maxColRef.current = Math.min(MAX_COL, col);
783
+ }
784
+ current += step;
785
+ if (dir === 1 && current > limit || dir === -1 && current < 1) {
786
+ clearInterval(refreshTimerRef.current);
787
+ setHoverSize({ row: current - step, col });
788
+ finalCallback(col, current - step);
789
+ }
790
+ }, 50);
791
+ clearTimeout(timer);
792
+ return () => {
793
+ clearInterval(refreshTimerRef.current);
794
+ };
795
+ }
796
+ function onCellEnterCheck(index) {
797
+ clearTimer();
798
+ const row = Math.floor(index / MAX_COL) + 1;
799
+ const col = index % MAX_COL + 1;
800
+ const isDown = row > hoverSize.row;
801
+ if (Math.abs(row - hoverSize.row) <= 1) {
802
+ extend(col, row);
803
+ } else {
804
+ if (isDown && (col > maxColRef.current || maxColRef.current >= MAX_COL)) {
805
+ const finalRow = Math.min(row, MAX_ROW);
806
+ flash(col, 1, finalRow, (c, r) => {
807
+ setConfirmedSize({ row: r, col: c });
808
+ });
809
+ } else if (!isDown) {
810
+ flash(col, -1, row, (c, r) => {
811
+ setConfirmedSize({ row: r, col: c });
812
+ if (r === 1) {
813
+ commitRef.current = { cols: Math.min(MAX_COL, col), rows: 1 };
814
+ setConfirmedSize({ row: 1, col: Math.min(MAX_COL, col) });
815
+ }
816
+ });
817
+ }
818
+ }
819
+ if (col >= MAX_COL && row >= prevHoverSize.row) {
820
+ if (row !== prevHoverSize.row) {
821
+ maxColRef.current = 0;
822
+ setPrevHoverSize({ row: 0, col: 0 });
823
+ }
824
+ }
825
+ setPrevHoverSize({ row, col });
826
+ }
827
+ function onMouseDown() {
828
+ const size = commitRef.current || { cols: maxColRef.current, rows: confirmedSize.row };
829
+ if (size.rows > 0 && size.cols > 0) {
830
+ onSelect(size);
831
+ }
832
+ }
833
+ return /* @__PURE__ */ jsxs4(
834
+ "div",
835
+ {
836
+ className: index_module_default.container,
837
+ onMouseDown,
838
+ children: [
839
+ /* @__PURE__ */ jsx6(
840
+ "div",
841
+ {
842
+ className: index_module_default.grid,
843
+ style: { gridTemplateColumns: `repeat(${MAX_COL}, 24px)` },
844
+ children: Array.from({ length: MAX_ROW * MAX_COL }).map((_, i) => {
845
+ const row = Math.floor(i / MAX_COL) + 1;
846
+ const col = i % MAX_COL + 1;
847
+ const isSelected = row <= hoverSize.row && col <= maxColRef.current;
848
+ return /* @__PURE__ */ jsx6(
849
+ "div",
850
+ {
851
+ className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
852
+ onMouseEnter: () => onCellEnterCheck(i)
853
+ },
854
+ i
855
+ );
856
+ })
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsxs4("div", { className: index_module_default.label, children: [
860
+ hoverSize.row,
861
+ " x ",
862
+ maxColRef.current || 1
863
+ ] })
864
+ ]
865
+ }
866
+ );
867
+ }
868
+
869
+ // src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
870
+ import { jsx as jsx7 } from "react/jsx-runtime";
871
+ function TableDropdown({ editorView }) {
872
+ const [open, setOpen] = React.useState(false);
873
+ const openCountRef = useRef3(0);
874
+ const handleTableSelect = useCallback3((rows, cols) => {
875
+ if (!editorView || rows <= 0 || cols <= 0) return;
876
+ editorView.focus();
877
+ const headerCells = Array(cols).fill(null).map((_, i) => ` \u5217${i + 1} `).join("|");
878
+ const separator = Array(cols).fill(" --- ").join("|");
879
+ const dataCell = Array(cols).fill(" \u5185\u5BB9 ").join("|");
880
+ const dataRows = Array(rows).fill(`|${dataCell}|`).join("\n");
881
+ const table = `
882
+ |${headerCells}|
883
+ |${separator}|
884
+ ${dataRows}
885
+ `;
886
+ const { from } = editorView.state.selection.main;
887
+ editorView.dispatch({ changes: { from, insert: table } });
888
+ }, [editorView]);
889
+ return /* @__PURE__ */ jsx7(
890
+ Dropdown2,
891
+ {
892
+ trigger: ["click"],
893
+ open,
894
+ onOpenChange: setOpen,
895
+ popupRender: () => {
896
+ openCountRef.current++;
897
+ return /* @__PURE__ */ jsx7("div", { style: { padding: 4 }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx7(
898
+ TablePicker,
899
+ {
900
+ onSelect: (size) => {
901
+ handleTableSelect(size.rows, size.cols);
902
+ setOpen(false);
903
+ }
904
+ },
905
+ openCountRef.current
906
+ ) });
907
+ },
908
+ placement: "bottomLeft",
909
+ children: /* @__PURE__ */ jsx7(Button2, { className: "toolbar-button", type: "text", size: "small", title: "\u8868\u683C", children: /* @__PURE__ */ jsx7(TableOutlined, {}) })
910
+ }
911
+ );
912
+ }
913
+
914
+ // src/components/Toolbar/index.tsx
915
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
916
+ function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
917
+ const { config, handleClick } = useToolbar(editorView, extraItems);
918
+ const renderFullscreen = useCallback4(() => {
919
+ if (!onToggleFullscreen) return null;
920
+ const Icon = fullscreen ? FullscreenExitOutlined : FullscreenOutlined;
921
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
922
+ /* @__PURE__ */ jsx8("div", { className: "toolbar-separator" }),
923
+ /* @__PURE__ */ jsx8(
924
+ Button3,
925
+ {
926
+ className: "toolbar-button",
927
+ type: "text",
928
+ size: "small",
929
+ title: fullscreen ? "\u9000\u51FA\u5168\u5C4F" : "\u5168\u5C4F",
930
+ onClick: () => {
931
+ onToggleFullscreen();
932
+ },
933
+ children: /* @__PURE__ */ jsx8(Icon, {})
934
+ }
935
+ )
936
+ ] });
937
+ }, [fullscreen, onToggleFullscreen]);
938
+ function isSeparator(item) {
939
+ return !!(!item.id && item.separatorBefore);
940
+ }
941
+ return /* @__PURE__ */ jsxs5("div", { className: "toolbar", children: [
942
+ config.map((item, idx) => {
943
+ if (isSeparator(item)) {
944
+ return /* @__PURE__ */ jsx8("div", { className: "toolbar-separator" }, `sep-${idx}`);
945
+ }
946
+ return /* @__PURE__ */ jsx8(
947
+ ToolbarItemRenderer,
948
+ {
949
+ item,
950
+ onClick: handleClick
951
+ },
952
+ item.id
953
+ );
954
+ }),
955
+ /* @__PURE__ */ jsx8("div", { className: "toolbar-separator" }),
956
+ /* @__PURE__ */ jsx8(TableDropdown, { editorView }),
957
+ renderFullscreen()
958
+ ] });
959
+ }
960
+
961
+ // src/components/Dialog/EditDialog.tsx
962
+ import { useState as useState2, useMemo as useMemo2 } from "react";
963
+ import { Modal } from "antd";
964
+ import { jsx as jsx9 } from "react/jsx-runtime";
965
+ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
966
+ const schema = componentRegistry_default.get(typeName);
967
+ const [formProps, setFormProps] = useState2({});
968
+ const mergedProps = useMemo2(() => {
969
+ var _a;
970
+ if (!schema) return currentProps;
971
+ const defaults = {};
972
+ for (const field of schema.fields) {
973
+ defaults[field.key] = (_a = field.defaultValue) != null ? _a : "";
974
+ }
975
+ return { ...defaults, ...currentProps };
976
+ }, [schema, currentProps]);
977
+ const handleDialogChange = (key, value) => {
978
+ setFormProps((prev) => ({ ...prev, [key]: value }));
979
+ };
980
+ const handleOk = () => {
981
+ const finalProps = visible ? { ...mergedProps, ...formProps } : mergedProps;
982
+ onSave(finalProps);
983
+ setFormProps({});
984
+ };
985
+ const handleClose = () => {
986
+ setFormProps({});
987
+ onClose();
988
+ };
989
+ if (!schema) {
990
+ return null;
991
+ }
992
+ return /* @__PURE__ */ jsx9(
993
+ Modal,
994
+ {
995
+ title: `${schema.label} \u5C5E\u6027\u7F16\u8F91`,
996
+ open: visible,
997
+ onOk: handleOk,
998
+ onCancel: handleClose,
999
+ okText: "\u4FDD\u5B58",
1000
+ cancelText: "\u53D6\u6D88",
1001
+ children: schema.renderDialog(visible ? { ...mergedProps, ...formProps } : mergedProps, handleDialogChange)
1002
+ }
1003
+ );
1004
+ }
1005
+
1006
+ // src/utils/writeContainer.ts
1007
+ function writeContainerProps(props) {
1008
+ const entries = Object.entries(props).map(([k, v]) => `${k}: '${v}'`).join(", ");
1009
+ return entries ? `{ ${entries} }` : "{}";
1010
+ }
1011
+ function updateContainerProps(view, propsLineFrom, propsLineTo, newProps) {
1012
+ const newPropsLine = writeContainerProps(newProps);
1013
+ view.dispatch({
1014
+ changes: {
1015
+ from: propsLineFrom,
1016
+ to: propsLineTo,
1017
+ insert: newPropsLine
1018
+ }
1019
+ });
1020
+ }
1021
+ function insertContainer(view, typeName, defaultProps) {
1022
+ const { from } = view.state.selection.main;
1023
+ const propsLine = writeContainerProps(defaultProps);
1024
+ const container = `:::${typeName}
1025
+ ${propsLine}
1026
+ :::
1027
+ `;
1028
+ view.dispatch({ changes: { from, insert: container } });
1029
+ }
1030
+
1031
+ // src/MarkdownEditor.tsx
1032
+ import { ReadOutlined } from "@ant-design/icons";
1033
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1034
+ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
1035
+ defaultValue = "",
1036
+ value,
1037
+ onChange,
1038
+ showPreview = true,
1039
+ fullscreen: fullscreenProp = false
1040
+ }, ref) {
1041
+ const [content, setContent] = useState3(value != null ? value : defaultValue);
1042
+ const [editorView, setEditorView] = useState3(null);
1043
+ const scrollDOMRef = useRef4(null);
1044
+ const previewElRef = useRef4(null);
1045
+ const [fullscreen, setFullscreen] = useState3(false);
1046
+ const [dialogVisible, setDialogVisible] = useState3(false);
1047
+ const [dialogTypeName, setDialogTypeName] = useState3("");
1048
+ const [dialogProps, setDialogProps] = useState3({});
1049
+ const [dialogPropsRange, setDialogPropsRange] = useState3([0, 0]);
1050
+ useEffect3(() => {
1051
+ if (value !== void 0) {
1052
+ setContent(value);
1053
+ }
1054
+ }, [value]);
1055
+ const internalSetContent = useCallback5((newContent) => {
1056
+ setContent(newContent);
1057
+ onChange == null ? void 0 : onChange(newContent);
1058
+ }, [onChange]);
1059
+ useImperativeHandle(ref, () => ({
1060
+ getContent: () => content
1061
+ }), [content]);
1062
+ const toggleFullscreen = useCallback5(() => {
1063
+ if (document.fullscreenElement) {
1064
+ document.exitFullscreen().catch((e) => {
1065
+ console.error("\u9000\u51FA\u5168\u5C4F\u5931\u8D25:", e);
1066
+ });
1067
+ } else {
1068
+ const el = document.documentElement;
1069
+ const requestMethod = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen;
1070
+ if (requestMethod) {
1071
+ requestMethod.call(el).catch((e) => {
1072
+ console.error("\u8FDB\u5165\u5168\u5C4F\u5931\u8D25:", e);
1073
+ });
1074
+ }
1075
+ }
1076
+ }, []);
1077
+ useEffect3(() => {
1078
+ const handleFullscreenChange = () => {
1079
+ setFullscreen(!!document.fullscreenElement);
1080
+ };
1081
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
1082
+ document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
1083
+ return () => {
1084
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
1085
+ document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
1086
+ };
1087
+ }, []);
1088
+ const extraToolbarItems = componentRegistry_default.getAll().map((schema) => {
1089
+ var _a, _b;
1090
+ const defaultProps = {};
1091
+ for (const field of schema.fields) {
1092
+ defaultProps[field.key] = (_a = field.defaultValue) != null ? _a : "";
1093
+ }
1094
+ const iconNode = (_b = schema.icon) != null ? _b : /* @__PURE__ */ jsx10(ReadOutlined, {});
1095
+ return {
1096
+ id: `container-${schema.name}`,
1097
+ label: schema.label,
1098
+ icon: () => iconNode,
1099
+ handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
1100
+ };
1101
+ });
1102
+ const handleScrollReady = useCallback5((scrollDOM) => {
1103
+ scrollDOMRef.current = scrollDOM;
1104
+ }, []);
1105
+ useEffect3(() => {
1106
+ const source = scrollDOMRef.current;
1107
+ const target = previewElRef.current;
1108
+ if (!source || !target) return;
1109
+ let rafId = null;
1110
+ const listener = () => {
1111
+ if (rafId) return;
1112
+ rafId = requestAnimationFrame(() => {
1113
+ const s = scrollDOMRef.current;
1114
+ const t = previewElRef.current;
1115
+ if (!s || !t) {
1116
+ rafId = null;
1117
+ return;
1118
+ }
1119
+ const sourceScrollHeight = s.scrollHeight - s.clientHeight;
1120
+ if (sourceScrollHeight <= 0) {
1121
+ rafId = null;
1122
+ return;
1123
+ }
1124
+ const ratio = s.scrollTop / sourceScrollHeight;
1125
+ const targetScrollHeight = t.scrollHeight - t.clientHeight;
1126
+ if (targetScrollHeight > 0) {
1127
+ t.scrollTop = ratio * targetScrollHeight;
1128
+ }
1129
+ rafId = null;
1130
+ });
1131
+ };
1132
+ source.addEventListener("scroll", listener, { passive: true });
1133
+ return () => {
1134
+ source.removeEventListener("scroll", listener);
1135
+ if (rafId) cancelAnimationFrame(rafId);
1136
+ };
1137
+ }, [previewElRef.current]);
1138
+ const handlePreviewRef = useCallback5((node) => {
1139
+ previewElRef.current = node;
1140
+ }, []);
1141
+ const handleViewReady = useCallback5((view) => {
1142
+ setEditorView(view);
1143
+ }, []);
1144
+ const handleEditContainer = useCallback5((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
1145
+ setDialogTypeName(typeName);
1146
+ setDialogProps({ ...currentProps });
1147
+ setDialogPropsRange([propsLineFrom, propsLineTo]);
1148
+ setDialogVisible(true);
1149
+ }, []);
1150
+ const handleDialogSave = useCallback5((newProps) => {
1151
+ setDialogVisible(false);
1152
+ if (editorView) {
1153
+ updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
1154
+ }
1155
+ }, [editorView, dialogPropsRange]);
1156
+ const handleDialogClose = useCallback5(() => {
1157
+ setDialogVisible(false);
1158
+ }, []);
1159
+ return /* @__PURE__ */ jsxs6("div", { className: fullscreen ? "app fullscreen" : "app", children: [
1160
+ /* @__PURE__ */ jsx10(
1161
+ Toolbar,
1162
+ {
1163
+ editorView,
1164
+ fullscreen,
1165
+ onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
1166
+ extraItems: extraToolbarItems
1167
+ }
1168
+ ),
1169
+ showPreview ? /* @__PURE__ */ jsx10(
1170
+ SplitPanels,
1171
+ {
1172
+ leftPanel: /* @__PURE__ */ jsx10(
1173
+ Editor,
1174
+ {
1175
+ value: content,
1176
+ onChange: internalSetContent,
1177
+ onScrollReady: handleScrollReady,
1178
+ onViewReady: handleViewReady,
1179
+ onEditContainer: handleEditContainer
1180
+ }
1181
+ ),
1182
+ rightPanel: /* @__PURE__ */ jsx10(
1183
+ Preview_default,
1184
+ {
1185
+ content,
1186
+ ref: handlePreviewRef
1187
+ }
1188
+ )
1189
+ }
1190
+ ) : /* @__PURE__ */ jsx10(
1191
+ Editor,
1192
+ {
1193
+ value: content,
1194
+ onChange: internalSetContent,
1195
+ onScrollReady: handleScrollReady,
1196
+ onViewReady: handleViewReady,
1197
+ onEditContainer: handleEditContainer
1198
+ }
1199
+ ),
1200
+ /* @__PURE__ */ jsx10(
1201
+ EditDialog,
1202
+ {
1203
+ visible: dialogVisible,
1204
+ typeName: dialogTypeName,
1205
+ currentProps: dialogProps,
1206
+ onSave: handleDialogSave,
1207
+ onClose: handleDialogClose
1208
+ }
1209
+ )
1210
+ ] });
1211
+ });
1212
+
1213
+ // src/hooks/useRegisterToolbar.ts
1214
+ import { useEffect as useEffect4 } from "react";
1215
+ function useRegisterToolbar(schema) {
1216
+ useEffect4(() => {
1217
+ componentRegistry_default.register(schema.name, schema);
1218
+ return () => {
1219
+ componentRegistry_default.unregister(schema.name);
1220
+ };
1221
+ }, [schema]);
1222
+ }
1223
+ export {
1224
+ MarkdownEditor_default as MarkdownEditor,
1225
+ useRegisterToolbar
1226
+ };
1227
+ //# sourceMappingURL=index.js.map