vditor 3.11.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/css/content-theme/ant-design.css +10 -0
- package/dist/css/content-theme/dark.css +10 -0
- package/dist/css/content-theme/light.css +13 -0
- package/dist/css/content-theme/wechat.css +10 -0
- package/dist/index.css +163 -81
- package/dist/index.js +3035 -2715
- package/dist/index.min.js +1 -1
- package/dist/js/i18n/ja_JP.js +2 -2
- package/dist/js/i18n/zh_TW.js +8 -8
- package/dist/js/lute/lute.min.js +75 -60
- package/dist/js/mermaid/mermaid.min.js +2027 -1047
- package/dist/js/wavedrom/LICENSE.WaveDrom +21 -0
- package/dist/js/wavedrom/wavedrom.min.js +9 -0
- package/dist/method.d.ts +2 -0
- package/dist/method.js +323 -9
- package/dist/method.min.js +1 -1
- package/dist/ts/constants.d.ts +4 -0
- package/dist/ts/markdown/adapterRender.d.ts +4 -0
- package/dist/ts/markdown/imageCaptionRender.d.ts +2 -0
- package/dist/ts/markdown/wavedromRender.d.ts +1 -0
- package/dist/ts/sv/index.d.ts +1 -5
- package/dist/ts/sv/process.d.ts +1 -3
- package/dist/ts/upload/getElement.d.ts +1 -1
- package/dist/ts/upload/index.d.ts +2 -0
- package/dist/ts/util/editorCommonEvent.d.ts +1 -1
- package/dist/ts/util/fixBrowserBehavior.d.ts +1 -0
- package/dist/types/index.d.ts +22 -5
- package/package.json +7 -4
- package/src/assets/less/_reset.less +103 -2
- package/src/assets/less/_sv.less +2 -98
- package/src/assets/less/_wysiwyg.less +30 -0
- package/src/assets/less/index.less +20 -0
- package/src/index.ts +48 -14
- package/src/method.ts +3 -0
- package/src/ts/constants.ts +5 -1
- package/src/ts/export/index.ts +2 -0
- package/src/ts/hint/index.ts +9 -4
- package/src/ts/ir/highlightToolbarIR.ts +4 -4
- package/src/ts/ir/index.ts +26 -0
- package/src/ts/ir/process.ts +24 -1
- package/src/ts/markdown/adapterRender.ts +4 -0
- package/src/ts/markdown/getHTML.ts +6 -3
- package/src/ts/markdown/getMarkdown.ts +3 -3
- package/src/ts/markdown/imageCaptionRender.ts +175 -0
- package/src/ts/markdown/mermaidRender.ts +1 -1
- package/src/ts/markdown/previewRender.ts +7 -1
- package/src/ts/markdown/setLute.ts +3 -0
- package/src/ts/markdown/wavedromRender.ts +54 -0
- package/src/ts/preview/index.ts +5 -0
- package/src/ts/sv/index.ts +37 -76
- package/src/ts/sv/process.ts +48 -164
- package/src/ts/sv/processKeydown.ts +19 -186
- package/src/ts/toolbar/EditMode.ts +3 -10
- package/src/ts/toolbar/Emoji.ts +8 -1
- package/src/ts/toolbar/Record.ts +10 -2
- package/src/ts/toolbar/index.ts +3 -0
- package/src/ts/ui/initUI.ts +7 -3
- package/src/ts/undo/index.ts +32 -17
- package/src/ts/upload/index.ts +42 -10
- package/src/ts/util/Options.ts +1 -1
- package/src/ts/util/editorCommonEvent.ts +18 -9
- package/src/ts/util/fixBrowserBehavior.ts +197 -51
- package/src/ts/util/processCode.ts +3 -0
- package/src/ts/util/selection.ts +11 -3
- package/src/ts/wysiwyg/afterRenderEvent.ts +2 -0
- package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +58 -2
- package/src/ts/wysiwyg/input.ts +5 -0
- package/src/ts/wysiwyg/processKeydown.ts +47 -0
- package/src/ts/wysiwyg/setHeading.ts +110 -6
- package/dist/ts/sv/combineFootnote.d.ts +0 -6
- package/dist/ts/sv/inputEvent.d.ts +0 -1
- package/src/ts/sv/combineFootnote.ts +0 -27
- package/src/ts/sv/inputEvent.ts +0 -188
package/src/ts/util/selection.ts
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
|
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
|
-
|
|
1206
|
+
genMobileToolbar(type, vditor);
|
|
1207
|
+
if (vditor.options.customWysiwygToolbar) {
|
|
1208
|
+
vditor.options.customWysiwygToolbar(type, vditor.wysiwyg.popover);
|
|
1209
|
+
}
|
|
1154
1210
|
};
|
package/src/ts/wysiwyg/input.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
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.
|
|
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 +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
|
-
}
|
package/src/ts/sv/inputEvent.ts
DELETED
|
@@ -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
|
-
};
|