vditor 3.11.2 → 4.0.0

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.
Files changed (74) hide show
  1. package/README.md +12 -1
  2. package/dist/css/content-theme/ant-design.css +10 -0
  3. package/dist/css/content-theme/dark.css +10 -0
  4. package/dist/css/content-theme/light.css +13 -0
  5. package/dist/css/content-theme/wechat.css +10 -0
  6. package/dist/index.css +163 -81
  7. package/dist/index.js +3035 -2715
  8. package/dist/index.min.js +1 -1
  9. package/dist/js/i18n/ja_JP.js +2 -2
  10. package/dist/js/i18n/zh_TW.js +8 -8
  11. package/dist/js/lute/lute.min.js +75 -60
  12. package/dist/js/mermaid/mermaid.min.js +2027 -1047
  13. package/dist/js/wavedrom/LICENSE.WaveDrom +21 -0
  14. package/dist/js/wavedrom/wavedrom.min.js +9 -0
  15. package/dist/method.d.ts +2 -0
  16. package/dist/method.js +323 -9
  17. package/dist/method.min.js +1 -1
  18. package/dist/ts/constants.d.ts +4 -0
  19. package/dist/ts/markdown/adapterRender.d.ts +4 -0
  20. package/dist/ts/markdown/imageCaptionRender.d.ts +2 -0
  21. package/dist/ts/markdown/wavedromRender.d.ts +1 -0
  22. package/dist/ts/sv/index.d.ts +1 -5
  23. package/dist/ts/sv/process.d.ts +1 -3
  24. package/dist/ts/upload/getElement.d.ts +1 -1
  25. package/dist/ts/upload/index.d.ts +2 -0
  26. package/dist/ts/util/editorCommonEvent.d.ts +1 -1
  27. package/dist/ts/util/fixBrowserBehavior.d.ts +1 -0
  28. package/dist/types/index.d.ts +22 -5
  29. package/package.json +7 -4
  30. package/src/assets/less/_reset.less +103 -2
  31. package/src/assets/less/_sv.less +2 -98
  32. package/src/assets/less/_wysiwyg.less +30 -0
  33. package/src/assets/less/index.less +20 -0
  34. package/src/index.ts +48 -14
  35. package/src/method.ts +3 -0
  36. package/src/ts/constants.ts +5 -1
  37. package/src/ts/export/index.ts +2 -0
  38. package/src/ts/hint/index.ts +9 -4
  39. package/src/ts/ir/highlightToolbarIR.ts +4 -4
  40. package/src/ts/ir/index.ts +26 -0
  41. package/src/ts/ir/process.ts +24 -1
  42. package/src/ts/markdown/adapterRender.ts +4 -0
  43. package/src/ts/markdown/getHTML.ts +6 -3
  44. package/src/ts/markdown/getMarkdown.ts +3 -3
  45. package/src/ts/markdown/imageCaptionRender.ts +175 -0
  46. package/src/ts/markdown/mermaidRender.ts +1 -1
  47. package/src/ts/markdown/previewRender.ts +7 -1
  48. package/src/ts/markdown/setLute.ts +3 -0
  49. package/src/ts/markdown/wavedromRender.ts +54 -0
  50. package/src/ts/preview/index.ts +5 -0
  51. package/src/ts/sv/index.ts +37 -76
  52. package/src/ts/sv/process.ts +48 -164
  53. package/src/ts/sv/processKeydown.ts +19 -186
  54. package/src/ts/toolbar/EditMode.ts +3 -10
  55. package/src/ts/toolbar/Emoji.ts +8 -1
  56. package/src/ts/toolbar/Record.ts +10 -2
  57. package/src/ts/toolbar/index.ts +3 -0
  58. package/src/ts/ui/initUI.ts +7 -3
  59. package/src/ts/undo/index.ts +32 -17
  60. package/src/ts/upload/index.ts +42 -10
  61. package/src/ts/util/Options.ts +1 -1
  62. package/src/ts/util/editorCommonEvent.ts +18 -9
  63. package/src/ts/util/fixBrowserBehavior.ts +197 -51
  64. package/src/ts/util/processCode.ts +3 -0
  65. package/src/ts/util/selection.ts +11 -3
  66. package/src/ts/wysiwyg/afterRenderEvent.ts +2 -0
  67. package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +58 -2
  68. package/src/ts/wysiwyg/input.ts +5 -0
  69. package/src/ts/wysiwyg/processKeydown.ts +47 -0
  70. package/src/ts/wysiwyg/setHeading.ts +110 -6
  71. package/dist/ts/sv/combineFootnote.d.ts +0 -6
  72. package/dist/ts/sv/inputEvent.d.ts +0 -1
  73. package/src/ts/sv/combineFootnote.ts +0 -27
  74. package/src/ts/sv/inputEvent.ts +0 -188
@@ -1,104 +1,14 @@
1
1
  import {getMarkdown} from "../markdown/getMarkdown";
2
2
  import {accessLocalStorage} from "../util/compatibility";
3
- import {scrollCenter} from "../util/editorCommonEvent";
4
- import {hasClosestBlock, hasClosestByAttribute} from "../util/hasClosest";
5
- import {hasClosestByTag} from "../util/hasClosestByHeadings";
6
- import {log} from "../util/log";
7
- import {getEditorRange, setRangeByWbr} from "../util/selection";
8
- import {inputEvent} from "./inputEvent";
9
- import {combineFootnote} from "./combineFootnote";
10
3
 
11
-
12
- export const processPaste = (vditor: IVditor, text: string) => {
13
- const range = getEditorRange(vditor);
14
- range.extractContents();
15
- range.insertNode(document.createTextNode(Lute.Caret));
16
- range.insertNode(document.createTextNode(text));
17
- let blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
18
- if (!blockElement) {
19
- blockElement = vditor.sv.element;
20
- }
21
- let spinHTML = vditor.lute.SpinVditorSVDOM(blockElement.textContent)
22
- spinHTML = "<div data-block='0'>" +
23
- spinHTML.replace(/<span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span><span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span></g, '<span data-type="newline"><br /><span style="display: none">\n</span></span><span data-type="newline"><br /><span style="display: none">\n</span></span></div><div data-block="0"><') +
24
- "</div>";
25
- if (blockElement.isEqualNode(vditor.sv.element)) {
26
- blockElement.innerHTML = spinHTML;
27
- } else {
28
- blockElement.outerHTML = spinHTML;
29
- }
30
- combineFootnote(vditor.sv.element)
31
- setRangeByWbr(vditor.sv.element, range);
32
-
33
- scrollCenter(vditor);
34
- };
35
-
36
- export const getSideByType = (spanNode: Node, type: string, isPrevious = true) => {
37
- let sideElement = spanNode as Element;
38
- if (sideElement.nodeType === 3) {
39
- sideElement = sideElement.parentElement;
40
- }
41
- while (sideElement) {
42
- if (sideElement.getAttribute("data-type") === type) {
43
- return sideElement;
44
- }
45
- if (isPrevious) {
46
- sideElement = sideElement.previousElementSibling;
47
- } else {
48
- sideElement = sideElement.nextElementSibling;
49
- }
50
- }
51
- return false;
4
+ export const replaceSelection = (vditor: IVditor, text: string, selectionMode: SelectionMode = "end") => {
5
+ const element = vditor.sv.element;
6
+ element.setRangeText(text, element.selectionStart, element.selectionEnd, selectionMode);
7
+ element.focus();
52
8
  };
53
9
 
54
- export const processSpinVditorSVDOM = (html: string, vditor: IVditor) => {
55
- log("SpinVditorSVDOM", html, "argument", vditor.options.debugger);
56
- const spinHTML = vditor.lute.SpinVditorSVDOM(html)
57
- html = "<div data-block='0'>" +
58
- spinHTML.replace(/<span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span><span data-type="newline"><br \/><span style="display: none">\n<\/span><\/span></g, '<span data-type="newline"><br /><span style="display: none">\n</span></span><span data-type="newline"><br /><span style="display: none">\n</span></span></div><div data-block="0"><') +
59
- "</div>";
60
- log("SpinVditorSVDOM", html, "result", vditor.options.debugger);
61
- return html;
62
- };
63
-
64
- export const processPreviousMarkers = (spanElement: HTMLElement) => {
65
- const spanType = spanElement.getAttribute("data-type");
66
- let previousElement = spanElement.previousElementSibling;
67
- // 有内容的子列表/标题,在其 marker 后换行
68
- let markerText = (spanType && spanType !== "text" && spanType !== "table" && spanType !== "heading-marker" &&
69
- spanType !== "newline" && spanType !== "yaml-front-matter-open-marker" && spanType !== "yaml-front-matter-close-marker"
70
- && spanType !== "code-block-info" && spanType !== "code-block-close-marker" && spanType !== "code-block-open-marker") ?
71
- spanElement.textContent : "";
72
- let hasNL = false;
73
- if (spanType === "newline") {
74
- hasNL = true;
75
- }
76
- while (previousElement && !hasNL) {
77
- const previousType = previousElement.getAttribute("data-type");
78
- if (previousType === "li-marker" || previousType === "blockquote-marker" || previousType === "task-marker" ||
79
- previousType === "padding") {
80
- const previousText = previousElement.textContent;
81
- if (previousType === "li-marker" &&
82
- (spanType === "code-block-open-marker" || spanType === "code-block-info")) {
83
- // https://github.com/Vanessa219/vditor/issues/586
84
- markerText = previousText.replace(/\S/g, " ") + markerText;
85
- } else if (spanType === "code-block-close-marker" &&
86
- previousElement.nextElementSibling.isSameNode(spanElement)) {
87
- // https://github.com/Vanessa219/vditor/issues/594
88
- const openMarker = getSideByType(spanElement, "code-block-open-marker");
89
- if (openMarker && openMarker.previousElementSibling) {
90
- previousElement = openMarker.previousElementSibling;
91
- markerText = previousText + markerText;
92
- }
93
- } else {
94
- markerText = previousText + markerText;
95
- }
96
- } else if (previousType === "newline") {
97
- hasNL = true;
98
- }
99
- previousElement = previousElement.previousElementSibling;
100
- }
101
- return markerText;
10
+ export const processPaste = (vditor: IVditor, text: string) => {
11
+ replaceSelection(vditor, text);
102
12
  };
103
13
 
104
14
  export const processAfterRender = (vditor: IVditor, options = {
@@ -106,10 +16,6 @@ export const processAfterRender = (vditor: IVditor, options = {
106
16
  enableHint: false,
107
17
  enableInput: true,
108
18
  }) => {
109
- if (options.enableHint) {
110
- vditor.hint.render(vditor);
111
- }
112
-
113
19
  vditor.preview.render(vditor);
114
20
 
115
21
  const text = getMarkdown(vditor);
@@ -141,75 +47,53 @@ export const processAfterRender = (vditor: IVditor, options = {
141
47
  };
142
48
 
143
49
  export const processHeading = (vditor: IVditor, value: string) => {
144
- const range = getEditorRange(vditor);
145
- const headingElement = hasClosestByTag(range.startContainer, "SPAN");
146
- if (headingElement && headingElement.textContent.trim() !== "") {
147
- value = "\n" + value;
148
- }
149
- range.collapse(true);
150
- document.execCommand("insertHTML", false, value);
50
+ const element = vditor.sv.element;
51
+ const lineStart = element.value.lastIndexOf("\n", element.selectionStart - 1) + 1;
52
+ const lineEndIndex = element.value.indexOf("\n", element.selectionEnd);
53
+ const lineEnd = lineEndIndex === -1 ? element.value.length : lineEndIndex;
54
+ const selectedLines = element.value.substring(lineStart, lineEnd);
55
+ const heading = value ? value + selectedLines.replace(/^#{1,6}\s+/, "") : selectedLines.replace(/^#{1,6}\s+/, "");
56
+ element.setRangeText(heading, lineStart, lineEnd, "select");
57
+ element.focus();
58
+ processAfterRender(vditor);
151
59
  };
152
60
 
153
61
  export const processToolbar = (vditor: IVditor, actionBtn: Element, prefix: string, suffix: string) => {
154
- const range = getEditorRange(vditor);
62
+ const element = vditor.sv.element;
155
63
  const commandName = actionBtn.getAttribute("data-type");
156
- // 添加
157
- if (vditor.sv.element.childNodes.length === 0) {
158
- vditor.sv.element.innerHTML = `<span data-type="p" data-block="0"><span data-type="text"><wbr></span></span><span data-type="newline"><br><span style="display: none">
159
- </span></span>`;
160
- setRangeByWbr(vditor.sv.element, range);
161
- }
162
- const blockElement = hasClosestBlock(range.startContainer);
163
- const spanElement = hasClosestByTag(range.startContainer, "SPAN");
164
- if (!blockElement) {
165
- return;
166
- }
167
- if (commandName === "link") {
168
- let html;
169
- if (range.toString() === "") {
170
- html = `${prefix}${Lute.Caret}${suffix}`;
171
- } else {
172
- html = `${prefix}${range.toString()}${suffix.replace(")", Lute.Caret + ")")}`;
173
- }
174
- document.execCommand("insertHTML", false, html);
175
- return;
176
- } else if (commandName === "italic" || commandName === "bold" || commandName === "strike" ||
177
- commandName === "inline-code" || commandName === "code" || commandName === "table" || commandName === "line") {
178
- let html;
179
- // https://github.com/Vanessa219/vditor/issues/563 代码块不需要后面的 ```
180
- if (range.toString() === "") {
181
- html = `${prefix}${Lute.Caret}${commandName === "code" ? "" : suffix}`;
182
- } else {
183
- html = `${prefix}${range.toString()}${Lute.Caret}${commandName === "code" ? "" : suffix}`;
184
- }
185
- if (commandName === "table" || (commandName === "code" && spanElement && spanElement.textContent !== "")) {
186
- html = "\n\n" + html;
187
- } else if (commandName === "line") {
188
- html = `\n\n${prefix}\n${Lute.Caret}`;
189
- }
190
- document.execCommand("insertHTML", false, html);
191
- return;
192
- } else if (commandName === "check" || commandName === "list" || commandName === "ordered-list" ||
64
+ const start = element.selectionStart;
65
+ const end = element.selectionEnd;
66
+ const selectedText = element.value.substring(start, end);
67
+ let text = "";
68
+ let selectionStart = start + prefix.length;
69
+ let selectionEnd = selectionStart + selectedText.length;
70
+
71
+ if (commandName === "check" || commandName === "list" || commandName === "ordered-list" ||
193
72
  commandName === "quote") {
194
- if (spanElement) {
195
- let marker = "* ";
196
- if (commandName === "check") {
197
- marker = "* [ ] ";
198
- } else if (commandName === "ordered-list") {
199
- marker = "1. ";
200
- } else if (commandName === "quote") {
201
- marker = "> ";
202
- }
203
- const newLine = getSideByType(spanElement, "newline");
204
- if (newLine) {
205
- newLine.insertAdjacentText("afterend", marker);
206
- } else {
207
- blockElement.insertAdjacentText("afterbegin", marker);
208
- }
209
- inputEvent(vditor);
210
- return;
211
- }
73
+ const lineStart = element.value.lastIndexOf("\n", start - 1) + 1;
74
+ const lineEndIndex = element.value.indexOf("\n", end);
75
+ const lineEnd = lineEndIndex === -1 ? element.value.length : lineEndIndex;
76
+ const lines = element.value.substring(lineStart, lineEnd);
77
+ text = lines.split("\n").map((line) => prefix + line).join("\n");
78
+ element.setRangeText(text, lineStart, lineEnd, "select");
79
+ } else if (commandName === "line") {
80
+ text = `${start === 0 ? "" : "\n\n"}${prefix}\n`;
81
+ element.setRangeText(text, start, end, "end");
82
+ } else if (commandName === "code") {
83
+ text = `${prefix}\n${selectedText}${suffix}`;
84
+ element.setRangeText(text, start, end, "end");
85
+ selectionStart = start + prefix.length + 1;
86
+ selectionEnd = selectionStart + selectedText.length;
87
+ element.setSelectionRange(selectionStart, selectionEnd);
88
+ } else if (commandName === "table") {
89
+ text = `${start === 0 ? "" : "\n\n"}${prefix}${suffix}`;
90
+ element.setRangeText(text, start, end, "end");
91
+ } else {
92
+ text = `${prefix}${selectedText}${suffix}`;
93
+ element.setRangeText(text, start, end, "end");
94
+ element.setSelectionRange(selectionStart, selectionEnd);
212
95
  }
213
- setRangeByWbr(vditor.sv.element, range);
96
+
97
+ element.focus();
214
98
  processAfterRender(vditor);
215
99
  };
@@ -1,10 +1,5 @@
1
1
  import {isCtrl} from "../util/compatibility";
2
- import {fixTab} from "../util/fixBrowserBehavior";
3
- import {hasClosestByAttribute} from "../util/hasClosest";
4
- import {hasClosestByTag} from "../util/hasClosestByHeadings";
5
- import {getEditorRange, getSelectPosition} from "../util/selection";
6
- import {inputEvent} from "./inputEvent";
7
- import {processAfterRender, processPreviousMarkers} from "./process";
2
+ import {processAfterRender} from "./process";
8
3
 
9
4
  export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => {
10
5
  vditor.sv.composingLock = event.isComposing;
@@ -16,187 +11,25 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => {
16
11
  event.key !== "Shift" && event.key !== "CapsLock" && event.key !== "Escape" && !/^F\d{1,2}$/.test(event.key)) {
17
12
  vditor.undo.recordFirstPosition(vditor, event);
18
13
  }
19
- // 仅处理以下快捷键操作
20
- if (event.key !== "Enter" && event.key !== "Tab" && event.key !== "Backspace" && event.key.indexOf("Arrow") === -1
21
- && !isCtrl(event) && event.key !== "Escape") {
22
- return false;
23
- }
24
- const range = getEditorRange(vditor);
25
- let startContainer = range.startContainer;
26
- if (range.startContainer.nodeType !== 3 && (range.startContainer as HTMLElement).tagName === "DIV") {
27
- startContainer = range.startContainer.childNodes[range.startOffset - 1];
28
- }
29
- const textElement = hasClosestByAttribute(startContainer, "data-type", "text");
30
-
31
- // blockquote
32
- let blockquoteMarkerElement = hasClosestByAttribute(startContainer, "data-type", "blockquote-marker");
33
- if (!blockquoteMarkerElement && range.startOffset === 0 && textElement && textElement.previousElementSibling &&
34
- textElement.previousElementSibling.getAttribute("data-type") === "blockquote-marker") {
35
- blockquoteMarkerElement = textElement.previousElementSibling as HTMLElement;
36
- }
37
- // 回车逐个删除 blockquote marker 标记
38
- if (blockquoteMarkerElement) {
39
- if (event.key === "Enter" && !isCtrl(event) && !event.altKey &&
40
- blockquoteMarkerElement.nextElementSibling.textContent.trim() === "" &&
41
- getSelectPosition(blockquoteMarkerElement, vditor.sv.element, range).start ===
42
- blockquoteMarkerElement.textContent.length) {
43
- if (blockquoteMarkerElement.previousElementSibling?.getAttribute("data-type") === "padding") {
44
- // 列表中存在多行 BQ 时,标记回车需跳出列表
45
- blockquoteMarkerElement.previousElementSibling.setAttribute("data-action", "enter-remove");
46
- }
47
- blockquoteMarkerElement.remove();
48
- processAfterRender(vditor);
49
- event.preventDefault();
50
- return true;
51
- }
52
- }
53
14
 
54
- // list item
55
- const listMarkerElement = hasClosestByAttribute(startContainer, "data-type", "li-marker") as HTMLElement;
56
- const taskMarkerElement = hasClosestByAttribute(startContainer, "data-type", "task-marker") as HTMLElement;
57
- let listLastMarkerElement = listMarkerElement;
58
- if (!listLastMarkerElement) {
59
- if (taskMarkerElement && taskMarkerElement.nextElementSibling.getAttribute("data-type") !== "task-marker") {
60
- listLastMarkerElement = taskMarkerElement;
61
- }
62
- }
63
- if (!listLastMarkerElement && range.startOffset === 0 && textElement && textElement.previousElementSibling &&
64
- (textElement.previousElementSibling.getAttribute("data-type") === "li-marker" ||
65
- textElement.previousElementSibling.getAttribute("data-type") === "task-marker")) {
66
- listLastMarkerElement = textElement.previousElementSibling as HTMLElement;
67
- }
68
- if (listLastMarkerElement) {
69
- const startIndex = getSelectPosition(listLastMarkerElement, vditor.sv.element, range).start;
70
- const isTask = listLastMarkerElement.getAttribute("data-type") === "task-marker";
71
- let listFirstMarkerElement = listLastMarkerElement;
72
- if (isTask) {
73
- listFirstMarkerElement = listLastMarkerElement.previousElementSibling.previousElementSibling
74
- .previousElementSibling as HTMLElement;
75
- }
76
- if (startIndex === listLastMarkerElement.textContent.length) {
77
- // 回车清空列表标记符
78
- if (event.key === "Enter" && !isCtrl(event) && !event.altKey && !event.shiftKey &&
79
- listLastMarkerElement.nextElementSibling.textContent.trim() === "") {
80
- if (listFirstMarkerElement.previousElementSibling?.getAttribute("data-type") === "padding") {
81
- listFirstMarkerElement.previousElementSibling.remove();
82
- inputEvent(vditor);
83
- } else {
84
- if (isTask) {
85
- listFirstMarkerElement.remove();
86
- listLastMarkerElement.previousElementSibling.previousElementSibling.remove();
87
- listLastMarkerElement.previousElementSibling.remove();
88
- }
89
- listLastMarkerElement.nextElementSibling.remove();
90
- listLastMarkerElement.remove();
91
- processAfterRender(vditor);
92
- }
93
- event.preventDefault();
94
- return true;
95
- }
96
- // 第一个 marker 后 tab 进行缩进
97
- if (event.key === "Tab") {
98
- if (event.shiftKey) {
99
- if (listFirstMarkerElement.previousElementSibling.getAttribute("data-type") === "padding") {
100
- listFirstMarkerElement.previousElementSibling.remove();
101
- }
102
- } else {
103
- listFirstMarkerElement.insertAdjacentHTML("beforebegin",
104
- `<span data-type="padding">${listFirstMarkerElement.textContent.replace(/\S/g, " ")}</span>`);
105
- }
106
- if (/^\d/.test(listFirstMarkerElement.textContent)) {
107
- listFirstMarkerElement.textContent = listFirstMarkerElement.textContent.replace(/^\d{1,}/, "1");
108
- range.selectNodeContents(listLastMarkerElement.firstChild);
109
- range.collapse(false);
110
- }
111
- inputEvent(vditor);
112
- event.preventDefault();
113
- return true;
114
- }
115
- }
116
- }
117
-
118
- // tab
119
- if (fixTab(vditor, range, event)) {
120
- return true;
121
- }
122
-
123
- const blockElement = hasClosestByAttribute(startContainer, "data-block", "0");
124
- const spanElement = hasClosestByTag(startContainer, "SPAN");
125
- // 回车
126
- if (event.key === "Enter" && !isCtrl(event) && !event.altKey && !event.shiftKey && blockElement) {
127
- let isFirst = false;
128
- const newLineMatch = blockElement.textContent.match(/^\n+/);
129
- if (getSelectPosition(blockElement, vditor.sv.element).start <= (newLineMatch ? newLineMatch[0].length : 0)) {
130
- // 允许段落开始换行
131
- isFirst = true;
132
- }
133
- let newLineText = "\n";
134
- if (spanElement) {
135
- if (spanElement.previousElementSibling?.getAttribute("data-action") === "enter-remove") {
136
- // https://github.com/Vanessa219/vditor/issues/596
137
- spanElement.previousElementSibling.remove();
138
- processAfterRender(vditor);
139
- event.preventDefault();
140
- return true;
141
- } else {
142
- newLineText += processPreviousMarkers(spanElement);
143
- }
144
- }
145
- range.insertNode(document.createTextNode(newLineText));
146
- range.collapse(false);
147
- if (blockElement && blockElement.textContent.trim() !== "" && !isFirst) {
148
- inputEvent(vditor);
149
- } else {
150
- processAfterRender(vditor);
151
- }
152
- event.preventDefault();
153
- return true;
15
+ if (event.key !== "Tab" || isCtrl(event) || event.altKey) {
16
+ return false;
154
17
  }
155
18
 
156
- // 删除后光标前有 newline 的处理
157
- if (event.key === "Backspace" && !isCtrl(event) && !event.altKey && !event.shiftKey) {
158
- if (spanElement && spanElement.previousElementSibling?.getAttribute("data-type") === "newline" &&
159
- getSelectPosition(spanElement, vditor.sv.element, range).start === 1 &&
160
- // 飘号的处理需在 inputEvent 中,否则上下飘号对不齐
161
- spanElement.getAttribute("data-type").indexOf("code-block-") === -1) {
162
- // 光标在每一行的第一个字符后
163
- range.setStart(spanElement, 0);
164
- range.extractContents();
165
- if (spanElement.textContent.trim() !== "") {
166
- inputEvent(vditor);
167
- } else {
168
- processAfterRender(vditor);
169
- }
170
- event.preventDefault();
171
- return true;
172
- }
173
- // 每一段第一个字符前
174
- if (blockElement && getSelectPosition(blockElement, vditor.sv.element, range).start === 0 &&
175
- blockElement.previousElementSibling) {
176
- range.extractContents();
177
- let previousLastElement = blockElement.previousElementSibling.lastElementChild;
178
- if (previousLastElement.getAttribute("data-type") === "newline") {
179
- previousLastElement.remove();
180
- previousLastElement = blockElement.previousElementSibling.lastElementChild;
181
- }
182
- // 场景:末尾无法删除 [```\ntext\n```\n\n]
183
- if (previousLastElement.getAttribute("data-type") !== "newline") {
184
- previousLastElement.insertAdjacentHTML("afterend", blockElement.innerHTML);
185
- blockElement.remove();
186
- }
187
- if (blockElement.textContent.trim() !== "" && !blockElement.previousElementSibling?.querySelector('[data-type="code-block-open-marker"]')) {
188
- inputEvent(vditor);
189
- } else {
190
- if (previousLastElement.getAttribute("data-type") !== "newline") {
191
- // https://github.com/Vanessa219/vditor/issues/597
192
- range.selectNodeContents(previousLastElement.lastChild);
193
- range.collapse(false);
194
- }
195
- processAfterRender(vditor);
196
- }
197
- event.preventDefault();
198
- return true;
199
- }
200
- }
201
- return false;
19
+ const element = vditor.sv.element;
20
+ const start = element.selectionStart;
21
+ const end = element.selectionEnd;
22
+ if (event.shiftKey) {
23
+ const lineStart = element.value.lastIndexOf("\n", start - 1) + 1;
24
+ const indentation = element.value.substring(lineStart, start).match(/^(\t| {1,4})/);
25
+ if (indentation) {
26
+ element.setRangeText("", lineStart, lineStart + indentation[0].length, "end");
27
+ element.setSelectionRange(start - indentation[0].length, end - indentation[0].length);
28
+ }
29
+ } else {
30
+ element.setRangeText(vditor.options.tab, start, end, "end");
31
+ }
32
+ processAfterRender(vditor);
33
+ event.preventDefault();
34
+ return true;
202
35
  };
@@ -2,7 +2,7 @@ import {Constants} from "../constants";
2
2
  import {processAfterRender} from "../ir/process";
3
3
  import {getMarkdown} from "../markdown/getMarkdown";
4
4
  import {mathRender} from "../markdown/mathRender";
5
- import {processAfterRender as processSVAfterRender, processSpinVditorSVDOM} from "../sv/process";
5
+ import {processAfterRender as processSVAfterRender} from "../sv/process";
6
6
  import {setPadding, setTypewriterPosition} from "../ui/initUI";
7
7
  import {getEventName, updateHotkeyTip} from "../util/compatibility";
8
8
  import {highlightToolbar} from "../util/highlightToolbar";
@@ -18,7 +18,6 @@ import {
18
18
  removeCurrentToolbar,
19
19
  showToolbar, toggleSubMenu,
20
20
  } from "./setToolbar";
21
- import {combineFootnote} from "../sv/combineFootnote";
22
21
 
23
22
  export const setEditMode = (vditor: IVditor, type: string, event: Event | string) => {
24
23
  let markdownText;
@@ -114,16 +113,10 @@ export const setEditMode = (vditor: IVditor, type: string, event: Event | string
114
113
 
115
114
  vditor.lute.SetVditorIR(false);
116
115
  vditor.lute.SetVditorWYSIWYG(false);
117
- vditor.lute.SetVditorSV(true);
116
+ vditor.lute.SetVditorSV(false);
118
117
 
119
118
  vditor.currentMode = "sv";
120
- let svHTML = processSpinVditorSVDOM(markdownText, vditor);
121
- if (svHTML === "<div data-block='0'></div>") {
122
- // https://github.com/Vanessa219/vditor/issues/654 SV 模式 Placeholder 显示问题
123
- svHTML = "";
124
- }
125
- vditor.sv.element.innerHTML = svHTML;
126
- combineFootnote(vditor.sv.element)
119
+ vditor.sv.element.value = markdownText;
127
120
  processSVAfterRender(vditor, {
128
121
  enableAddUndoStack: true,
129
122
  enableHint: false,
@@ -6,6 +6,7 @@ import {MenuItem} from "./MenuItem";
6
6
  import {toggleSubMenu} from "./setToolbar";
7
7
  import {hasClosestBlock} from "../util/hasClosest";
8
8
  import {modifyPre} from "../wysiwyg/inlineTag";
9
+ import {processPaste} from "../sv/process";
9
10
 
10
11
  export class Emoji extends MenuItem {
11
12
  public element: HTMLElement;
@@ -44,6 +45,12 @@ data-value=":${key}: " data-key=":${key}:" class="vditor-emojis__icon" src="${em
44
45
  if (btnElement) {
45
46
  event.preventDefault();
46
47
  const value = btnElement.getAttribute("data-value");
48
+ if (vditor.currentMode === "sv") {
49
+ processPaste(vditor, value);
50
+ (this.element.lastElementChild as HTMLElement).style.display = "none";
51
+ execAfterRender(vditor);
52
+ return;
53
+ }
47
54
  const range = getEditorRange(vditor);
48
55
  let html = value;
49
56
  if (vditor.currentMode === "wysiwyg") {
@@ -51,7 +58,7 @@ data-value=":${key}: " data-key=":${key}:" class="vditor-emojis__icon" src="${em
51
58
  } else if (vditor.currentMode === "ir") {
52
59
  html = vditor.lute.SpinVditorIRDOM(value);
53
60
  }
54
- if (value.indexOf(":") > -1 && vditor.currentMode !== "sv") {
61
+ if (value.indexOf(":") > -1) {
55
62
  const tempElement = document.createElement("div");
56
63
  tempElement.innerHTML = html;
57
64
  html = tempElement.firstElementChild.firstElementChild.outerHTML + " ";
@@ -34,7 +34,11 @@ export class Record extends MenuItem {
34
34
  };
35
35
  mediaRecorder.startRecordingNewWavFile();
36
36
  vditor.tip.show(window.VditorI18n.recording);
37
- editorElement.setAttribute("contenteditable", "false");
37
+ if (vditor.currentMode === "sv") {
38
+ vditor.sv.element.disabled = true;
39
+ } else {
40
+ editorElement.setAttribute("contenteditable", "false");
41
+ }
38
42
  this.element.children[0].classList.add("vditor-menu--current");
39
43
  }).catch(() => {
40
44
  vditor.tip.show(window.VditorI18n["record-tip"]);
@@ -51,7 +55,11 @@ export class Record extends MenuItem {
51
55
  this.element.children[0].classList.remove("vditor-menu--current");
52
56
  } else {
53
57
  vditor.tip.show(window.VditorI18n.recording);
54
- editorElement.setAttribute("contenteditable", "false");
58
+ if (vditor.currentMode === "sv") {
59
+ vditor.sv.element.disabled = true;
60
+ } else {
61
+ editorElement.setAttribute("contenteditable", "false");
62
+ }
55
63
  mediaRecorder.startRecordingNewWavFile();
56
64
  this.element.children[0].classList.add("vditor-menu--current");
57
65
  }
@@ -181,6 +181,9 @@ export class Toolbar {
181
181
  if (!menuItemObj) {
182
182
  return;
183
183
  }
184
+ menuItemObj.element.querySelectorAll("button").forEach((button) => {
185
+ button.setAttribute("type", "button");
186
+ });
184
187
  let key = menuItem.name;
185
188
  if (key === "br" || key === "|") {
186
189
  key = key + index;
@@ -146,9 +146,13 @@ export const setTypewriterPosition = (vditor: IVditor) => {
146
146
  if (vditor.element.classList.contains("vditor--fullscreen")) {
147
147
  height = window.innerHeight;
148
148
  }
149
- // 由于 Firefox padding-bottom bug,只能使用 :after
150
- vditor[vditor.currentMode].element.style.setProperty("--editor-bottom",
151
- ((height - vditor.toolbar.element.offsetHeight) / 2) + "px");
149
+ const editorBottom = ((height - vditor.toolbar.element.offsetHeight) / 2) + "px";
150
+ if (vditor.currentMode === "sv") {
151
+ vditor.sv.element.style.paddingBottom = editorBottom;
152
+ } else {
153
+ // 由于 Firefox padding-bottom bug,只能使用 :after
154
+ vditor[vditor.currentMode].element.style.setProperty("--editor-bottom", editorBottom);
155
+ }
152
156
  };
153
157
 
154
158
  let resizeCb: () => void;