@yltrcc/vditor 0.1.1 → 0.3.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.
@@ -1,1569 +1,1570 @@
1
- import {Constants} from "../constants";
2
- import {input as IRInput} from "../ir/input";
3
- import {processAfterRender} from "../ir/process";
4
- import {processAfterRender as processSVAfterRender, processPaste} from "../sv/process";
5
- import {uploadFiles} from "../upload/index";
6
- import {setHeaders} from "../upload/setHeaders";
7
- import {afterRenderEvent} from "../wysiwyg/afterRenderEvent";
8
- import {input} from "../wysiwyg/input";
9
- import {isCtrl, isFirefox} from "./compatibility";
10
- import {scrollCenter} from "./editorCommonEvent";
11
- import {
12
- getTopList,
13
- hasClosestBlock,
14
- hasClosestByAttribute,
15
- hasClosestByClassName,
16
- hasClosestByMatchTag,
17
- } from "./hasClosest";
18
- import {getLastNode} from "./hasClosest";
19
- import {highlightToolbar} from "./highlightToolbar";
20
- import {matchHotKey} from "./hotKey";
21
- import {processCodeRender, processPasteCode} from "./processCode";
22
- import {
23
- getEditorRange,
24
- getSelectPosition,
25
- insertHTML,
26
- setRangeByWbr,
27
- setSelectionByPosition, setSelectionFocus,
28
- } from "./selection";
29
-
30
- // https://github.com/Vanessa219/vditor/issues/508 软键盘无法删除空块
31
- export const fixGSKeyBackspace = (event: KeyboardEvent, vditor: IVditor, startContainer: Node) => {
32
- if (event.keyCode === 229 && event.code === "" && event.key === "Unidentified" && vditor.currentMode !== "sv") {
33
- const blockElement = hasClosestBlock(startContainer);
34
- // 移动端的标点符号都显示为 299,因此需限定为空删除的条件
35
- if (blockElement && blockElement.textContent.trim() === "") {
36
- vditor[vditor.currentMode].composingLock = true;
37
- return false;
38
- }
39
- }
40
- return true;
41
- };
42
-
43
- // https://github.com/Vanessa219/vditor/issues/361 代码块后输入中文
44
- export const fixCJKPosition = (range: Range, vditor: IVditor, event: KeyboardEvent) => {
45
- if (event.key === "Enter" || event.key === "Tab" || event.key === "Backspace" || event.key.indexOf("Arrow") > -1
46
- || isCtrl(event) || event.key === "Escape" || event.shiftKey || event.altKey) {
47
- return;
48
- }
49
- const pLiElement = hasClosestByMatchTag(range.startContainer, "P") ||
50
- hasClosestByMatchTag(range.startContainer, "LI");
51
- if (pLiElement && getSelectPosition(pLiElement, vditor[vditor.currentMode].element, range).start === 0) {
52
-
53
- // https://github.com/Vanessa219/vditor/issues/1289 WKWebView切换输入法产生六分之一空格,造成光标错位
54
- if (pLiElement.nodeValue) {
55
- pLiElement.nodeValue = pLiElement.nodeValue.replace(/\u2006/g, "");
56
- }
57
-
58
- const zwspNode = document.createTextNode(Constants.ZWSP);
59
- range.insertNode(zwspNode);
60
- range.setStartAfter(zwspNode);
61
- }
62
- };
63
-
64
- // https://github.com/Vanessa219/vditor/issues/381 光标在内联数学公式中无法向下移动
65
- export const fixCursorDownInlineMath = (range: Range, key: string) => {
66
- if (key === "ArrowDown" || key === "ArrowUp") {
67
- const inlineElement = hasClosestByAttribute(range.startContainer, "data-type", "math-inline") ||
68
- hasClosestByAttribute(range.startContainer, "data-type", "html-entity") ||
69
- hasClosestByAttribute(range.startContainer, "data-type", "html-inline");
70
- if (inlineElement) {
71
- if (key === "ArrowDown") {
72
- range.setStartAfter(inlineElement.parentElement);
73
- }
74
- if (key === "ArrowUp") {
75
- range.setStartBefore(inlineElement.parentElement);
76
- }
77
- }
78
- }
79
- };
80
-
81
- export const insertEmptyBlock = (vditor: IVditor, position: InsertPosition) => {
82
- const range = getEditorRange(vditor);
83
- const blockElement = hasClosestBlock(range.startContainer);
84
- if (blockElement) {
85
- blockElement.insertAdjacentHTML(position, `<p data-block="0">${Constants.ZWSP}<wbr>\n</p>`);
86
- setRangeByWbr(vditor[vditor.currentMode].element, range);
87
- highlightToolbar(vditor);
88
- execAfterRender(vditor);
89
- }
90
- };
91
-
92
- export const isFirstCell = (cellElement: HTMLElement) => {
93
- const tableElement = hasClosestByMatchTag(cellElement, "TABLE") as HTMLTableElement;
94
- if (tableElement && tableElement.rows[0].cells[0].isSameNode(cellElement)) {
95
- return tableElement;
96
- }
97
- return false;
98
- };
99
-
100
- export const isLastCell = (cellElement: HTMLElement) => {
101
- const tableElement = hasClosestByMatchTag(cellElement, "TABLE") as HTMLTableElement;
102
- if (tableElement && tableElement.lastElementChild.lastElementChild.lastElementChild.isSameNode(cellElement)) {
103
- return tableElement;
104
- }
105
- return false;
106
- };
107
-
108
- // 光标设置到前一个表格中
109
- const goPreviousCell = (cellElement: HTMLElement, range: Range, isSelected = true) => {
110
- let previousElement = cellElement.previousElementSibling;
111
- if (!previousElement) {
112
- if (cellElement.parentElement.previousElementSibling) {
113
- previousElement = cellElement.parentElement.previousElementSibling.lastElementChild;
114
- } else if (cellElement.parentElement.parentElement.tagName === "TBODY" &&
115
- cellElement.parentElement.parentElement.previousElementSibling) {
116
- previousElement = cellElement.parentElement
117
- .parentElement.previousElementSibling.lastElementChild.lastElementChild;
118
- } else {
119
- previousElement = null;
120
- }
121
- }
122
- if (previousElement) {
123
- range.selectNodeContents(previousElement);
124
- if (!isSelected) {
125
- range.collapse(false);
126
- }
127
- setSelectionFocus(range);
128
- }
129
- return previousElement;
130
- };
131
-
132
- export const insertAfterBlock = (vditor: IVditor, event: KeyboardEvent, range: Range, element: HTMLElement,
133
- blockElement: HTMLElement) => {
134
- const position = getSelectPosition(element, vditor[vditor.currentMode].element, range);
135
- if ((event.key === "ArrowDown" && element.textContent.trimRight().substr(position.start).indexOf("\n") === -1) ||
136
- (event.key === "ArrowRight" && position.start >= element.textContent.trimRight().length)) {
137
- const nextElement = blockElement.nextElementSibling;
138
- if (!nextElement ||
139
- (nextElement && (nextElement.tagName === "TABLE" || nextElement.getAttribute("data-type")))) {
140
- blockElement.insertAdjacentHTML("afterend",
141
- `<p data-block="0">${Constants.ZWSP}<wbr></p>`);
142
- setRangeByWbr(vditor[vditor.currentMode].element, range);
143
- } else {
144
- range.selectNodeContents(nextElement);
145
- range.collapse(true);
146
- setSelectionFocus(range);
147
- }
148
- event.preventDefault();
149
- return true;
150
- }
151
- return false;
152
- };
153
-
154
- export const insertBeforeBlock = (vditor: IVditor, event: KeyboardEvent, range: Range, element: HTMLElement,
155
- blockElement: HTMLElement) => {
156
- const position = getSelectPosition(element, vditor[vditor.currentMode].element, range);
157
- if ((event.key === "ArrowUp" && element.textContent.substr(0, position.start).indexOf("\n") === -1) ||
158
- ((event.key === "ArrowLeft" || (event.key === "Backspace" && range.toString() === "")) &&
159
- position.start === 0)) {
160
- const previousElement = blockElement.previousElementSibling;
161
- // table || code
162
- if (!previousElement ||
163
- (previousElement && (previousElement.tagName === "TABLE" || previousElement.getAttribute("data-type")))) {
164
- blockElement.insertAdjacentHTML("beforebegin",
165
- `<p data-block="0">${Constants.ZWSP}<wbr></p>`);
166
- setRangeByWbr(vditor[vditor.currentMode].element, range);
167
- } else {
168
- range.selectNodeContents(previousElement);
169
- range.collapse(false);
170
- setSelectionFocus(range);
171
- }
172
- event.preventDefault();
173
- return true;
174
- }
175
- return false;
176
- };
177
-
178
- export const listToggle = (vditor: IVditor, range: Range, type: string, cancel = true) => {
179
- const itemElement = hasClosestByMatchTag(range.startContainer, "LI");
180
- vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
181
- wbr.remove();
182
- });
183
- range.insertNode(document.createElement("wbr"));
184
-
185
- if (cancel && itemElement) {
186
- // 取消
187
- let pHTML = "";
188
- for (let i = 0; i < itemElement.parentElement.childElementCount; i++) {
189
- const inputElement = itemElement.parentElement.children[i].querySelector("input");
190
- if (inputElement) {
191
- inputElement.remove();
192
- }
193
- pHTML += `<p data-block="0">${itemElement.parentElement.children[i].innerHTML.trimLeft()}</p>`;
194
- }
195
- itemElement.parentElement.insertAdjacentHTML("beforebegin", pHTML);
196
- itemElement.parentElement.remove();
197
- } else {
198
- if (!itemElement) {
199
- // 添加
200
- let blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
201
- if (!blockElement) {
202
- vditor[vditor.currentMode].element.querySelector("wbr").remove();
203
- blockElement = vditor[vditor.currentMode].element.querySelector("p");
204
- blockElement.innerHTML = "<wbr>";
205
- }
206
- if (type === "check") {
207
- blockElement.insertAdjacentHTML("beforebegin",
208
- `<ul data-block="0"><li class="vditor-task"><input type="checkbox" /> ${blockElement.innerHTML}</li></ul>`);
209
- blockElement.remove();
210
- } else if (type === "list") {
211
- blockElement.insertAdjacentHTML("beforebegin",
212
- `<ul data-block="0"><li>${blockElement.innerHTML}</li></ul>`);
213
- blockElement.remove();
214
- } else if (type === "ordered-list") {
215
- blockElement.insertAdjacentHTML("beforebegin",
216
- `<ol data-block="0"><li>${blockElement.innerHTML}</li></ol>`);
217
- blockElement.remove();
218
- }
219
- } else {
220
- // 切换
221
- if (type === "check") {
222
- itemElement.parentElement.querySelectorAll("li").forEach((item) => {
223
- item.insertAdjacentHTML("afterbegin",
224
- `<input type="checkbox" />${item.textContent.indexOf(" ") === 0 ? "" : " "}`);
225
- item.classList.add("vditor-task");
226
- });
227
- } else {
228
- if (itemElement.querySelector("input")) {
229
- itemElement.parentElement.querySelectorAll("li").forEach((item) => {
230
- item.querySelector("input").remove();
231
- item.classList.remove("vditor-task");
232
- });
233
- }
234
- let element;
235
- if (type === "list") {
236
- element = document.createElement("ul");
237
- element.setAttribute("data-marker", "*");
238
- } else {
239
- element = document.createElement("ol");
240
- element.setAttribute("data-marker", "1.");
241
- }
242
- element.setAttribute("data-block", "0");
243
- element.setAttribute("data-tight", itemElement.parentElement.getAttribute("data-tight"));
244
- element.innerHTML = itemElement.parentElement.innerHTML;
245
- itemElement.parentElement.parentNode.replaceChild(element, itemElement.parentElement);
246
- }
247
- }
248
- }
249
- };
250
-
251
- export const listIndent = (vditor: IVditor, liElement: HTMLElement, range: Range) => {
252
- const previousElement = liElement.previousElementSibling;
253
- if (liElement && previousElement) {
254
- const liElements: HTMLElement[] = [liElement];
255
- Array.from(range.cloneContents().children).forEach((item, index) => {
256
- if (item.nodeType !== 3 && liElement && item.textContent.trim() !== ""
257
- && liElement.getAttribute("data-node-id") === item.getAttribute("data-node-id")) {
258
- if (index !== 0) {
259
- liElements.push(liElement);
260
- }
261
- liElement = liElement.nextElementSibling as HTMLElement;
262
- }
263
- });
264
-
265
- vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
266
- wbr.remove();
267
- });
268
- range.insertNode(document.createElement("wbr"));
269
- const liParentElement = previousElement.parentElement;
270
-
271
- let liHTML = "";
272
- liElements.forEach((item: HTMLElement) => {
273
- let marker = item.getAttribute("data-marker");
274
- if (marker.length !== 1) {
275
- marker = `1${marker.slice(-1)}`;
276
- }
277
- liHTML += `<li data-node-id="${item.getAttribute("data-node-id")}" data-marker="${marker}">${item.innerHTML}</li>`;
278
- item.remove();
279
- });
280
- previousElement.insertAdjacentHTML("beforeend",
281
- `<${liParentElement.tagName} data-block="0">${liHTML}</${liParentElement.tagName}>`);
282
-
283
- if (vditor.currentMode === "wysiwyg") {
284
- liParentElement.outerHTML = vditor.lute.SpinVditorDOM(liParentElement.outerHTML);
285
- } else {
286
- liParentElement.outerHTML = vditor.lute.SpinVditorIRDOM(liParentElement.outerHTML);
287
- }
288
-
289
- setRangeByWbr(vditor[vditor.currentMode].element, range);
290
- const tempTopListElement = getTopList(range.startContainer);
291
- if (tempTopListElement) {
292
- tempTopListElement.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`)
293
- .forEach((item: HTMLElement) => {
294
- processCodeRender(item, vditor);
295
- if (vditor.currentMode === "wysiwyg") {
296
- item.previousElementSibling.setAttribute("style", "display:none");
297
- }
298
- });
299
- }
300
- execAfterRender(vditor);
301
- highlightToolbar(vditor);
302
- } else {
303
- vditor[vditor.currentMode].element.focus();
304
- }
305
- };
306
-
307
- export const listOutdent = (vditor: IVditor, liElement: HTMLElement, range: Range, topListElement: HTMLElement) => {
308
- const liParentLiElement = hasClosestByMatchTag(liElement.parentElement, "LI");
309
- if (liParentLiElement) {
310
- vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
311
- wbr.remove();
312
- });
313
- range.insertNode(document.createElement("wbr"));
314
-
315
- const liParentElement = liElement.parentElement;
316
- const liParentAfterElement = liParentElement.cloneNode() as HTMLElement;
317
- const liElements: HTMLElement[] = [liElement];
318
- Array.from(range.cloneContents().children).forEach((item, index) => {
319
- if (item.nodeType !== 3 && liElement && item.textContent.trim() !== "" &&
320
- liElement.getAttribute("data-node-id") === item.getAttribute("data-node-id")) {
321
- if (index !== 0) {
322
- liElements.push(liElement);
323
- }
324
- liElement = liElement.nextElementSibling as HTMLElement;
325
- }
326
- });
327
- let isMatch = false;
328
- let afterHTML = "";
329
- liParentElement.querySelectorAll("li").forEach((item) => {
330
- if (isMatch) {
331
- afterHTML += item.outerHTML;
332
- if (!item.nextElementSibling && !item.previousElementSibling) {
333
- item.parentElement.remove();
334
- } else {
335
- item.remove();
336
- }
337
- }
338
- if (item.isSameNode(liElements[liElements.length - 1])) {
339
- isMatch = true;
340
- }
341
- });
342
-
343
- liElements.reverse().forEach((item) => {
344
- liParentLiElement.insertAdjacentElement("afterend", item);
345
- });
346
-
347
- if (afterHTML) {
348
- liParentAfterElement.innerHTML = afterHTML;
349
- liElements[0].insertAdjacentElement("beforeend", liParentAfterElement);
350
- }
351
-
352
- if (vditor.currentMode === "wysiwyg") {
353
- topListElement.outerHTML = vditor.lute.SpinVditorDOM(topListElement.outerHTML);
354
- } else {
355
- topListElement.outerHTML = vditor.lute.SpinVditorIRDOM(topListElement.outerHTML);
356
- }
357
-
358
- setRangeByWbr(vditor[vditor.currentMode].element, range);
359
- const tempTopListElement = getTopList(range.startContainer);
360
- if (tempTopListElement) {
361
- tempTopListElement.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`)
362
- .forEach((item: HTMLElement) => {
363
- processCodeRender(item, vditor);
364
- if (vditor.currentMode === "wysiwyg") {
365
- item.previousElementSibling.setAttribute("style", "display:none");
366
- }
367
- });
368
- }
369
- execAfterRender(vditor);
370
- highlightToolbar(vditor);
371
- } else {
372
- vditor[vditor.currentMode].element.focus();
373
- }
374
- };
375
-
376
- export const setTableAlign = (tableElement: HTMLTableElement, type: string) => {
377
- const cell = getSelection().getRangeAt(0).startContainer.parentElement;
378
-
379
- const columnCnt = tableElement.rows[0].cells.length;
380
- const rowCnt = tableElement.rows.length;
381
- let currentColumn = 0;
382
-
383
- for (let i = 0; i < rowCnt; i++) {
384
- for (let j = 0; j < columnCnt; j++) {
385
- if (tableElement.rows[i].cells[j].isSameNode(cell)) {
386
- currentColumn = j;
387
- break;
388
- }
389
- }
390
- }
391
- for (let k = 0; k < rowCnt; k++) {
392
- tableElement.rows[k].cells[currentColumn].setAttribute("align", type);
393
- }
394
- };
395
-
396
- export const isHrMD = (text: string) => {
397
- // - _ *
398
- const marker = text.trimRight().split("\n").pop();
399
- if (marker === "") {
400
- return false;
401
- }
402
- if (marker.replace(/ |-/g, "") === ""
403
- || marker.replace(/ |_/g, "") === ""
404
- || marker.replace(/ |\*/g, "") === "") {
405
- if (marker.replace(/ /g, "").length > 2) {
406
- if (marker.indexOf("-") > -1 && marker.trimLeft().indexOf(" ") === -1
407
- && text.trimRight().split("\n").length > 1) {
408
- // 满足 heading
409
- return false;
410
- }
411
- if (marker.indexOf(" ") === 0 || marker.indexOf("\t") === 0) {
412
- // 代码块
413
- return false;
414
- }
415
- return true;
416
- }
417
- return false;
418
- }
419
- return false;
420
- };
421
-
422
- export const isHeadingMD = (text: string) => {
423
- // - =
424
- const textArray = text.trimRight().split("\n");
425
- text = textArray.pop();
426
-
427
- if (text.indexOf(" ") === 0 || text.indexOf("\t") === 0) {
428
- return false;
429
- }
430
-
431
- text = text.trimLeft();
432
- if (text === "" || textArray.length === 0) {
433
- return false;
434
- }
435
- if (text.replace(/-/g, "") === ""
436
- || text.replace(/=/g, "") === "") {
437
- return true;
438
- }
439
- return false;
440
- };
441
-
442
- export const execAfterRender = (vditor: IVditor, options = {
443
- enableAddUndoStack: true,
444
- enableHint: false,
445
- enableInput: true,
446
- }) => {
447
- if (vditor.currentMode === "wysiwyg") {
448
- afterRenderEvent(vditor, options);
449
- } else if (vditor.currentMode === "ir") {
450
- processAfterRender(vditor, options);
451
- } else if (vditor.currentMode === "sv") {
452
- processSVAfterRender(vditor, options);
453
- }
454
- };
455
-
456
- export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | false, event: KeyboardEvent) => {
457
- const startContainer = range.startContainer;
458
- const liElement = hasClosestByMatchTag(startContainer, "LI");
459
- if (liElement) {
460
- if (!isCtrl(event) && !event.altKey && event.key === "Enter" &&
461
- // fix li 中有多个 P 时,在第一个 P 中换行会在下方生成新的 li
462
- (!event.shiftKey && pElement && liElement.contains(pElement) && pElement.nextElementSibling)) {
463
- if (liElement && !liElement.textContent.endsWith("\n")) {
464
- // li 结尾需 \n
465
- liElement.insertAdjacentText("beforeend", "\n");
466
- }
467
- range.insertNode(document.createTextNode("\n\n"));
468
- range.collapse(false);
469
- execAfterRender(vditor);
470
- event.preventDefault();
471
- return true;
472
- }
473
-
474
- if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
475
- !liElement.previousElementSibling && range.toString() === "" &&
476
- getSelectPosition(liElement, vditor[vditor.currentMode].element, range).start === 0) {
477
- // 光标位于点和第一个字符中间时,无法删除 li 元素
478
- if (liElement.nextElementSibling) {
479
- liElement.parentElement.insertAdjacentHTML("beforebegin",
480
- `<p data-block="0"><wbr>${liElement.innerHTML}</p>`);
481
- liElement.remove();
482
- } else {
483
- liElement.parentElement.outerHTML = `<p data-block="0"><wbr>${liElement.innerHTML}</p>`;
484
- }
485
- setRangeByWbr(vditor[vditor.currentMode].element, range);
486
- execAfterRender(vditor);
487
- event.preventDefault();
488
- return true;
489
- }
490
-
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
- if (!isCtrl(event) && !event.altKey && event.key === "Tab") {
506
- // 光标位于第一/零字符时,tab 用于列表的缩进
507
- let isFirst = false;
508
- if (range.startOffset === 0
509
- && ((startContainer.nodeType === 3 && !startContainer.previousSibling)
510
- || (startContainer.nodeType !== 3 && startContainer.nodeName === "LI"))) {
511
- // 有序/无序列表
512
- isFirst = true;
513
- } else if (liElement.classList.contains("vditor-task") && range.startOffset === 1
514
- && startContainer.previousSibling.nodeType !== 3
515
- && (startContainer.previousSibling as HTMLElement).tagName === "INPUT") {
516
- // 任务列表
517
- isFirst = true;
518
- }
519
-
520
- if (isFirst || range.toString() !== "") {
521
- if (event.shiftKey) {
522
- listOutdent(vditor, liElement, range, liElement.parentElement);
523
- } else {
524
- listIndent(vditor, liElement, range);
525
- }
526
- event.preventDefault();
527
- return true;
528
- }
529
- }
530
- }
531
- return false;
532
- };
533
-
534
- // tab 处理: block code render, table, 列表第一个字符中的 tab 处理单独写在上面
535
- export const fixTab = (vditor: IVditor, range: Range, event: KeyboardEvent) => {
536
- if (vditor.options.tab && event.key === "Tab") {
537
- if (event.shiftKey) {
538
- // TODO shift+tab
539
- } else {
540
- if (range.toString() === "") {
541
- range.insertNode(document.createTextNode(vditor.options.tab));
542
- range.collapse(false);
543
- } else {
544
- range.extractContents();
545
- range.insertNode(document.createTextNode(vditor.options.tab));
546
- range.collapse(false);
547
- }
548
- }
549
- setSelectionFocus(range);
550
- execAfterRender(vditor);
551
- event.preventDefault();
552
- return true;
553
- }
554
- };
555
-
556
- export const fixMarkdown = (event: KeyboardEvent, vditor: IVditor, pElement: HTMLElement | false, range: Range) => {
557
- if (!pElement) {
558
- return;
559
- }
560
- if (!isCtrl(event) && !event.altKey && event.key === "Enter") {
561
- const pText = String.raw`${pElement.textContent}`.replace(/\\\|/g, "").trim();
562
- const pTextList = pText.split("|");
563
- if (pText.startsWith("|") && pText.endsWith("|") && pTextList.length > 3) {
564
- // table 自动完成
565
- let tableHeaderMD = pTextList.map(() => "---").join("|");
566
- tableHeaderMD =
567
- pElement.textContent + "\n" + tableHeaderMD.substring(3, tableHeaderMD.length - 3) + "\n|<wbr>";
568
- pElement.outerHTML = vditor.lute.SpinVditorDOM(tableHeaderMD);
569
- setRangeByWbr(vditor[vditor.currentMode].element, range);
570
- execAfterRender(vditor);
571
- scrollCenter(vditor);
572
- event.preventDefault();
573
- return true;
574
- }
575
-
576
- // hr 渲染
577
- if (isHrMD(pElement.innerHTML) && pElement.previousElementSibling) {
578
- // 软换行后 hr 前有内容
579
- let pInnerHTML = "";
580
- const innerHTMLList = pElement.innerHTML.trimRight().split("\n");
581
- if (innerHTMLList.length > 1) {
582
- innerHTMLList.pop();
583
- pInnerHTML = `<p data-block="0">${innerHTMLList.join("\n")}</p>`;
584
- }
585
-
586
- pElement.insertAdjacentHTML("afterend",
587
- `${pInnerHTML}<hr data-block="0"><p data-block="0"><wbr>\n</p>`);
588
- pElement.remove();
589
- setRangeByWbr(vditor[vditor.currentMode].element, range);
590
- execAfterRender(vditor);
591
- scrollCenter(vditor);
592
- event.preventDefault();
593
- return true;
594
- }
595
-
596
- if (isHeadingMD(pElement.innerHTML)) {
597
- // heading 渲染
598
- if (vditor.currentMode === "wysiwyg") {
599
- pElement.outerHTML = vditor.lute.SpinVditorDOM(pElement.innerHTML + '<p data-block="0"><wbr>\n</p>');
600
- } else {
601
- pElement.outerHTML = vditor.lute.SpinVditorIRDOM(pElement.innerHTML + '<p data-block="0"><wbr>\n</p>');
602
- }
603
- setRangeByWbr(vditor[vditor.currentMode].element, range);
604
- execAfterRender(vditor);
605
- scrollCenter(vditor);
606
- event.preventDefault();
607
- return true;
608
- }
609
- }
610
-
611
- // 软换行会被切割 https://github.com/Vanessa219/vditor/issues/220
612
- if (range.collapsed && pElement.previousElementSibling && event.key === "Backspace" &&
613
- !isCtrl(event) && !event.altKey && !event.shiftKey &&
614
- pElement.textContent.trimRight().split("\n").length > 1 &&
615
- getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start === 0) {
616
- const lastElement = getLastNode(pElement.previousElementSibling) as HTMLElement;
617
- if (!lastElement.textContent.endsWith("\n")) {
618
- lastElement.textContent = lastElement.textContent + "\n";
619
- }
620
- lastElement.parentElement.insertAdjacentHTML("beforeend", `<wbr>${pElement.innerHTML}`);
621
- pElement.remove();
622
- setRangeByWbr(vditor[vditor.currentMode].element, range);
623
- return false;
624
- }
625
- return false;
626
- };
627
-
628
- export const insertRow = (vditor: IVditor, range: Range, cellElement: HTMLElement) => {
629
- let rowHTML = "";
630
- for (let m = 0; m < cellElement.parentElement.childElementCount; m++) {
631
- rowHTML += `<td align="${cellElement.parentElement.children[m].getAttribute("align")}"> </td>`;
632
- }
633
- if (cellElement.tagName === "TH") {
634
- cellElement.parentElement.parentElement.insertAdjacentHTML("afterend",
635
- `<tbody><tr>${rowHTML}</tr></tbody>`);
636
- } else {
637
- cellElement.parentElement.insertAdjacentHTML("afterend", `<tr>${rowHTML}</tr>`);
638
- }
639
- execAfterRender(vditor);
640
- };
641
-
642
- export const insertRowAbove = (vditor: IVditor, range: Range, cellElement: HTMLElement) => {
643
- let rowHTML = "";
644
- for (let m = 0; m < cellElement.parentElement.childElementCount; m++) {
645
- if (cellElement.tagName === "TH") {
646
- rowHTML += `<th align="${cellElement.parentElement.children[m].getAttribute("align")}"> </th>`;
647
- } else {
648
- rowHTML += `<td align="${cellElement.parentElement.children[m].getAttribute("align")}"> </td>`;
649
- }
650
- }
651
- if (cellElement.tagName === "TH") {
652
- cellElement.parentElement.parentElement.insertAdjacentHTML("beforebegin", `<thead><tr>${rowHTML}</tr></thead>`);
653
-
654
- range.insertNode(document.createElement("wbr"));
655
- const theadHTML = cellElement.parentElement.innerHTML.replace(/<th>/g, "<td>").replace(/<\/th>/g, "</td>");
656
- cellElement.parentElement.parentElement.nextElementSibling.insertAdjacentHTML("afterbegin", theadHTML);
657
-
658
- cellElement.parentElement.parentElement.remove();
659
- setRangeByWbr(vditor.ir.element, range);
660
- } else {
661
- cellElement.parentElement.insertAdjacentHTML("beforebegin", `<tr>${rowHTML}</tr>`);
662
- }
663
- execAfterRender(vditor);
664
- };
665
-
666
- export const insertColumn =
667
- (vditor: IVditor, tableElement: HTMLTableElement, cellElement: HTMLElement, type: InsertPosition = "afterend") => {
668
- let index = 0;
669
- let previousElement = cellElement.previousElementSibling;
670
- while (previousElement) {
671
- index++;
672
- previousElement = previousElement.previousElementSibling;
673
- }
674
- for (let i = 0; i < tableElement.rows.length; i++) {
675
- if (i === 0) {
676
- tableElement.rows[i].cells[index].insertAdjacentHTML(type, "<th> </th>");
677
- } else {
678
- tableElement.rows[i].cells[index].insertAdjacentHTML(type, "<td> </td>");
679
- }
680
- }
681
- execAfterRender(vditor);
682
- };
683
- export const deleteRow = (vditor: IVditor, range: Range, cellElement: HTMLElement) => {
684
- if (cellElement.tagName === "TD") {
685
- const tbodyElement = cellElement.parentElement.parentElement;
686
- if (cellElement.parentElement.previousElementSibling) {
687
- range.selectNodeContents(cellElement.parentElement.previousElementSibling.lastElementChild);
688
- } else {
689
- range.selectNodeContents(tbodyElement.previousElementSibling.lastElementChild.lastElementChild);
690
- }
691
-
692
- if (tbodyElement.childElementCount === 1) {
693
- tbodyElement.remove();
694
- } else {
695
- cellElement.parentElement.remove();
696
- }
697
-
698
- range.collapse(false);
699
- setSelectionFocus(range);
700
- execAfterRender(vditor);
701
- }
702
- };
703
-
704
- export const deleteColumn =
705
- (vditor: IVditor, range: Range, tableElement: HTMLTableElement, cellElement: HTMLElement) => {
706
- let index = 0;
707
- let previousElement = cellElement.previousElementSibling;
708
- while (previousElement) {
709
- index++;
710
- previousElement = previousElement.previousElementSibling;
711
- }
712
- if (cellElement.previousElementSibling || cellElement.nextElementSibling) {
713
- range.selectNodeContents(cellElement.previousElementSibling || cellElement.nextElementSibling);
714
- range.collapse(true);
715
- }
716
- for (let i = 0; i < tableElement.rows.length; i++) {
717
- const cells = tableElement.rows[i].cells;
718
- if (cells.length === 1) {
719
- tableElement.remove();
720
- highlightToolbar(vditor);
721
- break;
722
- }
723
- cells[index].remove();
724
- }
725
- setSelectionFocus(range);
726
- execAfterRender(vditor);
727
- };
728
-
729
- export const fixTable = (vditor: IVditor, event: KeyboardEvent, range: Range) => {
730
- const startContainer = range.startContainer;
731
- const cellElement = hasClosestByMatchTag(startContainer, "TD") ||
732
- hasClosestByMatchTag(startContainer, "TH");
733
- if (cellElement) {
734
- // 换行或软换行:在 cell 中添加 br
735
- if (!isCtrl(event) && !event.altKey && event.key === "Enter") {
736
- if (!cellElement.lastElementChild ||
737
- (cellElement.lastElementChild && (!cellElement.lastElementChild.isSameNode(cellElement.lastChild) ||
738
- cellElement.lastElementChild.tagName !== "BR"))) {
739
- cellElement.insertAdjacentHTML("beforeend", "<br>");
740
- }
741
- const brElement = document.createElement("br");
742
- range.insertNode(brElement);
743
- range.setStartAfter(brElement);
744
- execAfterRender(vditor);
745
- scrollCenter(vditor);
746
- event.preventDefault();
747
- return true;
748
- }
749
-
750
- // tab:光标移向下一个 cell
751
- if (event.key === "Tab") {
752
- if (event.shiftKey) {
753
- // shift + tab 光标移动到前一个 cell
754
- goPreviousCell(cellElement, range);
755
- event.preventDefault();
756
- return true;
757
- }
758
-
759
- let nextElement = cellElement.nextElementSibling;
760
- if (!nextElement) {
761
- if (cellElement.parentElement.nextElementSibling) {
762
- nextElement = cellElement.parentElement.nextElementSibling.firstElementChild;
763
- } else if (cellElement.parentElement.parentElement.tagName === "THEAD" &&
764
- cellElement.parentElement.parentElement.nextElementSibling) {
765
- nextElement =
766
- cellElement.parentElement.parentElement.nextElementSibling.firstElementChild.firstElementChild;
767
- } else {
768
- nextElement = null;
769
- }
770
- }
771
- if (nextElement) {
772
- range.selectNodeContents(nextElement);
773
- setSelectionFocus(range);
774
- }
775
- event.preventDefault();
776
- return true;
777
- }
778
-
779
- const tableElement = cellElement.parentElement.parentElement.parentElement as HTMLTableElement;
780
- if (event.key === "ArrowUp") {
781
- event.preventDefault();
782
- if (cellElement.tagName === "TH") {
783
- if (tableElement.previousElementSibling) {
784
- range.selectNodeContents(tableElement.previousElementSibling);
785
- range.collapse(false);
786
- setSelectionFocus(range);
787
- } else {
788
- insertEmptyBlock(vditor, "beforebegin");
789
- }
790
- return true;
791
- }
792
-
793
- let m = 0;
794
- const trElement = cellElement.parentElement as HTMLTableRowElement;
795
- for (; m < trElement.cells.length; m++) {
796
- if (trElement.cells[m].isSameNode(cellElement)) {
797
- break;
798
- }
799
- }
800
-
801
- let previousElement = trElement.previousElementSibling as HTMLTableRowElement;
802
- if (!previousElement) {
803
- previousElement = trElement.parentElement.previousElementSibling.firstChild as HTMLTableRowElement;
804
- }
805
- range.selectNodeContents(previousElement.cells[m]);
806
- range.collapse(false);
807
- setSelectionFocus(range);
808
- return true;
809
- }
810
-
811
- if (event.key === "ArrowDown") {
812
- event.preventDefault();
813
- const trElement = cellElement.parentElement as HTMLTableRowElement;
814
- if (!trElement.nextElementSibling && cellElement.tagName === "TD") {
815
- if (tableElement.nextElementSibling) {
816
- range.selectNodeContents(tableElement.nextElementSibling);
817
- range.collapse(true);
818
- setSelectionFocus(range);
819
- } else {
820
- insertEmptyBlock(vditor, "afterend");
821
- }
822
- return true;
823
- }
824
-
825
- let m = 0;
826
- for (; m < trElement.cells.length; m++) {
827
- if (trElement.cells[m].isSameNode(cellElement)) {
828
- break;
829
- }
830
- }
831
-
832
- let nextElement = trElement.nextElementSibling as HTMLTableRowElement;
833
- if (!nextElement) {
834
- nextElement = trElement.parentElement.nextElementSibling.firstChild as HTMLTableRowElement;
835
- }
836
- range.selectNodeContents(nextElement.cells[m]);
837
- range.collapse(true);
838
- setSelectionFocus(range);
839
- return true;
840
- }
841
-
842
- // focus row input, only wysiwyg
843
- if (vditor.currentMode === "wysiwyg" &&
844
- !isCtrl(event) && event.key === "Enter" && !event.shiftKey && event.altKey) {
845
- const inputElement = (vditor.wysiwyg.popover.querySelector(".vditor-input") as HTMLInputElement);
846
- inputElement.focus();
847
- inputElement.select();
848
- event.preventDefault();
849
- return true;
850
- }
851
-
852
- // Backspace:光标移动到前一个 cell
853
- if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace"
854
- && range.startOffset === 0 && range.toString() === "") {
855
- const previousCellElement = goPreviousCell(cellElement, range, false);
856
- if (!previousCellElement && tableElement) {
857
- if (tableElement.textContent.trim() === "") {
858
- tableElement.outerHTML = `<p data-block="0"><wbr>\n</p>`;
859
- setRangeByWbr(vditor[vditor.currentMode].element, range);
860
- } else {
861
- range.setStartBefore(tableElement);
862
- range.collapse(true);
863
- }
864
- execAfterRender(vditor);
865
- }
866
- event.preventDefault();
867
- return true;
868
- }
869
- // 上方新添加一行
870
- if (matchHotKey("⇧⌘F", event)) {
871
- insertRowAbove(vditor, range, cellElement);
872
- event.preventDefault();
873
- return true;
874
- }
875
-
876
- // 下方新添加一行 https://github.com/Vanessa219/vditor/issues/46
877
- if (matchHotKey("⌘=", event)) {
878
- insertRow(vditor, range, cellElement);
879
- event.preventDefault();
880
- return true;
881
- }
882
-
883
- // 左方新添加一列
884
- if (matchHotKey("⇧⌘G", event)) {
885
- insertColumn(vditor, tableElement, cellElement, "beforebegin");
886
- event.preventDefault();
887
- return true;
888
- }
889
-
890
- // 后方新添加一列
891
- if (matchHotKey("⇧⌘=", event)) {
892
- insertColumn(vditor, tableElement, cellElement);
893
- event.preventDefault();
894
- return true;
895
- }
896
-
897
- // 删除当前行
898
- if (matchHotKey("⌘-", event)) {
899
- deleteRow(vditor, range, cellElement);
900
- event.preventDefault();
901
- return true;
902
- }
903
-
904
- // 删除当前列
905
- if (matchHotKey("⇧⌘-", event)) {
906
- deleteColumn(vditor, range, tableElement, cellElement);
907
- event.preventDefault();
908
- return true;
909
- }
910
-
911
- // 剧左
912
- if (matchHotKey("⇧⌘L", event)) {
913
- if (vditor.currentMode === "ir") {
914
- setTableAlign(tableElement, "left");
915
- execAfterRender(vditor);
916
- event.preventDefault();
917
- return true;
918
- } else {
919
- const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="left"]');
920
- if (itemElement) {
921
- itemElement.click();
922
- event.preventDefault();
923
- return true;
924
- }
925
- }
926
- }
927
-
928
- // 剧中
929
- if (matchHotKey("⇧⌘C", event)) {
930
- if (vditor.currentMode === "ir") {
931
- setTableAlign(tableElement, "center");
932
- execAfterRender(vditor);
933
- event.preventDefault();
934
- return true;
935
- } else {
936
- const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="center"]');
937
- if (itemElement) {
938
- itemElement.click();
939
- event.preventDefault();
940
- return true;
941
- }
942
- }
943
- }
944
- // 剧右
945
- if (matchHotKey("⇧⌘R", event)) {
946
- if (vditor.currentMode === "ir") {
947
- setTableAlign(tableElement, "right");
948
- execAfterRender(vditor);
949
- event.preventDefault();
950
- return true;
951
- } else {
952
- const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="right"]');
953
- if (itemElement) {
954
- itemElement.click();
955
- event.preventDefault();
956
- return true;
957
- }
958
- }
959
- }
960
- }
961
- return false;
962
- };
963
-
964
- export const fixCodeBlock = (vditor: IVditor, event: KeyboardEvent, codeRenderElement: HTMLElement, range: Range) => {
965
- // 行级代码块中 command + a,近对当前代码块进行全选
966
- if (codeRenderElement.tagName === "PRE" && matchHotKey("⌘A", event)) {
967
- range.selectNodeContents(codeRenderElement.firstElementChild);
968
- event.preventDefault();
969
- return true;
970
- }
971
-
972
- // tab
973
- // TODO shift + tab, shift and 选中文字
974
- if (vditor.options.tab && event.key === "Tab" && !event.shiftKey && range.toString() === "") {
975
- range.insertNode(document.createTextNode(vditor.options.tab));
976
- range.collapse(false);
977
- execAfterRender(vditor);
978
- event.preventDefault();
979
- return true;
980
- }
981
-
982
- // Backspace: 光标位于第零个字符,仅删除代码块标签
983
- if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey) {
984
- const codePosition = getSelectPosition(codeRenderElement, vditor[vditor.currentMode].element, range);
985
- if ((codePosition.start === 0 ||
986
- (codePosition.start === 1 && codeRenderElement.innerText === "\n")) // 空代码块,光标在 \n 后
987
- && range.toString() === "") {
988
- codeRenderElement.parentElement.outerHTML =
989
- `<p data-block="0"><wbr>${codeRenderElement.firstElementChild.innerHTML}</p>`;
990
- setRangeByWbr(vditor[vditor.currentMode].element, range);
991
- execAfterRender(vditor);
992
- event.preventDefault();
993
- return true;
994
- }
995
- }
996
-
997
- // 换行
998
- if (!isCtrl(event) && !event.altKey && event.key === "Enter") {
999
- if (!codeRenderElement.firstElementChild.textContent.endsWith("\n")) {
1000
- codeRenderElement.firstElementChild.insertAdjacentText("beforeend", "\n");
1001
- }
1002
- range.extractContents();
1003
- range.insertNode(document.createTextNode("\n"));
1004
- range.collapse(false);
1005
- setSelectionFocus(range);
1006
- if (!isFirefox()) {
1007
- if (vditor.currentMode === "wysiwyg") {
1008
- input(vditor, range);
1009
- } else {
1010
- IRInput(vditor, range);
1011
- }
1012
- }
1013
- scrollCenter(vditor);
1014
- event.preventDefault();
1015
- return true;
1016
- }
1017
- return false;
1018
- };
1019
-
1020
- export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEvent, pElement: HTMLElement | false) => {
1021
- const startContainer = range.startContainer;
1022
- const blockquoteElement = hasClosestByMatchTag(startContainer, "BLOCKQUOTE");
1023
- if (blockquoteElement && range.toString() === "") {
1024
- if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey &&
1025
- getSelectPosition(blockquoteElement, vditor[vditor.currentMode].element, range).start === 0) {
1026
- // Backspace: 光标位于引用中的第零个字符,仅删除引用标签
1027
- range.insertNode(document.createElement("wbr"));
1028
- blockquoteElement.outerHTML = blockquoteElement.innerHTML;
1029
- setRangeByWbr(vditor[vditor.currentMode].element, range);
1030
- execAfterRender(vditor);
1031
- event.preventDefault();
1032
- return true;
1033
- }
1034
-
1035
- if (pElement && event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey
1036
- && pElement.parentElement.tagName === "BLOCKQUOTE") {
1037
- // Enter: 空行回车应逐层跳出
1038
- let isEmpty = false;
1039
- if (pElement.innerHTML.replace(Constants.ZWSP, "") === "\n" ||
1040
- pElement.innerHTML.replace(Constants.ZWSP, "") === "") {
1041
- // 空 P
1042
- isEmpty = true;
1043
- pElement.remove();
1044
- } else if (pElement.innerHTML.endsWith("\n\n") &&
1045
- getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start ===
1046
- pElement.textContent.length - 1) {
1047
- // 软换行
1048
- pElement.innerHTML = pElement.innerHTML.substr(0, pElement.innerHTML.length - 2);
1049
- isEmpty = true;
1050
- }
1051
- if (isEmpty) {
1052
- // 需添加零宽字符,否则的话无法记录 undo
1053
- blockquoteElement.insertAdjacentHTML("afterend", `<p data-block="0">${Constants.ZWSP}<wbr>\n</p>`);
1054
- setRangeByWbr(vditor[vditor.currentMode].element, range);
1055
- execAfterRender(vditor);
1056
- event.preventDefault();
1057
- return true;
1058
- }
1059
- }
1060
- const blockElement = hasClosestBlock(startContainer);
1061
- if (vditor.currentMode === "wysiwyg" && blockElement && matchHotKey("⇧⌘;", event)) {
1062
- // 插入 blockquote
1063
- range.insertNode(document.createElement("wbr"));
1064
- blockElement.outerHTML = `<blockquote data-block="0">${blockElement.outerHTML}</blockquote>`;
1065
- setRangeByWbr(vditor.wysiwyg.element, range);
1066
- afterRenderEvent(vditor);
1067
- event.preventDefault();
1068
- return true;
1069
- }
1070
-
1071
- if (insertAfterBlock(vditor, event, range, blockquoteElement, blockquoteElement)) {
1072
- return true;
1073
- }
1074
- if (insertBeforeBlock(vditor, event, range, blockquoteElement, blockquoteElement)) {
1075
- return true;
1076
- }
1077
- }
1078
- return false;
1079
- };
1080
-
1081
- export const fixTask = (vditor: IVditor, range: Range, event: KeyboardEvent) => {
1082
- const startContainer = range.startContainer;
1083
- const taskItemElement = hasClosestByMatchTag(startContainer, "LI");
1084
- if (taskItemElement && taskItemElement.classList.contains("vditor-task")) {
1085
- if (matchHotKey("⇧⌘J", event)) {
1086
- // ctrl + shift: toggle checked
1087
- const inputElement = taskItemElement.firstElementChild as HTMLInputElement;
1088
- if (inputElement.checked) {
1089
- inputElement.removeAttribute("checked");
1090
- } else {
1091
- inputElement.setAttribute("checked", "checked");
1092
- }
1093
- execAfterRender(vditor);
1094
- event.preventDefault();
1095
- return true;
1096
- }
1097
-
1098
- // Backspace: 在选择框前进行删除
1099
- if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey && range.toString() === ""
1100
- && range.startOffset === 1
1101
- && ((startContainer.nodeType === 3 && startContainer.previousSibling &&
1102
- (startContainer.previousSibling as HTMLElement).tagName === "INPUT")
1103
- || startContainer.nodeType !== 3)) {
1104
- const previousElement = taskItemElement.previousElementSibling;
1105
- taskItemElement.querySelector("input").remove();
1106
- if (previousElement) {
1107
- const lastNode = getLastNode(previousElement);
1108
- lastNode.parentElement.insertAdjacentHTML("beforeend", "<wbr>" + taskItemElement.innerHTML.trim());
1109
- taskItemElement.remove();
1110
- } else {
1111
- taskItemElement.parentElement.insertAdjacentHTML("beforebegin",
1112
- `<p data-block="0"><wbr>${taskItemElement.innerHTML.trim() || "\n"}</p>`);
1113
- if (taskItemElement.nextElementSibling) {
1114
- taskItemElement.remove();
1115
- } else {
1116
- taskItemElement.parentElement.remove();
1117
- }
1118
- }
1119
- setRangeByWbr(vditor[vditor.currentMode].element, range);
1120
- execAfterRender(vditor);
1121
- event.preventDefault();
1122
- return true;
1123
- }
1124
-
1125
- if (event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey) {
1126
- if (taskItemElement.textContent.trim() === "") {
1127
- // 当前任务列表无文字
1128
- if (hasClosestByClassName(taskItemElement.parentElement, "vditor-task")) {
1129
- // 为子元素时,需进行反向缩进
1130
- const topListElement = getTopList(startContainer);
1131
- if (topListElement) {
1132
- listOutdent(vditor, taskItemElement, range, topListElement);
1133
- }
1134
- } else {
1135
- // 仅有一级任务列表
1136
- if (taskItemElement.nextElementSibling) {
1137
- // 任务列表下方还有元素,需要使用用段落隔断
1138
- let afterHTML = "";
1139
- let beforeHTML = "";
1140
- let isAfter = false;
1141
- Array.from(taskItemElement.parentElement.children).forEach((taskItem) => {
1142
- if (taskItemElement.isSameNode(taskItem)) {
1143
- isAfter = true;
1144
- } else {
1145
- if (isAfter) {
1146
- afterHTML += taskItem.outerHTML;
1147
- } else {
1148
- beforeHTML += taskItem.outerHTML;
1149
- }
1150
- }
1151
- });
1152
- const parentTagName = taskItemElement.parentElement.tagName;
1153
- const dataMarker = taskItemElement.parentElement.tagName === "OL" ? "" : ` data-marker="${taskItemElement.parentElement.getAttribute("data-marker")}"`;
1154
- let startAttribute = "";
1155
- if (beforeHTML) {
1156
- startAttribute = taskItemElement.parentElement.tagName === "UL" ? "" : ` start="1"`;
1157
- beforeHTML = `<${parentTagName} data-tight="true"${dataMarker} data-block="0">${beforeHTML}</${parentTagName}>`;
1158
- }
1159
- // <p data-block="0">\n<wbr></p> => <p data-block="0"><wbr>\n</p>
1160
- // https://github.com/Vanessa219/vditor/issues/430
1161
- taskItemElement.parentElement.outerHTML = `${beforeHTML}<p data-block="0"><wbr>\n</p><${parentTagName}
1162
- data-tight="true"${dataMarker} data-block="0"${startAttribute}>${afterHTML}</${parentTagName}>`;
1163
- } else {
1164
- // 任务列表下方无任务列表元素
1165
- taskItemElement.parentElement.insertAdjacentHTML("afterend", `<p data-block="0"><wbr>\n</p>`);
1166
- if (taskItemElement.parentElement.querySelectorAll("li").length === 1) {
1167
- // 任务列表仅有一项时,使用 p 元素替换
1168
- taskItemElement.parentElement.remove();
1169
- } else {
1170
- // 任务列表有多项时,当前任务列表位于最后一项,移除该任务列表
1171
- taskItemElement.remove();
1172
- }
1173
- }
1174
- }
1175
- } else if (startContainer.nodeType !== 3 && range.startOffset === 0 &&
1176
- (startContainer.firstChild as HTMLElement).tagName === "INPUT") {
1177
- // 光标位于 input 之前
1178
- range.setStart(startContainer.childNodes[1], 1);
1179
- } else {
1180
- // 当前任务列表有文字,光标后的文字需添加到新任务列表中
1181
- range.setEndAfter(taskItemElement.lastChild);
1182
- taskItemElement.insertAdjacentHTML("afterend", `<li class="vditor-task" data-marker="${taskItemElement.getAttribute("data-marker")}"><input type="checkbox"> <wbr></li>`);
1183
- document.querySelector("wbr").after(range.extractContents());
1184
- }
1185
- setRangeByWbr(vditor[vditor.currentMode].element, range);
1186
- execAfterRender(vditor);
1187
- scrollCenter(vditor);
1188
- event.preventDefault();
1189
- return true;
1190
- }
1191
- }
1192
- return false;
1193
- };
1194
-
1195
- export const fixDelete = (vditor: IVditor, range: Range, event: KeyboardEvent, pElement: HTMLElement | false) => {
1196
- if (range.startContainer.nodeType !== 3) {
1197
- // 光标位于 hr 前,hr 前有内容
1198
- const rangeElement = (range.startContainer as HTMLElement).children[range.startOffset];
1199
- if (rangeElement && rangeElement.tagName === "HR") {
1200
- range.selectNodeContents(rangeElement.previousElementSibling);
1201
- range.collapse(false);
1202
- event.preventDefault();
1203
- return true;
1204
- }
1205
- }
1206
-
1207
- if (pElement) {
1208
- const previousElement = pElement.previousElementSibling;
1209
- if (previousElement && getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start === 0 &&
1210
- ((isFirefox() && previousElement.tagName === "HR") || previousElement.tagName === "TABLE")) {
1211
- if (previousElement.tagName === "TABLE") {
1212
- // table 后删除 https://github.com/Vanessa219/vditor/issues/243
1213
- const lastCellElement = previousElement.lastElementChild.lastElementChild.lastElementChild;
1214
- lastCellElement.innerHTML =
1215
- lastCellElement.innerHTML.trimLeft() + "<wbr>" + pElement.textContent.trim();
1216
- pElement.remove();
1217
- } else {
1218
- // 光标位于 hr 后进行删除
1219
- previousElement.remove();
1220
- }
1221
- setRangeByWbr(vditor[vditor.currentMode].element, range);
1222
- execAfterRender(vditor);
1223
- event.preventDefault();
1224
- return true;
1225
- }
1226
- }
1227
- return false;
1228
- };
1229
-
1230
- export const fixHR = (range: Range) => {
1231
- if (isFirefox() && range.startContainer.nodeType !== 3 &&
1232
- (range.startContainer as HTMLElement).tagName === "HR") {
1233
- range.setStartBefore(range.startContainer);
1234
- }
1235
- };
1236
-
1237
- // firefox https://github.com/Vanessa219/vditor/issues/407
1238
- export const fixFirefoxArrowUpTable = (event: KeyboardEvent, blockElement: false | HTMLElement, range: Range) => {
1239
- if (!isFirefox()) {
1240
- return false;
1241
- }
1242
- if (event.key === "ArrowUp" && blockElement && blockElement.previousElementSibling?.tagName === "TABLE") {
1243
- const tableElement = blockElement.previousElementSibling as HTMLTableElement;
1244
- range.selectNodeContents(tableElement.rows[tableElement.rows.length - 1].lastElementChild);
1245
- range.collapse(false);
1246
- event.preventDefault();
1247
- return true;
1248
- }
1249
- if (event.key === "ArrowDown" && blockElement && blockElement.nextElementSibling?.tagName === "TABLE") {
1250
- range.selectNodeContents((blockElement.nextElementSibling as HTMLTableElement).rows[0].cells[0]);
1251
- range.collapse(true);
1252
- event.preventDefault();
1253
- return true;
1254
- }
1255
- return false;
1256
- };
1257
-
1258
- export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent) & {target: HTMLElement}, callback: {
1259
- pasteCode(code: string): void,
1260
- }) => {
1261
- if (vditor[vditor.currentMode].element.getAttribute("contenteditable") !== "true") {
1262
- return;
1263
- }
1264
- event.stopPropagation();
1265
- event.preventDefault();
1266
- let textHTML;
1267
- let textPlain;
1268
- let files;
1269
-
1270
- if ("clipboardData" in event) {
1271
- textHTML = event.clipboardData.getData("text/html");
1272
- textPlain = event.clipboardData.getData("text/plain");
1273
- files = event.clipboardData.files;
1274
- } else {
1275
- textHTML = event.dataTransfer.getData("text/html");
1276
- textPlain = event.dataTransfer.getData("text/plain");
1277
- if (event.dataTransfer.types.includes("Files")) {
1278
- files = event.dataTransfer.items;
1279
- }
1280
- }
1281
- const renderers: {
1282
- HTML2VditorDOM?: ILuteRender,
1283
- HTML2VditorIRDOM?: ILuteRender,
1284
- Md2VditorDOM?: ILuteRender,
1285
- Md2VditorIRDOM?: ILuteRender,
1286
- Md2VditorSVDOM?: ILuteRender,
1287
- } = {};
1288
- const renderLinkDest: ILuteRenderCallback = (node, entering) => {
1289
- if (!entering) {
1290
- return ["", Lute.WalkContinue];
1291
- }
1292
-
1293
- if (vditor.options.upload.renderLinkDest) {
1294
- return vditor.options.upload.renderLinkDest(vditor, node, entering);
1295
- }
1296
-
1297
- const src = node.TokensStr();
1298
- if (node.__internal_object__.Parent.Type === 34 && src && src.indexOf("file://") === -1 &&
1299
- vditor.options.upload.linkToImgUrl) {
1300
- const xhr = new XMLHttpRequest();
1301
- xhr.open("POST", vditor.options.upload.linkToImgUrl);
1302
- if (vditor.options.upload.token) {
1303
- xhr.setRequestHeader("X-Upload-Token", vditor.options.upload.token);
1304
- }
1305
- if (vditor.options.upload.withCredentials) {
1306
- xhr.withCredentials = true;
1307
- }
1308
- setHeaders(vditor, xhr);
1309
- xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
1310
- xhr.onreadystatechange = () => {
1311
- if (xhr.readyState === XMLHttpRequest.DONE) {
1312
- if (xhr.status === 200) {
1313
- let responseText = xhr.responseText;
1314
- if (vditor.options.upload.linkToImgFormat) {
1315
- responseText = vditor.options.upload.linkToImgFormat(xhr.responseText);
1316
- }
1317
- const responseJSON = JSON.parse(responseText);
1318
- if (responseJSON.code !== 0) {
1319
- vditor.tip.show(responseJSON.msg);
1320
- return;
1321
- }
1322
- const original = responseJSON.data.originalURL;
1323
- if (vditor.currentMode === "sv") {
1324
- vditor.sv.element.querySelectorAll(".vditor-sv__marker--link")
1325
- .forEach((item: HTMLElement) => {
1326
- if (item.textContent === original) {
1327
- item.textContent = responseJSON.data.url;
1328
- }
1329
- });
1330
- } else {
1331
- const imgElement: HTMLImageElement =
1332
- vditor[vditor.currentMode].element.querySelector(`img[src="${original}"]`);
1333
- imgElement.src = responseJSON.data.url;
1334
- if (vditor.currentMode === "ir") {
1335
- imgElement.previousElementSibling.previousElementSibling.innerHTML =
1336
- responseJSON.data.url;
1337
- }
1338
- }
1339
- execAfterRender(vditor);
1340
- } else {
1341
- vditor.tip.show(xhr.responseText);
1342
- }
1343
- if (vditor.options.upload.linkToImgCallback) {
1344
- vditor.options.upload.linkToImgCallback(xhr.responseText);
1345
- }
1346
- }
1347
- };
1348
- xhr.send(JSON.stringify({url: src}));
1349
- }
1350
- if (vditor.currentMode === "ir") {
1351
- return [`<span class="vditor-ir__marker vditor-ir__marker--link">${Lute.EscapeHTMLStr(src)}</span>`, Lute.WalkContinue];
1352
- } else if (vditor.currentMode === "wysiwyg") {
1353
- return ["", Lute.WalkContinue];
1354
- } else {
1355
- return [`<span class="vditor-sv__marker--link">${Lute.EscapeHTMLStr(src)}</span>`, Lute.WalkContinue];
1356
- }
1357
- };
1358
-
1359
- // 浏览器地址栏拷贝处理
1360
- if (textHTML.replace(/&amp;/g, "&").replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() ===
1361
- `<a href="${textPlain}">${textPlain}</a>` ||
1362
- textHTML.replace(/&amp;/g, "&").replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() ===
1363
- `<!--StartFragment--><a href="${textPlain}">${textPlain}</a><!--EndFragment-->`) {
1364
- textHTML = "";
1365
- }
1366
-
1367
- // process word
1368
- const doc = new DOMParser().parseFromString(textHTML, "text/html");
1369
- if (doc.body) {
1370
- textHTML = doc.body.innerHTML;
1371
- }
1372
- textHTML = Lute.Sanitize(textHTML);
1373
- vditor.wysiwyg.getComments(vditor);
1374
-
1375
- // process code
1376
- const height = vditor[vditor.currentMode].element.scrollHeight;
1377
- const code = processPasteCode(textHTML, textPlain, vditor.currentMode);
1378
- const codeElement = vditor.currentMode === "sv" ?
1379
- hasClosestByAttribute(event.target, "data-type", "code-block") :
1380
- hasClosestByMatchTag(event.target, "CODE");
1381
- if (codeElement) {
1382
- // 粘贴在代码位置
1383
- if (vditor.currentMode === "sv") {
1384
- document.execCommand("insertHTML", false, textPlain.replace(/&/g, "&amp;").replace(/</g, "&lt;"));
1385
- } else {
1386
- const position = getSelectPosition(event.target, vditor[vditor.currentMode].element);
1387
- if (codeElement.parentElement.tagName !== "PRE") {
1388
- // https://github.com/Vanessa219/vditor/issues/463
1389
- textPlain += Constants.ZWSP;
1390
- }
1391
- codeElement.textContent = codeElement.textContent.substring(0, position.start)
1392
- + textPlain + codeElement.textContent.substring(position.end);
1393
- setSelectionByPosition(position.start + textPlain.length, position.start + textPlain.length,
1394
- codeElement.parentElement);
1395
- if (codeElement.parentElement?.nextElementSibling.classList
1396
- .contains(`vditor-${vditor.currentMode}__preview`)) {
1397
- codeElement.parentElement.nextElementSibling.innerHTML = codeElement.outerHTML;
1398
- processCodeRender(codeElement.parentElement.nextElementSibling as HTMLElement, vditor);
1399
- }
1400
- }
1401
- } else if (code) {
1402
- callback.pasteCode(code);
1403
- } else {
1404
- if (textHTML.trim() !== "") {
1405
- const tempElement = document.createElement("div");
1406
- tempElement.innerHTML = textHTML;
1407
- if (!vditor.options.upload.base64ToLink) {
1408
- // word 复制的图文混合,替换为 link: <v:imagedata src="file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image001.png" o:title="">
1409
- await processVMLImage(vditor, tempElement, ("clipboardData" in event ? event.clipboardData : event.dataTransfer).getData("text/rtf"));
1410
- }
1411
-
1412
- tempElement.querySelectorAll("[style]").forEach((e) => {
1413
- e.removeAttribute("style");
1414
- });
1415
- tempElement.querySelectorAll(".vditor-copy").forEach((e) => {
1416
- e.remove();
1417
- });
1418
- if (vditor.currentMode === "ir") {
1419
- renderers.HTML2VditorIRDOM = {renderLinkDest};
1420
- vditor.lute.SetJSRenderers({renderers});
1421
- insertHTML(vditor.lute.HTML2VditorIRDOM(tempElement.innerHTML), vditor);
1422
- } else if (vditor.currentMode === "wysiwyg") {
1423
- renderers.HTML2VditorDOM = {renderLinkDest};
1424
- vditor.lute.SetJSRenderers({renderers});
1425
- insertHTML(vditor.lute.HTML2VditorDOM(tempElement.innerHTML), vditor);
1426
- } else {
1427
- renderers.Md2VditorSVDOM = {renderLinkDest};
1428
- vditor.lute.SetJSRenderers({renderers});
1429
- processPaste(vditor, vditor.lute.HTML2Md(tempElement.innerHTML).trimRight());
1430
- }
1431
- vditor.outline.render(vditor);
1432
- } else if (files.length > 0) {
1433
- if (vditor.options.upload.url || vditor.options.upload.handler) {
1434
- await uploadFiles(vditor, files);
1435
- } else {
1436
- const fileReader = new FileReader();
1437
- let file: File;
1438
- if ("clipboardData" in event) {
1439
- files = event.clipboardData.files;
1440
- file = files[0];
1441
- } else if (event.dataTransfer.types.includes("Files")) {
1442
- files = event.dataTransfer.items;
1443
- file = files[0].getAsFile();
1444
- }
1445
- if (file && file.type.startsWith("image")) {
1446
- fileReader.readAsDataURL(file);
1447
- fileReader.onload = () => {
1448
- let imgHTML = "";
1449
- if (vditor.currentMode === "wysiwyg") {
1450
- imgHTML += `<img alt="${file.name}" src="${fileReader.result.toString()}">\n`;
1451
- } else {
1452
- imgHTML += `![${file.name}](${fileReader.result.toString()})\n`;
1453
- }
1454
- document.execCommand("insertHTML", false, imgHTML);
1455
- };
1456
- }
1457
- }
1458
- } else if (textPlain.trim() !== "" && files.length === 0) {
1459
- const range = getEditorRange(vditor);
1460
- if (range.toString() !== "" && vditor.lute.IsValidLinkDest(textPlain)) {
1461
- textPlain = `[${range.toString()}](${textPlain})`;
1462
- }
1463
- if (vditor.currentMode === "ir") {
1464
- renderers.Md2VditorIRDOM = {renderLinkDest};
1465
- vditor.lute.SetJSRenderers({renderers});
1466
- insertHTML(Lute.Sanitize(vditor.lute.Md2VditorIRDOM(textPlain)), vditor);
1467
- } else if (vditor.currentMode === "wysiwyg") {
1468
- renderers.Md2VditorDOM = {renderLinkDest};
1469
- vditor.lute.SetJSRenderers({renderers});
1470
- insertHTML(Lute.Sanitize(vditor.lute.Md2VditorDOM(textPlain)), vditor);
1471
- } else {
1472
- renderers.Md2VditorSVDOM = {renderLinkDest};
1473
- vditor.lute.SetJSRenderers({renderers});
1474
- processPaste(vditor, textPlain);
1475
- }
1476
- vditor.outline.render(vditor);
1477
- }
1478
- }
1479
- if (vditor.currentMode !== "sv") {
1480
- const blockElement = hasClosestBlock(getEditorRange(vditor).startContainer);
1481
- if (blockElement) {
1482
- // https://github.com/Vanessa219/vditor/issues/591
1483
- const range = getEditorRange(vditor);
1484
- vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
1485
- wbr.remove();
1486
- });
1487
- range.insertNode(document.createElement("wbr"));
1488
- if (vditor.currentMode === "wysiwyg") {
1489
- blockElement.outerHTML = vditor.lute.SpinVditorDOM(blockElement.outerHTML);
1490
- } else {
1491
- blockElement.outerHTML = vditor.lute.SpinVditorIRDOM(blockElement.outerHTML);
1492
- }
1493
- setRangeByWbr(vditor[vditor.currentMode].element, range);
1494
- }
1495
- vditor[vditor.currentMode].element.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`)
1496
- .forEach((item: HTMLElement) => {
1497
- processCodeRender(item, vditor);
1498
- });
1499
- }
1500
- vditor.wysiwyg.triggerRemoveComment(vditor);
1501
- execAfterRender(vditor);
1502
- if (vditor[vditor.currentMode].element.scrollHeight - height >
1503
- Math.min(vditor[vditor.currentMode].element.clientHeight, window.innerHeight) / 2) {
1504
- scrollCenter(vditor);
1505
- }
1506
- };
1507
-
1508
- const processVMLImage = async (vditor: IVditor, root: Element, rtfData: string) => {
1509
- if (!rtfData) {
1510
- return;
1511
-
1512
- }
1513
-
1514
- const regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
1515
- const regexPicture = new RegExp("(?:(" + regexPictureHeader.source + "))([\\da-fA-F\\s]+)\\}", "g");
1516
- const regImages = rtfData.match(regexPicture);
1517
- const images = [];
1518
- if (regImages) {
1519
- for (const image of regImages) {
1520
- let imageType;
1521
-
1522
- if (image.includes("\\pngblip")) {
1523
- imageType = "image/png";
1524
- } else if (image.includes("\\jpegblip")) {
1525
- imageType = "image/jpeg";
1526
- }
1527
-
1528
- if (imageType) {
1529
- images.push({
1530
- hex: image.replace(regexPictureHeader, "").replace(/[^\da-fA-F]/g, ""),
1531
- type: imageType,
1532
- });
1533
- }
1534
- }
1535
- }
1536
-
1537
- const shapes: Array<{shape: Element, img: Element}> = [];
1538
- walk(root, (child: Element) => {
1539
- if (child.tagName === "V:SHAPE") {
1540
- walk(child, (sub) => {
1541
- if (sub.tagName === "V:IMAGEDATA") shapes.push({shape: child, img: sub});
1542
- });
1543
- return false;
1544
- }
1545
- });
1546
- for (let i = 0; i < shapes.length; i++) {
1547
- const img = document.createElement("img");
1548
- const newSrc = "data:" + images[i].type + ";base64," + btoa((images[i].hex.match(/\w{2}/g) || []).map(char => {
1549
- return String.fromCharCode(parseInt(char, 16));
1550
- }).join(""));
1551
- img.src = newSrc;
1552
- img.title = shapes[i].img.getAttribute("title");
1553
- shapes[i].shape.parentNode.replaceChild(img, shapes[i].shape);
1554
- }
1555
-
1556
- const imgs = root.querySelectorAll("img");
1557
- for (let i = 0; i < imgs.length; i++) {
1558
- const src = imgs[i].src || "";
1559
- if (src) imgs[i].src = await vditor.options.upload.base64ToLink(src);
1560
- }
1561
- };
1562
-
1563
- const walk = (el: Element, fn: (el: Element) => boolean | void) => {
1564
- const goNext = fn(el);
1565
- if (goNext !== false)
1566
- for (let i = 0; i < el.children.length; i++) {
1567
- walk(el.children[i], fn);
1568
- }
1569
- };
1
+ import {Constants} from "../constants";
2
+ import {input as IRInput} from "../ir/input";
3
+ import {processAfterRender} from "../ir/process";
4
+ import {processAfterRender as processSVAfterRender, processPaste} from "../sv/process";
5
+ import {uploadFiles} from "../upload/index";
6
+ import {setHeaders} from "../upload/setHeaders";
7
+ import {afterRenderEvent} from "../wysiwyg/afterRenderEvent";
8
+ import {input} from "../wysiwyg/input";
9
+ import {isCtrl, isFirefox} from "./compatibility";
10
+ import {scrollCenter} from "./editorCommonEvent";
11
+ import {
12
+ getTopList,
13
+ hasClosestBlock,
14
+ hasClosestByAttribute,
15
+ hasClosestByClassName,
16
+ hasClosestByMatchTag,
17
+ } from "./hasClosest";
18
+ import {getLastNode} from "./hasClosest";
19
+ import {highlightToolbar} from "./highlightToolbar";
20
+ import {matchHotKey} from "./hotKey";
21
+ import {processCodeRender, processPasteCode} from "./processCode";
22
+ import {
23
+ getEditorRange,
24
+ getSelectPosition,
25
+ insertHTML,
26
+ setRangeByWbr,
27
+ setSelectionByPosition, setSelectionFocus,
28
+ } from "./selection";
29
+
30
+ // https://github.com/Vanessa219/vditor/issues/508 软键盘无法删除空块
31
+ export const fixGSKeyBackspace = (event: KeyboardEvent, vditor: IVditor, startContainer: Node) => {
32
+ if (event.keyCode === 229 && event.code === "" && event.key === "Unidentified" && vditor.currentMode !== "sv") {
33
+ const blockElement = hasClosestBlock(startContainer);
34
+ // 移动端的标点符号都显示为 299,因此需限定为空删除的条件
35
+ if (blockElement && blockElement.textContent.trim() === "") {
36
+ vditor[vditor.currentMode].composingLock = true;
37
+ return false;
38
+ }
39
+ }
40
+ return true;
41
+ };
42
+
43
+ // https://github.com/Vanessa219/vditor/issues/361 代码块后输入中文
44
+ export const fixCJKPosition = (range: Range, vditor: IVditor, event: KeyboardEvent) => {
45
+ if (event.key === "Enter" || event.key === "Tab" || event.key === "Backspace" || event.key.indexOf("Arrow") > -1
46
+ || isCtrl(event) || event.key === "Escape" || event.shiftKey || event.altKey) {
47
+ return;
48
+ }
49
+ const pLiElement = hasClosestByMatchTag(range.startContainer, "P") ||
50
+ hasClosestByMatchTag(range.startContainer, "LI");
51
+ if (pLiElement && getSelectPosition(pLiElement, vditor[vditor.currentMode].element, range).start === 0) {
52
+
53
+ // https://github.com/Vanessa219/vditor/issues/1289 WKWebView切换输入法产生六分之一空格,造成光标错位
54
+ if (pLiElement.nodeValue) {
55
+ pLiElement.nodeValue = pLiElement.nodeValue.replace(/\u2006/g, "");
56
+ }
57
+
58
+ const zwspNode = document.createTextNode(Constants.ZWSP);
59
+ range.insertNode(zwspNode);
60
+ range.setStartAfter(zwspNode);
61
+ }
62
+ };
63
+
64
+ // https://github.com/Vanessa219/vditor/issues/381 光标在内联数学公式中无法向下移动
65
+ export const fixCursorDownInlineMath = (range: Range, key: string) => {
66
+ if (key === "ArrowDown" || key === "ArrowUp") {
67
+ const inlineElement = hasClosestByAttribute(range.startContainer, "data-type", "math-inline") ||
68
+ hasClosestByAttribute(range.startContainer, "data-type", "html-entity") ||
69
+ hasClosestByAttribute(range.startContainer, "data-type", "html-inline");
70
+ if (inlineElement) {
71
+ if (key === "ArrowDown") {
72
+ range.setStartAfter(inlineElement.parentElement);
73
+ }
74
+ if (key === "ArrowUp") {
75
+ range.setStartBefore(inlineElement.parentElement);
76
+ }
77
+ }
78
+ }
79
+ };
80
+
81
+ export const insertEmptyBlock = (vditor: IVditor, position: InsertPosition) => {
82
+ const range = getEditorRange(vditor);
83
+ const blockElement = hasClosestBlock(range.startContainer);
84
+ if (blockElement) {
85
+ blockElement.insertAdjacentHTML(position, `<p data-block="0">${Constants.ZWSP}<wbr>\n</p>`);
86
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
87
+ highlightToolbar(vditor);
88
+ execAfterRender(vditor);
89
+ }
90
+ };
91
+
92
+ export const isFirstCell = (cellElement: HTMLElement) => {
93
+ const tableElement = hasClosestByMatchTag(cellElement, "TABLE") as HTMLTableElement;
94
+ if (tableElement && tableElement.rows[0].cells[0].isSameNode(cellElement)) {
95
+ return tableElement;
96
+ }
97
+ return false;
98
+ };
99
+
100
+ export const isLastCell = (cellElement: HTMLElement) => {
101
+ const tableElement = hasClosestByMatchTag(cellElement, "TABLE") as HTMLTableElement;
102
+ if (tableElement && tableElement.lastElementChild.lastElementChild.lastElementChild.isSameNode(cellElement)) {
103
+ return tableElement;
104
+ }
105
+ return false;
106
+ };
107
+
108
+ // 光标设置到前一个表格中
109
+ const goPreviousCell = (cellElement: HTMLElement, range: Range, isSelected = true) => {
110
+ let previousElement = cellElement.previousElementSibling;
111
+ if (!previousElement) {
112
+ if (cellElement.parentElement.previousElementSibling) {
113
+ previousElement = cellElement.parentElement.previousElementSibling.lastElementChild;
114
+ } else if (cellElement.parentElement.parentElement.tagName === "TBODY" &&
115
+ cellElement.parentElement.parentElement.previousElementSibling) {
116
+ previousElement = cellElement.parentElement
117
+ .parentElement.previousElementSibling.lastElementChild.lastElementChild;
118
+ } else {
119
+ previousElement = null;
120
+ }
121
+ }
122
+ if (previousElement) {
123
+ range.selectNodeContents(previousElement);
124
+ if (!isSelected) {
125
+ range.collapse(false);
126
+ }
127
+ setSelectionFocus(range);
128
+ }
129
+ return previousElement;
130
+ };
131
+
132
+ export const insertAfterBlock = (vditor: IVditor, event: KeyboardEvent, range: Range, element: HTMLElement,
133
+ blockElement: HTMLElement) => {
134
+ const position = getSelectPosition(element, vditor[vditor.currentMode].element, range);
135
+ if ((event.key === "ArrowDown" && element.textContent.trimRight().substr(position.start).indexOf("\n") === -1) ||
136
+ (event.key === "ArrowRight" && position.start >= element.textContent.trimRight().length)) {
137
+ const nextElement = blockElement.nextElementSibling;
138
+ if (!nextElement ||
139
+ (nextElement && (nextElement.tagName === "TABLE" || nextElement.getAttribute("data-type")))) {
140
+ blockElement.insertAdjacentHTML("afterend",
141
+ `<p data-block="0">${Constants.ZWSP}<wbr></p>`);
142
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
143
+ } else {
144
+ range.selectNodeContents(nextElement);
145
+ range.collapse(true);
146
+ setSelectionFocus(range);
147
+ }
148
+ event.preventDefault();
149
+ return true;
150
+ }
151
+ return false;
152
+ };
153
+
154
+ export const insertBeforeBlock = (vditor: IVditor, event: KeyboardEvent, range: Range, element: HTMLElement,
155
+ blockElement: HTMLElement) => {
156
+ const position = getSelectPosition(element, vditor[vditor.currentMode].element, range);
157
+ if ((event.key === "ArrowUp" && element.textContent.substr(0, position.start).indexOf("\n") === -1) ||
158
+ ((event.key === "ArrowLeft" || (event.key === "Backspace" && range.toString() === "")) &&
159
+ position.start === 0)) {
160
+ const previousElement = blockElement.previousElementSibling;
161
+ // table || code
162
+ if (!previousElement ||
163
+ (previousElement && (previousElement.tagName === "TABLE" || previousElement.getAttribute("data-type")))) {
164
+ blockElement.insertAdjacentHTML("beforebegin",
165
+ `<p data-block="0">${Constants.ZWSP}<wbr></p>`);
166
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
167
+ } else {
168
+ range.selectNodeContents(previousElement);
169
+ range.collapse(false);
170
+ setSelectionFocus(range);
171
+ }
172
+ event.preventDefault();
173
+ return true;
174
+ }
175
+ return false;
176
+ };
177
+
178
+ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel = true) => {
179
+ const itemElement = hasClosestByMatchTag(range.startContainer, "LI");
180
+ vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
181
+ wbr.remove();
182
+ });
183
+ range.insertNode(document.createElement("wbr"));
184
+
185
+ if (cancel && itemElement) {
186
+ // 取消
187
+ let pHTML = "";
188
+ for (let i = 0; i < itemElement.parentElement.childElementCount; i++) {
189
+ const inputElement = itemElement.parentElement.children[i].querySelector("input");
190
+ if (inputElement) {
191
+ inputElement.remove();
192
+ }
193
+ pHTML += `<p data-block="0">${itemElement.parentElement.children[i].innerHTML.trimLeft()}</p>`;
194
+ }
195
+ itemElement.parentElement.insertAdjacentHTML("beforebegin", pHTML);
196
+ itemElement.parentElement.remove();
197
+ } else {
198
+ if (!itemElement) {
199
+ // 添加
200
+ let blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
201
+ if (!blockElement) {
202
+ vditor[vditor.currentMode].element.querySelector("wbr").remove();
203
+ blockElement = vditor[vditor.currentMode].element.querySelector("p");
204
+ blockElement.innerHTML = "<wbr>";
205
+ }
206
+ if (type === "check") {
207
+ blockElement.insertAdjacentHTML("beforebegin",
208
+ `<ul data-block="0"><li class="vditor-task"><input type="checkbox" /> ${blockElement.innerHTML}</li></ul>`);
209
+ blockElement.remove();
210
+ } else if (type === "list") {
211
+ blockElement.insertAdjacentHTML("beforebegin",
212
+ `<ul data-block="0"><li>${blockElement.innerHTML}</li></ul>`);
213
+ blockElement.remove();
214
+ } else if (type === "ordered-list") {
215
+ blockElement.insertAdjacentHTML("beforebegin",
216
+ `<ol data-block="0"><li>${blockElement.innerHTML}</li></ol>`);
217
+ blockElement.remove();
218
+ }
219
+ } else {
220
+ // 切换
221
+ if (type === "check") {
222
+ itemElement.parentElement.querySelectorAll("li").forEach((item) => {
223
+ item.insertAdjacentHTML("afterbegin",
224
+ `<input type="checkbox" />${item.textContent.indexOf(" ") === 0 ? "" : " "}`);
225
+ item.classList.add("vditor-task");
226
+ });
227
+ } else {
228
+ if (itemElement.querySelector("input")) {
229
+ itemElement.parentElement.querySelectorAll("li").forEach((item) => {
230
+ item.querySelector("input").remove();
231
+ item.classList.remove("vditor-task");
232
+ });
233
+ }
234
+ let element;
235
+ if (type === "list") {
236
+ element = document.createElement("ul");
237
+ element.setAttribute("data-marker", "*");
238
+ } else {
239
+ element = document.createElement("ol");
240
+ element.setAttribute("data-marker", "1.");
241
+ }
242
+ element.setAttribute("data-block", "0");
243
+ element.setAttribute("data-tight", itemElement.parentElement.getAttribute("data-tight"));
244
+ element.innerHTML = itemElement.parentElement.innerHTML;
245
+ itemElement.parentElement.parentNode.replaceChild(element, itemElement.parentElement);
246
+ }
247
+ }
248
+ }
249
+ };
250
+
251
+ export const listIndent = (vditor: IVditor, liElement: HTMLElement, range: Range) => {
252
+ const previousElement = liElement.previousElementSibling;
253
+ if (liElement && previousElement) {
254
+ const liElements: HTMLElement[] = [liElement];
255
+ Array.from(range.cloneContents().children).forEach((item, index) => {
256
+ if (item.nodeType !== 3 && liElement && item.textContent.trim() !== ""
257
+ && liElement.getAttribute("data-node-id") === item.getAttribute("data-node-id")) {
258
+ if (index !== 0) {
259
+ liElements.push(liElement);
260
+ }
261
+ liElement = liElement.nextElementSibling as HTMLElement;
262
+ }
263
+ });
264
+
265
+ vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
266
+ wbr.remove();
267
+ });
268
+ range.insertNode(document.createElement("wbr"));
269
+ const liParentElement = previousElement.parentElement;
270
+
271
+ let liHTML = "";
272
+ liElements.forEach((item: HTMLElement) => {
273
+ let marker = item.getAttribute("data-marker");
274
+ if (marker.length !== 1) {
275
+ marker = `1${marker.slice(-1)}`;
276
+ }
277
+ liHTML += `<li data-node-id="${item.getAttribute("data-node-id")}" data-marker="${marker}">${item.innerHTML}</li>`;
278
+ item.remove();
279
+ });
280
+ previousElement.insertAdjacentHTML("beforeend",
281
+ `<${liParentElement.tagName} data-block="0">${liHTML}</${liParentElement.tagName}>`);
282
+
283
+ if (vditor.currentMode === "wysiwyg") {
284
+ liParentElement.outerHTML = vditor.lute.SpinVditorDOM(liParentElement.outerHTML);
285
+ } else {
286
+ liParentElement.outerHTML = vditor.lute.SpinVditorIRDOM(liParentElement.outerHTML);
287
+ }
288
+
289
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
290
+ const tempTopListElement = getTopList(range.startContainer);
291
+ if (tempTopListElement) {
292
+ tempTopListElement.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`)
293
+ .forEach((item: HTMLElement) => {
294
+ processCodeRender(item, vditor);
295
+ if (vditor.currentMode === "wysiwyg") {
296
+ item.previousElementSibling.setAttribute("style", "display:none");
297
+ }
298
+ });
299
+ }
300
+ execAfterRender(vditor);
301
+ highlightToolbar(vditor);
302
+ } else {
303
+ vditor[vditor.currentMode].element.focus();
304
+ }
305
+ };
306
+
307
+ export const listOutdent = (vditor: IVditor, liElement: HTMLElement, range: Range, topListElement: HTMLElement) => {
308
+ const liParentLiElement = hasClosestByMatchTag(liElement.parentElement, "LI");
309
+ if (liParentLiElement) {
310
+ vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
311
+ wbr.remove();
312
+ });
313
+ range.insertNode(document.createElement("wbr"));
314
+
315
+ const liParentElement = liElement.parentElement;
316
+ const liParentAfterElement = liParentElement.cloneNode() as HTMLElement;
317
+ const liElements: HTMLElement[] = [liElement];
318
+ Array.from(range.cloneContents().children).forEach((item, index) => {
319
+ if (item.nodeType !== 3 && liElement && item.textContent.trim() !== "" &&
320
+ liElement.getAttribute("data-node-id") === item.getAttribute("data-node-id")) {
321
+ if (index !== 0) {
322
+ liElements.push(liElement);
323
+ }
324
+ liElement = liElement.nextElementSibling as HTMLElement;
325
+ }
326
+ });
327
+ let isMatch = false;
328
+ let afterHTML = "";
329
+ liParentElement.querySelectorAll("li").forEach((item) => {
330
+ if (isMatch) {
331
+ afterHTML += item.outerHTML;
332
+ if (!item.nextElementSibling && !item.previousElementSibling) {
333
+ item.parentElement.remove();
334
+ } else {
335
+ item.remove();
336
+ }
337
+ }
338
+ if (item.isSameNode(liElements[liElements.length - 1])) {
339
+ isMatch = true;
340
+ }
341
+ });
342
+
343
+ liElements.reverse().forEach((item) => {
344
+ liParentLiElement.insertAdjacentElement("afterend", item);
345
+ });
346
+
347
+ if (afterHTML) {
348
+ liParentAfterElement.innerHTML = afterHTML;
349
+ liElements[0].insertAdjacentElement("beforeend", liParentAfterElement);
350
+ }
351
+
352
+ if (vditor.currentMode === "wysiwyg") {
353
+ topListElement.outerHTML = vditor.lute.SpinVditorDOM(topListElement.outerHTML);
354
+ } else {
355
+ topListElement.outerHTML = vditor.lute.SpinVditorIRDOM(topListElement.outerHTML);
356
+ }
357
+
358
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
359
+ const tempTopListElement = getTopList(range.startContainer);
360
+ if (tempTopListElement) {
361
+ tempTopListElement.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`)
362
+ .forEach((item: HTMLElement) => {
363
+ processCodeRender(item, vditor);
364
+ if (vditor.currentMode === "wysiwyg") {
365
+ item.previousElementSibling.setAttribute("style", "display:none");
366
+ }
367
+ });
368
+ }
369
+ execAfterRender(vditor);
370
+ highlightToolbar(vditor);
371
+ } else {
372
+ vditor[vditor.currentMode].element.focus();
373
+ }
374
+ };
375
+
376
+ export const setTableAlign = (tableElement: HTMLTableElement, type: string) => {
377
+ const cell = getSelection().getRangeAt(0).startContainer.parentElement;
378
+
379
+ const columnCnt = tableElement.rows[0].cells.length;
380
+ const rowCnt = tableElement.rows.length;
381
+ let currentColumn = 0;
382
+
383
+ for (let i = 0; i < rowCnt; i++) {
384
+ for (let j = 0; j < columnCnt; j++) {
385
+ if (tableElement.rows[i].cells[j].isSameNode(cell)) {
386
+ currentColumn = j;
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ for (let k = 0; k < rowCnt; k++) {
392
+ tableElement.rows[k].cells[currentColumn].setAttribute("align", type);
393
+ }
394
+ };
395
+
396
+ export const isHrMD = (text: string) => {
397
+ // - _ *
398
+ const marker = text.trimRight().split("\n").pop();
399
+ if (marker === "") {
400
+ return false;
401
+ }
402
+ if (marker.replace(/ |-/g, "") === ""
403
+ || marker.replace(/ |_/g, "") === ""
404
+ || marker.replace(/ |\*/g, "") === "") {
405
+ if (marker.replace(/ /g, "").length > 2) {
406
+ if (marker.indexOf("-") > -1 && marker.trimLeft().indexOf(" ") === -1
407
+ && text.trimRight().split("\n").length > 1) {
408
+ // 满足 heading
409
+ return false;
410
+ }
411
+ if (marker.indexOf(" ") === 0 || marker.indexOf("\t") === 0) {
412
+ // 代码块
413
+ return false;
414
+ }
415
+ return true;
416
+ }
417
+ return false;
418
+ }
419
+ return false;
420
+ };
421
+
422
+ export const isHeadingMD = (text: string) => {
423
+ // - =
424
+ const textArray = text.trimRight().split("\n");
425
+ text = textArray.pop();
426
+
427
+ if (text.indexOf(" ") === 0 || text.indexOf("\t") === 0) {
428
+ return false;
429
+ }
430
+
431
+ text = text.trimLeft();
432
+ if (text === "" || textArray.length === 0) {
433
+ return false;
434
+ }
435
+ if (text.replace(/-/g, "") === ""
436
+ || text.replace(/=/g, "") === "") {
437
+ return true;
438
+ }
439
+ return false;
440
+ };
441
+
442
+ export const execAfterRender = (vditor: IVditor, options = {
443
+ enableAddUndoStack: true,
444
+ enableHint: false,
445
+ enableInput: true,
446
+ }) => {
447
+ if (vditor.currentMode === "wysiwyg") {
448
+ afterRenderEvent(vditor, options);
449
+ } else if (vditor.currentMode === "ir") {
450
+ processAfterRender(vditor, options);
451
+ } else if (vditor.currentMode === "sv") {
452
+ processSVAfterRender(vditor, options);
453
+ }
454
+ };
455
+
456
+ export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | false, event: KeyboardEvent) => {
457
+ const startContainer = range.startContainer;
458
+ const liElement = hasClosestByMatchTag(startContainer, "LI");
459
+ if (liElement) {
460
+ if (!isCtrl(event) && !event.altKey && event.key === "Enter" &&
461
+ // fix li 中有多个 P 时,在第一个 P 中换行会在下方生成新的 li
462
+ (!event.shiftKey && pElement && liElement.contains(pElement) && pElement.nextElementSibling)) {
463
+ if (liElement && !liElement.textContent.endsWith("\n")) {
464
+ // li 结尾需 \n
465
+ liElement.insertAdjacentText("beforeend", "\n");
466
+ }
467
+ range.insertNode(document.createTextNode("\n\n"));
468
+ range.collapse(false);
469
+ execAfterRender(vditor);
470
+ event.preventDefault();
471
+ return true;
472
+ }
473
+
474
+ if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" &&
475
+ !liElement.previousElementSibling && range.toString() === "" &&
476
+ getSelectPosition(liElement, vditor[vditor.currentMode].element, range).start === 0) {
477
+ // 光标位于点和第一个字符中间时,无法删除 li 元素
478
+ if (liElement.nextElementSibling) {
479
+ liElement.parentElement.insertAdjacentHTML("beforebegin",
480
+ `<p data-block="0"><wbr>${liElement.innerHTML}</p>`);
481
+ liElement.remove();
482
+ } else {
483
+ liElement.parentElement.outerHTML = `<p data-block="0"><wbr>${liElement.innerHTML}</p>`;
484
+ }
485
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
486
+ execAfterRender(vditor);
487
+ event.preventDefault();
488
+ return true;
489
+ }
490
+
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
+ if (!isCtrl(event) && !event.altKey && event.key === "Tab") {
506
+ // 光标位于第一/零字符时,tab 用于列表的缩进
507
+ let isFirst = false;
508
+ if (range.startOffset === 0
509
+ && ((startContainer.nodeType === 3 && !startContainer.previousSibling)
510
+ || (startContainer.nodeType !== 3 && startContainer.nodeName === "LI"))) {
511
+ // 有序/无序列表
512
+ isFirst = true;
513
+ } else if (liElement.classList.contains("vditor-task") && range.startOffset === 1
514
+ && startContainer.previousSibling.nodeType !== 3
515
+ && (startContainer.previousSibling as HTMLElement).tagName === "INPUT") {
516
+ // 任务列表
517
+ isFirst = true;
518
+ }
519
+
520
+ if (isFirst || range.toString() !== "") {
521
+ if (event.shiftKey) {
522
+ listOutdent(vditor, liElement, range, liElement.parentElement);
523
+ } else {
524
+ listIndent(vditor, liElement, range);
525
+ }
526
+ event.preventDefault();
527
+ return true;
528
+ }
529
+ }
530
+ }
531
+ return false;
532
+ };
533
+
534
+ // tab 处理: block code render, table, 列表第一个字符中的 tab 处理单独写在上面
535
+ export const fixTab = (vditor: IVditor, range: Range, event: KeyboardEvent) => {
536
+ if (vditor.options.tab && event.key === "Tab") {
537
+ if (event.shiftKey) {
538
+ // TODO shift+tab
539
+ } else {
540
+ if (range.toString() === "") {
541
+ range.insertNode(document.createTextNode(vditor.options.tab));
542
+ range.collapse(false);
543
+ } else {
544
+ range.extractContents();
545
+ range.insertNode(document.createTextNode(vditor.options.tab));
546
+ range.collapse(false);
547
+ }
548
+ }
549
+ setSelectionFocus(range);
550
+ execAfterRender(vditor);
551
+ event.preventDefault();
552
+ return true;
553
+ }
554
+ };
555
+
556
+ export const fixMarkdown = (event: KeyboardEvent, vditor: IVditor, pElement: HTMLElement | false, range: Range) => {
557
+ if (!pElement) {
558
+ return;
559
+ }
560
+ if (!isCtrl(event) && !event.altKey && event.key === "Enter") {
561
+ const pText = String.raw`${pElement.textContent}`.replace(/\\\|/g, "").trim();
562
+ const pTextList = pText.split("|");
563
+ if (pText.startsWith("|") && pText.endsWith("|") && pTextList.length > 3) {
564
+ // table 自动完成
565
+ let tableHeaderMD = pTextList.map(() => "---").join("|");
566
+ tableHeaderMD =
567
+ pElement.textContent + "\n" + tableHeaderMD.substring(3, tableHeaderMD.length - 3) + "\n|<wbr>";
568
+ pElement.outerHTML = vditor.lute.SpinVditorDOM(tableHeaderMD);
569
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
570
+ execAfterRender(vditor);
571
+ scrollCenter(vditor);
572
+ event.preventDefault();
573
+ return true;
574
+ }
575
+
576
+ // hr 渲染
577
+ if (isHrMD(pElement.innerHTML) && pElement.previousElementSibling) {
578
+ // 软换行后 hr 前有内容
579
+ let pInnerHTML = "";
580
+ const innerHTMLList = pElement.innerHTML.trimRight().split("\n");
581
+ if (innerHTMLList.length > 1) {
582
+ innerHTMLList.pop();
583
+ pInnerHTML = `<p data-block="0">${innerHTMLList.join("\n")}</p>`;
584
+ }
585
+
586
+ pElement.insertAdjacentHTML("afterend",
587
+ `${pInnerHTML}<hr data-block="0"><p data-block="0"><wbr>\n</p>`);
588
+ pElement.remove();
589
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
590
+ execAfterRender(vditor);
591
+ scrollCenter(vditor);
592
+ event.preventDefault();
593
+ return true;
594
+ }
595
+
596
+ if (isHeadingMD(pElement.innerHTML)) {
597
+ // heading 渲染
598
+ if (vditor.currentMode === "wysiwyg") {
599
+ pElement.outerHTML = vditor.lute.SpinVditorDOM(pElement.innerHTML + '<p data-block="0"><wbr>\n</p>');
600
+ } else {
601
+ pElement.outerHTML = vditor.lute.SpinVditorIRDOM(pElement.innerHTML + '<p data-block="0"><wbr>\n</p>');
602
+ }
603
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
604
+ execAfterRender(vditor);
605
+ scrollCenter(vditor);
606
+ event.preventDefault();
607
+ return true;
608
+ }
609
+ }
610
+
611
+ // 软换行会被切割 https://github.com/Vanessa219/vditor/issues/220
612
+ if (range.collapsed && pElement.previousElementSibling && event.key === "Backspace" &&
613
+ !isCtrl(event) && !event.altKey && !event.shiftKey &&
614
+ pElement.textContent.trimRight().split("\n").length > 1 &&
615
+ getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start === 0) {
616
+ const lastElement = getLastNode(pElement.previousElementSibling) as HTMLElement;
617
+ if (!lastElement.textContent.endsWith("\n")) {
618
+ lastElement.textContent = lastElement.textContent + "\n";
619
+ }
620
+ lastElement.parentElement.insertAdjacentHTML("beforeend", `<wbr>${pElement.innerHTML}`);
621
+ pElement.remove();
622
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
623
+ return false;
624
+ }
625
+ return false;
626
+ };
627
+
628
+ export const insertRow = (vditor: IVditor, range: Range, cellElement: HTMLElement) => {
629
+ let rowHTML = "";
630
+ for (let m = 0; m < cellElement.parentElement.childElementCount; m++) {
631
+ rowHTML += `<td align="${cellElement.parentElement.children[m].getAttribute("align")}"> </td>`;
632
+ }
633
+ if (cellElement.tagName === "TH") {
634
+ cellElement.parentElement.parentElement.insertAdjacentHTML("afterend",
635
+ `<tbody><tr>${rowHTML}</tr></tbody>`);
636
+ } else {
637
+ cellElement.parentElement.insertAdjacentHTML("afterend", `<tr>${rowHTML}</tr>`);
638
+ }
639
+ execAfterRender(vditor);
640
+ };
641
+
642
+ export const insertRowAbove = (vditor: IVditor, range: Range, cellElement: HTMLElement) => {
643
+ let rowHTML = "";
644
+ for (let m = 0; m < cellElement.parentElement.childElementCount; m++) {
645
+ if (cellElement.tagName === "TH") {
646
+ rowHTML += `<th align="${cellElement.parentElement.children[m].getAttribute("align")}"> </th>`;
647
+ } else {
648
+ rowHTML += `<td align="${cellElement.parentElement.children[m].getAttribute("align")}"> </td>`;
649
+ }
650
+ }
651
+ if (cellElement.tagName === "TH") {
652
+ cellElement.parentElement.parentElement.insertAdjacentHTML("beforebegin", `<thead><tr>${rowHTML}</tr></thead>`);
653
+
654
+ range.insertNode(document.createElement("wbr"));
655
+ const theadHTML = cellElement.parentElement.innerHTML.replace(/<th>/g, "<td>").replace(/<\/th>/g, "</td>");
656
+ cellElement.parentElement.parentElement.nextElementSibling.insertAdjacentHTML("afterbegin", theadHTML);
657
+
658
+ cellElement.parentElement.parentElement.remove();
659
+ setRangeByWbr(vditor.ir.element, range);
660
+ } else {
661
+ cellElement.parentElement.insertAdjacentHTML("beforebegin", `<tr>${rowHTML}</tr>`);
662
+ }
663
+ execAfterRender(vditor);
664
+ };
665
+
666
+ export const insertColumn =
667
+ (vditor: IVditor, tableElement: HTMLTableElement, cellElement: HTMLElement, type: InsertPosition = "afterend") => {
668
+ let index = 0;
669
+ let previousElement = cellElement.previousElementSibling;
670
+ while (previousElement) {
671
+ index++;
672
+ previousElement = previousElement.previousElementSibling;
673
+ }
674
+ for (let i = 0; i < tableElement.rows.length; i++) {
675
+ if (i === 0) {
676
+ tableElement.rows[i].cells[index].insertAdjacentHTML(type, "<th> </th>");
677
+ } else {
678
+ tableElement.rows[i].cells[index].insertAdjacentHTML(type, "<td> </td>");
679
+ }
680
+ }
681
+ execAfterRender(vditor);
682
+ };
683
+ export const deleteRow = (vditor: IVditor, range: Range, cellElement: HTMLElement) => {
684
+ if (cellElement.tagName === "TD") {
685
+ const tbodyElement = cellElement.parentElement.parentElement;
686
+ if (cellElement.parentElement.previousElementSibling) {
687
+ range.selectNodeContents(cellElement.parentElement.previousElementSibling.lastElementChild);
688
+ } else {
689
+ range.selectNodeContents(tbodyElement.previousElementSibling.lastElementChild.lastElementChild);
690
+ }
691
+
692
+ if (tbodyElement.childElementCount === 1) {
693
+ tbodyElement.remove();
694
+ } else {
695
+ cellElement.parentElement.remove();
696
+ }
697
+
698
+ range.collapse(false);
699
+ setSelectionFocus(range);
700
+ execAfterRender(vditor);
701
+ }
702
+ };
703
+
704
+ export const deleteColumn =
705
+ (vditor: IVditor, range: Range, tableElement: HTMLTableElement, cellElement: HTMLElement) => {
706
+ let index = 0;
707
+ let previousElement = cellElement.previousElementSibling;
708
+ while (previousElement) {
709
+ index++;
710
+ previousElement = previousElement.previousElementSibling;
711
+ }
712
+ if (cellElement.previousElementSibling || cellElement.nextElementSibling) {
713
+ range.selectNodeContents(cellElement.previousElementSibling || cellElement.nextElementSibling);
714
+ range.collapse(true);
715
+ }
716
+ for (let i = 0; i < tableElement.rows.length; i++) {
717
+ const cells = tableElement.rows[i].cells;
718
+ if (cells.length === 1) {
719
+ tableElement.remove();
720
+ highlightToolbar(vditor);
721
+ break;
722
+ }
723
+ cells[index].remove();
724
+ }
725
+ setSelectionFocus(range);
726
+ execAfterRender(vditor);
727
+ };
728
+
729
+ export const fixTable = (vditor: IVditor, event: KeyboardEvent, range: Range) => {
730
+ const startContainer = range.startContainer;
731
+ const cellElement = hasClosestByMatchTag(startContainer, "TD") ||
732
+ hasClosestByMatchTag(startContainer, "TH");
733
+ if (cellElement) {
734
+ // 换行或软换行:在 cell 中添加 br
735
+ if (!isCtrl(event) && !event.altKey && event.key === "Enter") {
736
+ if (!cellElement.lastElementChild ||
737
+ (cellElement.lastElementChild && (!cellElement.lastElementChild.isSameNode(cellElement.lastChild) ||
738
+ cellElement.lastElementChild.tagName !== "BR"))) {
739
+ cellElement.insertAdjacentHTML("beforeend", "<br>");
740
+ }
741
+ const brElement = document.createElement("br");
742
+ range.insertNode(brElement);
743
+ range.setStartAfter(brElement);
744
+ execAfterRender(vditor);
745
+ scrollCenter(vditor);
746
+ event.preventDefault();
747
+ return true;
748
+ }
749
+
750
+ // tab:光标移向下一个 cell
751
+ if (event.key === "Tab") {
752
+ if (event.shiftKey) {
753
+ // shift + tab 光标移动到前一个 cell
754
+ goPreviousCell(cellElement, range);
755
+ event.preventDefault();
756
+ return true;
757
+ }
758
+
759
+ let nextElement = cellElement.nextElementSibling;
760
+ if (!nextElement) {
761
+ if (cellElement.parentElement.nextElementSibling) {
762
+ nextElement = cellElement.parentElement.nextElementSibling.firstElementChild;
763
+ } else if (cellElement.parentElement.parentElement.tagName === "THEAD" &&
764
+ cellElement.parentElement.parentElement.nextElementSibling) {
765
+ nextElement =
766
+ cellElement.parentElement.parentElement.nextElementSibling.firstElementChild.firstElementChild;
767
+ } else {
768
+ nextElement = null;
769
+ }
770
+ }
771
+ if (nextElement) {
772
+ range.selectNodeContents(nextElement);
773
+ setSelectionFocus(range);
774
+ }
775
+ event.preventDefault();
776
+ return true;
777
+ }
778
+
779
+ const tableElement = cellElement.parentElement.parentElement.parentElement as HTMLTableElement;
780
+ if (event.key === "ArrowUp") {
781
+ event.preventDefault();
782
+ if (cellElement.tagName === "TH") {
783
+ if (tableElement.previousElementSibling) {
784
+ range.selectNodeContents(tableElement.previousElementSibling);
785
+ range.collapse(false);
786
+ setSelectionFocus(range);
787
+ } else {
788
+ insertEmptyBlock(vditor, "beforebegin");
789
+ }
790
+ return true;
791
+ }
792
+
793
+ let m = 0;
794
+ const trElement = cellElement.parentElement as HTMLTableRowElement;
795
+ for (; m < trElement.cells.length; m++) {
796
+ if (trElement.cells[m].isSameNode(cellElement)) {
797
+ break;
798
+ }
799
+ }
800
+
801
+ let previousElement = trElement.previousElementSibling as HTMLTableRowElement;
802
+ if (!previousElement) {
803
+ previousElement = trElement.parentElement.previousElementSibling.firstChild as HTMLTableRowElement;
804
+ }
805
+ range.selectNodeContents(previousElement.cells[m]);
806
+ range.collapse(false);
807
+ setSelectionFocus(range);
808
+ return true;
809
+ }
810
+
811
+ if (event.key === "ArrowDown") {
812
+ event.preventDefault();
813
+ const trElement = cellElement.parentElement as HTMLTableRowElement;
814
+ if (!trElement.nextElementSibling && cellElement.tagName === "TD") {
815
+ if (tableElement.nextElementSibling) {
816
+ range.selectNodeContents(tableElement.nextElementSibling);
817
+ range.collapse(true);
818
+ setSelectionFocus(range);
819
+ } else {
820
+ insertEmptyBlock(vditor, "afterend");
821
+ }
822
+ return true;
823
+ }
824
+
825
+ let m = 0;
826
+ for (; m < trElement.cells.length; m++) {
827
+ if (trElement.cells[m].isSameNode(cellElement)) {
828
+ break;
829
+ }
830
+ }
831
+
832
+ let nextElement = trElement.nextElementSibling as HTMLTableRowElement;
833
+ if (!nextElement) {
834
+ nextElement = trElement.parentElement.nextElementSibling.firstChild as HTMLTableRowElement;
835
+ }
836
+ range.selectNodeContents(nextElement.cells[m]);
837
+ range.collapse(true);
838
+ setSelectionFocus(range);
839
+ return true;
840
+ }
841
+
842
+ // focus row input, only wysiwyg
843
+ if (vditor.currentMode === "wysiwyg" &&
844
+ !isCtrl(event) && event.key === "Enter" && !event.shiftKey && event.altKey) {
845
+ const inputElement = (vditor.wysiwyg.popover.querySelector(".vditor-input") as HTMLInputElement);
846
+ inputElement.focus();
847
+ inputElement.select();
848
+ event.preventDefault();
849
+ return true;
850
+ }
851
+
852
+ // Backspace:光标移动到前一个 cell
853
+ if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace"
854
+ && range.startOffset === 0 && range.toString() === "") {
855
+ const previousCellElement = goPreviousCell(cellElement, range, false);
856
+ if (!previousCellElement && tableElement) {
857
+ if (tableElement.textContent.trim() === "") {
858
+ tableElement.outerHTML = `<p data-block="0"><wbr>\n</p>`;
859
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
860
+ } else {
861
+ range.setStartBefore(tableElement);
862
+ range.collapse(true);
863
+ }
864
+ execAfterRender(vditor);
865
+ }
866
+ event.preventDefault();
867
+ return true;
868
+ }
869
+ // 上方新添加一行
870
+ if (matchHotKey("⇧⌘F", event)) {
871
+ insertRowAbove(vditor, range, cellElement);
872
+ event.preventDefault();
873
+ return true;
874
+ }
875
+
876
+ // 下方新添加一行 https://github.com/Vanessa219/vditor/issues/46
877
+ if (matchHotKey("⌘=", event)) {
878
+ insertRow(vditor, range, cellElement);
879
+ event.preventDefault();
880
+ return true;
881
+ }
882
+
883
+ // 左方新添加一列
884
+ if (matchHotKey("⇧⌘G", event)) {
885
+ insertColumn(vditor, tableElement, cellElement, "beforebegin");
886
+ event.preventDefault();
887
+ return true;
888
+ }
889
+
890
+ // 后方新添加一列
891
+ if (matchHotKey("⇧⌘=", event)) {
892
+ insertColumn(vditor, tableElement, cellElement);
893
+ event.preventDefault();
894
+ return true;
895
+ }
896
+
897
+ // 删除当前行
898
+ if (matchHotKey("⌘-", event)) {
899
+ deleteRow(vditor, range, cellElement);
900
+ event.preventDefault();
901
+ return true;
902
+ }
903
+
904
+ // 删除当前列
905
+ if (matchHotKey("⇧⌘-", event)) {
906
+ deleteColumn(vditor, range, tableElement, cellElement);
907
+ event.preventDefault();
908
+ return true;
909
+ }
910
+
911
+ // 剧左
912
+ if (matchHotKey("⇧⌘L", event)) {
913
+ if (vditor.currentMode === "ir") {
914
+ setTableAlign(tableElement, "left");
915
+ execAfterRender(vditor);
916
+ event.preventDefault();
917
+ return true;
918
+ } else {
919
+ const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="left"]');
920
+ if (itemElement) {
921
+ itemElement.click();
922
+ event.preventDefault();
923
+ return true;
924
+ }
925
+ }
926
+ }
927
+
928
+ // 剧中
929
+ if (matchHotKey("⇧⌘C", event)) {
930
+ if (vditor.currentMode === "ir") {
931
+ setTableAlign(tableElement, "center");
932
+ execAfterRender(vditor);
933
+ event.preventDefault();
934
+ return true;
935
+ } else {
936
+ const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="center"]');
937
+ if (itemElement) {
938
+ itemElement.click();
939
+ event.preventDefault();
940
+ return true;
941
+ }
942
+ }
943
+ }
944
+ // 剧右
945
+ if (matchHotKey("⇧⌘R", event)) {
946
+ if (vditor.currentMode === "ir") {
947
+ setTableAlign(tableElement, "right");
948
+ execAfterRender(vditor);
949
+ event.preventDefault();
950
+ return true;
951
+ } else {
952
+ const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="right"]');
953
+ if (itemElement) {
954
+ itemElement.click();
955
+ event.preventDefault();
956
+ return true;
957
+ }
958
+ }
959
+ }
960
+ }
961
+ return false;
962
+ };
963
+
964
+ export const fixCodeBlock = (vditor: IVditor, event: KeyboardEvent, codeRenderElement: HTMLElement, range: Range) => {
965
+ // 行级代码块中 command + a,近对当前代码块进行全选
966
+ if (codeRenderElement.tagName === "PRE" && matchHotKey("⌘A", event)) {
967
+ range.selectNodeContents(codeRenderElement.firstElementChild);
968
+ event.preventDefault();
969
+ return true;
970
+ }
971
+
972
+ // tab
973
+ // TODO shift + tab, shift and 选中文字
974
+ if (vditor.options.tab && event.key === "Tab" && !event.shiftKey && range.toString() === "") {
975
+ range.insertNode(document.createTextNode(vditor.options.tab));
976
+ range.collapse(false);
977
+ execAfterRender(vditor);
978
+ event.preventDefault();
979
+ return true;
980
+ }
981
+
982
+ // Backspace: 光标位于第零个字符,仅删除代码块标签
983
+ if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey) {
984
+ const codePosition = getSelectPosition(codeRenderElement, vditor[vditor.currentMode].element, range);
985
+ if ((codePosition.start === 0 ||
986
+ (codePosition.start === 1 && codeRenderElement.innerText === "\n")) // 空代码块,光标在 \n 后
987
+ && range.toString() === "") {
988
+ codeRenderElement.parentElement.outerHTML =
989
+ `<p data-block="0"><wbr>${codeRenderElement.firstElementChild.innerHTML}</p>`;
990
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
991
+ execAfterRender(vditor);
992
+ event.preventDefault();
993
+ return true;
994
+ }
995
+ }
996
+
997
+ // 换行
998
+ if (!isCtrl(event) && !event.altKey && event.key === "Enter") {
999
+ if (!codeRenderElement.firstElementChild.textContent.endsWith("\n")) {
1000
+ codeRenderElement.firstElementChild.insertAdjacentText("beforeend", "\n");
1001
+ }
1002
+ range.extractContents();
1003
+ range.insertNode(document.createTextNode("\n"));
1004
+ range.collapse(false);
1005
+ setSelectionFocus(range);
1006
+ if (!isFirefox()) {
1007
+ if (vditor.currentMode === "wysiwyg") {
1008
+ input(vditor, range);
1009
+ } else {
1010
+ IRInput(vditor, range);
1011
+ }
1012
+ }
1013
+ scrollCenter(vditor);
1014
+ event.preventDefault();
1015
+ return true;
1016
+ }
1017
+ return false;
1018
+ };
1019
+
1020
+ export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEvent, pElement: HTMLElement | false) => {
1021
+ const startContainer = range.startContainer;
1022
+ const blockquoteElement = hasClosestByMatchTag(startContainer, "BLOCKQUOTE");
1023
+ if (blockquoteElement && range.toString() === "") {
1024
+ if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey &&
1025
+ getSelectPosition(blockquoteElement, vditor[vditor.currentMode].element, range).start === 0) {
1026
+ // Backspace: 光标位于引用中的第零个字符,仅删除引用标签
1027
+ range.insertNode(document.createElement("wbr"));
1028
+ blockquoteElement.outerHTML = blockquoteElement.innerHTML;
1029
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
1030
+ execAfterRender(vditor);
1031
+ event.preventDefault();
1032
+ return true;
1033
+ }
1034
+
1035
+ if (pElement && event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey
1036
+ && pElement.parentElement.tagName === "BLOCKQUOTE") {
1037
+ // Enter: 空行回车应逐层跳出
1038
+ let isEmpty = false;
1039
+ if (pElement.innerHTML.replace(Constants.ZWSP, "") === "\n" ||
1040
+ pElement.innerHTML.replace(Constants.ZWSP, "") === "") {
1041
+ // 空 P
1042
+ isEmpty = true;
1043
+ pElement.remove();
1044
+ } else if (pElement.innerHTML.endsWith("\n\n") &&
1045
+ getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start ===
1046
+ pElement.textContent.length - 1) {
1047
+ // 软换行
1048
+ pElement.innerHTML = pElement.innerHTML.substr(0, pElement.innerHTML.length - 2);
1049
+ isEmpty = true;
1050
+ }
1051
+ if (isEmpty) {
1052
+ // 需添加零宽字符,否则的话无法记录 undo
1053
+ blockquoteElement.insertAdjacentHTML("afterend", `<p data-block="0">${Constants.ZWSP}<wbr>\n</p>`);
1054
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
1055
+ execAfterRender(vditor);
1056
+ event.preventDefault();
1057
+ return true;
1058
+ }
1059
+ }
1060
+ const blockElement = hasClosestBlock(startContainer);
1061
+ if (vditor.currentMode === "wysiwyg" && blockElement && matchHotKey("⇧⌘;", event)) {
1062
+ // 插入 blockquote
1063
+ range.insertNode(document.createElement("wbr"));
1064
+ blockElement.outerHTML = `<blockquote data-block="0">${blockElement.outerHTML}</blockquote>`;
1065
+ setRangeByWbr(vditor.wysiwyg.element, range);
1066
+ afterRenderEvent(vditor);
1067
+ event.preventDefault();
1068
+ return true;
1069
+ }
1070
+
1071
+ if (insertAfterBlock(vditor, event, range, blockquoteElement, blockquoteElement)) {
1072
+ return true;
1073
+ }
1074
+ if (insertBeforeBlock(vditor, event, range, blockquoteElement, blockquoteElement)) {
1075
+ return true;
1076
+ }
1077
+ }
1078
+ return false;
1079
+ };
1080
+
1081
+ export const fixTask = (vditor: IVditor, range: Range, event: KeyboardEvent) => {
1082
+ const startContainer = range.startContainer;
1083
+ const taskItemElement = hasClosestByMatchTag(startContainer, "LI");
1084
+ if (taskItemElement && taskItemElement.classList.contains("vditor-task")) {
1085
+ if (matchHotKey("⇧⌘J", event)) {
1086
+ // ctrl + shift: toggle checked
1087
+ const inputElement = taskItemElement.firstElementChild as HTMLInputElement;
1088
+ if (inputElement.checked) {
1089
+ inputElement.removeAttribute("checked");
1090
+ } else {
1091
+ inputElement.setAttribute("checked", "checked");
1092
+ }
1093
+ execAfterRender(vditor);
1094
+ event.preventDefault();
1095
+ return true;
1096
+ }
1097
+
1098
+ // Backspace: 在选择框前进行删除
1099
+ if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey && range.toString() === ""
1100
+ && range.startOffset === 1
1101
+ && ((startContainer.nodeType === 3 && startContainer.previousSibling &&
1102
+ (startContainer.previousSibling as HTMLElement).tagName === "INPUT")
1103
+ || startContainer.nodeType !== 3)) {
1104
+ const previousElement = taskItemElement.previousElementSibling;
1105
+ taskItemElement.querySelector("input").remove();
1106
+ if (previousElement) {
1107
+ const lastNode = getLastNode(previousElement);
1108
+ lastNode.parentElement.insertAdjacentHTML("beforeend", "<wbr>" + taskItemElement.innerHTML.trim());
1109
+ taskItemElement.remove();
1110
+ } else {
1111
+ taskItemElement.parentElement.insertAdjacentHTML("beforebegin",
1112
+ `<p data-block="0"><wbr>${taskItemElement.innerHTML.trim() || "\n"}</p>`);
1113
+ if (taskItemElement.nextElementSibling) {
1114
+ taskItemElement.remove();
1115
+ } else {
1116
+ taskItemElement.parentElement.remove();
1117
+ }
1118
+ }
1119
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
1120
+ execAfterRender(vditor);
1121
+ event.preventDefault();
1122
+ return true;
1123
+ }
1124
+
1125
+ if (event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey) {
1126
+ if (taskItemElement.textContent.trim() === "") {
1127
+ // 当前任务列表无文字
1128
+ if (hasClosestByClassName(taskItemElement.parentElement, "vditor-task")) {
1129
+ // 为子元素时,需进行反向缩进
1130
+ const topListElement = getTopList(startContainer);
1131
+ if (topListElement) {
1132
+ listOutdent(vditor, taskItemElement, range, topListElement);
1133
+ }
1134
+ } else {
1135
+ // 仅有一级任务列表
1136
+ if (taskItemElement.nextElementSibling) {
1137
+ // 任务列表下方还有元素,需要使用用段落隔断
1138
+ let afterHTML = "";
1139
+ let beforeHTML = "";
1140
+ let isAfter = false;
1141
+ Array.from(taskItemElement.parentElement.children).forEach((taskItem) => {
1142
+ if (taskItemElement.isSameNode(taskItem)) {
1143
+ isAfter = true;
1144
+ } else {
1145
+ if (isAfter) {
1146
+ afterHTML += taskItem.outerHTML;
1147
+ } else {
1148
+ beforeHTML += taskItem.outerHTML;
1149
+ }
1150
+ }
1151
+ });
1152
+ const parentTagName = taskItemElement.parentElement.tagName;
1153
+ const dataMarker = taskItemElement.parentElement.tagName === "OL" ? "" : ` data-marker="${taskItemElement.parentElement.getAttribute("data-marker")}"`;
1154
+ let startAttribute = "";
1155
+ if (beforeHTML) {
1156
+ startAttribute = taskItemElement.parentElement.tagName === "UL" ? "" : ` start="1"`;
1157
+ beforeHTML = `<${parentTagName} data-tight="true"${dataMarker} data-block="0">${beforeHTML}</${parentTagName}>`;
1158
+ }
1159
+ // <p data-block="0">\n<wbr></p> => <p data-block="0"><wbr>\n</p>
1160
+ // https://github.com/Vanessa219/vditor/issues/430
1161
+ taskItemElement.parentElement.outerHTML = `${beforeHTML}<p data-block="0"><wbr>\n</p><${parentTagName}
1162
+ data-tight="true"${dataMarker} data-block="0"${startAttribute}>${afterHTML}</${parentTagName}>`;
1163
+ } else {
1164
+ // 任务列表下方无任务列表元素
1165
+ taskItemElement.parentElement.insertAdjacentHTML("afterend", `<p data-block="0"><wbr>\n</p>`);
1166
+ if (taskItemElement.parentElement.querySelectorAll("li").length === 1) {
1167
+ // 任务列表仅有一项时,使用 p 元素替换
1168
+ taskItemElement.parentElement.remove();
1169
+ } else {
1170
+ // 任务列表有多项时,当前任务列表位于最后一项,移除该任务列表
1171
+ taskItemElement.remove();
1172
+ }
1173
+ }
1174
+ }
1175
+ } else if (startContainer.nodeType !== 3 && range.startOffset === 0 &&
1176
+ (startContainer.firstChild as HTMLElement).tagName === "INPUT") {
1177
+ // 光标位于 input 之前
1178
+ range.setStart(startContainer.childNodes[1], 1);
1179
+ } else {
1180
+ // 当前任务列表有文字,光标后的文字需添加到新任务列表中
1181
+ range.setEndAfter(taskItemElement.lastChild);
1182
+ taskItemElement.insertAdjacentHTML("afterend", `<li class="vditor-task" data-marker="${taskItemElement.getAttribute("data-marker")}"><input type="checkbox"> <wbr></li>`);
1183
+ document.querySelector("wbr").after(range.extractContents());
1184
+ }
1185
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
1186
+ execAfterRender(vditor);
1187
+ scrollCenter(vditor);
1188
+ event.preventDefault();
1189
+ return true;
1190
+ }
1191
+ }
1192
+ return false;
1193
+ };
1194
+
1195
+ export const fixDelete = (vditor: IVditor, range: Range, event: KeyboardEvent, pElement: HTMLElement | false) => {
1196
+ if (range.startContainer.nodeType !== 3) {
1197
+ // 光标位于 hr 前,hr 前有内容
1198
+ const rangeElement = (range.startContainer as HTMLElement).children[range.startOffset];
1199
+ if (rangeElement && rangeElement.tagName === "HR") {
1200
+ range.selectNodeContents(rangeElement.previousElementSibling);
1201
+ range.collapse(false);
1202
+ event.preventDefault();
1203
+ return true;
1204
+ }
1205
+ }
1206
+
1207
+ if (pElement) {
1208
+ const previousElement = pElement.previousElementSibling;
1209
+ if (previousElement && getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start === 0 &&
1210
+ ((isFirefox() && previousElement.tagName === "HR") || previousElement.tagName === "TABLE")) {
1211
+ if (previousElement.tagName === "TABLE") {
1212
+ // table 后删除 https://github.com/Vanessa219/vditor/issues/243
1213
+ const lastCellElement = previousElement.lastElementChild.lastElementChild.lastElementChild;
1214
+ lastCellElement.innerHTML =
1215
+ lastCellElement.innerHTML.trimLeft() + "<wbr>" + pElement.textContent.trim();
1216
+ pElement.remove();
1217
+ } else {
1218
+ // 光标位于 hr 后进行删除
1219
+ previousElement.remove();
1220
+ }
1221
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
1222
+ execAfterRender(vditor);
1223
+ event.preventDefault();
1224
+ return true;
1225
+ }
1226
+ }
1227
+ return false;
1228
+ };
1229
+
1230
+ export const fixHR = (range: Range) => {
1231
+ if (isFirefox() && range.startContainer.nodeType !== 3 &&
1232
+ (range.startContainer as HTMLElement).tagName === "HR") {
1233
+ range.setStartBefore(range.startContainer);
1234
+ }
1235
+ };
1236
+
1237
+ // firefox https://github.com/Vanessa219/vditor/issues/407
1238
+ export const fixFirefoxArrowUpTable = (event: KeyboardEvent, blockElement: false | HTMLElement, range: Range) => {
1239
+ if (!isFirefox()) {
1240
+ return false;
1241
+ }
1242
+ if (event.key === "ArrowUp" && blockElement && blockElement.previousElementSibling?.tagName === "TABLE") {
1243
+ const tableElement = blockElement.previousElementSibling as HTMLTableElement;
1244
+ range.selectNodeContents(tableElement.rows[tableElement.rows.length - 1].lastElementChild);
1245
+ range.collapse(false);
1246
+ event.preventDefault();
1247
+ return true;
1248
+ }
1249
+ if (event.key === "ArrowDown" && blockElement && blockElement.nextElementSibling?.tagName === "TABLE") {
1250
+ range.selectNodeContents((blockElement.nextElementSibling as HTMLTableElement).rows[0].cells[0]);
1251
+ range.collapse(true);
1252
+ event.preventDefault();
1253
+ return true;
1254
+ }
1255
+ return false;
1256
+ };
1257
+
1258
+ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent) & {target: HTMLElement}, callback: {
1259
+ pasteCode(code: string): void,
1260
+ }) => {
1261
+ if (vditor[vditor.currentMode].element.getAttribute("contenteditable") !== "true") {
1262
+ return;
1263
+ }
1264
+ event.stopPropagation();
1265
+ event.preventDefault();
1266
+ let textHTML;
1267
+ let textPlain;
1268
+ let files;
1269
+
1270
+ if ("clipboardData" in event) {
1271
+ textHTML = event.clipboardData.getData("text/html");
1272
+ textPlain = event.clipboardData.getData("text/plain");
1273
+ files = event.clipboardData.files;
1274
+ } else {
1275
+ textHTML = event.dataTransfer.getData("text/html");
1276
+ textPlain = event.dataTransfer.getData("text/plain");
1277
+ if (event.dataTransfer.types.includes("Files")) {
1278
+ files = event.dataTransfer.items;
1279
+ }
1280
+ }
1281
+ const renderers: {
1282
+ HTML2VditorDOM?: ILuteRender,
1283
+ HTML2VditorIRDOM?: ILuteRender,
1284
+ Md2VditorDOM?: ILuteRender,
1285
+ Md2VditorIRDOM?: ILuteRender,
1286
+ Md2VditorSVDOM?: ILuteRender,
1287
+ } = {};
1288
+ const renderLinkDest: ILuteRenderCallback = (node, entering) => {
1289
+ if (!entering) {
1290
+ return ["", Lute.WalkContinue];
1291
+ }
1292
+
1293
+ if (vditor.options.upload.renderLinkDest) {
1294
+ return vditor.options.upload.renderLinkDest(vditor, node, entering);
1295
+ }
1296
+
1297
+ const src = node.TokensStr();
1298
+ if (node.__internal_object__.Parent.Type === 34 && src && src.indexOf("file://") === -1 &&
1299
+ vditor.options.upload.linkToImgUrl) {
1300
+ const xhr = new XMLHttpRequest();
1301
+ xhr.open("POST", vditor.options.upload.linkToImgUrl);
1302
+ if (vditor.options.upload.token) {
1303
+ xhr.setRequestHeader("X-Upload-Token", vditor.options.upload.token);
1304
+ }
1305
+ if (vditor.options.upload.withCredentials) {
1306
+ xhr.withCredentials = true;
1307
+ }
1308
+ setHeaders(vditor, xhr);
1309
+ xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
1310
+ xhr.onreadystatechange = () => {
1311
+ if (xhr.readyState === XMLHttpRequest.DONE) {
1312
+ if (xhr.status === 200) {
1313
+ let responseText = xhr.responseText;
1314
+ if (vditor.options.upload.linkToImgFormat) {
1315
+ responseText = vditor.options.upload.linkToImgFormat(xhr.responseText);
1316
+ }
1317
+ const responseJSON = JSON.parse(responseText);
1318
+ if (responseJSON.code !== 0) {
1319
+ vditor.tip.show(responseJSON.msg);
1320
+ return;
1321
+ }
1322
+ const original = responseJSON.data.originalURL;
1323
+ if (vditor.currentMode === "sv") {
1324
+ vditor.sv.element.querySelectorAll(".vditor-sv__marker--link")
1325
+ .forEach((item: HTMLElement) => {
1326
+ if (item.textContent === original) {
1327
+ item.textContent = responseJSON.data.url;
1328
+ }
1329
+ });
1330
+ } else {
1331
+ const imgElement: HTMLImageElement =
1332
+ vditor[vditor.currentMode].element.querySelector(`img[src="${original}"]`);
1333
+ imgElement.src = responseJSON.data.url;
1334
+ if (vditor.currentMode === "ir") {
1335
+ imgElement.previousElementSibling.previousElementSibling.innerHTML =
1336
+ responseJSON.data.url;
1337
+ }
1338
+ }
1339
+ execAfterRender(vditor);
1340
+ } else {
1341
+ vditor.tip.show(xhr.responseText);
1342
+ }
1343
+ if (vditor.options.upload.linkToImgCallback) {
1344
+ vditor.options.upload.linkToImgCallback(xhr.responseText);
1345
+ }
1346
+ }
1347
+ };
1348
+ xhr.send(JSON.stringify({url: src}));
1349
+ }
1350
+ if (vditor.currentMode === "ir") {
1351
+ return [`<span class="vditor-ir__marker vditor-ir__marker--link">${Lute.EscapeHTMLStr(src)}</span>`, Lute.WalkContinue];
1352
+ } else if (vditor.currentMode === "wysiwyg") {
1353
+ return ["", Lute.WalkContinue];
1354
+ } else {
1355
+ return [`<span class="vditor-sv__marker--link">${Lute.EscapeHTMLStr(src)}</span>`, Lute.WalkContinue];
1356
+ }
1357
+ };
1358
+
1359
+ // 浏览器地址栏拷贝处理
1360
+ if (textHTML.replace(/&amp;/g, "&").replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() ===
1361
+ `<a href="${textPlain}">${textPlain}</a>` ||
1362
+ textHTML.replace(/&amp;/g, "&").replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() ===
1363
+ `<!--StartFragment--><a href="${textPlain}">${textPlain}</a><!--EndFragment-->`) {
1364
+ textHTML = "";
1365
+ }
1366
+
1367
+ // process word
1368
+ const doc = new DOMParser().parseFromString(textHTML, "text/html");
1369
+ if (doc.body) {
1370
+ textHTML = doc.body.innerHTML;
1371
+ }
1372
+ textHTML = Lute.Sanitize(textHTML);
1373
+ vditor.wysiwyg.getComments(vditor);
1374
+
1375
+ // process code
1376
+ const height = vditor[vditor.currentMode].element.scrollHeight;
1377
+ const code = processPasteCode(textHTML, textPlain, vditor.currentMode);
1378
+ const codeElement = vditor.currentMode === "sv" ?
1379
+ hasClosestByAttribute(event.target, "data-type", "code-block") :
1380
+ hasClosestByMatchTag(event.target, "CODE");
1381
+ if (codeElement) {
1382
+ // 粘贴在代码位置
1383
+ if (vditor.currentMode === "sv") {
1384
+ document.execCommand("insertHTML", false, textPlain.replace(/&/g, "&amp;").replace(/</g, "&lt;"));
1385
+ } else {
1386
+ const position = getSelectPosition(event.target, vditor[vditor.currentMode].element);
1387
+ if (codeElement.parentElement.tagName !== "PRE") {
1388
+ // https://github.com/Vanessa219/vditor/issues/463
1389
+ textPlain += Constants.ZWSP;
1390
+ }
1391
+ codeElement.textContent = codeElement.textContent.substring(0, position.start)
1392
+ + textPlain + codeElement.textContent.substring(position.end);
1393
+ setSelectionByPosition(position.start + textPlain.length, position.start + textPlain.length,
1394
+ codeElement.parentElement);
1395
+ const nextSibling = codeElement.parentElement?.nextElementSibling;
1396
+ if (nextSibling && nextSibling.classList
1397
+ .contains(`vditor-${vditor.currentMode}__preview`)) {
1398
+ nextSibling.innerHTML = codeElement.outerHTML;
1399
+ processCodeRender(nextSibling as HTMLElement, vditor);
1400
+ }
1401
+ }
1402
+ } else if (code) {
1403
+ callback.pasteCode(code);
1404
+ } else {
1405
+ if (textHTML.trim() !== "") {
1406
+ const tempElement = document.createElement("div");
1407
+ tempElement.innerHTML = textHTML;
1408
+ if (!vditor.options.upload.base64ToLink) {
1409
+ // word 复制的图文混合,替换为 link: <v:imagedata src="file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image001.png" o:title="">
1410
+ await processVMLImage(vditor, tempElement, ("clipboardData" in event ? event.clipboardData : event.dataTransfer).getData("text/rtf"));
1411
+ }
1412
+
1413
+ tempElement.querySelectorAll("[style]").forEach((e) => {
1414
+ e.removeAttribute("style");
1415
+ });
1416
+ tempElement.querySelectorAll(".vditor-copy").forEach((e) => {
1417
+ e.remove();
1418
+ });
1419
+ if (vditor.currentMode === "ir") {
1420
+ renderers.HTML2VditorIRDOM = {renderLinkDest};
1421
+ vditor.lute.SetJSRenderers({renderers});
1422
+ insertHTML(vditor.lute.HTML2VditorIRDOM(tempElement.innerHTML), vditor);
1423
+ } else if (vditor.currentMode === "wysiwyg") {
1424
+ renderers.HTML2VditorDOM = {renderLinkDest};
1425
+ vditor.lute.SetJSRenderers({renderers});
1426
+ insertHTML(vditor.lute.HTML2VditorDOM(tempElement.innerHTML), vditor);
1427
+ } else {
1428
+ renderers.Md2VditorSVDOM = {renderLinkDest};
1429
+ vditor.lute.SetJSRenderers({renderers});
1430
+ processPaste(vditor, vditor.lute.HTML2Md(tempElement.innerHTML).trimRight());
1431
+ }
1432
+ vditor.outline.render(vditor);
1433
+ } else if (files.length > 0) {
1434
+ if (vditor.options.upload.url || vditor.options.upload.handler) {
1435
+ await uploadFiles(vditor, files);
1436
+ } else {
1437
+ const fileReader = new FileReader();
1438
+ let file: File;
1439
+ if ("clipboardData" in event) {
1440
+ files = event.clipboardData.files;
1441
+ file = files[0];
1442
+ } else if (event.dataTransfer.types.includes("Files")) {
1443
+ files = event.dataTransfer.items;
1444
+ file = files[0].getAsFile();
1445
+ }
1446
+ if (file && file.type.startsWith("image")) {
1447
+ fileReader.readAsDataURL(file);
1448
+ fileReader.onload = () => {
1449
+ let imgHTML = "";
1450
+ if (vditor.currentMode === "wysiwyg") {
1451
+ imgHTML += `<img alt="${file.name}" src="${fileReader.result.toString()}">\n`;
1452
+ } else {
1453
+ imgHTML += `![${file.name}](${fileReader.result.toString()})\n`;
1454
+ }
1455
+ document.execCommand("insertHTML", false, imgHTML);
1456
+ };
1457
+ }
1458
+ }
1459
+ } else if (textPlain.trim() !== "" && files.length === 0) {
1460
+ const range = getEditorRange(vditor);
1461
+ if (range.toString() !== "" && vditor.lute.IsValidLinkDest(textPlain)) {
1462
+ textPlain = `[${range.toString()}](${textPlain})`;
1463
+ }
1464
+ if (vditor.currentMode === "ir") {
1465
+ renderers.Md2VditorIRDOM = {renderLinkDest};
1466
+ vditor.lute.SetJSRenderers({renderers});
1467
+ insertHTML(Lute.Sanitize(vditor.lute.Md2VditorIRDOM(textPlain)), vditor);
1468
+ } else if (vditor.currentMode === "wysiwyg") {
1469
+ renderers.Md2VditorDOM = {renderLinkDest};
1470
+ vditor.lute.SetJSRenderers({renderers});
1471
+ insertHTML(Lute.Sanitize(vditor.lute.Md2VditorDOM(textPlain)), vditor);
1472
+ } else {
1473
+ renderers.Md2VditorSVDOM = {renderLinkDest};
1474
+ vditor.lute.SetJSRenderers({renderers});
1475
+ processPaste(vditor, textPlain);
1476
+ }
1477
+ vditor.outline.render(vditor);
1478
+ }
1479
+ }
1480
+ if (vditor.currentMode !== "sv") {
1481
+ const blockElement = hasClosestBlock(getEditorRange(vditor).startContainer);
1482
+ if (blockElement) {
1483
+ // https://github.com/Vanessa219/vditor/issues/591
1484
+ const range = getEditorRange(vditor);
1485
+ vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
1486
+ wbr.remove();
1487
+ });
1488
+ range.insertNode(document.createElement("wbr"));
1489
+ if (vditor.currentMode === "wysiwyg") {
1490
+ blockElement.outerHTML = vditor.lute.SpinVditorDOM(blockElement.outerHTML);
1491
+ } else {
1492
+ blockElement.outerHTML = vditor.lute.SpinVditorIRDOM(blockElement.outerHTML);
1493
+ }
1494
+ setRangeByWbr(vditor[vditor.currentMode].element, range);
1495
+ }
1496
+ vditor[vditor.currentMode].element.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`)
1497
+ .forEach((item: HTMLElement) => {
1498
+ processCodeRender(item, vditor);
1499
+ });
1500
+ }
1501
+ vditor.wysiwyg.triggerRemoveComment(vditor);
1502
+ execAfterRender(vditor);
1503
+ if (vditor[vditor.currentMode].element.scrollHeight - height >
1504
+ Math.min(vditor[vditor.currentMode].element.clientHeight, window.innerHeight) / 2) {
1505
+ scrollCenter(vditor);
1506
+ }
1507
+ };
1508
+
1509
+ const processVMLImage = async (vditor: IVditor, root: Element, rtfData: string) => {
1510
+ if (!rtfData) {
1511
+ return;
1512
+
1513
+ }
1514
+
1515
+ const regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
1516
+ const regexPicture = new RegExp("(?:(" + regexPictureHeader.source + "))([\\da-fA-F\\s]+)\\}", "g");
1517
+ const regImages = rtfData.match(regexPicture);
1518
+ const images = [];
1519
+ if (regImages) {
1520
+ for (const image of regImages) {
1521
+ let imageType;
1522
+
1523
+ if (image.includes("\\pngblip")) {
1524
+ imageType = "image/png";
1525
+ } else if (image.includes("\\jpegblip")) {
1526
+ imageType = "image/jpeg";
1527
+ }
1528
+
1529
+ if (imageType) {
1530
+ images.push({
1531
+ hex: image.replace(regexPictureHeader, "").replace(/[^\da-fA-F]/g, ""),
1532
+ type: imageType,
1533
+ });
1534
+ }
1535
+ }
1536
+ }
1537
+
1538
+ const shapes: Array<{shape: Element, img: Element}> = [];
1539
+ walk(root, (child: Element) => {
1540
+ if (child.tagName === "V:SHAPE") {
1541
+ walk(child, (sub) => {
1542
+ if (sub.tagName === "V:IMAGEDATA") shapes.push({shape: child, img: sub});
1543
+ });
1544
+ return false;
1545
+ }
1546
+ });
1547
+ for (let i = 0; i < shapes.length; i++) {
1548
+ const img = document.createElement("img");
1549
+ const newSrc = "data:" + images[i].type + ";base64," + btoa((images[i].hex.match(/\w{2}/g) || []).map(char => {
1550
+ return String.fromCharCode(parseInt(char, 16));
1551
+ }).join(""));
1552
+ img.src = newSrc;
1553
+ img.title = shapes[i].img.getAttribute("title");
1554
+ shapes[i].shape.parentNode.replaceChild(img, shapes[i].shape);
1555
+ }
1556
+
1557
+ const imgs = root.querySelectorAll("img");
1558
+ for (let i = 0; i < imgs.length; i++) {
1559
+ const src = imgs[i].src || "";
1560
+ if (src) imgs[i].src = await vditor.options.upload.base64ToLink(src);
1561
+ }
1562
+ };
1563
+
1564
+ const walk = (el: Element, fn: (el: Element) => boolean | void) => {
1565
+ const goNext = fn(el);
1566
+ if (goNext !== false)
1567
+ for (let i = 0; i < el.children.length; i++) {
1568
+ walk(el.children[i], fn);
1569
+ }
1570
+ };