vditor 3.11.3 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +2 -0
  2. package/dist/css/content-theme/ant-design.css +7 -22
  3. package/dist/css/content-theme/dark.css +7 -16
  4. package/dist/css/content-theme/light.css +7 -15
  5. package/dist/css/content-theme/wechat.css +7 -19
  6. package/dist/index.css +132 -135
  7. package/dist/index.js +3791 -4022
  8. package/dist/index.min.js +1 -1
  9. package/dist/js/lute/lute.min.js +2 -2
  10. package/dist/js/mermaid/mermaid.min.js +516 -467
  11. package/dist/method.js +41 -33
  12. package/dist/method.min.js +1 -1
  13. package/dist/ts/constants.d.ts +1 -0
  14. package/dist/ts/sv/index.d.ts +1 -5
  15. package/dist/ts/sv/process.d.ts +1 -3
  16. package/dist/ts/upload/getElement.d.ts +1 -1
  17. package/dist/ts/upload/index.d.ts +2 -0
  18. package/dist/ts/util/editorCommonEvent.d.ts +1 -1
  19. package/dist/types/index.d.ts +10 -5
  20. package/package.json +1 -1
  21. package/src/assets/less/_reset.less +80 -28
  22. package/src/assets/less/_sv.less +2 -98
  23. package/src/assets/less/_wysiwyg.less +30 -0
  24. package/src/assets/less/index.less +14 -20
  25. package/src/index.ts +46 -14
  26. package/src/ts/constants.ts +1 -0
  27. package/src/ts/export/index.ts +1 -0
  28. package/src/ts/hint/index.ts +9 -4
  29. package/src/ts/ir/highlightToolbarIR.ts +4 -4
  30. package/src/ts/markdown/getMarkdown.ts +3 -3
  31. package/src/ts/markdown/mediaRender.ts +23 -38
  32. package/src/ts/markdown/mermaidRender.ts +1 -1
  33. package/src/ts/markdown/previewRender.ts +1 -0
  34. package/src/ts/markdown/setLute.ts +1 -0
  35. package/src/ts/sv/index.ts +37 -76
  36. package/src/ts/sv/process.ts +48 -164
  37. package/src/ts/sv/processKeydown.ts +19 -186
  38. package/src/ts/toolbar/EditMode.ts +3 -10
  39. package/src/ts/toolbar/Emoji.ts +8 -1
  40. package/src/ts/toolbar/Record.ts +10 -2
  41. package/src/ts/toolbar/index.ts +3 -0
  42. package/src/ts/ui/initUI.ts +7 -3
  43. package/src/ts/undo/index.ts +32 -17
  44. package/src/ts/upload/index.ts +42 -10
  45. package/src/ts/util/editorCommonEvent.ts +18 -9
  46. package/src/ts/util/fixBrowserBehavior.ts +106 -36
  47. package/src/ts/util/selection.ts +11 -3
  48. package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +53 -1
  49. package/src/ts/wysiwyg/processKeydown.ts +47 -0
  50. package/dist/ts/sv/combineFootnote.d.ts +0 -6
  51. package/dist/ts/sv/inputEvent.d.ts +0 -1
  52. package/src/ts/sv/combineFootnote.ts +0 -27
  53. package/src/ts/sv/inputEvent.ts +0 -188
@@ -79,6 +79,21 @@ export const fixCursorDownInlineMath = (range: Range, key: string) => {
79
79
  };
80
80
 
81
81
  export const insertEmptyBlock = (vditor: IVditor, position: InsertPosition) => {
82
+ if (vditor.currentMode === "sv") {
83
+ const element = vditor.sv.element;
84
+ if (position === "beforebegin") {
85
+ const lineStart = element.value.lastIndexOf("\n", element.selectionStart - 1) + 1;
86
+ element.setRangeText("\n", lineStart, lineStart, "end");
87
+ element.setSelectionRange(lineStart, lineStart);
88
+ } else {
89
+ const lineEndIndex = element.value.indexOf("\n", element.selectionEnd);
90
+ const lineEnd = lineEndIndex === -1 ? element.value.length : lineEndIndex;
91
+ element.setRangeText("\n", lineEnd, lineEnd, "end");
92
+ }
93
+ element.focus();
94
+ processSVAfterRender(vditor);
95
+ return;
96
+ }
82
97
  const range = getEditorRange(vditor);
83
98
  const blockElement = hasClosestBlock(range.startContainer);
84
99
  if (blockElement) {
@@ -177,10 +192,40 @@ export const insertBeforeBlock = (vditor: IVditor, event: KeyboardEvent, range:
177
192
 
178
193
  export const listToggle = (vditor: IVditor, range: Range, type: string, cancel = true) => {
179
194
  const itemElement = hasClosestByMatchTag(range.startContainer, "LI");
195
+ const selectedBlockElements: HTMLElement[] = [];
196
+ const startBlockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
197
+ const endBlockElement = hasClosestByAttribute(range.endContainer, "data-block", "0");
198
+ if (startBlockElement && endBlockElement &&
199
+ startBlockElement.parentElement.isSameNode(endBlockElement.parentElement)) {
200
+ let blockElement: Element = startBlockElement;
201
+ while (blockElement) {
202
+ selectedBlockElements.push(blockElement as HTMLElement);
203
+ if (blockElement.isSameNode(endBlockElement)) {
204
+ break;
205
+ }
206
+ blockElement = blockElement.nextElementSibling;
207
+ }
208
+ }
180
209
  vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
181
210
  wbr.remove();
182
211
  });
183
- range.insertNode(document.createElement("wbr"));
212
+ const keepSelection = !range.collapsed;
213
+ if (keepSelection) {
214
+ // 列表转换会重建块元素,分别标记选区起止位置以便恢复原始选区
215
+ const endRange = range.cloneRange();
216
+ endRange.collapse(false);
217
+ const endElement = document.createElement("wbr");
218
+ endElement.setAttribute("data-type", "list-selection-end");
219
+ endRange.insertNode(endElement);
220
+
221
+ const startRange = range.cloneRange();
222
+ startRange.collapse(true);
223
+ const startElement = document.createElement("wbr");
224
+ startElement.setAttribute("data-type", "list-selection-start");
225
+ startRange.insertNode(startElement);
226
+ } else {
227
+ range.insertNode(document.createElement("wbr"));
228
+ }
184
229
 
185
230
  if (cancel && itemElement) {
186
231
  // 取消
@@ -197,25 +242,24 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
197
242
  } else {
198
243
  if (!itemElement) {
199
244
  // 添加
200
- let blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
201
- if (!blockElement) {
245
+ if (selectedBlockElements.length === 0) {
202
246
  vditor[vditor.currentMode].element.querySelector("wbr").remove();
203
- blockElement = vditor[vditor.currentMode].element.querySelector("p");
247
+ const blockElement = vditor[vditor.currentMode].element.querySelector("p");
204
248
  blockElement.innerHTML = "<wbr>";
249
+ selectedBlockElements.push(blockElement);
205
250
  }
206
- 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
- }
251
+ let listHTML = "";
252
+ selectedBlockElements.forEach((blockElement) => {
253
+ if (type === "check") {
254
+ listHTML += `<li class="vditor-task"><input type="checkbox" /> ${blockElement.innerHTML}</li>`;
255
+ } else {
256
+ listHTML += `<li>${blockElement.innerHTML}</li>`;
257
+ }
258
+ });
259
+ const listTagName = type === "ordered-list" ? "ol" : "ul";
260
+ selectedBlockElements[0].insertAdjacentHTML("beforebegin",
261
+ `<${listTagName} data-block="0">${listHTML}</${listTagName}>`);
262
+ selectedBlockElements.forEach((blockElement) => blockElement.remove());
219
263
  } else {
220
264
  // 切换
221
265
  if (type === "check") {
@@ -227,7 +271,14 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
227
271
  } else {
228
272
  if (itemElement.querySelector("input")) {
229
273
  itemElement.parentElement.querySelectorAll("li").forEach((item) => {
230
- item.querySelector("input").remove();
274
+ const inputElement = item.querySelector("input");
275
+ if (inputElement) {
276
+ const nextSibling = inputElement.nextSibling;
277
+ inputElement.remove();
278
+ if (nextSibling && nextSibling.nodeType === 3 && nextSibling.textContent.startsWith(" ")) {
279
+ nextSibling.textContent = nextSibling.textContent.substring(1);
280
+ }
281
+ }
231
282
  item.classList.remove("vditor-task");
232
283
  });
233
284
  }
@@ -246,6 +297,18 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
246
297
  }
247
298
  }
248
299
  }
300
+ if (keepSelection) {
301
+ const editorElement = vditor[vditor.currentMode].element;
302
+ const startElement = editorElement.querySelector('[data-type="list-selection-start"]');
303
+ const endElement = editorElement.querySelector('[data-type="list-selection-end"]');
304
+ if (startElement && endElement) {
305
+ range.setStartAfter(startElement);
306
+ range.setEndBefore(endElement);
307
+ startElement.remove();
308
+ endElement.remove();
309
+ setSelectionFocus(range);
310
+ }
311
+ }
249
312
  };
250
313
 
251
314
  export const listIndent = (vditor: IVditor, liElement: HTMLElement, range: Range) => {
@@ -1328,7 +1391,8 @@ export const fixFirefoxArrowUpTable = (event: KeyboardEvent, blockElement: false
1328
1391
  export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent) & {target: HTMLElement}, callback: {
1329
1392
  pasteCode(code: string): void,
1330
1393
  }) => {
1331
- if (vditor[vditor.currentMode].element.getAttribute("contenteditable") !== "true") {
1394
+ if (vditor.currentMode === "sv" ? vditor.sv.element.disabled :
1395
+ vditor[vditor.currentMode].element.getAttribute("contenteditable") !== "true") {
1332
1396
  return;
1333
1397
  }
1334
1398
  event.stopPropagation();
@@ -1353,7 +1417,6 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1353
1417
  HTML2VditorIRDOM?: ILuteRender,
1354
1418
  Md2VditorDOM?: ILuteRender,
1355
1419
  Md2VditorIRDOM?: ILuteRender,
1356
- Md2VditorSVDOM?: ILuteRender,
1357
1420
  } = {};
1358
1421
  const renderLinkDest: ILuteRenderCallback = (node, entering) => {
1359
1422
  if (!entering) {
@@ -1397,12 +1460,11 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1397
1460
  }
1398
1461
  const original = responseJSON.data.originalURL;
1399
1462
  if (vditor.currentMode === "sv") {
1400
- vditor.sv.element.querySelectorAll(".vditor-sv__marker--link")
1401
- .forEach((item: HTMLElement) => {
1402
- if (item.textContent === original) {
1403
- item.textContent = responseJSON.data.url;
1404
- }
1405
- });
1463
+ const selectionStart = vditor.sv.element.selectionStart;
1464
+ const selectionEnd = vditor.sv.element.selectionEnd;
1465
+ vditor.sv.element.value = vditor.sv.element.value.split(original)
1466
+ .join(responseJSON.data.url);
1467
+ vditor.sv.element.setSelectionRange(selectionStart, selectionEnd);
1406
1468
  } else {
1407
1469
  const imgElement: HTMLImageElement =
1408
1470
  vditor[vditor.currentMode].element.querySelector(`img[src="${original}"]`);
@@ -1428,7 +1490,7 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1428
1490
  } else if (vditor.currentMode === "wysiwyg") {
1429
1491
  return ["", Lute.WalkContinue];
1430
1492
  } else {
1431
- return [`<span class="vditor-sv__marker--link">${Lute.EscapeHTMLStr(src)}</span>`, Lute.WalkContinue];
1493
+ return ["", Lute.WalkContinue];
1432
1494
  }
1433
1495
  };
1434
1496
 
@@ -1454,7 +1516,13 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1454
1516
  const codeElement = vditor.currentMode === "sv" ?
1455
1517
  hasClosestByAttribute(event.target, "data-type", "code-block") :
1456
1518
  hasClosestByMatchTag(event.target, "CODE");
1457
- if (codeElement) {
1519
+ const cellElement = vditor.currentMode === "wysiwyg" &&
1520
+ (hasClosestByMatchTag(getEditorRange(vditor).startContainer, "TD") ||
1521
+ hasClosestByMatchTag(getEditorRange(vditor).startContainer, "TH"));
1522
+ if (cellElement && /[\r\n]/.test(textPlain)) {
1523
+ // 表格单元格内的换行使用 br 表示,避免自旋时解析为新的表格行
1524
+ insertHTML(Lute.EscapeHTMLStr(textPlain).replace(/\r\n|\r|\n/g, "<br>"), vditor);
1525
+ } else if (codeElement) {
1458
1526
  // 粘贴在代码位置
1459
1527
  if (vditor.currentMode === "sv") {
1460
1528
  document.execCommand("insertHTML", false, textPlain.replace(/&/g, "&amp;").replace(/</g, "&lt;"));
@@ -1500,8 +1568,6 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1500
1568
  vditor.lute.SetJSRenderers({renderers});
1501
1569
  insertHTML(vditor.lute.HTML2VditorDOM(tempElement.innerHTML), vditor);
1502
1570
  } else {
1503
- renderers.Md2VditorSVDOM = {renderLinkDest};
1504
- vditor.lute.SetJSRenderers({renderers});
1505
1571
  processPaste(vditor, vditor.lute.HTML2Md(tempElement.innerHTML).trimRight());
1506
1572
  }
1507
1573
  vditor.outline.render(vditor);
@@ -1527,14 +1593,20 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1527
1593
  } else {
1528
1594
  imgHTML += `![${file.name}](${fileReader.result.toString()})\n`;
1529
1595
  }
1530
- document.execCommand("insertHTML", false, imgHTML);
1596
+ if (vditor.currentMode === "sv") {
1597
+ processPaste(vditor, imgHTML);
1598
+ execAfterRender(vditor);
1599
+ } else {
1600
+ document.execCommand("insertHTML", false, imgHTML);
1601
+ }
1531
1602
  };
1532
1603
  }
1533
1604
  }
1534
1605
  } else if (textPlain.trim() !== "" && files.length === 0) {
1535
- const range = getEditorRange(vditor);
1536
- if (range.toString() !== "" && vditor.lute.IsValidLinkDest(textPlain)) {
1537
- textPlain = `[${range.toString()}](${textPlain})`;
1606
+ const selectedText = vditor.currentMode === "sv" ? vditor.sv.element.value.substring(
1607
+ vditor.sv.element.selectionStart, vditor.sv.element.selectionEnd) : getEditorRange(vditor).toString();
1608
+ if (selectedText !== "" && vditor.lute.IsValidLinkDest(textPlain)) {
1609
+ textPlain = `[${selectedText}](${textPlain})`;
1538
1610
  }
1539
1611
  if (vditor.currentMode === "ir") {
1540
1612
  renderers.Md2VditorIRDOM = {renderLinkDest};
@@ -1545,8 +1617,6 @@ export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent)
1545
1617
  vditor.lute.SetJSRenderers({renderers});
1546
1618
  insertHTML(Lute.Sanitize(vditor.lute.Md2VditorDOM(textPlain)), vditor);
1547
1619
  } else {
1548
- renderers.Md2VditorSVDOM = {renderLinkDest};
1549
- vditor.lute.SetJSRenderers({renderers});
1550
1620
  processPaste(vditor, textPlain);
1551
1621
  }
1552
1622
  vditor.outline.render(vditor);
@@ -3,6 +3,9 @@ import {isChrome} from "./compatibility";
3
3
  import {hasClosestBlock, hasClosestByClassName, hasClosestByMatchTag} from "./hasClosest";
4
4
 
5
5
  export const getEditorRange = (vditor: IVditor) => {
6
+ if (vditor.currentMode === "sv") {
7
+ throw new Error("SV mode uses textarea selection");
8
+ }
6
9
  let range: Range;
7
10
  const element = vditor[vditor.currentMode].element;
8
11
  if (getSelection().rangeCount > 0) {
@@ -11,8 +14,9 @@ export const getEditorRange = (vditor: IVditor) => {
11
14
  return range;
12
15
  }
13
16
  }
14
- if (vditor[vditor.currentMode].range) {
15
- return vditor[vditor.currentMode].range;
17
+ const lastRange = vditor.currentMode === "ir" ? vditor.ir.range : vditor.wysiwyg.range;
18
+ if (lastRange) {
19
+ return lastRange;
16
20
  }
17
21
  element.focus();
18
22
  range = element.ownerDocument.createRange();
@@ -250,7 +254,11 @@ export const insertHTML = (html: string, vditor: IVditor) => {
250
254
 
251
255
  const range = getEditorRange(vditor);
252
256
  if (range.toString() !== "") {
253
- vditor[vditor.currentMode].preventInput = true;
257
+ if (vditor.currentMode === "ir") {
258
+ vditor.ir.preventInput = true;
259
+ } else if (vditor.currentMode === "wysiwyg") {
260
+ vditor.wysiwyg.preventInput = true;
261
+ }
254
262
  document.execCommand("delete", false, "");
255
263
  }
256
264
 
@@ -3,7 +3,7 @@ import {disableToolbar} from "../toolbar/setToolbar";
3
3
  import {enableToolbar} from "../toolbar/setToolbar";
4
4
  import {removeCurrentToolbar} from "../toolbar/setToolbar";
5
5
  import {setCurrentToolbar} from "../toolbar/setToolbar";
6
- import {isCtrl, updateHotkeyTip} from "../util/compatibility";
6
+ import {getEventName, isCtrl, updateHotkeyTip} from "../util/compatibility";
7
7
  import {scrollCenter} from "../util/editorCommonEvent";
8
8
  import {
9
9
  deleteColumn,
@@ -793,6 +793,57 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
793
793
  }, 200);
794
794
  };
795
795
 
796
+ const genMobileToolbar = (type: TWYSISYGToolbar, vditor: IVditor) => {
797
+ const customToolbar = vditor.options.customWysiwygMobileToolbar;
798
+ if (!customToolbar || window.innerWidth > 520) {
799
+ return;
800
+ }
801
+ const toolbarElement = document.createElement("div");
802
+ toolbarElement.className = "vditor-wysiwyg__mobile-toolbar";
803
+
804
+ const menuButton = document.createElement("button");
805
+ menuButton.type = "button";
806
+ menuButton.className = "vditor-icon";
807
+ menuButton.setAttribute("data-type", "mobile-menu");
808
+ menuButton.setAttribute("aria-label", window.VditorI18n.more);
809
+ menuButton.textContent = "+";
810
+
811
+ const actionsElement = document.createElement("div");
812
+ actionsElement.className = "vditor-panel vditor-wysiwyg__mobile-actions";
813
+ const savedRange = getEditorRange(vditor).cloneRange();
814
+ customToolbar(type, actionsElement);
815
+
816
+ if (!actionsElement.hasChildNodes()) {
817
+ return;
818
+ }
819
+
820
+ const restoreSelection = () => {
821
+ if (!vditor.wysiwyg.element.contains(savedRange.commonAncestorContainer)) {
822
+ return;
823
+ }
824
+ const restoredRange = savedRange.cloneRange();
825
+ vditor.wysiwyg.range = restoredRange;
826
+ setSelectionFocus(restoredRange);
827
+ actionsElement.classList.remove("vditor-wysiwyg__mobile-actions--open");
828
+ };
829
+ actionsElement.addEventListener("touchstart", restoreSelection, true);
830
+ actionsElement.addEventListener("click", restoreSelection, true);
831
+
832
+ menuButton.addEventListener(getEventName(), (event) => {
833
+ event.preventDefault();
834
+ event.stopPropagation();
835
+ actionsElement.classList.toggle("vditor-wysiwyg__mobile-actions--open");
836
+ actionsElement.classList.remove("vditor-panel--left");
837
+ if (actionsElement.getBoundingClientRect().right > window.innerWidth) {
838
+ actionsElement.classList.add("vditor-panel--left");
839
+ }
840
+ });
841
+
842
+ toolbarElement.appendChild(menuButton);
843
+ toolbarElement.appendChild(actionsElement);
844
+ vditor.wysiwyg.popover.insertAdjacentElement("afterbegin", toolbarElement);
845
+ };
846
+
796
847
  const setPopoverPosition = (vditor: IVditor, element: HTMLElement) => {
797
848
  let targetElement = element;
798
849
  const tableElement = hasClosestByMatchTag(element, "TABLE");
@@ -1152,6 +1203,7 @@ const focusToElement = (event: KeyboardEvent, range: Range) => {
1152
1203
  };
1153
1204
 
1154
1205
  const customWysiwygToolbar = (vditor: IVditor, type: TWYSISYGToolbar) => {
1206
+ genMobileToolbar(type, vditor);
1155
1207
  if (vditor.options.customWysiwygToolbar) {
1156
1208
  vditor.options.customWysiwygToolbar(type, vditor.wysiwyg.popover);
1157
1209
  }
@@ -26,6 +26,48 @@ import {nextIsCode} from "./inlineTag";
26
26
  import {removeHeading, setHeading} from "./setHeading";
27
27
  import {showCode} from "./showCode";
28
28
 
29
+ const fixAndroidCursor = (vditor: IVditor, range: Range, event: KeyboardEvent) => {
30
+ if (navigator.userAgent.indexOf("Android") === -1 || event.ctrlKey || event.metaKey || event.altKey) {
31
+ return false;
32
+ }
33
+ const pElement = hasClosestByMatchTag(range.startContainer, "P");
34
+ const startElement = range.startContainer.nodeType === 3 ?
35
+ range.startContainer.parentElement : range.startContainer;
36
+ if (!pElement || !pElement.isSameNode(startElement) ||
37
+ !pElement.parentElement.isSameNode(vditor.wysiwyg.element)) {
38
+ return false;
39
+ }
40
+
41
+ let direction: "backward" | "forward";
42
+ let granularity: "character" | "documentboundary" | "line";
43
+ if (event.key === "ArrowLeft") {
44
+ direction = "backward";
45
+ granularity = "character";
46
+ } else if (event.key === "ArrowRight") {
47
+ direction = "forward";
48
+ granularity = "character";
49
+ } else if (event.key === "ArrowUp") {
50
+ direction = "backward";
51
+ granularity = "line";
52
+ } else if (event.key === "ArrowDown") {
53
+ direction = "forward";
54
+ granularity = "line";
55
+ } else if (event.key === "End") {
56
+ direction = "forward";
57
+ granularity = "documentboundary";
58
+ } else {
59
+ return false;
60
+ }
61
+
62
+ const selection = window.getSelection();
63
+ if (typeof selection.modify !== "function") {
64
+ return false;
65
+ }
66
+ selection.modify(event.shiftKey ? "extend" : "move", direction, granularity);
67
+ event.preventDefault();
68
+ return true;
69
+ };
70
+
29
71
  export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => {
30
72
  // Chrome firefox 触发 compositionend 机制不一致 https://github.com/Vanessa219/vditor/issues/188
31
73
  vditor.wysiwyg.composingLock = event.isComposing;
@@ -42,6 +84,11 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => {
42
84
  const range = getEditorRange(vditor);
43
85
  const startContainer = range.startContainer;
44
86
 
87
+ // Android 输入法的编辑面板无法在 contenteditable 中正确处理方向键和 End
88
+ if (fixAndroidCursor(vditor, range, event)) {
89
+ return true;
90
+ }
91
+
45
92
  if (!fixGSKeyBackspace(event, vditor, startContainer)) {
46
93
  return false;
47
94
  }
@@ -1,6 +0,0 @@
1
- /**
2
- * 合并脚注
3
- * @param elements vditor.sv.element
4
- * @param afterCombine 每个脚注块合并完成后的回调, param: root为合并后的脚注块
5
- */
6
- export declare const combineFootnote: (elements: HTMLElement, afterCombine?: (root: HTMLElement) => void) => void;
@@ -1 +0,0 @@
1
- export declare const inputEvent: (vditor: IVditor, event?: InputEvent) => void;
@@ -1,27 +0,0 @@
1
- /**
2
- * 合并脚注
3
- * @param elements vditor.sv.element
4
- * @param afterCombine 每个脚注块合并完成后的回调, param: root为合并后的脚注块
5
- */
6
- export const combineFootnote = (elements: HTMLElement, afterCombine?: (root: HTMLElement) => void ) => {
7
- elements.querySelectorAll("[data-type=footnotes-link]").forEach((el: Element) => {
8
- const root = el.parentElement
9
- let footnote = root.nextSibling
10
- // 寻找所有该脚注的块
11
- while (footnote) {
12
- if (footnote.textContent.startsWith(" ")) {
13
- // 解析到四个空格,加入到root并继续解析
14
- const thisNode = footnote
15
- thisNode.childNodes.forEach(node => {
16
- root.append(node.cloneNode(true))
17
- })
18
- footnote = footnote.nextSibling
19
- thisNode.remove()
20
- } else {
21
- // 非空格停止解析
22
- break
23
- }
24
- }
25
- afterCombine && afterCombine(root)
26
- })
27
- }
@@ -1,188 +0,0 @@
1
- import {scrollCenter} from "../util/editorCommonEvent";
2
- import {hasClosestByAttribute} from "../util/hasClosest";
3
- import {getSelectPosition, setRangeByWbr} from "../util/selection";
4
- import {getSideByType, processAfterRender, processSpinVditorSVDOM} from "./process";
5
- import {combineFootnote} from "./combineFootnote";
6
-
7
- export const inputEvent = (vditor: IVditor, event?: InputEvent) => {
8
- const range = getSelection().getRangeAt(0).cloneRange();
9
- let startContainer = range.startContainer;
10
- if (range.startContainer.nodeType !== 3 && (range.startContainer as HTMLElement).tagName === "DIV") {
11
- startContainer = range.startContainer.childNodes[range.startOffset - 1];
12
- }
13
- let blockElement = hasClosestByAttribute(startContainer, "data-block", "0");
14
- // 不调用 lute 解析
15
- if (blockElement && event && (event.inputType === "deleteContentBackward" || event.data === " ")) {
16
- // 开始可以输入空格
17
- const startOffset = getSelectPosition(blockElement, vditor.sv.element, range).start;
18
- let startSpace = true;
19
- for (let i = startOffset - 1;
20
- // 软换行后有空格
21
- i > blockElement.textContent.substr(0, startOffset).lastIndexOf("\n"); i--) {
22
- if (blockElement.textContent.charAt(i) !== " " &&
23
- // 多个 tab 前删除不形成代码块 https://github.com/Vanessa219/vditor/issues/162 1
24
- blockElement.textContent.charAt(i) !== "\t") {
25
- startSpace = false;
26
- break;
27
- }
28
- }
29
- if (startOffset === 0) {
30
- startSpace = false;
31
- }
32
- if (startSpace) {
33
- processAfterRender(vditor);
34
- return;
35
- }
36
-
37
- if (event.inputType === "deleteContentBackward") {
38
- // https://github.com/Vanessa219/vditor/issues/584 代码块 marker 删除
39
- const codeBlockMarkerElement =
40
- hasClosestByAttribute(startContainer, "data-type", "code-block-open-marker") ||
41
- hasClosestByAttribute(startContainer, "data-type", "code-block-close-marker");
42
- if (codeBlockMarkerElement) {
43
- if (codeBlockMarkerElement.getAttribute("data-type") === "code-block-close-marker") {
44
- const openMarkerElement = getSideByType(startContainer, "code-block-open-marker");
45
- if (openMarkerElement) {
46
- openMarkerElement.textContent = codeBlockMarkerElement.textContent;
47
- processAfterRender(vditor);
48
- return;
49
- }
50
- }
51
- if (codeBlockMarkerElement.getAttribute("data-type") === "code-block-open-marker") {
52
- const openMarkerElement = getSideByType(startContainer, "code-block-close-marker", false);
53
- if (openMarkerElement) {
54
- openMarkerElement.textContent = codeBlockMarkerElement.textContent;
55
- processAfterRender(vditor);
56
- return;
57
- }
58
- }
59
- }
60
- // https://github.com/Vanessa219/vditor/issues/877 数学公式输入删除生成节点
61
- const mathBlockMarkerElement =
62
- hasClosestByAttribute(startContainer, "data-type", "math-block-open-marker");
63
- if (mathBlockMarkerElement) {
64
- const mathBlockCloseElement = mathBlockMarkerElement.nextElementSibling.nextElementSibling;
65
- if (mathBlockCloseElement && mathBlockCloseElement.getAttribute("data-type") === "math-block-close-marker") {
66
- mathBlockCloseElement.remove();
67
- processAfterRender(vditor);
68
- }
69
- return;
70
- }
71
-
72
- blockElement.querySelectorAll('[data-type="code-block-open-marker"]').forEach((item: HTMLElement) => {
73
- if (item.textContent.length === 1) {
74
- item.remove();
75
- }
76
- });
77
- blockElement.querySelectorAll('[data-type="code-block-close-marker"]').forEach((item: HTMLElement) => {
78
- if (item.textContent.length === 1) {
79
- item.remove();
80
- }
81
- });
82
-
83
- // 标题删除
84
- const headingElement = hasClosestByAttribute(startContainer, "data-type", "heading-marker");
85
- if (headingElement && headingElement.textContent.indexOf("#") === -1) {
86
- processAfterRender(vditor);
87
- return;
88
- }
89
- }
90
- // 删除或空格不解析,否则会 format 回去
91
- if ((event.data === " " || event.inputType === "deleteContentBackward") &&
92
- (hasClosestByAttribute(startContainer, "data-type", "padding") // 场景:b 前进行删除 [> 1. a\n> b]
93
- || hasClosestByAttribute(startContainer, "data-type", "li-marker") // 场景:删除最后一个字符 [* 1\n* ]
94
- || hasClosestByAttribute(startContainer, "data-type", "task-marker") // 场景:删除最后一个字符 [* [ ] ]
95
- || hasClosestByAttribute(startContainer, "data-type", "blockquote-marker") // 场景:删除最后一个字符 [> ]
96
- )) {
97
- processAfterRender(vditor);
98
- return;
99
- }
100
- }
101
- if (blockElement && blockElement.textContent.trimRight() === "$$") {
102
- // 内联数学公式
103
- processAfterRender(vditor);
104
- return;
105
- }
106
- if (!blockElement) {
107
- blockElement = vditor.sv.element;
108
- }
109
- if (blockElement.firstElementChild?.getAttribute("data-type") === "link-ref-defs-block") {
110
- // 修改链接引用
111
- blockElement = vditor.sv.element;
112
- }
113
- if (hasClosestByAttribute(startContainer, "data-type", "footnotes-link")) {
114
- // 修改脚注角标
115
- blockElement = vditor.sv.element;
116
- }
117
- // 添加光标位置
118
- if (blockElement.textContent.indexOf(Lute.Caret) === -1) {
119
- // 点击工具栏会插入 Caret
120
- range.insertNode(document.createTextNode(Lute.Caret));
121
- }
122
- // 清除浏览器自带的样式
123
- blockElement.querySelectorAll("[style]").forEach((item) => { // 不可前置,否则会影响 newline 的样式
124
- item.removeAttribute("style");
125
- });
126
- blockElement.querySelectorAll("font").forEach((item) => { // 不可前置,否则会影响光标的位置
127
- item.outerHTML = item.innerHTML;
128
- });
129
- let html = blockElement.textContent;
130
- const isSVElement = blockElement.isEqualNode(vditor.sv.element);
131
- if (isSVElement) {
132
- html = blockElement.textContent;
133
- } else {
134
- // 添加前一个块元素
135
- if (blockElement.previousElementSibling) {
136
- html = blockElement.previousElementSibling.textContent + html;
137
- blockElement.previousElementSibling.remove();
138
- }
139
- if (blockElement.previousElementSibling && html.indexOf("---\n") === 0) {
140
- // 确认 yaml-front 是否为首行
141
- html = blockElement.previousElementSibling.textContent + html;
142
- blockElement.previousElementSibling.remove();
143
- }
144
- // 添加链接引用
145
- let footnotes = ""
146
-
147
- vditor.sv.element.querySelectorAll("[data-type='link-ref-defs-block']").forEach((item, index) => {
148
- if (item && !(blockElement as HTMLElement).isEqualNode(item.parentElement)) {
149
- footnotes += item.parentElement.textContent + "\n";
150
- item.parentElement.remove();
151
- }
152
- });
153
-
154
- // 添加脚注到文章头,便于lute处理
155
- vditor.sv.element.querySelectorAll("[data-type='footnotes-link']").forEach((item, index) => {
156
- if (item && !(blockElement as HTMLElement).isEqualNode(item.parentElement)) {
157
- footnotes += item.parentElement.textContent + "\n";
158
- item.parentElement.remove();
159
- }
160
- });
161
- html = footnotes + html;
162
- }
163
- html = processSpinVditorSVDOM(html, vditor);
164
- if (isSVElement) {
165
- blockElement.innerHTML = html;
166
- } else {
167
- blockElement.outerHTML = html;
168
- }
169
-
170
- vditor.sv.element.querySelectorAll("[data-type='link-ref-defs-block']").forEach((item) => {
171
- vditor.sv.element.insertAdjacentElement("beforeend", item.parentElement)
172
- })
173
-
174
- // 合并脚注
175
- combineFootnote(vditor.sv.element, (root: HTMLElement) => {
176
- vditor.sv.element.insertAdjacentElement("beforeend", root)
177
- })
178
-
179
- setRangeByWbr(vditor.sv.element, range);
180
-
181
- scrollCenter(vditor);
182
-
183
- processAfterRender(vditor, {
184
- enableAddUndoStack: true,
185
- enableHint: true,
186
- enableInput: true,
187
- });
188
- };