remons-markdown-editor 1.0.0-beta.16 → 1.0.0-beta.18

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.css CHANGED
@@ -41,85 +41,85 @@
41
41
  min-height: 20px;
42
42
  text-align: center;
43
43
  }
44
- html,
45
- body {
46
- margin: 0;
47
- padding: 0;
48
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
49
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
50
- sans-serif;
51
- -webkit-font-smoothing: antialiased;
52
- -moz-osx-font-smoothing: grayscale;
53
- }
54
-
55
- #markdown-editor {
56
- height: 100vh;
57
- width: 100vw;
58
- overflow: hidden;
59
- }
60
-
61
- /* ===== CSS 自定义属性(主题变量) ===== */
62
- /* 浅色主题(默认) */
63
- :root,
64
- [data-theme="light"] {
65
- /* 背景色 */
66
- --remons-md-editor-bg-primary: #f3f3f3;
67
- --remons-md-editor-bg-white: rgba(255, 255, 255, 0.6);
68
- --remons-md-editor-bg-hover: #e1e4e8;
69
- --remons-md-editor-bg-active: #d0d7de;
70
- --remons-md-editor-bg-fullscreen: #fff;
71
-
72
- /* 文字颜色 */
73
- --remons-md-editor-color-text: #24292f;
74
- --remons-md-editor-color-text-hover: #0969da;
75
-
76
- /* 边框 */
77
- --remons-md-editor-color-border: rgba(255, 255, 255, 0.8);
78
- --remons-md-editor-color-border-light: #d0d7de;
79
-
80
- /* 焦点 */
81
- --remons-md-editor-color-focus: #0969da;
82
- --remons-md-editor-color-focus-shadow: rgba(9, 105, 218, 0.1);
83
-
84
- /* 工具选择器 */
85
- --remons-md-editor-bg-picker: #fff;
86
- --remons-md-editor-bg-cell-active: #ddf4ff;
87
- --remons-md-editor-color-cell-active: #91caff;
88
- --remons-md-editor-color-hint: #999;
89
- --remons-md-editor-color-label: #666;
90
- }
91
-
92
- /* ===== 深色主题 ===== */
93
- [data-theme="dark"] {
94
- /* 背景色 */
95
- --remons-md-editor-bg-primary: #1a1d23;
96
- --remons-md-editor-bg-white: rgba(46, 51, 80, 0.8);
97
- --remons-md-editor-bg-hover: #4a5163;
98
- --remons-md-editor-bg-active: #5a6278;
99
- --remons-md-editor-bg-fullscreen: #1a1d23;
100
-
101
- /* 文字颜色 */
102
- --remons-md-editor-color-text: #c9d1d9;
103
- --remons-md-editor-color-text-hover: #58a6ff;
104
-
105
- /* 边框 */
106
- --remons-md-editor-color-border: rgba(255, 255, 255, 0.1);
107
- --remons-md-editor-color-border-light: #3a3f4b;
108
-
109
- /* 焦点 */
110
- --remons-md-editor-color-focus: #58a6ff;
111
- --remons-md-editor-color-focus-shadow: rgba(88, 166, 255, 0.15);
112
-
113
- /* 工具选择器 */
114
- --remons-md-editor-bg-picker: #2e3350;
115
- --remons-md-editor-bg-cell-active: #1a3a5c;
116
- --remons-md-editor-color-cell-active: #58a6ff;
117
- --remons-md-editor-color-hint: #8b949e;
118
- --remons-md-editor-color-label: #8b949e;
119
- }
120
-
121
- .ant-anchor-link-title {
122
- white-space: inherit !important;
44
+ html,
45
+ body {
46
+ margin: 0;
47
+ padding: 0;
48
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
49
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
50
+ sans-serif;
51
+ -webkit-font-smoothing: antialiased;
52
+ -moz-osx-font-smoothing: grayscale;
53
+ }
54
+
55
+ #markdown-editor {
56
+ height: 100vh;
57
+ width: 100vw;
58
+ overflow: hidden;
59
+ }
60
+
61
+ /* ===== CSS 自定义属性(主题变量) ===== */
62
+ /* 浅色主题(默认) */
63
+ :root,
64
+ [data-theme="light"] {
65
+ /* 背景色 */
66
+ --remons-md-editor-bg-primary: #f3f3f3;
67
+ --remons-md-editor-bg-white: rgba(255, 255, 255, 0.6);
68
+ --remons-md-editor-bg-hover: #e1e4e8;
69
+ --remons-md-editor-bg-active: #d0d7de;
70
+ --remons-md-editor-bg-fullscreen: #fff;
71
+
72
+ /* 文字颜色 */
73
+ --remons-md-editor-color-text: #24292f;
74
+ --remons-md-editor-color-text-hover: #0969da;
75
+
76
+ /* 边框 */
77
+ --remons-md-editor-color-border: rgba(255, 255, 255, 0.8);
78
+ --remons-md-editor-color-border-light: #d0d7de;
79
+
80
+ /* 焦点 */
81
+ --remons-md-editor-color-focus: #0969da;
82
+ --remons-md-editor-color-focus-shadow: rgba(9, 105, 218, 0.1);
83
+
84
+ /* 工具选择器 */
85
+ --remons-md-editor-bg-picker: #fff;
86
+ --remons-md-editor-bg-cell-active: #ddf4ff;
87
+ --remons-md-editor-color-cell-active: #91caff;
88
+ --remons-md-editor-color-hint: #999;
89
+ --remons-md-editor-color-label: #666;
90
+ }
91
+
92
+ /* ===== 深色主题 ===== */
93
+ [data-theme="dark"] {
94
+ /* 背景色 */
95
+ --remons-md-editor-bg-primary: #1a1d23;
96
+ --remons-md-editor-bg-white: rgba(46, 51, 80, 0.8);
97
+ --remons-md-editor-bg-hover: #4a5163;
98
+ --remons-md-editor-bg-active: #5a6278;
99
+ --remons-md-editor-bg-fullscreen: #1a1d23;
100
+
101
+ /* 文字颜色 */
102
+ --remons-md-editor-color-text: #c9d1d9;
103
+ --remons-md-editor-color-text-hover: #58a6ff;
104
+
105
+ /* 边框 */
106
+ --remons-md-editor-color-border: rgba(255, 255, 255, 0.1);
107
+ --remons-md-editor-color-border-light: #3a3f4b;
108
+
109
+ /* 焦点 */
110
+ --remons-md-editor-color-focus: #58a6ff;
111
+ --remons-md-editor-color-focus-shadow: rgba(88, 166, 255, 0.15);
112
+
113
+ /* 工具选择器 */
114
+ --remons-md-editor-bg-picker: #2e3350;
115
+ --remons-md-editor-bg-cell-active: #1a3a5c;
116
+ --remons-md-editor-color-cell-active: #58a6ff;
117
+ --remons-md-editor-color-hint: #8b949e;
118
+ --remons-md-editor-color-label: #8b949e;
119
+ }
120
+
121
+ .ant-anchor-link-title {
122
+ white-space: inherit !important;
123
123
  }@font-face {
124
124
  font-family: 'Inter';
125
125
  font-style: normal;
@@ -153,28 +153,28 @@ body {
153
153
  font-style: normal;
154
154
  font-weight: 400;
155
155
  font-display: swap;
156
- src: url(https://fonts.gstatic.com/s/notosanssc/v40/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYw.ttf) format('truetype');
156
+ src: url(https://fonts.gstatic.com/s/notosanssc/v41/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYw.ttf) format('truetype');
157
157
  }
158
158
  @font-face {
159
159
  font-family: 'Noto Sans SC';
160
160
  font-style: normal;
161
161
  font-weight: 500;
162
162
  font-display: swap;
163
- src: url(https://fonts.gstatic.com/s/notosanssc/v40/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG-3FnYw.ttf) format('truetype');
163
+ src: url(https://fonts.gstatic.com/s/notosanssc/v41/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG-3FnYw.ttf) format('truetype');
164
164
  }
165
165
  @font-face {
166
166
  font-family: 'Noto Sans SC';
167
167
  font-style: normal;
168
168
  font-weight: 600;
169
169
  font-display: swap;
170
- src: url(https://fonts.gstatic.com/s/notosanssc/v40/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaGwHCnYw.ttf) format('truetype');
170
+ src: url(https://fonts.gstatic.com/s/notosanssc/v41/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaGwHCnYw.ttf) format('truetype');
171
171
  }
172
172
  @font-face {
173
173
  font-family: 'Noto Sans SC';
174
174
  font-style: normal;
175
175
  font-weight: 700;
176
176
  font-display: swap;
177
- src: url(https://fonts.gstatic.com/s/notosanssc/v40/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaGzjCnYw.ttf) format('truetype');
177
+ src: url(https://fonts.gstatic.com/s/notosanssc/v41/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaGzjCnYw.ttf) format('truetype');
178
178
  }
179
179
  .markdown-editor-app {
180
180
  height: 100%;
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { oneDark } from "@codemirror/theme-one-dark";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  import RenderMarkdown, { initHighlighter, languagesCommon } from "remons-render-markdown";
11
11
  import "remons-render-markdown/dist/index.css";
12
- import { BlockOutlined, BoldOutlined, CodeOutlined, DownOutlined, FieldStringOutlined, FontSizeOutlined, FullscreenExitOutlined, FullscreenOutlined, ItalicOutlined, LinkOutlined, MinusOutlined, OrderedListOutlined, PictureOutlined, ReadOutlined, StrikethroughOutlined, TableOutlined, UnderlineOutlined, UnorderedListOutlined } from "@ant-design/icons";
12
+ import { BlockOutlined, BoldOutlined, CheckSquareOutlined, CodeOutlined, DownOutlined, FieldStringOutlined, FontSizeOutlined, FullscreenExitOutlined, FullscreenOutlined, ItalicOutlined, LinkOutlined, MinusOutlined, OrderedListOutlined, PictureOutlined, ReadOutlined, StrikethroughOutlined, TableOutlined, UnderlineOutlined, UnorderedListOutlined } from "@ant-design/icons";
13
13
  //#region src/utils/parseContainer.ts
14
14
  /**
15
15
  * 解析容器属性 JSON 字符串。
@@ -308,6 +308,104 @@ function insertAtLineStart(view, text) {
308
308
  view.dispatch({ selection: { anchor: line.from + text.length } });
309
309
  }
310
310
  /**
311
+ * 切换标题:
312
+ * - 当前行已是目标级别标题 → 移除行首 # 前缀(变回普通文本)
313
+ * - 当前行是其他级别标题 → 替换为目标级别
314
+ * - 当前行是普通文本 → 插入目标级别前缀
315
+ * @param view - CodeMirror 编辑器实例
316
+ * @param level - 标题级别(1-6)
317
+ */
318
+ function toggleHeading(view, level) {
319
+ const line = view.state.doc.lineAt(view.state.selection.main.from);
320
+ const m = line.text.match(/^(\s*)(#{1,6})(\s+|\s*$)/);
321
+ const indent = m ? m[1] : "";
322
+ const text = "#".repeat(level) + " ";
323
+ if (m && m[2].length === level) view.dispatch({ changes: {
324
+ from: line.from,
325
+ to: line.from + m[0].length,
326
+ insert: indent
327
+ } });
328
+ else {
329
+ view.dispatch({ changes: {
330
+ from: line.from,
331
+ to: m ? line.from + m[0].length : line.from,
332
+ insert: indent + text
333
+ } });
334
+ view.dispatch({ selection: { anchor: line.from + indent.length + text.length } });
335
+ }
336
+ }
337
+ /**
338
+ * 切换任务列表(checkbox 待办)
339
+ * - 支持多行:对选区内每一行应用 `- [ ] `
340
+ * - 普通列表项(如 `- 内容`)直接转换为 `- [ ] 内容`
341
+ * - 切换:若所有目标行都已是任务项,则移除行首前缀
342
+ */
343
+ function toggleTaskList(view) {
344
+ const doc = view.state.doc;
345
+ const { from, to } = view.state.selection.main;
346
+ const first = doc.lineAt(from);
347
+ const last = doc.lineAt(to);
348
+ const lines = [];
349
+ for (let n = first.number; n <= last.number; n++) lines.push(doc.line(n));
350
+ const taskRe = /^(\s*)([-*+])\s\[( |x)\]\s?/;
351
+ const allTask = lines.every((line) => taskRe.test(line.text));
352
+ const changes = [];
353
+ for (const line of lines) {
354
+ const taskMatch = line.text.match(taskRe);
355
+ if (taskMatch) changes.push({
356
+ from: line.from,
357
+ to: line.from + taskMatch[0].length,
358
+ insert: allTask ? "" : `${taskMatch[1]}${taskMatch[2]} [ ] `
359
+ });
360
+ else {
361
+ const bulletMatch = line.text.match(/^(\s*)([-*+])\s/);
362
+ if (bulletMatch) {
363
+ const idx = line.from + bulletMatch[1].length + bulletMatch[2].length;
364
+ changes.push({
365
+ from: idx,
366
+ to: idx + 1,
367
+ insert: " [ ] "
368
+ });
369
+ } else changes.push({
370
+ from: line.from,
371
+ to: line.from,
372
+ insert: "- [ ] "
373
+ });
374
+ }
375
+ }
376
+ view.dispatch({ changes });
377
+ }
378
+ /**
379
+ * 切换行首前缀(支持多行选区)
380
+ * - 已是对应前缀的行 → 移除前缀(变回普通文本)
381
+ * - 未加前缀的行 → 在行首插入前缀
382
+ * @param view - CodeMirror 编辑器实例
383
+ * @param pattern - 匹配行首前缀的正则
384
+ * @param insertText - 未匹配时插入的前缀文本
385
+ */
386
+ function toggleLinePrefix(view, pattern, insertText) {
387
+ const doc = view.state.doc;
388
+ const { from, to } = view.state.selection.main;
389
+ const first = doc.lineAt(from);
390
+ const last = doc.lineAt(Math.max(to, from));
391
+ const changes = [];
392
+ for (let n = first.number; n <= last.number; n++) {
393
+ const line = doc.line(n);
394
+ const m = line.text.match(pattern);
395
+ if (m) changes.push({
396
+ from: line.from,
397
+ to: line.from + m[0].length,
398
+ insert: ""
399
+ });
400
+ else changes.push({
401
+ from: line.from,
402
+ to: line.from,
403
+ insert: insertText
404
+ });
405
+ }
406
+ view.dispatch({ changes });
407
+ }
408
+ /**
311
409
  * 上传图片
312
410
  * @param file - 要上传的文件
313
411
  * @param uploadConfig - 上传配置
@@ -591,39 +689,33 @@ function createToolbarConfig(options) {
591
689
  children: [
592
690
  {
593
691
  id: "heading1",
594
- label: "标题 1",
595
- icon: FontSizeOutlined,
596
- handler: (v) => insertAtLineStart(v, "# ")
692
+ label: "H1",
693
+ handler: (v) => toggleHeading(v, 1)
597
694
  },
598
695
  {
599
696
  id: "heading2",
600
- label: "标题 2",
601
- icon: FontSizeOutlined,
602
- handler: (v) => insertAtLineStart(v, "## ")
697
+ label: "H2",
698
+ handler: (v) => toggleHeading(v, 2)
603
699
  },
604
700
  {
605
701
  id: "heading3",
606
- label: "标题 3",
607
- icon: FontSizeOutlined,
608
- handler: (v) => insertAtLineStart(v, "### ")
702
+ label: "H3",
703
+ handler: (v) => toggleHeading(v, 3)
609
704
  },
610
705
  {
611
706
  id: "heading4",
612
- label: "标题 4",
613
- icon: FontSizeOutlined,
614
- handler: (v) => insertAtLineStart(v, "#### ")
707
+ label: "H4",
708
+ handler: (v) => toggleHeading(v, 4)
615
709
  },
616
710
  {
617
711
  id: "heading5",
618
- label: "标题 5",
619
- icon: FontSizeOutlined,
620
- handler: (v) => insertAtLineStart(v, "##### ")
712
+ label: "H5",
713
+ handler: (v) => toggleHeading(v, 5)
621
714
  },
622
715
  {
623
716
  id: "heading6",
624
- label: "标题 6",
625
- icon: FontSizeOutlined,
626
- handler: (v) => insertAtLineStart(v, "###### ")
717
+ label: "H6",
718
+ handler: (v) => toggleHeading(v, 6)
627
719
  }
628
720
  ]
629
721
  },
@@ -632,38 +724,44 @@ function createToolbarConfig(options) {
632
724
  id: "bold",
633
725
  label: "加粗",
634
726
  icon: BoldOutlined,
635
- handler: (v) => wrapSelection(v, "**", "**")
727
+ handler: (v) => toggleWrap(v, "**", "**")
636
728
  },
637
729
  {
638
730
  id: "italic",
639
731
  label: "斜体",
640
732
  icon: ItalicOutlined,
641
- handler: (v) => wrapSelection(v, "*", "*")
733
+ handler: (v) => toggleWrap(v, "*", "*")
642
734
  },
643
735
  {
644
736
  id: "strikethrough",
645
737
  label: "删除线",
646
738
  icon: StrikethroughOutlined,
647
- handler: (v) => wrapSelection(v, "~~", "~~")
739
+ handler: (v) => toggleWrap(v, "~~", "~~")
648
740
  },
649
741
  {
650
742
  id: "underline",
651
743
  label: "下划线",
652
744
  icon: UnderlineOutlined,
653
- handler: (v) => wrapSelection(v, "<u>", "</u>")
745
+ handler: (v) => toggleWrap(v, "<u>", "</u>")
654
746
  },
655
747
  { separatorBefore: true },
656
748
  {
657
749
  id: "ul",
658
750
  label: "无序列表",
659
751
  icon: UnorderedListOutlined,
660
- handler: (v) => insertAtLineStart(v, "- ")
752
+ handler: (v) => toggleLinePrefix(v, /^(\s*)-(?!\s*\[)\s/, "- ")
661
753
  },
662
754
  {
663
755
  id: "ol",
664
756
  label: "有序列表",
665
757
  icon: OrderedListOutlined,
666
- handler: (v) => insertAtLineStart(v, "1. ")
758
+ handler: (v) => toggleLinePrefix(v, /^(\s*)\d+\.\s/, "1. ")
759
+ },
760
+ {
761
+ id: "checkbox",
762
+ label: "待办",
763
+ icon: CheckSquareOutlined,
764
+ handler: (v) => toggleTaskList(v)
667
765
  },
668
766
  { separatorBefore: true },
669
767
  {