vditor 3.11.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +12 -1
  2. package/dist/css/content-theme/ant-design.css +10 -0
  3. package/dist/css/content-theme/dark.css +10 -0
  4. package/dist/css/content-theme/light.css +13 -0
  5. package/dist/css/content-theme/wechat.css +10 -0
  6. package/dist/index.css +163 -81
  7. package/dist/index.js +3035 -2715
  8. package/dist/index.min.js +1 -1
  9. package/dist/js/i18n/ja_JP.js +2 -2
  10. package/dist/js/i18n/zh_TW.js +8 -8
  11. package/dist/js/lute/lute.min.js +75 -60
  12. package/dist/js/mermaid/mermaid.min.js +2027 -1047
  13. package/dist/js/wavedrom/LICENSE.WaveDrom +21 -0
  14. package/dist/js/wavedrom/wavedrom.min.js +9 -0
  15. package/dist/method.d.ts +2 -0
  16. package/dist/method.js +323 -9
  17. package/dist/method.min.js +1 -1
  18. package/dist/ts/constants.d.ts +4 -0
  19. package/dist/ts/markdown/adapterRender.d.ts +4 -0
  20. package/dist/ts/markdown/imageCaptionRender.d.ts +2 -0
  21. package/dist/ts/markdown/wavedromRender.d.ts +1 -0
  22. package/dist/ts/sv/index.d.ts +1 -5
  23. package/dist/ts/sv/process.d.ts +1 -3
  24. package/dist/ts/upload/getElement.d.ts +1 -1
  25. package/dist/ts/upload/index.d.ts +2 -0
  26. package/dist/ts/util/editorCommonEvent.d.ts +1 -1
  27. package/dist/ts/util/fixBrowserBehavior.d.ts +1 -0
  28. package/dist/types/index.d.ts +22 -5
  29. package/package.json +7 -4
  30. package/src/assets/less/_reset.less +103 -2
  31. package/src/assets/less/_sv.less +2 -98
  32. package/src/assets/less/_wysiwyg.less +30 -0
  33. package/src/assets/less/index.less +20 -0
  34. package/src/index.ts +48 -14
  35. package/src/method.ts +3 -0
  36. package/src/ts/constants.ts +5 -1
  37. package/src/ts/export/index.ts +2 -0
  38. package/src/ts/hint/index.ts +9 -4
  39. package/src/ts/ir/highlightToolbarIR.ts +4 -4
  40. package/src/ts/ir/index.ts +26 -0
  41. package/src/ts/ir/process.ts +24 -1
  42. package/src/ts/markdown/adapterRender.ts +4 -0
  43. package/src/ts/markdown/getHTML.ts +6 -3
  44. package/src/ts/markdown/getMarkdown.ts +3 -3
  45. package/src/ts/markdown/imageCaptionRender.ts +175 -0
  46. package/src/ts/markdown/mermaidRender.ts +1 -1
  47. package/src/ts/markdown/previewRender.ts +7 -1
  48. package/src/ts/markdown/setLute.ts +3 -0
  49. package/src/ts/markdown/wavedromRender.ts +54 -0
  50. package/src/ts/preview/index.ts +5 -0
  51. package/src/ts/sv/index.ts +37 -76
  52. package/src/ts/sv/process.ts +48 -164
  53. package/src/ts/sv/processKeydown.ts +19 -186
  54. package/src/ts/toolbar/EditMode.ts +3 -10
  55. package/src/ts/toolbar/Emoji.ts +8 -1
  56. package/src/ts/toolbar/Record.ts +10 -2
  57. package/src/ts/toolbar/index.ts +3 -0
  58. package/src/ts/ui/initUI.ts +7 -3
  59. package/src/ts/undo/index.ts +32 -17
  60. package/src/ts/upload/index.ts +42 -10
  61. package/src/ts/util/Options.ts +1 -1
  62. package/src/ts/util/editorCommonEvent.ts +18 -9
  63. package/src/ts/util/fixBrowserBehavior.ts +197 -51
  64. package/src/ts/util/processCode.ts +3 -0
  65. package/src/ts/util/selection.ts +11 -3
  66. package/src/ts/wysiwyg/afterRenderEvent.ts +2 -0
  67. package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +58 -2
  68. package/src/ts/wysiwyg/input.ts +5 -0
  69. package/src/ts/wysiwyg/processKeydown.ts +47 -0
  70. package/src/ts/wysiwyg/setHeading.ts +110 -6
  71. package/dist/ts/sv/combineFootnote.d.ts +0 -6
  72. package/dist/ts/sv/inputEvent.d.ts +0 -1
  73. package/src/ts/sv/combineFootnote.ts +0 -27
  74. package/src/ts/sv/inputEvent.ts +0 -188
@@ -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
 
@@ -1,4 +1,5 @@
1
1
  import {getMarkdown} from "../markdown/getMarkdown";
2
+ import {renderImageCaptions} from "../markdown/imageCaptionRender";
2
3
  import {accessLocalStorage} from "../util/compatibility";
3
4
 
4
5
  export const afterRenderEvent = (vditor: IVditor, options = {
@@ -6,6 +7,7 @@ export const afterRenderEvent = (vditor: IVditor, options = {
6
7
  enableHint: false,
7
8
  enableInput: true,
8
9
  }) => {
10
+ renderImageCaptions(vditor.wysiwyg.element, "wysiwyg", vditor.options.preview.markdown.imageCaption);
9
11
  if (options.enableHint) {
10
12
  vditor.hint.render(vditor);
11
13
  }
@@ -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,
@@ -33,6 +33,7 @@ import {afterRenderEvent} from "./afterRenderEvent";
33
33
  import {removeBlockElement} from "./processKeydown";
34
34
  import {renderToc} from "../util/toc";
35
35
  import {getMarkdown} from "../markdown/getMarkdown";
36
+ import {renderImageCaptions} from "../markdown/imageCaptionRender";
36
37
 
37
38
  export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
38
39
  clearTimeout(vditor.wysiwyg.hlToolbarTimeoutId);
@@ -792,6 +793,57 @@ export const highlightToolbarWYSIWYG = (vditor: IVditor) => {
792
793
  }, 200);
793
794
  };
794
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
+
795
847
  const setPopoverPosition = (vditor: IVditor, element: HTMLElement) => {
796
848
  let targetElement = element;
797
849
  const tableElement = hasClosestByMatchTag(element, "TABLE");
@@ -1079,6 +1131,7 @@ export const genImagePopover = (event: Event, vditor: IVditor) => {
1079
1131
  imgElement.setAttribute("src", inputElement.value);
1080
1132
  imgElement.setAttribute("alt", alt.value);
1081
1133
  imgElement.setAttribute("title", title.value);
1134
+ renderImageCaptions(vditor.wysiwyg.element, "wysiwyg", vditor.options.preview.markdown.imageCaption);
1082
1135
  if (typeof vditor.options.input === "function") {
1083
1136
  vditor.options.input(getMarkdown(vditor));
1084
1137
  }
@@ -1150,5 +1203,8 @@ const focusToElement = (event: KeyboardEvent, range: Range) => {
1150
1203
  };
1151
1204
 
1152
1205
  const customWysiwygToolbar = (vditor: IVditor, type: TWYSISYGToolbar) => {
1153
- vditor.options.customWysiwygToolbar(type, vditor.wysiwyg.popover);
1206
+ genMobileToolbar(type, vditor);
1207
+ if (vditor.options.customWysiwygToolbar) {
1208
+ vditor.options.customWysiwygToolbar(type, vditor.wysiwyg.popover);
1209
+ }
1154
1210
  };
@@ -71,6 +71,7 @@ export const input = (vditor: IVditor, range: Range, event?: InputEvent) => {
71
71
 
72
72
  const isWYSIWYGElement = blockElement.isEqualNode(vditor.wysiwyg.element);
73
73
  const footnoteElement = hasClosestByAttribute(blockElement, "data-type", "footnotes-block");
74
+ const calloutElement = hasClosestByAttribute(range.startContainer, "data-type", "callout");
74
75
 
75
76
  if (!isWYSIWYGElement) {
76
77
  // 列表需要到最顶层
@@ -90,6 +91,10 @@ export const input = (vditor: IVditor, range: Range, event?: InputEvent) => {
90
91
  blockElement = footnoteElement;
91
92
  }
92
93
 
94
+ if (calloutElement) {
95
+ blockElement = calloutElement;
96
+ }
97
+
93
98
  html = blockElement.outerHTML;
94
99
 
95
100
  if (blockElement.tagName === "UL" || blockElement.tagName === "OL") {
@@ -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,7 +1,94 @@
1
- import {hasClosestBlock} from "../util/hasClosest";
1
+ import {hasClosestBlock, hasClosestByMatchTag} from "../util/hasClosest";
2
2
  import {getEditorRange, setRangeByWbr} from "../util/selection";
3
3
  import {renderToc} from "../util/toc";
4
4
 
5
+ const createBlockElement = (element: HTMLElement, tagName: string) => {
6
+ const blockElement = element.ownerDocument.createElement(tagName);
7
+ blockElement.setAttribute("data-block", "0");
8
+ return blockElement;
9
+ };
10
+
11
+ const replaceBlockElement = (element: HTMLElement, tagName: string) => {
12
+ const blockElement = createBlockElement(element, tagName);
13
+ while (element.firstChild) {
14
+ blockElement.appendChild(element.firstChild);
15
+ }
16
+ element.replaceWith(blockElement);
17
+ return blockElement;
18
+ };
19
+
20
+ const isListItemBlock = (node: Node) => {
21
+ if (node.nodeType !== 1) {
22
+ return false;
23
+ }
24
+ const element = node as HTMLElement;
25
+ return element.getAttribute("data-block") === "0" || element.tagName === "INPUT";
26
+ };
27
+
28
+ const getListItemInlineNodes = (itemElement: HTMLElement, wbrElement: HTMLElement) => {
29
+ let anchorNode: Node = wbrElement;
30
+ while (anchorNode.parentNode && !anchorNode.parentNode.isSameNode(itemElement)) {
31
+ anchorNode = anchorNode.parentNode;
32
+ }
33
+ if (!anchorNode.parentNode?.isSameNode(itemElement)) {
34
+ return [];
35
+ }
36
+
37
+ let firstNode = anchorNode;
38
+ let lastNode = anchorNode;
39
+ while (firstNode.previousSibling && !isListItemBlock(firstNode.previousSibling)) {
40
+ firstNode = firstNode.previousSibling;
41
+ }
42
+ while (lastNode.nextSibling && !isListItemBlock(lastNode.nextSibling)) {
43
+ lastNode = lastNode.nextSibling;
44
+ }
45
+
46
+ // 任务列表的复选框和正文之间需要保留分隔空格。
47
+ if (firstNode.previousSibling instanceof HTMLInputElement && firstNode.nodeType === 3) {
48
+ const leadingWhitespace = firstNode.textContent.match(/^\s+/)?.[0];
49
+ if (leadingWhitespace) {
50
+ firstNode.textContent = firstNode.textContent.substring(leadingWhitespace.length);
51
+ itemElement.insertBefore(itemElement.ownerDocument.createTextNode(leadingWhitespace), firstNode);
52
+ }
53
+ }
54
+
55
+ const nodes: Node[] = [];
56
+ let node: Node = firstNode;
57
+ while (node) {
58
+ nodes.push(node);
59
+ if (node.isSameNode(lastNode)) {
60
+ break;
61
+ }
62
+ node = node.nextSibling;
63
+ }
64
+ return nodes;
65
+ };
66
+
67
+ const setListItemHeading = (itemElement: HTMLElement, wbrElement: HTMLElement, tagName: string) => {
68
+ const inlineNodes = getListItemInlineNodes(itemElement, wbrElement);
69
+ if (inlineNodes.length === 0) {
70
+ return false;
71
+ }
72
+
73
+ const headingElement = createBlockElement(itemElement, tagName);
74
+ itemElement.insertBefore(headingElement, inlineNodes[0]);
75
+ inlineNodes.forEach((node) => {
76
+ headingElement.appendChild(node);
77
+ });
78
+ if (headingElement.innerHTML.trim() === "<wbr>") {
79
+ headingElement.appendChild(itemElement.ownerDocument.createElement("br"));
80
+ }
81
+ return true;
82
+ };
83
+
84
+ const unwrapBlockElement = (element: HTMLElement) => {
85
+ const fragment = element.ownerDocument.createDocumentFragment();
86
+ while (element.firstChild) {
87
+ fragment.appendChild(element.firstChild);
88
+ }
89
+ element.replaceWith(fragment);
90
+ };
91
+
5
92
  export const setHeading = (vditor: IVditor, tagName: string) => {
6
93
  const range = getEditorRange(vditor);
7
94
  let blockElement = hasClosestBlock(range.startContainer);
@@ -12,16 +99,27 @@ export const setHeading = (vditor: IVditor, tagName: string) => {
12
99
  blockElement = vditor.wysiwyg.element;
13
100
  }
14
101
  if (blockElement && !blockElement.classList.contains("vditor-wysiwyg__block")) {
15
- range.insertNode(document.createElement("wbr"));
102
+ const wbrElement = document.createElement("wbr");
103
+ range.insertNode(wbrElement);
16
104
  // Firefox 需要 trim https://github.com/Vanessa219/vditor/issues/207
17
105
  if (blockElement.innerHTML.trim() === "<wbr>") {
18
106
  // Firefox 光标对不齐 https://github.com/Vanessa219/vditor/issues/199 1
19
107
  blockElement.innerHTML = "<wbr><br>";
20
108
  }
21
- if (blockElement.tagName === "BLOCKQUOTE" || blockElement.classList.contains("vditor-reset")) {
22
- blockElement.innerHTML = `<${tagName} data-block="0">${blockElement.innerHTML.trim()}</${tagName}>`;
109
+ const itemElement = hasClosestByMatchTag(wbrElement, "LI");
110
+ if (itemElement && blockElement.isSameNode(itemElement.parentElement)) {
111
+ if (!setListItemHeading(itemElement, wbrElement, tagName)) {
112
+ wbrElement.remove();
113
+ return;
114
+ }
115
+ } else if (blockElement.tagName === "BLOCKQUOTE" || blockElement.classList.contains("vditor-reset")) {
116
+ const headingElement = createBlockElement(blockElement, tagName);
117
+ while (blockElement.firstChild) {
118
+ headingElement.appendChild(blockElement.firstChild);
119
+ }
120
+ blockElement.appendChild(headingElement);
23
121
  } else {
24
- blockElement.outerHTML = `<${tagName} data-block="0">${blockElement.innerHTML.trim()}</${tagName}>`;
122
+ replaceBlockElement(blockElement, tagName);
25
123
  }
26
124
  setRangeByWbr(vditor.wysiwyg.element, range);
27
125
  renderToc(vditor);
@@ -36,8 +134,14 @@ export const removeHeading = (vditor: IVditor) => {
36
134
  }
37
135
  if (blockElement) {
38
136
  range.insertNode(document.createElement("wbr"));
39
- blockElement.outerHTML = `<p data-block="0">${blockElement.innerHTML}</p>`;
137
+ if (blockElement.parentElement?.tagName === "LI" &&
138
+ blockElement.parentElement.parentElement?.getAttribute("data-tight") === "true") {
139
+ unwrapBlockElement(blockElement);
140
+ } else {
141
+ replaceBlockElement(blockElement, "p");
142
+ }
40
143
  setRangeByWbr(vditor.wysiwyg.element, range);
144
+ renderToc(vditor);
41
145
  }
42
146
  vditor.wysiwyg.popover.style.display = "none";
43
147
  };
@@ -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
- };