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