remons-markdown-editor 1.0.0-beta.2 → 1.0.0-beta.4

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/MarkdownEditor.tsx
2
- import { useState as useState3, useRef as useRef4, useCallback as useCallback5, useEffect as useEffect3, forwardRef as forwardRef2, useImperativeHandle } from "react";
2
+ import { useState as useState4, useRef as useRef5, useCallback as useCallback6, useEffect as useEffect6, forwardRef as forwardRef2, useImperativeHandle } from "react";
3
+ import { message } from "antd";
3
4
 
4
5
  // src/components/Editor/index.tsx
5
6
  import { useEffect, useRef } from "react";
@@ -11,6 +12,7 @@ import { EditorView, keymap } from "@codemirror/view";
11
12
  import { defaultKeymap, indentWithTab } from "@codemirror/commands";
12
13
  import { markdown } from "@codemirror/lang-markdown";
13
14
  import { oneDark } from "@codemirror/theme-one-dark";
15
+ import { Facet as Facet2 } from "@codemirror/state";
14
16
 
15
17
  // src/components/Editor/containerWidget.ts
16
18
  import { ViewPlugin, Decoration, WidgetType } from "@codemirror/view";
@@ -88,8 +90,6 @@ function findContainers(docText) {
88
90
  }
89
91
 
90
92
  // src/registry/componentRegistry.tsx
91
- import { Input, Select } from "antd";
92
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
93
93
  var ComponentRegistry = class {
94
94
  constructor() {
95
95
  this.schemas = /* @__PURE__ */ new Map();
@@ -132,80 +132,6 @@ var ComponentRegistry = class {
132
132
  }
133
133
  };
134
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
135
  var componentRegistry_default = componentRegistry;
210
136
 
211
137
  // src/components/Editor/containerWidget.ts
@@ -351,13 +277,151 @@ var containerWidgetPlugin = ViewPlugin.fromClass(
351
277
  }
352
278
  );
353
279
 
280
+ // src/components/Toolbar/buttons.tsx
281
+ function insertAtCursor(view, before, after = "", placeholder = "") {
282
+ const { from, to } = view.state.selection.main;
283
+ const selectedText = view.state.doc.sliceString(from, to);
284
+ const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
285
+ view.dispatch({ changes: { from, to, insert } });
286
+ if (!selectedText && placeholder) {
287
+ view.dispatch({ selection: { anchor: from + before.length } });
288
+ }
289
+ }
290
+ function insertAtLineStart(view, text) {
291
+ const { from } = view.state.selection.main;
292
+ const line = view.state.doc.lineAt(from);
293
+ view.dispatch({ changes: { from: line.from, to: line.from, insert: text } });
294
+ view.dispatch({ selection: { anchor: line.from + text.length } });
295
+ }
296
+ async function uploadFileToServer(file, uploadConfig) {
297
+ var _a;
298
+ if (uploadConfig.onUploadImage) {
299
+ return uploadConfig.onUploadImage(file);
300
+ }
301
+ if (uploadConfig.uploadUrl) {
302
+ const formData = new FormData();
303
+ formData.append("file", file);
304
+ const response = await fetch(uploadConfig.uploadUrl, {
305
+ method: "POST",
306
+ body: formData
307
+ });
308
+ if (!response.ok) return null;
309
+ const result = await response.json();
310
+ if (uploadConfig.onGetUploadUrl) {
311
+ return uploadConfig.onGetUploadUrl(result);
312
+ }
313
+ return ((_a = result.data) == null ? void 0 : _a.path) || null;
314
+ }
315
+ return null;
316
+ }
317
+ function isUploadConfigured(uploadConfig) {
318
+ return !!((uploadConfig == null ? void 0 : uploadConfig.uploadUrl) || (uploadConfig == null ? void 0 : uploadConfig.onUploadImage));
319
+ }
320
+ function uploadImage(view, uploadConfig) {
321
+ const input = document.createElement("input");
322
+ input.type = "file";
323
+ input.accept = "image/*";
324
+ input.style.display = "none";
325
+ input.addEventListener("change", async () => {
326
+ if (!input.files || input.files.length === 0) return;
327
+ const file = input.files[0];
328
+ const { from, to } = view.state.selection.main;
329
+ const selectedText = view.state.doc.sliceString(from, to) || "\u63CF\u8FF0";
330
+ const url = await uploadFileToServer(file, uploadConfig);
331
+ if (url) {
332
+ const insert = `![${selectedText}](${url})`;
333
+ view.dispatch({ changes: { from, to, insert } });
334
+ }
335
+ input.remove();
336
+ });
337
+ document.body.appendChild(input);
338
+ input.click();
339
+ }
340
+ function wrapSelection(view, prefix, suffix) {
341
+ const { from, to, empty } = view.state.selection.main;
342
+ if (empty) return;
343
+ const selected = view.state.doc.sliceString(from, to);
344
+ view.dispatch({ changes: { from, to, insert: `${prefix}${selected}${suffix}` } });
345
+ }
346
+ function toggleWrap(view, openTag, closeTag) {
347
+ const { from, to, empty } = view.state.selection.main;
348
+ const doc = view.state.doc;
349
+ if (!empty) {
350
+ const selected = doc.sliceString(from, to);
351
+ if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
352
+ const inner = selected.slice(openTag.length, -closeTag.length);
353
+ view.dispatch({ changes: { from, to, insert: inner } });
354
+ return;
355
+ }
356
+ }
357
+ {
358
+ const cursorPos = empty ? from : to;
359
+ const line = doc.lineAt(cursorPos);
360
+ const lineText = doc.sliceString(line.from, line.to);
361
+ const openIdx = lineText.indexOf(openTag);
362
+ const closeIdx = lineText.lastIndexOf(closeTag);
363
+ if (openIdx >= 0 && closeIdx > openIdx) {
364
+ const absOpen = line.from + openIdx;
365
+ const absClose = line.from + closeIdx;
366
+ if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
367
+ const inner = doc.sliceString(absOpen + openTag.length, absClose);
368
+ view.dispatch({
369
+ changes: {
370
+ from: absOpen,
371
+ to: absClose + closeTag.length,
372
+ insert: inner
373
+ }
374
+ });
375
+ return;
376
+ }
377
+ }
378
+ }
379
+ wrapSelection(view, openTag, closeTag);
380
+ }
381
+
354
382
  // src/components/Editor/codemirrorExtensions.ts
383
+ var uploadConfigFacet = Facet2.define({
384
+ combine: (values) => values[values.length - 1]
385
+ });
386
+ var messageFacet = Facet2.define({
387
+ combine: (values) => values[values.length - 1]
388
+ });
389
+ var pasteHandler = EditorView.domEventHandlers({
390
+ paste: (event, view) => {
391
+ var _a;
392
+ const files = (_a = event.clipboardData) == null ? void 0 : _a.files;
393
+ if (!files) return false;
394
+ const imageFiles = Array.from(files).filter((f) => f.type.startsWith("image/"));
395
+ if (imageFiles.length === 0) return false;
396
+ event.preventDefault();
397
+ const uploadConfig = view.state.facet(uploadConfigFacet);
398
+ const showMessage = view.state.facet(messageFacet);
399
+ if (!isUploadConfigured(uploadConfig)) {
400
+ showMessage == null ? void 0 : showMessage();
401
+ return true;
402
+ }
403
+ const { from, to } = view.state.selection.main;
404
+ let offset = 0;
405
+ for (const file of imageFiles) {
406
+ uploadFileToServer(file, uploadConfig).then((url) => {
407
+ if (url) {
408
+ const name = file.name.replace(/\.[^.]+$/, "");
409
+ const insert = `![${name}](${url})`;
410
+ view.dispatch({ changes: { from: from + offset, to: to + offset, insert } });
411
+ offset += insert.length;
412
+ }
413
+ });
414
+ }
415
+ return true;
416
+ }
417
+ });
355
418
  var getCodeMirrorExtensions = (opts) => {
356
419
  const extensions = [
357
420
  markdown(),
358
421
  oneDark,
359
422
  keymap.of([...defaultKeymap, indentWithTab]),
360
423
  EditorView.lineWrapping,
424
+ pasteHandler,
361
425
  EditorView.baseTheme({
362
426
  ".cm-scroller": {
363
427
  height: "100%",
@@ -402,19 +466,25 @@ var getCodeMirrorExtensions = (opts) => {
402
466
  if (opts == null ? void 0 : opts.onEditContainer) {
403
467
  extensions.push(containerEditCallback.of(opts.onEditContainer));
404
468
  }
469
+ if (opts == null ? void 0 : opts.uploadConfig) {
470
+ extensions.push(uploadConfigFacet.of(opts.uploadConfig));
471
+ }
472
+ if (opts == null ? void 0 : opts.onMessageNoUploadConfig) {
473
+ extensions.push(messageFacet.of(opts.onMessageNoUploadConfig));
474
+ }
405
475
  return extensions;
406
476
  };
407
477
 
408
478
  // src/components/Editor/index.tsx
409
- import { jsx as jsx2 } from "react/jsx-runtime";
410
- function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer }) {
479
+ import { jsx } from "react/jsx-runtime";
480
+ function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer, uploadConfig, onMessageNoUploadConfig }) {
411
481
  const editorRef = useRef(null);
412
482
  const viewRef = useRef(null);
413
483
  useEffect(() => {
414
484
  if (!editorRef.current) return;
415
485
  const extensions = [
416
486
  basicSetup,
417
- ...getCodeMirrorExtensions({ onEditContainer }),
487
+ ...getCodeMirrorExtensions({ onEditContainer, uploadConfig, onMessageNoUploadConfig }),
418
488
  EditorView2.updateListener.of((update) => {
419
489
  if (update.docChanged) {
420
490
  onChange(update.state.doc.toString());
@@ -447,30 +517,33 @@ function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer }
447
517
  });
448
518
  }
449
519
  }, [value]);
450
- return /* @__PURE__ */ jsx2("div", { ref: editorRef, className: "editor" });
520
+ return /* @__PURE__ */ jsx("div", { ref: editorRef, className: "editor" });
451
521
  }
452
522
 
453
523
  // src/components/Preview/index.tsx
454
- import { forwardRef } from "react";
455
- import { registerAll, excludedSelectors } from "remons-markdown-plugins";
524
+ import { forwardRef, useEffect as useEffect2 } from "react";
456
525
  import RenderMarkdown, { languagesCommon, initHighlighter } from "remons-render-markdown";
457
- import "remons-markdown-plugins/style.css";
458
526
  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)] }) });
527
+ import { jsx as jsx2 } from "react/jsx-runtime";
528
+ var Preview = forwardRef(({ content, previewOptions }, ref) => {
529
+ useEffect2(() => {
530
+ initHighlighter({
531
+ ...languagesCommon,
532
+ ...(previewOptions == null ? void 0 : previewOptions.languages) ? previewOptions == null ? void 0 : previewOptions.languages : {}
533
+ });
534
+ }, [previewOptions == null ? void 0 : previewOptions.languages]);
535
+ return /* @__PURE__ */ jsx2("div", { ref, className: "preview", children: /* @__PURE__ */ jsx2(RenderMarkdown, { content, ...previewOptions }) });
463
536
  });
464
537
  Preview.displayName = "Preview";
465
538
  var Preview_default = Preview;
466
539
 
467
540
  // src/components/Panel/SplitPanels.tsx
468
541
  import { Splitter } from "antd";
469
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
542
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
470
543
  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 })
544
+ return /* @__PURE__ */ jsxs(Splitter, { className: "split-panels", children: [
545
+ /* @__PURE__ */ jsx3(Splitter.Panel, { defaultSize: "50%", min: "20%", max: "80%", children: leftPanel }),
546
+ /* @__PURE__ */ jsx3(Splitter.Panel, { children: rightPanel })
474
547
  ] });
475
548
  }
476
549
 
@@ -496,105 +569,10 @@ import {
496
569
  BlockOutlined,
497
570
  MinusOutlined,
498
571
  FieldStringOutlined,
499
- FontSizeOutlined,
500
- TagsOutlined
572
+ FontSizeOutlined
501
573
  } 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() {
574
+ function createToolbarConfig(options) {
575
+ const hasUploadConfig = isUploadConfigured(options);
598
576
  return [
599
577
  {
600
578
  id: "heading",
@@ -619,7 +597,14 @@ function createToolbarConfig() {
619
597
  { id: "ol", label: "\u6709\u5E8F\u5217\u8868", icon: OrderedListOutlined, handler: (v) => insertAtLineStart(v, "1. ") },
620
598
  { separatorBefore: true },
621
599
  { 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) },
600
+ // 图片按钮:未配置上传时禁用并提示
601
+ {
602
+ id: "image",
603
+ label: hasUploadConfig ? "\u56FE\u7247" : "\u8BF7\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u5730\u5740",
604
+ icon: PictureOutlined,
605
+ disabled: !hasUploadConfig,
606
+ handler: hasUploadConfig ? (v) => uploadImage(v, { uploadUrl: options == null ? void 0 : options.uploadUrl, onUploadImage: options == null ? void 0 : options.onUploadImage }) : void 0
607
+ },
623
608
  { separatorBefore: true },
624
609
  { id: "code", label: "\u884C\u5185\u4EE3\u7801", icon: FieldStringOutlined, handler: (v) => wrapSelection(v, "`", "`") },
625
610
  {
@@ -634,7 +619,6 @@ function createToolbarConfig() {
634
619
  }
635
620
  },
636
621
  { 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
622
  {
639
623
  id: "hr",
640
624
  label: "\u5206\u5272\u7EBF",
@@ -648,13 +632,20 @@ function createToolbarConfig() {
648
632
  }
649
633
 
650
634
  // src/components/Toolbar/hooks/useToolbar.ts
651
- function useToolbar(editorView, extraItems) {
635
+ function useToolbar(editorView, options) {
636
+ const { extraItems, uploadConfig } = options || {};
652
637
  const config = useMemo(() => {
653
- const base = createToolbarConfig();
654
- return extraItems ? [...base, { separatorBefore: true }, ...extraItems] : base;
655
- }, [extraItems]);
638
+ const base = createToolbarConfig(uploadConfig);
639
+ return extraItems ? [...base, ...extraItems] : base;
640
+ }, [extraItems, uploadConfig]);
656
641
  const handleClick = useCallback((button) => {
657
- if (button.handler && editorView) {
642
+ if (!editorView) return;
643
+ if (button.wrapTags) {
644
+ const [openTag, closeTag] = button.wrapTags;
645
+ toggleWrap(editorView, openTag, closeTag);
646
+ return;
647
+ }
648
+ if (button.handler) {
658
649
  editorView.focus();
659
650
  button.handler(editorView, editorView.state);
660
651
  }
@@ -663,15 +654,15 @@ function useToolbar(editorView, extraItems) {
663
654
  }
664
655
 
665
656
  // src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
666
- import { Button, Dropdown } from "antd";
657
+ import { Button, Dropdown, Tooltip } from "antd";
667
658
  import { DownOutlined } from "@ant-design/icons";
668
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
659
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
669
660
  function ToolbarItemRenderer({ item, onClick }) {
670
661
  const Icon = item.icon;
671
662
  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(
663
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
664
+ item.separatorBefore && /* @__PURE__ */ jsx4("div", { className: "toolbar-separator" }),
665
+ /* @__PURE__ */ jsx4("div", { className: "toolbar-group", children: /* @__PURE__ */ jsx4(Tooltip, { title: item.label, children: /* @__PURE__ */ jsx4(
675
666
  Dropdown,
676
667
  {
677
668
  menu: {
@@ -680,10 +671,10 @@ function ToolbarItemRenderer({ item, onClick }) {
680
671
  return {
681
672
  type: "item",
682
673
  key: (_a = child.id) != null ? _a : "",
683
- label: /* @__PURE__ */ jsxs3("span", { onClick: () => onClick(child), children: [
674
+ label: /* @__PURE__ */ jsxs2("span", { onClick: () => onClick(child), children: [
684
675
  child.icon && (() => {
685
676
  const ChildIcon = child.icon;
686
- return /* @__PURE__ */ jsx5(ChildIcon, {});
677
+ return /* @__PURE__ */ jsx4(ChildIcon, {});
687
678
  })(),
688
679
  " ",
689
680
  child.label
@@ -692,28 +683,29 @@ function ToolbarItemRenderer({ item, onClick }) {
692
683
  })
693
684
  },
694
685
  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 } })
686
+ children: /* @__PURE__ */ jsxs2(Button, { className: "toolbar-button", type: "text", size: "small", children: [
687
+ /* @__PURE__ */ jsx4(Icon, {}),
688
+ /* @__PURE__ */ jsx4(DownOutlined, { style: { marginLeft: 2, fontSize: 10 } })
698
689
  ] })
699
690
  }
700
- ) })
691
+ ) }) })
701
692
  ] });
702
693
  }
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(
694
+ const isDisabled = item.disabled;
695
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
696
+ item.separatorBefore && /* @__PURE__ */ jsx4("div", { className: "toolbar-separator" }),
697
+ /* @__PURE__ */ jsx4("div", { className: "toolbar-group", children: /* @__PURE__ */ jsx4(Tooltip, { title: item.label, children: /* @__PURE__ */ jsx4(
706
698
  Button,
707
699
  {
708
700
  id: item.id,
709
701
  className: "toolbar-button",
710
702
  type: "text",
711
703
  size: "small",
712
- title: item.label,
713
- onClick: () => onClick(item),
714
- children: /* @__PURE__ */ jsx5(Icon, {})
704
+ disabled: isDisabled,
705
+ onClick: () => !isDisabled && onClick(item),
706
+ children: /* @__PURE__ */ jsx4(Icon, {})
715
707
  }
716
- ) })
708
+ ) }) })
717
709
  ] });
718
710
  }
719
711
 
@@ -723,33 +715,36 @@ import { Button as Button2, Dropdown as Dropdown2 } from "antd";
723
715
  import { TableOutlined } from "@ant-design/icons";
724
716
 
725
717
  // src/components/Toolbar/renderers/TablePicker/index.tsx
726
- import { useState, useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
718
+ import { useState, useRef as useRef2, useEffect as useEffect3, useCallback as useCallback2 } from "react";
727
719
 
728
720
  // src/components/Toolbar/renderers/TablePicker/index.module.less
729
721
  var index_module_default = {};
730
722
 
731
723
  // 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;
724
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
725
+ var MAX_ROW = 8;
726
+ var MAX_COL = 8;
736
727
  function TablePicker({ onSelect }) {
737
728
  const [hoverSize, setHoverSize] = useState({ row: 0, col: 0 });
738
- const [prevHoverSize, setPrevHoverSize] = useState({ row: 0, col: 0 });
739
729
  const [confirmedSize, setConfirmedSize] = useState({ row: 0, col: 0 });
740
730
  const maxColRef = useRef2(0);
731
+ const prevRowRef = useRef2(0);
741
732
  const timerIdRef = useRef2(null);
742
733
  const refreshTimerRef = useRef2(null);
743
734
  const commitRef = useRef2(null);
744
- const clearTimer = useCallback2((flushFn) => {
735
+ const clearTimer = useCallback2(() => {
745
736
  if (timerIdRef.current) {
737
+ clearTimeout(timerIdRef.current);
746
738
  timerIdRef.current = null;
747
739
  }
748
- if (flushFn && maxColRef.current > 0) {
749
- timerIdRef.current = setTimeout(flushFn, REFRESH_TIME);
740
+ }, []);
741
+ const clearFlash = useCallback2(() => {
742
+ if (refreshTimerRef.current) {
743
+ clearInterval(refreshTimerRef.current);
744
+ refreshTimerRef.current = null;
750
745
  }
751
746
  }, []);
752
- useEffect2(
747
+ useEffect3(
753
748
  () => () => {
754
749
  if (timerIdRef.current) clearTimeout(timerIdRef.current);
755
750
  if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
@@ -757,25 +752,21 @@ function TablePicker({ onSelect }) {
757
752
  []
758
753
  );
759
754
  function extend(col, row) {
755
+ if (row < prevRowRef.current || col < maxColRef.current) {
756
+ maxColRef.current = 0;
757
+ }
760
758
  if (col > maxColRef.current) {
761
759
  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
- }
760
+ }
761
+ setHoverSize({ row, col });
762
+ setConfirmedSize({ row, col: maxColRef.current });
763
+ if (row === 1) {
764
+ commitRef.current = { cols: maxColRef.current, rows: 1 };
773
765
  }
774
766
  }
775
767
  function flash(col, dir, limit, finalCallback) {
776
768
  let current = dir === 1 ? 1 : limit;
777
769
  const step = dir === 1 ? 1 : -1;
778
- let timer = 0;
779
770
  refreshTimerRef.current = setInterval(() => {
780
771
  setHoverSize({ row: current, col });
781
772
  if (current > confirmedSize.row || dir === -1) {
@@ -784,45 +775,43 @@ function TablePicker({ onSelect }) {
784
775
  current += step;
785
776
  if (dir === 1 && current > limit || dir === -1 && current < 1) {
786
777
  clearInterval(refreshTimerRef.current);
787
- setHoverSize({ row: current - step, col });
788
- finalCallback(col, current - step);
778
+ refreshTimerRef.current = null;
779
+ const finalRow = current - step;
780
+ setHoverSize({ row: finalRow, col });
781
+ finalCallback(col, finalRow);
789
782
  }
790
783
  }, 50);
791
- clearTimeout(timer);
792
- return () => {
793
- clearInterval(refreshTimerRef.current);
794
- };
795
784
  }
796
785
  function onCellEnterCheck(index) {
797
786
  clearTimer();
787
+ clearFlash();
798
788
  const row = Math.floor(index / MAX_COL) + 1;
799
789
  const col = index % MAX_COL + 1;
800
- const isDown = row > hoverSize.row;
801
- if (Math.abs(row - hoverSize.row) <= 1) {
790
+ const prevRow = prevRowRef.current;
791
+ const isJump = Math.abs(row - prevRow) > 1;
792
+ const isDown = row > prevRow;
793
+ prevRowRef.current = row;
794
+ if (!isJump) {
802
795
  extend(col, row);
803
- } else {
804
- if (isDown && (col > maxColRef.current || maxColRef.current >= MAX_COL)) {
796
+ } else if (isDown) {
797
+ if (col > maxColRef.current || maxColRef.current >= MAX_COL) {
805
798
  const finalRow = Math.min(row, MAX_ROW);
806
799
  flash(col, 1, finalRow, (c, r) => {
807
800
  setConfirmedSize({ row: r, col: c });
808
801
  });
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 });
802
+ } else {
803
+ extend(col, row);
823
804
  }
805
+ } else {
806
+ maxColRef.current = 0;
807
+ flash(col, -1, row, (c, r) => {
808
+ setConfirmedSize({ row: r, col: c });
809
+ if (r === 1) {
810
+ commitRef.current = { cols: Math.min(MAX_COL, col), rows: 1 };
811
+ setConfirmedSize({ row: 1, col: Math.min(MAX_COL, col) });
812
+ }
813
+ });
824
814
  }
825
- setPrevHoverSize({ row, col });
826
815
  }
827
816
  function onMouseDown() {
828
817
  const size = commitRef.current || { cols: maxColRef.current, rows: confirmedSize.row };
@@ -830,13 +819,13 @@ function TablePicker({ onSelect }) {
830
819
  onSelect(size);
831
820
  }
832
821
  }
833
- return /* @__PURE__ */ jsxs4(
822
+ return /* @__PURE__ */ jsxs3(
834
823
  "div",
835
824
  {
836
825
  className: index_module_default.container,
837
826
  onMouseDown,
838
827
  children: [
839
- /* @__PURE__ */ jsx6(
828
+ /* @__PURE__ */ jsx5(
840
829
  "div",
841
830
  {
842
831
  className: index_module_default.grid,
@@ -845,7 +834,7 @@ function TablePicker({ onSelect }) {
845
834
  const row = Math.floor(i / MAX_COL) + 1;
846
835
  const col = i % MAX_COL + 1;
847
836
  const isSelected = row <= hoverSize.row && col <= maxColRef.current;
848
- return /* @__PURE__ */ jsx6(
837
+ return /* @__PURE__ */ jsx5(
849
838
  "div",
850
839
  {
851
840
  className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
@@ -856,7 +845,7 @@ function TablePicker({ onSelect }) {
856
845
  })
857
846
  }
858
847
  ),
859
- /* @__PURE__ */ jsxs4("div", { className: index_module_default.label, children: [
848
+ /* @__PURE__ */ jsxs3("div", { className: index_module_default.label, children: [
860
849
  hoverSize.row,
861
850
  " x ",
862
851
  maxColRef.current || 1
@@ -867,7 +856,7 @@ function TablePicker({ onSelect }) {
867
856
  }
868
857
 
869
858
  // src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
870
- import { jsx as jsx7 } from "react/jsx-runtime";
859
+ import { jsx as jsx6 } from "react/jsx-runtime";
871
860
  function TableDropdown({ editorView }) {
872
861
  const [open, setOpen] = React.useState(false);
873
862
  const openCountRef = useRef3(0);
@@ -886,7 +875,7 @@ ${dataRows}
886
875
  const { from } = editorView.state.selection.main;
887
876
  editorView.dispatch({ changes: { from, insert: table } });
888
877
  }, [editorView]);
889
- return /* @__PURE__ */ jsx7(
878
+ return /* @__PURE__ */ jsx6(
890
879
  Dropdown2,
891
880
  {
892
881
  trigger: ["click"],
@@ -894,7 +883,7 @@ ${dataRows}
894
883
  onOpenChange: setOpen,
895
884
  popupRender: () => {
896
885
  openCountRef.current++;
897
- return /* @__PURE__ */ jsx7("div", { style: { padding: 4 }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx7(
886
+ return /* @__PURE__ */ jsx6("div", { style: { padding: 4 }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx6(
898
887
  TablePicker,
899
888
  {
900
889
  onSelect: (size) => {
@@ -906,44 +895,41 @@ ${dataRows}
906
895
  ) });
907
896
  },
908
897
  placement: "bottomLeft",
909
- children: /* @__PURE__ */ jsx7(Button2, { className: "toolbar-button", type: "text", size: "small", title: "\u8868\u683C", children: /* @__PURE__ */ jsx7(TableOutlined, {}) })
898
+ children: /* @__PURE__ */ jsx6(Button2, { className: "toolbar-button", type: "text", size: "small", title: "\u8868\u683C", children: /* @__PURE__ */ jsx6(TableOutlined, {}) })
910
899
  }
911
900
  );
912
901
  }
913
902
 
914
903
  // 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);
904
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
905
+ function Toolbar({ editorView, fullscreen = true, onToggleFullscreen, extraItems, uploadConfig }) {
906
+ const { config, handleClick } = useToolbar(editorView, { extraItems, uploadConfig });
918
907
  const renderFullscreen = useCallback4(() => {
919
908
  if (!onToggleFullscreen) return null;
920
909
  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
- ] });
910
+ return /* @__PURE__ */ jsx7(
911
+ Button3,
912
+ {
913
+ className: "toolbar-button",
914
+ type: "text",
915
+ size: "small",
916
+ title: fullscreen ? "\u9000\u51FA\u5168\u5C4F" : "\u5168\u5C4F",
917
+ onClick: () => {
918
+ onToggleFullscreen();
919
+ },
920
+ children: /* @__PURE__ */ jsx7(Icon, {})
921
+ }
922
+ );
937
923
  }, [fullscreen, onToggleFullscreen]);
938
924
  function isSeparator(item) {
939
925
  return !!(!item.id && item.separatorBefore);
940
926
  }
941
- return /* @__PURE__ */ jsxs5("div", { className: "toolbar", children: [
927
+ return /* @__PURE__ */ jsxs4("div", { className: "toolbar", children: [
942
928
  config.map((item, idx) => {
943
929
  if (isSeparator(item)) {
944
- return /* @__PURE__ */ jsx8("div", { className: "toolbar-separator" }, `sep-${idx}`);
930
+ return /* @__PURE__ */ jsx7("div", { className: "toolbar-separator" }, `sep-${idx}`);
945
931
  }
946
- return /* @__PURE__ */ jsx8(
932
+ return /* @__PURE__ */ jsx7(
947
933
  ToolbarItemRenderer,
948
934
  {
949
935
  item,
@@ -952,8 +938,7 @@ function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
952
938
  item.id
953
939
  );
954
940
  }),
955
- /* @__PURE__ */ jsx8("div", { className: "toolbar-separator" }),
956
- /* @__PURE__ */ jsx8(TableDropdown, { editorView }),
941
+ /* @__PURE__ */ jsx7(TableDropdown, { editorView }),
957
942
  renderFullscreen()
958
943
  ] });
959
944
  }
@@ -961,7 +946,7 @@ function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
961
946
  // src/components/Dialog/EditDialog.tsx
962
947
  import { useState as useState2, useMemo as useMemo2 } from "react";
963
948
  import { Modal } from "antd";
964
- import { jsx as jsx9 } from "react/jsx-runtime";
949
+ import { jsx as jsx8 } from "react/jsx-runtime";
965
950
  function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
966
951
  const schema = componentRegistry_default.get(typeName);
967
952
  const [formProps, setFormProps] = useState2({});
@@ -989,7 +974,7 @@ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
989
974
  if (!schema) {
990
975
  return null;
991
976
  }
992
- return /* @__PURE__ */ jsx9(
977
+ return /* @__PURE__ */ jsx8(
993
978
  Modal,
994
979
  {
995
980
  title: `${schema.label} \u5C5E\u6027\u7F16\u8F91`,
@@ -1030,35 +1015,11 @@ ${propsLine}
1030
1015
 
1031
1016
  // src/MarkdownEditor.tsx
1032
1017
  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);
1018
+
1019
+ // src/hooks/useFullscreen.ts
1020
+ import { useState as useState3, useEffect as useEffect4, useCallback as useCallback5 } from "react";
1021
+ function useFullscreen() {
1045
1022
  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
1023
  const toggleFullscreen = useCallback5(() => {
1063
1024
  if (document.fullscreenElement) {
1064
1025
  document.exitFullscreen().catch((e) => {
@@ -1074,7 +1035,7 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
1074
1035
  }
1075
1036
  }
1076
1037
  }, []);
1077
- useEffect3(() => {
1038
+ useEffect4(() => {
1078
1039
  const handleFullscreenChange = () => {
1079
1040
  setFullscreen(!!document.fullscreenElement);
1080
1041
  };
@@ -1085,40 +1046,29 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
1085
1046
  document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
1086
1047
  };
1087
1048
  }, []);
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;
1049
+ return { fullscreen, toggleFullscreen };
1050
+ }
1051
+
1052
+ // src/hooks/useScrollSync.ts
1053
+ import { useEffect as useEffect5, useRef as useRef4 } from "react";
1054
+ function useScrollSync(sourceRef, targetRef) {
1055
+ const rafIdRef = useRef4(null);
1056
+ useEffect5(() => {
1057
+ const source = sourceRef.current;
1058
+ const target = targetRef.current;
1108
1059
  if (!source || !target) return;
1109
- let rafId = null;
1110
1060
  const listener = () => {
1111
- if (rafId) return;
1112
- rafId = requestAnimationFrame(() => {
1113
- const s = scrollDOMRef.current;
1114
- const t = previewElRef.current;
1061
+ if (rafIdRef.current) return;
1062
+ rafIdRef.current = requestAnimationFrame(() => {
1063
+ const s = sourceRef.current;
1064
+ const t = targetRef.current;
1115
1065
  if (!s || !t) {
1116
- rafId = null;
1066
+ rafIdRef.current = null;
1117
1067
  return;
1118
1068
  }
1119
1069
  const sourceScrollHeight = s.scrollHeight - s.clientHeight;
1120
1070
  if (sourceScrollHeight <= 0) {
1121
- rafId = null;
1071
+ rafIdRef.current = null;
1122
1072
  return;
1123
1073
  }
1124
1074
  const ratio = s.scrollTop / sourceScrollHeight;
@@ -1126,78 +1076,136 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
1126
1076
  if (targetScrollHeight > 0) {
1127
1077
  t.scrollTop = ratio * targetScrollHeight;
1128
1078
  }
1129
- rafId = null;
1079
+ rafIdRef.current = null;
1130
1080
  });
1131
1081
  };
1132
1082
  source.addEventListener("scroll", listener, { passive: true });
1133
1083
  return () => {
1134
1084
  source.removeEventListener("scroll", listener);
1135
- if (rafId) cancelAnimationFrame(rafId);
1085
+ if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
1086
+ };
1087
+ }, [sourceRef, targetRef]);
1088
+ }
1089
+
1090
+ // src/MarkdownEditor.tsx
1091
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1092
+ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
1093
+ defaultValue = "",
1094
+ value,
1095
+ onChange,
1096
+ showPreview = true,
1097
+ fullscreen: fullscreenProp = false,
1098
+ previewOptions,
1099
+ uploadConfig
1100
+ }, ref) {
1101
+ const [content, setContent] = useState4(value != null ? value : defaultValue);
1102
+ const [editorView, setEditorView] = useState4(null);
1103
+ const scrollDOMRef = useRef5(null);
1104
+ const previewElRef = useRef5(null);
1105
+ const { fullscreen, toggleFullscreen } = useFullscreen();
1106
+ useScrollSync(scrollDOMRef, previewElRef);
1107
+ const [dialogVisible, setDialogVisible] = useState4(false);
1108
+ const [dialogTypeName, setDialogTypeName] = useState4("");
1109
+ const [dialogProps, setDialogProps] = useState4({});
1110
+ const [dialogPropsRange, setDialogPropsRange] = useState4([0, 0]);
1111
+ useEffect6(() => {
1112
+ if (value !== void 0) {
1113
+ setContent(value);
1114
+ }
1115
+ }, [value]);
1116
+ const internalSetContent = useCallback6((newContent) => {
1117
+ setContent(newContent);
1118
+ onChange == null ? void 0 : onChange(newContent);
1119
+ }, [onChange]);
1120
+ useImperativeHandle(ref, () => ({
1121
+ getContent: () => content
1122
+ }), [content]);
1123
+ const extraToolbarItems = componentRegistry_default.getAll().map((schema) => {
1124
+ var _a, _b, _c;
1125
+ const defaultProps = {};
1126
+ for (const field of (_a = schema == null ? void 0 : schema.fields) != null ? _a : []) {
1127
+ defaultProps[field.key] = (_b = field.defaultValue) != null ? _b : "";
1128
+ }
1129
+ const iconNode = (_c = schema.icon) != null ? _c : /* @__PURE__ */ jsx9(ReadOutlined, {});
1130
+ return {
1131
+ id: `container-${schema.name}`,
1132
+ label: schema.label,
1133
+ icon: () => iconNode,
1134
+ handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
1136
1135
  };
1137
- }, [previewElRef.current]);
1138
- const handlePreviewRef = useCallback5((node) => {
1136
+ });
1137
+ const handleScrollReady = useCallback6((scrollDOM) => {
1138
+ scrollDOMRef.current = scrollDOM;
1139
+ }, []);
1140
+ const handlePreviewRef = useCallback6((node) => {
1139
1141
  previewElRef.current = node;
1140
1142
  }, []);
1141
- const handleViewReady = useCallback5((view) => {
1143
+ const handleViewReady = useCallback6((view) => {
1142
1144
  setEditorView(view);
1143
1145
  }, []);
1144
- const handleEditContainer = useCallback5((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
1146
+ const handleEditContainer = useCallback6((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
1145
1147
  setDialogTypeName(typeName);
1146
1148
  setDialogProps({ ...currentProps });
1147
1149
  setDialogPropsRange([propsLineFrom, propsLineTo]);
1148
1150
  setDialogVisible(true);
1149
1151
  }, []);
1150
- const handleDialogSave = useCallback5((newProps) => {
1152
+ const handleDialogSave = useCallback6((newProps) => {
1151
1153
  setDialogVisible(false);
1152
1154
  if (editorView) {
1153
1155
  updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
1154
1156
  }
1155
1157
  }, [editorView, dialogPropsRange]);
1156
- const handleDialogClose = useCallback5(() => {
1158
+ const handleDialogClose = useCallback6(() => {
1157
1159
  setDialogVisible(false);
1158
1160
  }, []);
1159
- return /* @__PURE__ */ jsxs6("div", { className: fullscreen ? "app fullscreen" : "app", children: [
1160
- /* @__PURE__ */ jsx10(
1161
+ return /* @__PURE__ */ jsxs5("div", { className: fullscreen ? "app fullscreen" : "app", children: [
1162
+ /* @__PURE__ */ jsx9(
1161
1163
  Toolbar,
1162
1164
  {
1163
1165
  editorView,
1164
1166
  fullscreen,
1165
1167
  onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
1166
- extraItems: extraToolbarItems
1168
+ extraItems: extraToolbarItems,
1169
+ uploadConfig
1167
1170
  }
1168
1171
  ),
1169
- showPreview ? /* @__PURE__ */ jsx10(
1172
+ showPreview ? /* @__PURE__ */ jsx9(
1170
1173
  SplitPanels,
1171
1174
  {
1172
- leftPanel: /* @__PURE__ */ jsx10(
1175
+ leftPanel: /* @__PURE__ */ jsx9(
1173
1176
  Editor,
1174
1177
  {
1175
1178
  value: content,
1176
1179
  onChange: internalSetContent,
1177
1180
  onScrollReady: handleScrollReady,
1178
1181
  onViewReady: handleViewReady,
1179
- onEditContainer: handleEditContainer
1182
+ onEditContainer: handleEditContainer,
1183
+ uploadConfig,
1184
+ onMessageNoUploadConfig: () => message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
1180
1185
  }
1181
1186
  ),
1182
- rightPanel: /* @__PURE__ */ jsx10(
1187
+ rightPanel: /* @__PURE__ */ jsx9(
1183
1188
  Preview_default,
1184
1189
  {
1185
1190
  content,
1186
- ref: handlePreviewRef
1191
+ ref: handlePreviewRef,
1192
+ previewOptions
1187
1193
  }
1188
1194
  )
1189
1195
  }
1190
- ) : /* @__PURE__ */ jsx10(
1196
+ ) : /* @__PURE__ */ jsx9(
1191
1197
  Editor,
1192
1198
  {
1193
1199
  value: content,
1194
1200
  onChange: internalSetContent,
1195
1201
  onScrollReady: handleScrollReady,
1196
1202
  onViewReady: handleViewReady,
1197
- onEditContainer: handleEditContainer
1203
+ onEditContainer: handleEditContainer,
1204
+ uploadConfig,
1205
+ onMessageNoUploadConfig: () => message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
1198
1206
  }
1199
1207
  ),
1200
- /* @__PURE__ */ jsx10(
1208
+ /* @__PURE__ */ jsx9(
1201
1209
  EditDialog,
1202
1210
  {
1203
1211
  visible: dialogVisible,
@@ -1211,10 +1219,10 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
1211
1219
  });
1212
1220
 
1213
1221
  // src/hooks/useRegisterToolbar.ts
1214
- import { useEffect as useEffect4 } from "react";
1222
+ import { useEffect as useEffect7 } from "react";
1215
1223
  function useRegisterToolbar(schemas) {
1216
1224
  const schemaList = Array.isArray(schemas) ? schemas : [schemas];
1217
- useEffect4(() => {
1225
+ useEffect7(() => {
1218
1226
  schemaList.forEach((schema) => {
1219
1227
  componentRegistry_default.register(schema.name, schema);
1220
1228
  });