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.
- package/README.md +12 -1
- package/dist/css/content-theme/ant-design.css +10 -0
- package/dist/css/content-theme/dark.css +10 -0
- package/dist/css/content-theme/light.css +13 -0
- package/dist/css/content-theme/wechat.css +10 -0
- package/dist/index.css +163 -81
- package/dist/index.js +3035 -2715
- package/dist/index.min.js +1 -1
- package/dist/js/i18n/ja_JP.js +2 -2
- package/dist/js/i18n/zh_TW.js +8 -8
- package/dist/js/lute/lute.min.js +75 -60
- package/dist/js/mermaid/mermaid.min.js +2027 -1047
- package/dist/js/wavedrom/LICENSE.WaveDrom +21 -0
- package/dist/js/wavedrom/wavedrom.min.js +9 -0
- package/dist/method.d.ts +2 -0
- package/dist/method.js +323 -9
- package/dist/method.min.js +1 -1
- package/dist/ts/constants.d.ts +4 -0
- package/dist/ts/markdown/adapterRender.d.ts +4 -0
- package/dist/ts/markdown/imageCaptionRender.d.ts +2 -0
- package/dist/ts/markdown/wavedromRender.d.ts +1 -0
- package/dist/ts/sv/index.d.ts +1 -5
- package/dist/ts/sv/process.d.ts +1 -3
- package/dist/ts/upload/getElement.d.ts +1 -1
- package/dist/ts/upload/index.d.ts +2 -0
- package/dist/ts/util/editorCommonEvent.d.ts +1 -1
- package/dist/ts/util/fixBrowserBehavior.d.ts +1 -0
- package/dist/types/index.d.ts +22 -5
- package/package.json +7 -4
- package/src/assets/less/_reset.less +103 -2
- package/src/assets/less/_sv.less +2 -98
- package/src/assets/less/_wysiwyg.less +30 -0
- package/src/assets/less/index.less +20 -0
- package/src/index.ts +48 -14
- package/src/method.ts +3 -0
- package/src/ts/constants.ts +5 -1
- package/src/ts/export/index.ts +2 -0
- package/src/ts/hint/index.ts +9 -4
- package/src/ts/ir/highlightToolbarIR.ts +4 -4
- package/src/ts/ir/index.ts +26 -0
- package/src/ts/ir/process.ts +24 -1
- package/src/ts/markdown/adapterRender.ts +4 -0
- package/src/ts/markdown/getHTML.ts +6 -3
- package/src/ts/markdown/getMarkdown.ts +3 -3
- package/src/ts/markdown/imageCaptionRender.ts +175 -0
- package/src/ts/markdown/mermaidRender.ts +1 -1
- package/src/ts/markdown/previewRender.ts +7 -1
- package/src/ts/markdown/setLute.ts +3 -0
- package/src/ts/markdown/wavedromRender.ts +54 -0
- package/src/ts/preview/index.ts +5 -0
- package/src/ts/sv/index.ts +37 -76
- package/src/ts/sv/process.ts +48 -164
- package/src/ts/sv/processKeydown.ts +19 -186
- package/src/ts/toolbar/EditMode.ts +3 -10
- package/src/ts/toolbar/Emoji.ts +8 -1
- package/src/ts/toolbar/Record.ts +10 -2
- package/src/ts/toolbar/index.ts +3 -0
- package/src/ts/ui/initUI.ts +7 -3
- package/src/ts/undo/index.ts +32 -17
- package/src/ts/upload/index.ts +42 -10
- package/src/ts/util/Options.ts +1 -1
- package/src/ts/util/editorCommonEvent.ts +18 -9
- package/src/ts/util/fixBrowserBehavior.ts +197 -51
- package/src/ts/util/processCode.ts +3 -0
- package/src/ts/util/selection.ts +11 -3
- package/src/ts/wysiwyg/afterRenderEvent.ts +2 -0
- package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +58 -2
- package/src/ts/wysiwyg/input.ts +5 -0
- package/src/ts/wysiwyg/processKeydown.ts +47 -0
- package/src/ts/wysiwyg/setHeading.ts +110 -6
- package/dist/ts/sv/combineFootnote.d.ts +0 -6
- package/dist/ts/sv/inputEvent.d.ts +0 -1
- package/src/ts/sv/combineFootnote.ts +0 -27
- package/src/ts/sv/inputEvent.ts +0 -188
package/src/ts/undo/index.ts
CHANGED
|
@@ -52,7 +52,8 @@ class Undo {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
public undo(vditor: IVditor) {
|
|
55
|
-
if (vditor
|
|
55
|
+
if (vditor.currentMode === "sv" ? vditor.sv.element.disabled :
|
|
56
|
+
vditor[vditor.currentMode].element.getAttribute("contenteditable") === "false") {
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
if (this[vditor.currentMode].undoStack.length < 2) {
|
|
@@ -70,7 +71,8 @@ class Undo {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
public redo(vditor: IVditor) {
|
|
73
|
-
if (vditor
|
|
74
|
+
if (vditor.currentMode === "sv" ? vditor.sv.element.disabled :
|
|
75
|
+
vditor[vditor.currentMode].element.getAttribute("contenteditable") === "false") {
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
const state = this[vditor.currentMode].redoStack.pop();
|
|
@@ -82,7 +84,7 @@ class Undo {
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
public recordFirstPosition(vditor: IVditor, event: KeyboardEvent) {
|
|
85
|
-
if (getSelection().rangeCount === 0) {
|
|
87
|
+
if (vditor.currentMode !== "sv" && getSelection().rangeCount === 0) {
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
90
|
if (this[vditor.currentMode].undoStack.length !== 1 || this[vditor.currentMode].undoStack[0].length === 0 ||
|
|
@@ -147,8 +149,15 @@ class Undo {
|
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
this[vditor.currentMode].lastText = text;
|
|
150
|
-
vditor
|
|
151
|
-
|
|
152
|
+
if (vditor.currentMode === "sv") {
|
|
153
|
+
const caretIndex = text.indexOf("<wbr>");
|
|
154
|
+
vditor.sv.element.value = text.replace("<wbr>", "");
|
|
155
|
+
vditor.sv.element.focus();
|
|
156
|
+
if (caretIndex > -1) {
|
|
157
|
+
vditor.sv.element.setSelectionRange(caretIndex, caretIndex);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
vditor[vditor.currentMode].element.innerHTML = text;
|
|
152
161
|
vditor[vditor.currentMode].element.querySelectorAll(`.vditor-${vditor.currentMode}__preview`)
|
|
153
162
|
.forEach((blockElement: HTMLElement) => {
|
|
154
163
|
if (blockElement.parentElement.querySelector(".language-echarts")) {
|
|
@@ -163,17 +172,16 @@ class Undo {
|
|
|
163
172
|
.forEach((blockElement: HTMLElement) => {
|
|
164
173
|
processCodeRender(blockElement, vditor);
|
|
165
174
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
scrollCenter(vditor);
|
|
175
|
+
if (!vditor[vditor.currentMode].element.querySelector("wbr")) {
|
|
176
|
+
// Safari 第一次输入没有光标,需手动定位到结尾
|
|
177
|
+
const range = getSelection().getRangeAt(0);
|
|
178
|
+
range.setEndBefore(vditor[vditor.currentMode].element);
|
|
179
|
+
range.collapse(false);
|
|
180
|
+
} else {
|
|
181
|
+
setRangeByWbr(
|
|
182
|
+
vditor[vditor.currentMode].element, vditor[vditor.currentMode].element.ownerDocument.createRange());
|
|
183
|
+
scrollCenter(vditor);
|
|
184
|
+
}
|
|
177
185
|
}
|
|
178
186
|
|
|
179
187
|
renderToc(vditor);
|
|
@@ -183,7 +191,9 @@ class Undo {
|
|
|
183
191
|
enableHint: false,
|
|
184
192
|
enableInput: true,
|
|
185
193
|
});
|
|
186
|
-
|
|
194
|
+
if (vditor.currentMode !== "sv") {
|
|
195
|
+
highlightToolbar(vditor);
|
|
196
|
+
}
|
|
187
197
|
|
|
188
198
|
vditor[vditor.currentMode].element.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`)
|
|
189
199
|
.forEach((item: HTMLElement) => {
|
|
@@ -225,6 +235,11 @@ class Undo {
|
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
private addCaret(vditor: IVditor, setFocus = false) {
|
|
238
|
+
if (vditor.currentMode === "sv") {
|
|
239
|
+
const element = vditor.sv.element;
|
|
240
|
+
return element.value.substring(0, element.selectionStart) + "<wbr>" +
|
|
241
|
+
element.value.substring(element.selectionStart);
|
|
242
|
+
}
|
|
228
243
|
let cloneRange: Range;
|
|
229
244
|
if (getSelection().rangeCount !== 0 && !vditor[vditor.currentMode].element.querySelector("wbr")) {
|
|
230
245
|
const range = getSelection().getRangeAt(0);
|
package/src/ts/upload/index.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import {getEditorRange, setSelectionFocus} from "../util/selection";
|
|
2
|
+
import {processAfterRender as processSVAfterRender, processPaste} from "../sv/process";
|
|
3
|
+
import {getElement} from "./getElement";
|
|
4
|
+
import {setHeaders} from "./setHeaders";
|
|
4
5
|
|
|
5
6
|
class Upload {
|
|
6
7
|
public element: HTMLElement;
|
|
7
8
|
public isUploading: boolean;
|
|
8
9
|
public range: Range;
|
|
10
|
+
public selectionEnd: number;
|
|
11
|
+
public selectionStart: number;
|
|
9
12
|
|
|
10
13
|
constructor() {
|
|
11
14
|
this.isUploading = false;
|
|
@@ -146,9 +149,25 @@ const genUploadedLabel = (responseText: string, vditor: IVditor) => {
|
|
|
146
149
|
}
|
|
147
150
|
}
|
|
148
151
|
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
if (!succFileText) {
|
|
153
|
+
if (vditor.currentMode === "sv") {
|
|
154
|
+
vditor.sv.element.setSelectionRange(vditor.upload.selectionStart, vditor.upload.selectionEnd);
|
|
155
|
+
} else {
|
|
156
|
+
setSelectionFocus(vditor.upload.range);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (vditor.currentMode === "sv") {
|
|
161
|
+
vditor.sv.element.setSelectionRange(vditor.upload.selectionStart, vditor.upload.selectionEnd);
|
|
162
|
+
processPaste(vditor, succFileText);
|
|
163
|
+
processSVAfterRender(vditor);
|
|
164
|
+
vditor.upload.selectionStart = vditor.sv.element.selectionStart;
|
|
165
|
+
vditor.upload.selectionEnd = vditor.sv.element.selectionEnd;
|
|
166
|
+
} else {
|
|
167
|
+
setSelectionFocus(vditor.upload.range);
|
|
168
|
+
document.execCommand("insertHTML", false, succFileText);
|
|
169
|
+
vditor.upload.range = getSelection().getRangeAt(0).cloneRange();
|
|
170
|
+
}
|
|
152
171
|
};
|
|
153
172
|
|
|
154
173
|
const uploadFiles =
|
|
@@ -197,7 +216,12 @@ const uploadFiles =
|
|
|
197
216
|
}
|
|
198
217
|
const editorElement = getElement(vditor);
|
|
199
218
|
|
|
200
|
-
vditor.
|
|
219
|
+
if (vditor.currentMode === "sv") {
|
|
220
|
+
vditor.upload.selectionStart = vditor.sv.element.selectionStart;
|
|
221
|
+
vditor.upload.selectionEnd = vditor.sv.element.selectionEnd;
|
|
222
|
+
} else {
|
|
223
|
+
vditor.upload.range = getEditorRange(vditor);
|
|
224
|
+
}
|
|
201
225
|
|
|
202
226
|
const validateResult = validateFile(vditor, fileList);
|
|
203
227
|
if (validateResult.length === 0) {
|
|
@@ -229,11 +253,19 @@ const uploadFiles =
|
|
|
229
253
|
}
|
|
230
254
|
setHeaders(vditor, xhr);
|
|
231
255
|
vditor.upload.isUploading = true;
|
|
232
|
-
|
|
256
|
+
if (vditor.currentMode === "sv") {
|
|
257
|
+
vditor.sv.element.disabled = true;
|
|
258
|
+
} else {
|
|
259
|
+
editorElement.setAttribute("contenteditable", "false");
|
|
260
|
+
}
|
|
233
261
|
xhr.onreadystatechange = () => {
|
|
234
262
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
235
263
|
vditor.upload.isUploading = false;
|
|
236
|
-
|
|
264
|
+
if (vditor.currentMode === "sv") {
|
|
265
|
+
vditor.sv.element.disabled = false;
|
|
266
|
+
} else {
|
|
267
|
+
editorElement.setAttribute("contenteditable", "true");
|
|
268
|
+
}
|
|
237
269
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
238
270
|
if (vditor.options.upload.success) {
|
|
239
271
|
vditor.options.upload.success(editorElement, xhr.responseText);
|
|
@@ -270,4 +302,4 @@ const uploadFiles =
|
|
|
270
302
|
xhr.send(formData);
|
|
271
303
|
};
|
|
272
304
|
|
|
273
|
-
export {
|
|
305
|
+
export {Upload, uploadFiles};
|
package/src/ts/util/Options.ts
CHANGED
|
@@ -45,18 +45,22 @@ export const blurEvent = (vditor: IVditor, editorElement: HTMLElement) => {
|
|
|
45
45
|
if (expandElement) {
|
|
46
46
|
expandElement.classList.remove("vditor-ir__node--expand");
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
vditor.wysiwyg.
|
|
48
|
+
vditor.ir.range = getEditorRange(vditor);
|
|
49
|
+
} else if (vditor.currentMode === "wysiwyg") {
|
|
50
|
+
if (!vditor.wysiwyg.selectPopover.contains(event.relatedTarget as HTMLElement)) {
|
|
51
|
+
vditor.wysiwyg.hideComment();
|
|
52
|
+
}
|
|
53
|
+
vditor.wysiwyg.range = getEditorRange(vditor);
|
|
51
54
|
}
|
|
52
|
-
vditor[vditor.currentMode].range = getEditorRange(vditor);
|
|
53
55
|
if (vditor.options.blur) {
|
|
54
56
|
vditor.options.blur(getMarkdown(vditor));
|
|
55
57
|
}
|
|
56
58
|
});
|
|
57
59
|
};
|
|
58
60
|
|
|
59
|
-
export const dropEvent = (vditor: IVditor, editorElement: HTMLElement) => {
|
|
61
|
+
export const dropEvent = (vditor: IVditor, editorElement: HTMLElement, pasteCode = (code: string) => {
|
|
62
|
+
document.execCommand("insertHTML", false, code);
|
|
63
|
+
}) => {
|
|
60
64
|
editorElement.addEventListener("dragstart", (event) => {
|
|
61
65
|
// 选中编辑器中的文字进行拖拽
|
|
62
66
|
event.dataTransfer.setData(Constants.DROP_EDITOR, Constants.DROP_EDITOR);
|
|
@@ -69,9 +73,7 @@ export const dropEvent = (vditor: IVditor, editorElement: HTMLElement) => {
|
|
|
69
73
|
} else if (event.dataTransfer.types.includes("Files") || event.dataTransfer.types.includes("text/html")) {
|
|
70
74
|
// 外部文件拖入编辑器中或者编辑器内选中文字拖拽
|
|
71
75
|
paste(vditor, event, {
|
|
72
|
-
pasteCode
|
|
73
|
-
document.execCommand("insertHTML", false, code);
|
|
74
|
-
},
|
|
76
|
+
pasteCode,
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
79
|
});
|
|
@@ -102,7 +104,14 @@ export const scrollCenter = (vditor: IVditor) => {
|
|
|
102
104
|
return;
|
|
103
105
|
}
|
|
104
106
|
const editorElement = vditor[vditor.currentMode].element;
|
|
105
|
-
|
|
107
|
+
let cursorTop: number;
|
|
108
|
+
if (vditor.currentMode === "sv") {
|
|
109
|
+
const lineHeight = parseInt(getComputedStyle(vditor.sv.element).lineHeight, 10) || 22;
|
|
110
|
+
cursorTop = (vditor.sv.element.value.substring(0, vditor.sv.element.selectionStart).split("\n").length - 1) *
|
|
111
|
+
lineHeight;
|
|
112
|
+
} else {
|
|
113
|
+
cursorTop = getCursorPosition(editorElement).top;
|
|
114
|
+
}
|
|
106
115
|
if (vditor.options.height === "auto" && !vditor.element.classList.contains("vditor--fullscreen")) {
|
|
107
116
|
window.scrollTo(window.scrollX,
|
|
108
117
|
cursorTop + vditor.element.offsetTop + vditor.toolbar.element.offsetHeight - window.innerHeight / 2 + 10);
|
|
@@ -79,6 +79,21 @@ export const fixCursorDownInlineMath = (range: Range, key: string) => {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
export const insertEmptyBlock = (vditor: IVditor, position: InsertPosition) => {
|
|
82
|
+
if (vditor.currentMode === "sv") {
|
|
83
|
+
const element = vditor.sv.element;
|
|
84
|
+
if (position === "beforebegin") {
|
|
85
|
+
const lineStart = element.value.lastIndexOf("\n", element.selectionStart - 1) + 1;
|
|
86
|
+
element.setRangeText("\n", lineStart, lineStart, "end");
|
|
87
|
+
element.setSelectionRange(lineStart, lineStart);
|
|
88
|
+
} else {
|
|
89
|
+
const lineEndIndex = element.value.indexOf("\n", element.selectionEnd);
|
|
90
|
+
const lineEnd = lineEndIndex === -1 ? element.value.length : lineEndIndex;
|
|
91
|
+
element.setRangeText("\n", lineEnd, lineEnd, "end");
|
|
92
|
+
}
|
|
93
|
+
element.focus();
|
|
94
|
+
processSVAfterRender(vditor);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
82
97
|
const range = getEditorRange(vditor);
|
|
83
98
|
const blockElement = hasClosestBlock(range.startContainer);
|
|
84
99
|
if (blockElement) {
|
|
@@ -177,10 +192,40 @@ export const insertBeforeBlock = (vditor: IVditor, event: KeyboardEvent, range:
|
|
|
177
192
|
|
|
178
193
|
export const listToggle = (vditor: IVditor, range: Range, type: string, cancel = true) => {
|
|
179
194
|
const itemElement = hasClosestByMatchTag(range.startContainer, "LI");
|
|
195
|
+
const selectedBlockElements: HTMLElement[] = [];
|
|
196
|
+
const startBlockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
|
|
197
|
+
const endBlockElement = hasClosestByAttribute(range.endContainer, "data-block", "0");
|
|
198
|
+
if (startBlockElement && endBlockElement &&
|
|
199
|
+
startBlockElement.parentElement.isSameNode(endBlockElement.parentElement)) {
|
|
200
|
+
let blockElement: Element = startBlockElement;
|
|
201
|
+
while (blockElement) {
|
|
202
|
+
selectedBlockElements.push(blockElement as HTMLElement);
|
|
203
|
+
if (blockElement.isSameNode(endBlockElement)) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
blockElement = blockElement.nextElementSibling;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
180
209
|
vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
|
|
181
210
|
wbr.remove();
|
|
182
211
|
});
|
|
183
|
-
range.
|
|
212
|
+
const keepSelection = !range.collapsed;
|
|
213
|
+
if (keepSelection) {
|
|
214
|
+
// 列表转换会重建块元素,分别标记选区起止位置以便恢复原始选区
|
|
215
|
+
const endRange = range.cloneRange();
|
|
216
|
+
endRange.collapse(false);
|
|
217
|
+
const endElement = document.createElement("wbr");
|
|
218
|
+
endElement.setAttribute("data-type", "list-selection-end");
|
|
219
|
+
endRange.insertNode(endElement);
|
|
220
|
+
|
|
221
|
+
const startRange = range.cloneRange();
|
|
222
|
+
startRange.collapse(true);
|
|
223
|
+
const startElement = document.createElement("wbr");
|
|
224
|
+
startElement.setAttribute("data-type", "list-selection-start");
|
|
225
|
+
startRange.insertNode(startElement);
|
|
226
|
+
} else {
|
|
227
|
+
range.insertNode(document.createElement("wbr"));
|
|
228
|
+
}
|
|
184
229
|
|
|
185
230
|
if (cancel && itemElement) {
|
|
186
231
|
// 取消
|
|
@@ -197,25 +242,24 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
|
|
|
197
242
|
} else {
|
|
198
243
|
if (!itemElement) {
|
|
199
244
|
// 添加
|
|
200
|
-
|
|
201
|
-
if (!blockElement) {
|
|
245
|
+
if (selectedBlockElements.length === 0) {
|
|
202
246
|
vditor[vditor.currentMode].element.querySelector("wbr").remove();
|
|
203
|
-
blockElement = vditor[vditor.currentMode].element.querySelector("p");
|
|
247
|
+
const blockElement = vditor[vditor.currentMode].element.querySelector("p");
|
|
204
248
|
blockElement.innerHTML = "<wbr>";
|
|
249
|
+
selectedBlockElements.push(blockElement);
|
|
205
250
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
251
|
+
let listHTML = "";
|
|
252
|
+
selectedBlockElements.forEach((blockElement) => {
|
|
253
|
+
if (type === "check") {
|
|
254
|
+
listHTML += `<li class="vditor-task"><input type="checkbox" /> ${blockElement.innerHTML}</li>`;
|
|
255
|
+
} else {
|
|
256
|
+
listHTML += `<li>${blockElement.innerHTML}</li>`;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const listTagName = type === "ordered-list" ? "ol" : "ul";
|
|
260
|
+
selectedBlockElements[0].insertAdjacentHTML("beforebegin",
|
|
261
|
+
`<${listTagName} data-block="0">${listHTML}</${listTagName}>`);
|
|
262
|
+
selectedBlockElements.forEach((blockElement) => blockElement.remove());
|
|
219
263
|
} else {
|
|
220
264
|
// 切换
|
|
221
265
|
if (type === "check") {
|
|
@@ -227,7 +271,14 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
|
|
|
227
271
|
} else {
|
|
228
272
|
if (itemElement.querySelector("input")) {
|
|
229
273
|
itemElement.parentElement.querySelectorAll("li").forEach((item) => {
|
|
230
|
-
item.querySelector("input")
|
|
274
|
+
const inputElement = item.querySelector("input");
|
|
275
|
+
if (inputElement) {
|
|
276
|
+
const nextSibling = inputElement.nextSibling;
|
|
277
|
+
inputElement.remove();
|
|
278
|
+
if (nextSibling && nextSibling.nodeType === 3 && nextSibling.textContent.startsWith(" ")) {
|
|
279
|
+
nextSibling.textContent = nextSibling.textContent.substring(1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
231
282
|
item.classList.remove("vditor-task");
|
|
232
283
|
});
|
|
233
284
|
}
|
|
@@ -246,6 +297,18 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
|
|
|
246
297
|
}
|
|
247
298
|
}
|
|
248
299
|
}
|
|
300
|
+
if (keepSelection) {
|
|
301
|
+
const editorElement = vditor[vditor.currentMode].element;
|
|
302
|
+
const startElement = editorElement.querySelector('[data-type="list-selection-start"]');
|
|
303
|
+
const endElement = editorElement.querySelector('[data-type="list-selection-end"]');
|
|
304
|
+
if (startElement && endElement) {
|
|
305
|
+
range.setStartAfter(startElement);
|
|
306
|
+
range.setEndBefore(endElement);
|
|
307
|
+
startElement.remove();
|
|
308
|
+
endElement.remove();
|
|
309
|
+
setSelectionFocus(range);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
249
312
|
};
|
|
250
313
|
|
|
251
314
|
export const listIndent = (vditor: IVditor, liElement: HTMLElement, range: Range) => {
|
|
@@ -453,6 +516,41 @@ export const execAfterRender = (vditor: IVditor, options = {
|
|
|
453
516
|
}
|
|
454
517
|
};
|
|
455
518
|
|
|
519
|
+
const isEmptyListItem = (liElement: HTMLElement) => {
|
|
520
|
+
if (liElement.textContent.split(Constants.ZWSP).join("").trim() !== "") {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
return !liElement.querySelector([
|
|
524
|
+
"audio", "canvas", "embed", "hr", "iframe", "img", "object", "ol", "pre", "svg", "table", "ul", "video",
|
|
525
|
+
"input:not([type='checkbox'])",
|
|
526
|
+
].join(", "));
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
export const exitEmptyListItem = (liElement: HTMLElement) => {
|
|
530
|
+
const listElement = liElement.parentElement;
|
|
531
|
+
const listParent = listElement.parentElement;
|
|
532
|
+
const paragraphElement = document.createElement("p");
|
|
533
|
+
paragraphElement.setAttribute("data-block", "0");
|
|
534
|
+
paragraphElement.innerHTML = "<wbr>";
|
|
535
|
+
|
|
536
|
+
let trailingListElement: HTMLElement | undefined;
|
|
537
|
+
if (liElement.nextElementSibling) {
|
|
538
|
+
trailingListElement = listElement.cloneNode(false) as HTMLElement;
|
|
539
|
+
while (liElement.nextElementSibling) {
|
|
540
|
+
trailingListElement.appendChild(liElement.nextElementSibling);
|
|
541
|
+
}
|
|
542
|
+
listParent.insertBefore(trailingListElement, listElement.nextSibling);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
liElement.remove();
|
|
546
|
+
if (listElement.childElementCount === 0) {
|
|
547
|
+
listParent.replaceChild(paragraphElement, listElement);
|
|
548
|
+
} else {
|
|
549
|
+
listParent.insertBefore(paragraphElement, trailingListElement || listElement.nextSibling);
|
|
550
|
+
}
|
|
551
|
+
return paragraphElement;
|
|
552
|
+
};
|
|
553
|
+
|
|
456
554
|
export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | false, event: KeyboardEvent) => {
|
|
457
555
|
const startContainer = range.startContainer;
|
|
458
556
|
const liElement = hasClosestByMatchTag(startContainer, "LI");
|
|
@@ -471,6 +569,28 @@ export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | f
|
|
|
471
569
|
return true;
|
|
472
570
|
}
|
|
473
571
|
|
|
572
|
+
// 嵌套列表末尾的空列表项回车后转为父列表项中的段落
|
|
573
|
+
// https://github.com/Vanessa219/vditor/issues/939
|
|
574
|
+
if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Enter" && range.collapsed &&
|
|
575
|
+
!liElement.nextElementSibling && liElement.parentElement.parentElement.tagName === "LI" &&
|
|
576
|
+
isEmptyListItem(liElement)) {
|
|
577
|
+
const paragraphElement = exitEmptyListItem(liElement);
|
|
578
|
+
setRangeByWbr(paragraphElement, range);
|
|
579
|
+
execAfterRender(vditor);
|
|
580
|
+
event.preventDefault();
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// 空列表项删除标记后退出当前列表
|
|
585
|
+
if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
|
|
586
|
+
liElement.textContent.trim().replace(Constants.ZWSP, "") === "" && range.toString() === "") {
|
|
587
|
+
const paragraphElement = exitEmptyListItem(liElement);
|
|
588
|
+
setRangeByWbr(paragraphElement, range);
|
|
589
|
+
execAfterRender(vditor);
|
|
590
|
+
event.preventDefault();
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
|
|
474
594
|
if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
|
|
475
595
|
!liElement.previousElementSibling && range.toString() === "" &&
|
|
476
596
|
getSelectPosition(liElement, vditor[vditor.currentMode].element, range).start === 0) {
|
|
@@ -488,20 +608,6 @@ export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | f
|
|
|
488
608
|
return true;
|
|
489
609
|
}
|
|
490
610
|
|
|
491
|
-
// 空列表删除后与上一级段落对齐
|
|
492
|
-
if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
|
|
493
|
-
liElement.textContent.trim().replace(Constants.ZWSP, "") === "" &&
|
|
494
|
-
range.toString() === "" && liElement.previousElementSibling?.tagName === "LI") {
|
|
495
|
-
liElement.previousElementSibling.insertAdjacentText("beforeend", "\n\n");
|
|
496
|
-
range.selectNodeContents(liElement.previousElementSibling);
|
|
497
|
-
range.collapse(false);
|
|
498
|
-
liElement.remove();
|
|
499
|
-
setRangeByWbr(vditor[vditor.currentMode].element, range);
|
|
500
|
-
execAfterRender(vditor);
|
|
501
|
-
event.preventDefault();
|
|
502
|
-
return true;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
611
|
if (!isCtrl(event) && !event.altKey && event.key === "Tab") {
|
|
506
612
|
// 光标位于第一/零字符时,tab 用于列表的缩进
|
|
507
613
|
let isFirst = false;
|
|
@@ -1021,6 +1127,27 @@ export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEven
|
|
|
1021
1127
|
const startContainer = range.startContainer;
|
|
1022
1128
|
const blockquoteElement = hasClosestByMatchTag(startContainer, "BLOCKQUOTE");
|
|
1023
1129
|
if (blockquoteElement && range.toString() === "") {
|
|
1130
|
+
if (pElement && event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey &&
|
|
1131
|
+
pElement.parentElement.isSameNode(blockquoteElement) &&
|
|
1132
|
+
hasClosestByMatchTag(blockquoteElement.parentElement, "LI") &&
|
|
1133
|
+
(range.collapsed || !range.cloneContents().firstElementChild) &&
|
|
1134
|
+
pElement.innerHTML.replace(Constants.ZWSP, "") !== "\n" &&
|
|
1135
|
+
pElement.innerHTML.replace(Constants.ZWSP, "") !== "") {
|
|
1136
|
+
// 列表内的引用由浏览器处理回车时会新建列表项 https://github.com/Vanessa219/vditor/issues/1925
|
|
1137
|
+
const trailingRange = range.cloneRange();
|
|
1138
|
+
trailingRange.setEnd(pElement, pElement.childNodes.length);
|
|
1139
|
+
const paragraphElement = document.createElement("p");
|
|
1140
|
+
paragraphElement.setAttribute("data-block", "0");
|
|
1141
|
+
paragraphElement.appendChild(trailingRange.extractContents());
|
|
1142
|
+
pElement.insertAdjacentElement("afterend", paragraphElement);
|
|
1143
|
+
range.setStart(paragraphElement, 0);
|
|
1144
|
+
range.collapse(true);
|
|
1145
|
+
setSelectionFocus(range);
|
|
1146
|
+
execAfterRender(vditor);
|
|
1147
|
+
event.preventDefault();
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1024
1151
|
if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey &&
|
|
1025
1152
|
getSelectPosition(blockquoteElement, vditor[vditor.currentMode].element, range).start === 0) {
|
|
1026
1153
|
// Backspace: 光标位于引用中的第零个字符,仅删除引用标签
|
|
@@ -1068,9 +1195,15 @@ export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEven
|
|
|
1068
1195
|
return true;
|
|
1069
1196
|
}
|
|
1070
1197
|
|
|
1071
|
-
|
|
1198
|
+
const itemElement = blockquoteElement.parentElement.tagName === "LI" ? blockquoteElement.parentElement : null;
|
|
1199
|
+
const afterBlockElement = !blockquoteElement.nextElementSibling && itemElement?.nextElementSibling?.tagName === "LI" ?
|
|
1200
|
+
itemElement : blockquoteElement;
|
|
1201
|
+
if (insertAfterBlock(vditor, event, range, blockquoteElement, afterBlockElement)) {
|
|
1072
1202
|
return true;
|
|
1073
1203
|
}
|
|
1204
|
+
if (event.key === "ArrowUp" && itemElement && !blockquoteElement.previousElementSibling) {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1074
1207
|
if (insertBeforeBlock(vditor, event, range, blockquoteElement, blockquoteElement)) {
|
|
1075
1208
|
return true;
|
|
1076
1209
|
}
|
|
@@ -1258,7 +1391,8 @@ export const fixFirefoxArrowUpTable = (event: KeyboardEvent, blockElement: false
|
|
|
1258
1391
|
export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent) & {target: HTMLElement}, callback: {
|
|
1259
1392
|
pasteCode(code: string): void,
|
|
1260
1393
|
}) => {
|
|
1261
|
-
if (vditor
|
|
1394
|
+
if (vditor.currentMode === "sv" ? vditor.sv.element.disabled :
|
|
1395
|
+
vditor[vditor.currentMode].element.getAttribute("contenteditable") !== "true") {
|
|
1262
1396
|
return;
|
|
1263
1397
|
}
|
|
1264
1398
|
event.stopPropagation();
|
|
@@ -1283,13 +1417,18 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
|
|
|
1283
1417
|
HTML2VditorIRDOM?: ILuteRender,
|
|
1284
1418
|
Md2VditorDOM?: ILuteRender,
|
|
1285
1419
|
Md2VditorIRDOM?: ILuteRender,
|
|
1286
|
-
Md2VditorSVDOM?: ILuteRender,
|
|
1287
1420
|
} = {};
|
|
1288
1421
|
const renderLinkDest: ILuteRenderCallback = (node, entering) => {
|
|
1289
1422
|
if (!entering) {
|
|
1290
1423
|
return ["", Lute.WalkContinue];
|
|
1291
1424
|
}
|
|
1292
1425
|
|
|
1426
|
+
const parent = node.__internal_object__.Parent;
|
|
1427
|
+
// 链接引用的目标地址由定义节点提供,不在引用位置重复渲染
|
|
1428
|
+
if (parent.Type === 33 && parent.LinkType === 3) {
|
|
1429
|
+
return ["", Lute.WalkContinue];
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1293
1432
|
if (vditor.options.upload.renderLinkDest) {
|
|
1294
1433
|
return vditor.options.upload.renderLinkDest(vditor, node, entering);
|
|
1295
1434
|
}
|
|
@@ -1321,12 +1460,11 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
|
|
|
1321
1460
|
}
|
|
1322
1461
|
const original = responseJSON.data.originalURL;
|
|
1323
1462
|
if (vditor.currentMode === "sv") {
|
|
1324
|
-
vditor.sv.element.
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
});
|
|
1463
|
+
const selectionStart = vditor.sv.element.selectionStart;
|
|
1464
|
+
const selectionEnd = vditor.sv.element.selectionEnd;
|
|
1465
|
+
vditor.sv.element.value = vditor.sv.element.value.split(original)
|
|
1466
|
+
.join(responseJSON.data.url);
|
|
1467
|
+
vditor.sv.element.setSelectionRange(selectionStart, selectionEnd);
|
|
1330
1468
|
} else {
|
|
1331
1469
|
const imgElement: HTMLImageElement =
|
|
1332
1470
|
vditor[vditor.currentMode].element.querySelector(`img[src="${original}"]`);
|
|
@@ -1352,7 +1490,7 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
|
|
|
1352
1490
|
} else if (vditor.currentMode === "wysiwyg") {
|
|
1353
1491
|
return ["", Lute.WalkContinue];
|
|
1354
1492
|
} else {
|
|
1355
|
-
return [
|
|
1493
|
+
return ["", Lute.WalkContinue];
|
|
1356
1494
|
}
|
|
1357
1495
|
};
|
|
1358
1496
|
|
|
@@ -1378,7 +1516,13 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
|
|
|
1378
1516
|
const codeElement = vditor.currentMode === "sv" ?
|
|
1379
1517
|
hasClosestByAttribute(event.target, "data-type", "code-block") :
|
|
1380
1518
|
hasClosestByMatchTag(event.target, "CODE");
|
|
1381
|
-
|
|
1519
|
+
const cellElement = vditor.currentMode === "wysiwyg" &&
|
|
1520
|
+
(hasClosestByMatchTag(getEditorRange(vditor).startContainer, "TD") ||
|
|
1521
|
+
hasClosestByMatchTag(getEditorRange(vditor).startContainer, "TH"));
|
|
1522
|
+
if (cellElement && /[\r\n]/.test(textPlain)) {
|
|
1523
|
+
// 表格单元格内的换行使用 br 表示,避免自旋时解析为新的表格行
|
|
1524
|
+
insertHTML(Lute.EscapeHTMLStr(textPlain).replace(/\r\n|\r|\n/g, "<br>"), vditor);
|
|
1525
|
+
} else if (codeElement) {
|
|
1382
1526
|
// 粘贴在代码位置
|
|
1383
1527
|
if (vditor.currentMode === "sv") {
|
|
1384
1528
|
document.execCommand("insertHTML", false, textPlain.replace(/&/g, "&").replace(/</g, "<"));
|
|
@@ -1424,8 +1568,6 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
|
|
|
1424
1568
|
vditor.lute.SetJSRenderers({renderers});
|
|
1425
1569
|
insertHTML(vditor.lute.HTML2VditorDOM(tempElement.innerHTML), vditor);
|
|
1426
1570
|
} else {
|
|
1427
|
-
renderers.Md2VditorSVDOM = {renderLinkDest};
|
|
1428
|
-
vditor.lute.SetJSRenderers({renderers});
|
|
1429
1571
|
processPaste(vditor, vditor.lute.HTML2Md(tempElement.innerHTML).trimRight());
|
|
1430
1572
|
}
|
|
1431
1573
|
vditor.outline.render(vditor);
|
|
@@ -1451,14 +1593,20 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
|
|
|
1451
1593
|
} else {
|
|
1452
1594
|
imgHTML += `})\n`;
|
|
1453
1595
|
}
|
|
1454
|
-
|
|
1596
|
+
if (vditor.currentMode === "sv") {
|
|
1597
|
+
processPaste(vditor, imgHTML);
|
|
1598
|
+
execAfterRender(vditor);
|
|
1599
|
+
} else {
|
|
1600
|
+
document.execCommand("insertHTML", false, imgHTML);
|
|
1601
|
+
}
|
|
1455
1602
|
};
|
|
1456
1603
|
}
|
|
1457
1604
|
}
|
|
1458
1605
|
} else if (textPlain.trim() !== "" && files.length === 0) {
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1606
|
+
const selectedText = vditor.currentMode === "sv" ? vditor.sv.element.value.substring(
|
|
1607
|
+
vditor.sv.element.selectionStart, vditor.sv.element.selectionEnd) : getEditorRange(vditor).toString();
|
|
1608
|
+
if (selectedText !== "" && vditor.lute.IsValidLinkDest(textPlain)) {
|
|
1609
|
+
textPlain = `[${selectedText}](${textPlain})`;
|
|
1462
1610
|
}
|
|
1463
1611
|
if (vditor.currentMode === "ir") {
|
|
1464
1612
|
renderers.Md2VditorIRDOM = {renderLinkDest};
|
|
@@ -1469,8 +1617,6 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
|
|
|
1469
1617
|
vditor.lute.SetJSRenderers({renderers});
|
|
1470
1618
|
insertHTML(Lute.Sanitize(vditor.lute.Md2VditorDOM(textPlain)), vditor);
|
|
1471
1619
|
} else {
|
|
1472
|
-
renderers.Md2VditorSVDOM = {renderLinkDest};
|
|
1473
|
-
vditor.lute.SetJSRenderers({renderers});
|
|
1474
1620
|
processPaste(vditor, textPlain);
|
|
1475
1621
|
}
|
|
1476
1622
|
vditor.outline.render(vditor);
|
|
@@ -10,6 +10,7 @@ import {markmapRender} from "../markdown/markmapRender";
|
|
|
10
10
|
import {mindmapRender} from "../markdown/mindmapRender";
|
|
11
11
|
import {plantumlRender} from "../markdown/plantumlRender";
|
|
12
12
|
import {SMILESRender} from "../markdown/SMILESRender";
|
|
13
|
+
import {wavedromRender} from "../markdown/wavedromRender";
|
|
13
14
|
|
|
14
15
|
export const processPasteCode = (html: string, text: string, type = "sv") => {
|
|
15
16
|
const tempElement = document.createElement("div");
|
|
@@ -82,6 +83,8 @@ export const processCodeRender = (previewPanel: HTMLElement, vditor: IVditor) =>
|
|
|
82
83
|
plantumlRender(previewPanel, vditor.options.cdn);
|
|
83
84
|
} else if (language === "graphviz") {
|
|
84
85
|
graphvizRender(previewPanel, vditor.options.cdn);
|
|
86
|
+
} else if (language === "wavedrom") {
|
|
87
|
+
wavedromRender(previewPanel, vditor.options.cdn);
|
|
85
88
|
} else if (language === "math") {
|
|
86
89
|
mathRender(previewPanel, {cdn: vditor.options.cdn, math: vditor.options.preview.math});
|
|
87
90
|
} else {
|