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.
- package/README.md +2 -0
- package/dist/css/content-theme/ant-design.css +7 -22
- package/dist/css/content-theme/dark.css +7 -16
- package/dist/css/content-theme/light.css +7 -15
- package/dist/css/content-theme/wechat.css +7 -19
- package/dist/index.css +132 -135
- package/dist/index.js +3791 -4022
- package/dist/index.min.js +1 -1
- package/dist/js/lute/lute.min.js +2 -2
- package/dist/js/mermaid/mermaid.min.js +516 -467
- package/dist/method.js +41 -33
- package/dist/method.min.js +1 -1
- package/dist/ts/constants.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/types/index.d.ts +10 -5
- package/package.json +1 -1
- package/src/assets/less/_reset.less +80 -28
- package/src/assets/less/_sv.less +2 -98
- package/src/assets/less/_wysiwyg.less +30 -0
- package/src/assets/less/index.less +14 -20
- package/src/index.ts +46 -14
- package/src/ts/constants.ts +1 -0
- package/src/ts/export/index.ts +1 -0
- package/src/ts/hint/index.ts +9 -4
- package/src/ts/ir/highlightToolbarIR.ts +4 -4
- package/src/ts/markdown/getMarkdown.ts +3 -3
- package/src/ts/markdown/mediaRender.ts +23 -38
- package/src/ts/markdown/mermaidRender.ts +1 -1
- package/src/ts/markdown/previewRender.ts +1 -0
- package/src/ts/markdown/setLute.ts +1 -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/editorCommonEvent.ts +18 -9
- package/src/ts/util/fixBrowserBehavior.ts +106 -36
- package/src/ts/util/selection.ts +11 -3
- package/src/ts/wysiwyg/highlightToolbarWYSIWYG.ts +53 -1
- package/src/ts/wysiwyg/processKeydown.ts +47 -0
- 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
|
@@ -79,6 +79,21 @@ export const fixCursorDownInlineMath = (range: Range, key: string) => {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
export const insertEmptyBlock = (vditor: IVditor, position: InsertPosition) => {
|
|
82
|
+
if (vditor.currentMode === "sv") {
|
|
83
|
+
const element = vditor.sv.element;
|
|
84
|
+
if (position === "beforebegin") {
|
|
85
|
+
const lineStart = element.value.lastIndexOf("\n", element.selectionStart - 1) + 1;
|
|
86
|
+
element.setRangeText("\n", lineStart, lineStart, "end");
|
|
87
|
+
element.setSelectionRange(lineStart, lineStart);
|
|
88
|
+
} else {
|
|
89
|
+
const lineEndIndex = element.value.indexOf("\n", element.selectionEnd);
|
|
90
|
+
const lineEnd = lineEndIndex === -1 ? element.value.length : lineEndIndex;
|
|
91
|
+
element.setRangeText("\n", lineEnd, lineEnd, "end");
|
|
92
|
+
}
|
|
93
|
+
element.focus();
|
|
94
|
+
processSVAfterRender(vditor);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
82
97
|
const range = getEditorRange(vditor);
|
|
83
98
|
const blockElement = hasClosestBlock(range.startContainer);
|
|
84
99
|
if (blockElement) {
|
|
@@ -177,10 +192,40 @@ export const insertBeforeBlock = (vditor: IVditor, event: KeyboardEvent, range:
|
|
|
177
192
|
|
|
178
193
|
export const listToggle = (vditor: IVditor, range: Range, type: string, cancel = true) => {
|
|
179
194
|
const itemElement = hasClosestByMatchTag(range.startContainer, "LI");
|
|
195
|
+
const selectedBlockElements: HTMLElement[] = [];
|
|
196
|
+
const startBlockElement = hasClosestByAttribute(range.startContainer, "data-block", "0");
|
|
197
|
+
const endBlockElement = hasClosestByAttribute(range.endContainer, "data-block", "0");
|
|
198
|
+
if (startBlockElement && endBlockElement &&
|
|
199
|
+
startBlockElement.parentElement.isSameNode(endBlockElement.parentElement)) {
|
|
200
|
+
let blockElement: Element = startBlockElement;
|
|
201
|
+
while (blockElement) {
|
|
202
|
+
selectedBlockElements.push(blockElement as HTMLElement);
|
|
203
|
+
if (blockElement.isSameNode(endBlockElement)) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
blockElement = blockElement.nextElementSibling;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
180
209
|
vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => {
|
|
181
210
|
wbr.remove();
|
|
182
211
|
});
|
|
183
|
-
range.
|
|
212
|
+
const keepSelection = !range.collapsed;
|
|
213
|
+
if (keepSelection) {
|
|
214
|
+
// 列表转换会重建块元素,分别标记选区起止位置以便恢复原始选区
|
|
215
|
+
const endRange = range.cloneRange();
|
|
216
|
+
endRange.collapse(false);
|
|
217
|
+
const endElement = document.createElement("wbr");
|
|
218
|
+
endElement.setAttribute("data-type", "list-selection-end");
|
|
219
|
+
endRange.insertNode(endElement);
|
|
220
|
+
|
|
221
|
+
const startRange = range.cloneRange();
|
|
222
|
+
startRange.collapse(true);
|
|
223
|
+
const startElement = document.createElement("wbr");
|
|
224
|
+
startElement.setAttribute("data-type", "list-selection-start");
|
|
225
|
+
startRange.insertNode(startElement);
|
|
226
|
+
} else {
|
|
227
|
+
range.insertNode(document.createElement("wbr"));
|
|
228
|
+
}
|
|
184
229
|
|
|
185
230
|
if (cancel && itemElement) {
|
|
186
231
|
// 取消
|
|
@@ -197,25 +242,24 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
|
|
|
197
242
|
} else {
|
|
198
243
|
if (!itemElement) {
|
|
199
244
|
// 添加
|
|
200
|
-
|
|
201
|
-
if (!blockElement) {
|
|
245
|
+
if (selectedBlockElements.length === 0) {
|
|
202
246
|
vditor[vditor.currentMode].element.querySelector("wbr").remove();
|
|
203
|
-
blockElement = vditor[vditor.currentMode].element.querySelector("p");
|
|
247
|
+
const blockElement = vditor[vditor.currentMode].element.querySelector("p");
|
|
204
248
|
blockElement.innerHTML = "<wbr>";
|
|
249
|
+
selectedBlockElements.push(blockElement);
|
|
205
250
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
251
|
+
let listHTML = "";
|
|
252
|
+
selectedBlockElements.forEach((blockElement) => {
|
|
253
|
+
if (type === "check") {
|
|
254
|
+
listHTML += `<li class="vditor-task"><input type="checkbox" /> ${blockElement.innerHTML}</li>`;
|
|
255
|
+
} else {
|
|
256
|
+
listHTML += `<li>${blockElement.innerHTML}</li>`;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const listTagName = type === "ordered-list" ? "ol" : "ul";
|
|
260
|
+
selectedBlockElements[0].insertAdjacentHTML("beforebegin",
|
|
261
|
+
`<${listTagName} data-block="0">${listHTML}</${listTagName}>`);
|
|
262
|
+
selectedBlockElements.forEach((blockElement) => blockElement.remove());
|
|
219
263
|
} else {
|
|
220
264
|
// 切换
|
|
221
265
|
if (type === "check") {
|
|
@@ -227,7 +271,14 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
|
|
|
227
271
|
} else {
|
|
228
272
|
if (itemElement.querySelector("input")) {
|
|
229
273
|
itemElement.parentElement.querySelectorAll("li").forEach((item) => {
|
|
230
|
-
item.querySelector("input")
|
|
274
|
+
const inputElement = item.querySelector("input");
|
|
275
|
+
if (inputElement) {
|
|
276
|
+
const nextSibling = inputElement.nextSibling;
|
|
277
|
+
inputElement.remove();
|
|
278
|
+
if (nextSibling && nextSibling.nodeType === 3 && nextSibling.textContent.startsWith(" ")) {
|
|
279
|
+
nextSibling.textContent = nextSibling.textContent.substring(1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
231
282
|
item.classList.remove("vditor-task");
|
|
232
283
|
});
|
|
233
284
|
}
|
|
@@ -246,6 +297,18 @@ export const listToggle = (vditor: IVditor, range: Range, type: string, cancel =
|
|
|
246
297
|
}
|
|
247
298
|
}
|
|
248
299
|
}
|
|
300
|
+
if (keepSelection) {
|
|
301
|
+
const editorElement = vditor[vditor.currentMode].element;
|
|
302
|
+
const startElement = editorElement.querySelector('[data-type="list-selection-start"]');
|
|
303
|
+
const endElement = editorElement.querySelector('[data-type="list-selection-end"]');
|
|
304
|
+
if (startElement && endElement) {
|
|
305
|
+
range.setStartAfter(startElement);
|
|
306
|
+
range.setEndBefore(endElement);
|
|
307
|
+
startElement.remove();
|
|
308
|
+
endElement.remove();
|
|
309
|
+
setSelectionFocus(range);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
249
312
|
};
|
|
250
313
|
|
|
251
314
|
export const listIndent = (vditor: IVditor, liElement: HTMLElement, range: Range) => {
|
|
@@ -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
|
|
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.
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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 [
|
|
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
|
-
|
|
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, "&").replace(/</g, "<"));
|
|
@@ -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 += `})\n`;
|
|
1529
1595
|
}
|
|
1530
|
-
|
|
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
|
|
1536
|
-
|
|
1537
|
-
|
|
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);
|
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
|
|
|
@@ -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 +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
|
-
};
|