remons-markdown-editor 1.0.0-beta.15 → 1.0.0-beta.17
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/components/Toolbar/buttons.d.ts +25 -0
- package/dist/index.cjs +137 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +4 -4
- package/dist/index.js +138 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,6 +8,31 @@ export declare function insertAtCursor(view: EditorView, before: string, after?:
|
|
|
8
8
|
* 在当前行行首插入文本
|
|
9
9
|
*/
|
|
10
10
|
export declare function insertAtLineStart(view: EditorView, text: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* 切换标题:
|
|
13
|
+
* - 当前行已是目标级别标题 → 移除行首 # 前缀(变回普通文本)
|
|
14
|
+
* - 当前行是其他级别标题 → 替换为目标级别
|
|
15
|
+
* - 当前行是普通文本 → 插入目标级别前缀
|
|
16
|
+
* @param view - CodeMirror 编辑器实例
|
|
17
|
+
* @param level - 标题级别(1-6)
|
|
18
|
+
*/
|
|
19
|
+
export declare function toggleHeading(view: EditorView, level: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* 切换任务列表(checkbox 待办)
|
|
22
|
+
* - 支持多行:对选区内每一行应用 `- [ ] `
|
|
23
|
+
* - 普通列表项(如 `- 内容`)直接转换为 `- [ ] 内容`
|
|
24
|
+
* - 切换:若所有目标行都已是任务项,则移除行首前缀
|
|
25
|
+
*/
|
|
26
|
+
export declare function toggleTaskList(view: EditorView): void;
|
|
27
|
+
/**
|
|
28
|
+
* 切换行首前缀(支持多行选区)
|
|
29
|
+
* - 已是对应前缀的行 → 移除前缀(变回普通文本)
|
|
30
|
+
* - 未加前缀的行 → 在行首插入前缀
|
|
31
|
+
* @param view - CodeMirror 编辑器实例
|
|
32
|
+
* @param pattern - 匹配行首前缀的正则
|
|
33
|
+
* @param insertText - 未匹配时插入的前缀文本
|
|
34
|
+
*/
|
|
35
|
+
export declare function toggleLinePrefix(view: EditorView, pattern: RegExp, insertText: string): void;
|
|
11
36
|
/**
|
|
12
37
|
* 上传图片
|
|
13
38
|
* @param file - 要上传的文件
|
package/dist/index.cjs
CHANGED
|
@@ -333,6 +333,104 @@ function insertAtLineStart(view, text) {
|
|
|
333
333
|
view.dispatch({ selection: { anchor: line.from + text.length } });
|
|
334
334
|
}
|
|
335
335
|
/**
|
|
336
|
+
* 切换标题:
|
|
337
|
+
* - 当前行已是目标级别标题 → 移除行首 # 前缀(变回普通文本)
|
|
338
|
+
* - 当前行是其他级别标题 → 替换为目标级别
|
|
339
|
+
* - 当前行是普通文本 → 插入目标级别前缀
|
|
340
|
+
* @param view - CodeMirror 编辑器实例
|
|
341
|
+
* @param level - 标题级别(1-6)
|
|
342
|
+
*/
|
|
343
|
+
function toggleHeading(view, level) {
|
|
344
|
+
const line = view.state.doc.lineAt(view.state.selection.main.from);
|
|
345
|
+
const m = line.text.match(/^(\s*)(#{1,6})(\s+|\s*$)/);
|
|
346
|
+
const indent = m ? m[1] : "";
|
|
347
|
+
const text = "#".repeat(level) + " ";
|
|
348
|
+
if (m && m[2].length === level) view.dispatch({ changes: {
|
|
349
|
+
from: line.from,
|
|
350
|
+
to: line.from + m[0].length,
|
|
351
|
+
insert: indent
|
|
352
|
+
} });
|
|
353
|
+
else {
|
|
354
|
+
view.dispatch({ changes: {
|
|
355
|
+
from: line.from,
|
|
356
|
+
to: m ? line.from + m[0].length : line.from,
|
|
357
|
+
insert: indent + text
|
|
358
|
+
} });
|
|
359
|
+
view.dispatch({ selection: { anchor: line.from + indent.length + text.length } });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* 切换任务列表(checkbox 待办)
|
|
364
|
+
* - 支持多行:对选区内每一行应用 `- [ ] `
|
|
365
|
+
* - 普通列表项(如 `- 内容`)直接转换为 `- [ ] 内容`
|
|
366
|
+
* - 切换:若所有目标行都已是任务项,则移除行首前缀
|
|
367
|
+
*/
|
|
368
|
+
function toggleTaskList(view) {
|
|
369
|
+
const doc = view.state.doc;
|
|
370
|
+
const { from, to } = view.state.selection.main;
|
|
371
|
+
const first = doc.lineAt(from);
|
|
372
|
+
const last = doc.lineAt(to);
|
|
373
|
+
const lines = [];
|
|
374
|
+
for (let n = first.number; n <= last.number; n++) lines.push(doc.line(n));
|
|
375
|
+
const taskRe = /^(\s*)([-*+])\s\[( |x)\]\s?/;
|
|
376
|
+
const allTask = lines.every((line) => taskRe.test(line.text));
|
|
377
|
+
const changes = [];
|
|
378
|
+
for (const line of lines) {
|
|
379
|
+
const taskMatch = line.text.match(taskRe);
|
|
380
|
+
if (taskMatch) changes.push({
|
|
381
|
+
from: line.from,
|
|
382
|
+
to: line.from + taskMatch[0].length,
|
|
383
|
+
insert: allTask ? "" : `${taskMatch[1]}${taskMatch[2]} [ ] `
|
|
384
|
+
});
|
|
385
|
+
else {
|
|
386
|
+
const bulletMatch = line.text.match(/^(\s*)([-*+])\s/);
|
|
387
|
+
if (bulletMatch) {
|
|
388
|
+
const idx = line.from + bulletMatch[1].length + bulletMatch[2].length;
|
|
389
|
+
changes.push({
|
|
390
|
+
from: idx,
|
|
391
|
+
to: idx + 1,
|
|
392
|
+
insert: " [ ] "
|
|
393
|
+
});
|
|
394
|
+
} else changes.push({
|
|
395
|
+
from: line.from,
|
|
396
|
+
to: line.from,
|
|
397
|
+
insert: "- [ ] "
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
view.dispatch({ changes });
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 切换行首前缀(支持多行选区)
|
|
405
|
+
* - 已是对应前缀的行 → 移除前缀(变回普通文本)
|
|
406
|
+
* - 未加前缀的行 → 在行首插入前缀
|
|
407
|
+
* @param view - CodeMirror 编辑器实例
|
|
408
|
+
* @param pattern - 匹配行首前缀的正则
|
|
409
|
+
* @param insertText - 未匹配时插入的前缀文本
|
|
410
|
+
*/
|
|
411
|
+
function toggleLinePrefix(view, pattern, insertText) {
|
|
412
|
+
const doc = view.state.doc;
|
|
413
|
+
const { from, to } = view.state.selection.main;
|
|
414
|
+
const first = doc.lineAt(from);
|
|
415
|
+
const last = doc.lineAt(Math.max(to, from));
|
|
416
|
+
const changes = [];
|
|
417
|
+
for (let n = first.number; n <= last.number; n++) {
|
|
418
|
+
const line = doc.line(n);
|
|
419
|
+
const m = line.text.match(pattern);
|
|
420
|
+
if (m) changes.push({
|
|
421
|
+
from: line.from,
|
|
422
|
+
to: line.from + m[0].length,
|
|
423
|
+
insert: ""
|
|
424
|
+
});
|
|
425
|
+
else changes.push({
|
|
426
|
+
from: line.from,
|
|
427
|
+
to: line.from,
|
|
428
|
+
insert: insertText
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
view.dispatch({ changes });
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
336
434
|
* 上传图片
|
|
337
435
|
* @param file - 要上传的文件
|
|
338
436
|
* @param uploadConfig - 上传配置
|
|
@@ -616,39 +714,33 @@ function createToolbarConfig(options) {
|
|
|
616
714
|
children: [
|
|
617
715
|
{
|
|
618
716
|
id: "heading1",
|
|
619
|
-
label: "
|
|
620
|
-
|
|
621
|
-
handler: (v) => insertAtLineStart(v, "# ")
|
|
717
|
+
label: "H1",
|
|
718
|
+
handler: (v) => toggleHeading(v, 1)
|
|
622
719
|
},
|
|
623
720
|
{
|
|
624
721
|
id: "heading2",
|
|
625
|
-
label: "
|
|
626
|
-
|
|
627
|
-
handler: (v) => insertAtLineStart(v, "## ")
|
|
722
|
+
label: "H2",
|
|
723
|
+
handler: (v) => toggleHeading(v, 2)
|
|
628
724
|
},
|
|
629
725
|
{
|
|
630
726
|
id: "heading3",
|
|
631
|
-
label: "
|
|
632
|
-
|
|
633
|
-
handler: (v) => insertAtLineStart(v, "### ")
|
|
727
|
+
label: "H3",
|
|
728
|
+
handler: (v) => toggleHeading(v, 3)
|
|
634
729
|
},
|
|
635
730
|
{
|
|
636
731
|
id: "heading4",
|
|
637
|
-
label: "
|
|
638
|
-
|
|
639
|
-
handler: (v) => insertAtLineStart(v, "#### ")
|
|
732
|
+
label: "H4",
|
|
733
|
+
handler: (v) => toggleHeading(v, 4)
|
|
640
734
|
},
|
|
641
735
|
{
|
|
642
736
|
id: "heading5",
|
|
643
|
-
label: "
|
|
644
|
-
|
|
645
|
-
handler: (v) => insertAtLineStart(v, "##### ")
|
|
737
|
+
label: "H5",
|
|
738
|
+
handler: (v) => toggleHeading(v, 5)
|
|
646
739
|
},
|
|
647
740
|
{
|
|
648
741
|
id: "heading6",
|
|
649
|
-
label: "
|
|
650
|
-
|
|
651
|
-
handler: (v) => insertAtLineStart(v, "###### ")
|
|
742
|
+
label: "H6",
|
|
743
|
+
handler: (v) => toggleHeading(v, 6)
|
|
652
744
|
}
|
|
653
745
|
]
|
|
654
746
|
},
|
|
@@ -657,38 +749,44 @@ function createToolbarConfig(options) {
|
|
|
657
749
|
id: "bold",
|
|
658
750
|
label: "加粗",
|
|
659
751
|
icon: _ant_design_icons.BoldOutlined,
|
|
660
|
-
handler: (v) =>
|
|
752
|
+
handler: (v) => toggleWrap(v, "**", "**")
|
|
661
753
|
},
|
|
662
754
|
{
|
|
663
755
|
id: "italic",
|
|
664
756
|
label: "斜体",
|
|
665
757
|
icon: _ant_design_icons.ItalicOutlined,
|
|
666
|
-
handler: (v) =>
|
|
758
|
+
handler: (v) => toggleWrap(v, "*", "*")
|
|
667
759
|
},
|
|
668
760
|
{
|
|
669
761
|
id: "strikethrough",
|
|
670
762
|
label: "删除线",
|
|
671
763
|
icon: _ant_design_icons.StrikethroughOutlined,
|
|
672
|
-
handler: (v) =>
|
|
764
|
+
handler: (v) => toggleWrap(v, "~~", "~~")
|
|
673
765
|
},
|
|
674
766
|
{
|
|
675
767
|
id: "underline",
|
|
676
768
|
label: "下划线",
|
|
677
769
|
icon: _ant_design_icons.UnderlineOutlined,
|
|
678
|
-
handler: (v) =>
|
|
770
|
+
handler: (v) => toggleWrap(v, "<u>", "</u>")
|
|
679
771
|
},
|
|
680
772
|
{ separatorBefore: true },
|
|
681
773
|
{
|
|
682
774
|
id: "ul",
|
|
683
775
|
label: "无序列表",
|
|
684
776
|
icon: _ant_design_icons.UnorderedListOutlined,
|
|
685
|
-
handler: (v) =>
|
|
777
|
+
handler: (v) => toggleLinePrefix(v, /^(\s*)-(?!\s*\[)\s/, "- ")
|
|
686
778
|
},
|
|
687
779
|
{
|
|
688
780
|
id: "ol",
|
|
689
781
|
label: "有序列表",
|
|
690
782
|
icon: _ant_design_icons.OrderedListOutlined,
|
|
691
|
-
handler: (v) =>
|
|
783
|
+
handler: (v) => toggleLinePrefix(v, /^(\s*)\d+\.\s/, "1. ")
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
id: "checkbox",
|
|
787
|
+
label: "待办",
|
|
788
|
+
icon: _ant_design_icons.CheckSquareOutlined,
|
|
789
|
+
handler: (v) => toggleTaskList(v)
|
|
692
790
|
},
|
|
693
791
|
{ separatorBefore: true },
|
|
694
792
|
{
|
|
@@ -1131,6 +1229,9 @@ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
|
1131
1229
|
const schema = componentRegistry.get(typeName);
|
|
1132
1230
|
const [formProps, setFormProps] = (0, react.useState)({});
|
|
1133
1231
|
const [form] = antd.Form.useForm();
|
|
1232
|
+
const onSaveRef = (0, react.useRef)(onSave);
|
|
1233
|
+
onSaveRef.current = onSave;
|
|
1234
|
+
const dirtyRef = (0, react.useRef)(false);
|
|
1134
1235
|
const mergedProps = (0, react.useMemo)(() => {
|
|
1135
1236
|
if (!schema) return currentProps;
|
|
1136
1237
|
const defaults = {};
|
|
@@ -1168,18 +1269,28 @@ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
|
1168
1269
|
...formValues
|
|
1169
1270
|
};
|
|
1170
1271
|
} catch {}
|
|
1171
|
-
|
|
1272
|
+
onSaveRef.current(finalProps);
|
|
1273
|
+
dirtyRef.current = false;
|
|
1274
|
+
onClose();
|
|
1172
1275
|
};
|
|
1173
1276
|
const handleClose = () => {
|
|
1174
1277
|
setFormProps({});
|
|
1278
|
+
dirtyRef.current = false;
|
|
1175
1279
|
onClose();
|
|
1176
1280
|
};
|
|
1177
1281
|
if (!schema) return null;
|
|
1282
|
+
const contextSave = (props) => {
|
|
1283
|
+
setFormProps((prev) => ({
|
|
1284
|
+
...prev,
|
|
1285
|
+
...props
|
|
1286
|
+
}));
|
|
1287
|
+
dirtyRef.current = true;
|
|
1288
|
+
};
|
|
1178
1289
|
const context = {
|
|
1179
1290
|
props: mergedProps,
|
|
1180
1291
|
form,
|
|
1181
1292
|
onChange: handleDialogChange,
|
|
1182
|
-
onSave
|
|
1293
|
+
onSave: contextSave
|
|
1183
1294
|
};
|
|
1184
1295
|
const renderDialogContent = () => {
|
|
1185
1296
|
if (schema.renderDialog) return schema.renderDialog(context);
|
|
@@ -1371,7 +1482,6 @@ var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ def
|
|
|
1371
1482
|
setDialogVisible(true);
|
|
1372
1483
|
}, []);
|
|
1373
1484
|
const handleDialogSave = (0, react.useCallback)((newProps) => {
|
|
1374
|
-
setDialogVisible(false);
|
|
1375
1485
|
if (editorView) updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
|
|
1376
1486
|
}, [editorView, dialogPropsRange]);
|
|
1377
1487
|
const handleDialogClose = (0, react.useCallback)(() => {
|