From 54064722deeb48f7bf4c15cc079576f6eae4dc84 Mon Sep 17 00:00:00 2001 From: Van Date: Tue, 21 Jan 2020 00:59:52 +0800 Subject: [PATCH] :sparkles: fix #66 --- src/ts/editor/processKeydown.ts | 121 ++++++++ src/ts/util/editorCommenEvent.ts | 260 ++-------------- src/ts/util/processKeymap.ts | 15 + src/ts/wysiwyg/index.ts | 143 --------- src/ts/wysiwyg/processKeydown.ts | 494 +++++++++++++++++++++++++------ tslint.json | 3 +- 6 files changed, 567 insertions(+), 469 deletions(-) create mode 100644 src/ts/editor/processKeydown.ts create mode 100644 src/ts/util/processKeymap.ts diff --git a/src/ts/editor/processKeydown.ts b/src/ts/editor/processKeydown.ts new file mode 100644 index 000000000..8a9edf49f --- /dev/null +++ b/src/ts/editor/processKeydown.ts @@ -0,0 +1,121 @@ +import {getCurrentLinePosition} from "../util/getCurrentLinePosition"; +import {getMarkdown} from "../util/getMarkdown"; +import {processKeymap} from "../util/processKeymap"; +import {formatRender} from "./formatRender"; +import {getSelectPosition} from "./getSelectPosition"; +import {insertText} from "./insertText"; + +export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { + const editorElement = vditor.editor.element; + const position = getSelectPosition(editorElement); + const text = getMarkdown(vditor); + // tab and shift + tab + if (vditor.options.tab && event.key === "Tab") { + const selectLinePosition = getCurrentLinePosition(position, text); + const selectLineList = text.substring(selectLinePosition.start, selectLinePosition.end - 1).split("\n"); + + if (event.shiftKey) { + let shiftCount = 0; + let startIsShift = false; + const selectionShiftResult = selectLineList.map((value, index) => { + let shiftLineValue = value; + if (value.indexOf(vditor.options.tab) === 0) { + if (index === 0) { + startIsShift = true; + } + shiftCount++; + shiftLineValue = value.replace(vditor.options.tab, ""); + } + return shiftLineValue; + }).join("\n"); + + formatRender(vditor, text.substring(0, selectLinePosition.start) + + selectionShiftResult + text.substring(selectLinePosition.end - 1), + { + end: position.end - shiftCount * vditor.options.tab.length, + start: position.start - (startIsShift ? vditor.options.tab.length : 0), + }); + return true; + } + + if (position.start === position.end) { + insertText(vditor, vditor.options.tab, ""); + return true; + } + const selectionResult = selectLineList.map((value) => { + return vditor.options.tab + value; + }).join("\n"); + formatRender(vditor, text.substring(0, selectLinePosition.start) + selectionResult + + text.substring(selectLinePosition.end - 1), + { + end: position.end + selectLineList.length * vditor.options.tab.length, + start: position.start + vditor.options.tab.length, + }); + event.preventDefault(); + event.stopPropagation(); + return true; + } + + // delete + if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.key === "Backspace") { + if (position.start !== position.end) { + insertText(vditor, "", "", true); + } else { + // delete emoji + const emojiMatch = text.substring(0, position.start).match(/([\u{1F300}-\u{1F5FF}][\u{2000}-\u{206F}][\u{2700}-\u{27BF}]|([\u{1F900}-\u{1F9FF}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F600}-\u{1F64F}])[\u{2000}-\u{206F}][\u{2600}-\u{26FF}]|[\u{1F300}-\u{1F5FF}]|[\u{1F100}-\u{1F1FF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F200}-\u{1F2FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1F000}-\u{1F02F}]|[\u{FE00}-\u{FE0F}]|[\u{1F0A0}-\u{1F0FF}]|[\u{0000}-\u{007F}][\u{20D0}-\u{20FF}]|[\u{0000}-\u{007F}][\u{FE00}-\u{FE0F}][\u{20D0}-\u{20FF}])$/u); + const deleteChar = emojiMatch ? emojiMatch[0].length : 1; + formatRender(vditor, + text.substring(0, position.start - deleteChar) + text.substring(position.start), + { + end: position.start - deleteChar, + start: position.start - deleteChar, + }); + } + event.preventDefault(); + event.stopPropagation(); + return true; + } + + // hotkey command + delete + if (vditor.options.keymap.deleteLine) { + if (processKeymap(vditor.options.keymap.deleteLine, event, () => { + const linePosition = getCurrentLinePosition(position, text); + const deletedText = text.substring(0, linePosition.start) + text.substring(linePosition.end); + const startIndex = Math.min(deletedText.length, position.start); + formatRender(vditor, deletedText, { + end: startIndex, + start: startIndex, + }); + })) { + return true; + } + } + + // hotkey command + d + if (vditor.options.keymap.duplicate) { + if (processKeymap(vditor.options.keymap.duplicate, event, () => { + let lineText = text.substring(position.start, position.end); + if (position.start === position.end) { + const linePosition = getCurrentLinePosition(position, text); + lineText = text.substring(linePosition.start, linePosition.end); + formatRender(vditor, + text.substring(0, linePosition.end) + lineText + text.substring(linePosition.end), + { + end: position.end + lineText.length, + start: position.start + lineText.length, + }); + } else { + formatRender(vditor, + text.substring(0, position.end) + lineText + text.substring(position.end), + { + end: position.end + lineText.length, + start: position.start + lineText.length, + }); + } + })) { + return true; + } + } + + return false; +}; diff --git a/src/ts/util/editorCommenEvent.ts b/src/ts/util/editorCommenEvent.ts index 0143d56a6..1c5bacd37 100644 --- a/src/ts/util/editorCommenEvent.ts +++ b/src/ts/util/editorCommenEvent.ts @@ -1,13 +1,9 @@ -import {formatRender} from "../editor/formatRender"; -import {getSelectPosition} from "../editor/getSelectPosition"; import {getSelectText} from "../editor/getSelectText"; -import {insertText} from "../editor/insertText"; +import {processKeydown as mdProcessKeydown} from "../editor/processKeydown"; import {getCursorPosition} from "../hint/getCursorPosition"; -import {altEnterKey, deleteKey, tabKey} from "../wysiwyg/processKeydown"; -import {setHeading} from "../wysiwyg/setHeading"; -import {getCurrentLinePosition} from "./getCurrentLinePosition"; +import {deleteKey, processKeydown} from "../wysiwyg/processKeydown"; import {getMarkdown} from "./getMarkdown"; -import {hasClosestByClassName, hasClosestByTag} from "./hasClosest"; +import {processKeymap} from "./processKeymap"; export const focusEvent = (vditor: IVditor, editorElement: HTMLElement) => { editorElement.addEventListener("focus", () => { @@ -35,30 +31,14 @@ export const scrollCenter = (editorElement: HTMLElement) => { }; export const hotkeyEvent = (vditor: IVditor, editorElement: HTMLElement) => { - const processKeymap = (hotkey: string, event: KeyboardEvent, action: () => void) => { - const hotkeys = hotkey.split("-"); - const hasShift = hotkeys.length === 3 && (hotkeys[1] === "shift" || hotkeys[1] === "⇧"); - const key = (hasShift ? hotkeys[2] : hotkeys[1]) || "-"; - if ((hotkeys[0] === "ctrl" || hotkeys[0] === "⌘") && (event.metaKey || event.ctrlKey) - && event.key.toLowerCase() === key.toLowerCase()) { - if ((!hasShift && !event.shiftKey) || (hasShift && event.shiftKey)) { - action(); - event.preventDefault(); - event.stopPropagation(); - return true; - } - } - return false; - }; - const hint = (event: KeyboardEvent, hintElement: HTMLElement) => { if (!hintElement) { - return; + return false; } if (hintElement.querySelectorAll("button").length === 0 || hintElement.style.display === "none") { - return; + return false; } const currentHintElement: HTMLElement = hintElement.querySelector(".vditor-hint--current"); @@ -72,6 +52,7 @@ export const hotkeyEvent = (vditor: IVditor, editorElement: HTMLElement) => { currentHintElement.nextElementSibling.className = "vditor-hint--current"; } currentHintElement.removeAttribute("class"); + return true; } else if (event.key === "ArrowUp") { event.preventDefault(); event.stopPropagation(); @@ -82,11 +63,14 @@ export const hotkeyEvent = (vditor: IVditor, editorElement: HTMLElement) => { currentHintElement.previousElementSibling.className = "vditor-hint--current"; } currentHintElement.removeAttribute("class"); + return true; } else if (event.key === "Enter") { event.preventDefault(); event.stopPropagation(); vditor.hint.fillEmoji(currentHintElement, vditor); + return true; } + return false; }; editorElement.addEventListener("keydown", (event: KeyboardEvent & { target: HTMLElement }) => { @@ -94,119 +78,31 @@ export const hotkeyEvent = (vditor: IVditor, editorElement: HTMLElement) => { return; } const hintElement = vditor.hint && vditor.hint.element; - const range = getSelection().getRangeAt(0); - - vditor.undo.recordFirstPosition(vditor); - - if ((event.metaKey || event.ctrlKey) && vditor.options.ctrlEnter && event.key === "Enter") { - vditor.options.ctrlEnter(getMarkdown(vditor)); - return; - } - - if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.altKey && event.key === "Enter" - && vditor.currentMode === "wysiwyg") { - const matchKey = altEnterKey(vditor, event, range); - if (matchKey) { - return; - } - } - - // esc - if (event.key === "Escape") { - if (vditor.options.esc) { - vditor.options.esc(getMarkdown(vditor)); - } - if (hintElement && hintElement.style.display === "block") { - hintElement.style.display = "none"; - } - if (vditor.currentMode === "wysiwyg") { - const codeRenderElement = hasClosestByClassName(range.startContainer, "vditor-wysiwyg__block"); - if (codeRenderElement) { - vditor.wysiwyg.popover.style.display = "none"; - (codeRenderElement.firstElementChild as HTMLElement).style.display = "none"; - vditor.wysiwyg.element.blur(); - } - } + // hint: 上下选择 + if ((vditor.options.hint.at || vditor.toolbar.elements.emoji) && hint(event, hintElement)) { return; } - // tab - if (vditor.options.tab && event.key === "Tab") { - event.preventDefault(); - event.stopPropagation(); - if (vditor.currentMode === "wysiwyg") { - tabKey(vditor, event); + if (vditor.currentMode === "markdown") { + vditor.undo.recordFirstPosition(vditor); + if (mdProcessKeydown(vditor, event)) { return; } - - const position = getSelectPosition(editorElement); - const text = getMarkdown(vditor); - const selectLinePosition = getCurrentLinePosition(position, text); - const selectLineList = text.substring(selectLinePosition.start, selectLinePosition.end - 1).split("\n"); - - if (event.shiftKey) { - let shiftCount = 0; - let startIsShift = false; - const selectionShiftResult = selectLineList.map((value, index) => { - let shiftLineValue = value; - if (value.indexOf(vditor.options.tab) === 0) { - if (index === 0) { - startIsShift = true; - } - shiftCount++; - shiftLineValue = value.replace(vditor.options.tab, ""); - } - return shiftLineValue; - }).join("\n"); - - formatRender(vditor, text.substring(0, selectLinePosition.start) + - selectionShiftResult + text.substring(selectLinePosition.end - 1), - { - end: position.end - shiftCount * vditor.options.tab.length, - start: position.start - (startIsShift ? vditor.options.tab.length : 0), - }); + } else { + if (processKeydown(vditor, event)) { return; } - if (position.start === position.end) { - insertText(vditor, vditor.options.tab, ""); + if (event.key === "Backspace" && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) { + deleteKey(vditor, event); return; } - const selectionResult = selectLineList.map((value) => { - return vditor.options.tab + value; - }).join("\n"); - formatRender(vditor, text.substring(0, selectLinePosition.start) + selectionResult + - text.substring(selectLinePosition.end - 1), - { - end: position.end + selectLineList.length * vditor.options.tab.length, - start: position.start + vditor.options.tab.length, - }); - return; } - // delete - if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.keyCode === 8) { - if (vditor.currentMode === "markdown") { - const position = getSelectPosition(editorElement); - if (position.start !== position.end) { - insertText(vditor, "", "", true); - } else { - // delete emoji - const text = getMarkdown(vditor); - const emojiMatch = text.substring(0, position.start).match(/([\u{1F300}-\u{1F5FF}][\u{2000}-\u{206F}][\u{2700}-\u{27BF}]|([\u{1F900}-\u{1F9FF}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F600}-\u{1F64F}])[\u{2000}-\u{206F}][\u{2600}-\u{26FF}]|[\u{1F300}-\u{1F5FF}]|[\u{1F100}-\u{1F1FF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F200}-\u{1F2FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1F000}-\u{1F02F}]|[\u{FE00}-\u{FE0F}]|[\u{1F0A0}-\u{1F0FF}]|[\u{0000}-\u{007F}][\u{20D0}-\u{20FF}]|[\u{0000}-\u{007F}][\u{FE00}-\u{FE0F}][\u{20D0}-\u{20FF}])$/u); - const deleteChar = emojiMatch ? emojiMatch[0].length : 1; - formatRender(vditor, - text.substring(0, position.start - deleteChar) + text.substring(position.start), - { - end: position.start - deleteChar, - start: position.start - deleteChar, - }); - } - event.preventDefault(); - event.stopPropagation(); - } else { - deleteKey(vditor, event); - } + if ((event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && + vditor.options.ctrlEnter && event.key === "Enter") { + vditor.options.ctrlEnter(getMarkdown(vditor)); + event.preventDefault(); return; } @@ -232,107 +128,16 @@ export const hotkeyEvent = (vditor: IVditor, editorElement: HTMLElement) => { return; } - // hotkey command + delete - if (vditor.options.keymap.deleteLine && vditor.currentMode === "markdown") { - if (processKeymap(vditor.options.keymap.deleteLine, event, () => { - const position = getSelectPosition(editorElement); - const text = getMarkdown(vditor); - const linePosition = getCurrentLinePosition(position, text); - const deletedText = text.substring(0, linePosition.start) + text.substring(linePosition.end); - const startIndex = Math.min(deletedText.length, position.start); - formatRender(vditor, deletedText, { - end: startIndex, - start: startIndex, - }); - })) { - return; - } - } - - // hotkey command + d - if (vditor.options.keymap.duplicate && vditor.currentMode === "markdown") { - if (processKeymap(vditor.options.keymap.duplicate, event, () => { - const position = getSelectPosition(editorElement); - const text = getMarkdown(vditor); - let lineText = text.substring(position.start, position.end); - if (position.start === position.end) { - const linePosition = getCurrentLinePosition(position, text); - lineText = text.substring(linePosition.start, linePosition.end); - formatRender(vditor, - text.substring(0, linePosition.end) + lineText + text.substring(linePosition.end), - { - end: position.end + lineText.length, - start: position.start + lineText.length, - }); - } else { - formatRender(vditor, - text.substring(0, position.end) + lineText + text.substring(position.end), - { - end: position.end + lineText.length, - start: position.start + lineText.length, - }); - } - })) { - return; - } - } - - if (vditor.currentMode === "wysiwyg") { - processKeymap("⌘-⇧-x", event, () => { - const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="remove"]'); - if (itemElement) { - itemElement.click(); - } - }); - processKeymap("⌘-⇧-e", event, () => { - const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="insert-after"]') - || vditor.wysiwyg.popover.querySelector('[data-type="indent"]'); - if (itemElement) { - itemElement.click(); - } - }); - processKeymap("⌘-⇧-s", event, () => { - const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="insert-before"]') - || vditor.wysiwyg.popover.querySelector('[data-type="outdent"]'); - if (itemElement) { - itemElement.click(); - } - }); - - // 行级代码块中 command + a,近对当前代码块进行全选 - if (range.startContainer.parentElement.tagName === "CODE" && - hasClosestByClassName(range.startContainer, "vditor-wysiwyg__block")) { - if (processKeymap("⌘-a", event, () => { - range.selectNodeContents(range.startContainer.parentElement); - })) { - return; - } + // esc + if (event.key === "Escape") { + if (vditor.options.esc) { + vditor.options.esc(getMarkdown(vditor)); } - - const headingElement = hasClosestByTag(range.startContainer, "H"); - if (headingElement) { - if (processKeymap("⌘-=", event, () => { - event.preventDefault(); - const index = parseInt((headingElement as HTMLElement).tagName.substr(1), 10) - 1; - if (index < 1) { - return; - } - setHeading(vditor, `h${index}`); - })) { - return; - } - - if (processKeymap("⌘--", event, () => { - event.preventDefault(); - const index = parseInt((headingElement as HTMLElement).tagName.substr(1), 10) + 1; - if (index > 6) { - return; - } - setHeading(vditor, `h${index}`); - })) { - return; - } + if (hintElement && hintElement.style.display === "block") { + hintElement.style.display = "none"; } + event.preventDefault(); + return; } // toolbar action @@ -344,11 +149,6 @@ export const hotkeyEvent = (vditor: IVditor, editorElement: HTMLElement) => { (vditor.toolbar.elements[menuItem.name].children[0] as HTMLElement).click(); }); }); - - // hint: 上下选择 - if (vditor.options.hint.at || vditor.toolbar.elements.emoji) { - hint(event, hintElement); - } }); }; diff --git a/src/ts/util/processKeymap.ts b/src/ts/util/processKeymap.ts new file mode 100644 index 000000000..ce95faccd --- /dev/null +++ b/src/ts/util/processKeymap.ts @@ -0,0 +1,15 @@ +export const processKeymap = (hotkey: string, event: KeyboardEvent, action: () => void) => { + const hotkeys = hotkey.split("-"); + const hasShift = hotkeys.length === 3 && (hotkeys[1] === "shift" || hotkeys[1] === "⇧"); + const key = (hasShift ? hotkeys[2] : hotkeys[1]) || "-"; + if ((hotkeys[0] === "ctrl" || hotkeys[0] === "⌘") && (event.metaKey || event.ctrlKey) + && event.key.toLowerCase() === key.toLowerCase()) { + if ((!hasShift && !event.shiftKey) || (hasShift && event.shiftKey)) { + action(); + event.preventDefault(); + event.stopPropagation(); + return true; + } + } + return false; +}; diff --git a/src/ts/wysiwyg/index.ts b/src/ts/wysiwyg/index.ts index 5136dc784..cca526aee 100644 --- a/src/ts/wysiwyg/index.ts +++ b/src/ts/wysiwyg/index.ts @@ -310,149 +310,6 @@ class WYSIWYG { setSelectionFocus(range); } }); - - this.element.addEventListener("keypress", (event: KeyboardEvent & { target: HTMLElement }) => { - if (event.target.tagName === "INPUT") { - return; - } - if (event.key !== "Enter") { - return; - } - - const range = getSelection().getRangeAt(0).cloneRange(); - const isPureEnter = !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey; - - // TABLE - const cellElement = hasClosestByMatchTag(range.startContainer, "TD") || - hasClosestByMatchTag(range.startContainer, "TH"); - if (cellElement) { - if (isPureEnter || (!event.metaKey && !event.ctrlKey && event.shiftKey && !event.altKey)) { - event.preventDefault(); - const brElement = document.createElement("span"); - brElement.className = "vditor-wysiwyg__block"; - brElement.setAttribute("data-type", "html-inline"); - brElement.innerHTML = '<br />'; - processCodeRender(brElement, vditor); - range.insertNode(document.createTextNode(" ")); - range.insertNode(brElement); - range.setStartAfter(brElement.nextSibling); - range.collapse(false); - setSelectionFocus(range); - afterRenderEvent(vditor); - return; - } - - // table 添加行 /~https://github.com/Vanessa219/vditor/issues/46 - if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.altKey) { - let rowHTML = ""; - for (let m = 0; m < cellElement.parentElement.childElementCount; m++) { - rowHTML += ""; - } - cellElement.parentElement.insertAdjacentHTML("afterend", rowHTML); - range.setStart(cellElement.parentElement.nextElementSibling.firstChild, 0); - setSelectionFocus(range); - afterRenderEvent(vditor); - event.preventDefault(); - return; - } - } - - // 表格自动完成 - const pElement = hasClosestByMatchTag(range.startContainer, "P"); - if (pElement) { - const pText = String.raw`${pElement.textContent}`.replace(/\\\|/g, "").trim(); - const pTextList = pText.split("|"); - if (pText.startsWith("|") && pText.endsWith("|") && pTextList.length > 3) { - event.preventDefault(); - let tableHeaderMD = pTextList.map(() => "---").join("|"); - tableHeaderMD = pElement.textContent + tableHeaderMD.substring(3, tableHeaderMD.length - 3) + "\n|"; - pElement.outerHTML = vditor.lute.SpinVditorDOM(tableHeaderMD); - setRangeByWbr(vditor.wysiwyg.element, range); - afterRenderEvent(vditor); - return; - } - } - - // 软换行或者代码块中的换行,不需要软换行处理的需写在该块之上 - const preCodeElement = hasClosestByClassName(range.startContainer, "vditor-wysiwyg__block"); - if ((!event.metaKey && !event.ctrlKey && event.shiftKey && !event.altKey) || - (isPureEnter && preCodeElement)) { - if (range.startContainer.nodeType === 3 && range.startContainer.parentElement && - !range.startContainer.parentElement.textContent.endsWith("\n") && - (range.startContainer.parentElement.tagName === "LI" || preCodeElement || - range.startContainer.parentElement.tagName.indexOf("H") === 0)) { - // 最后需要一个 \n,否则换行需按两次回车 - range.startContainer.parentElement.insertAdjacentText("beforeend", "\n"); - } - range.insertNode(document.createTextNode("\n")); - range.collapse(false); - setSelectionFocus(range); - afterRenderEvent(vditor); - event.preventDefault(); - scrollCenter(this.element); - return; - } - - // task list - const taskItemElement = hasClosestByClassName(range.startContainer, "vditor-task"); - if (taskItemElement) { - if (taskItemElement.lastChild.textContent.trim() === "") { - if (taskItemElement.nextElementSibling) { - // 用段落隔断 - let afterHTML = ""; - let beforeHTML = ""; - let isAfter = false; - taskItemElement.parentElement.querySelectorAll("li").forEach((liElement) => { - if (liElement.isEqualNode(taskItemElement)) { - isAfter = true; - } else { - if (isAfter) { - afterHTML += liElement.outerHTML; - } else { - beforeHTML += liElement.outerHTML; - } - } - }); - if (beforeHTML) { - beforeHTML = ``; - } - taskItemElement.parentElement.outerHTML = `${beforeHTML}

\n

`; - } else { - // 变成段落 - taskItemElement.parentElement.insertAdjacentHTML("afterend", `

\n

`); - if (taskItemElement.parentElement.querySelectorAll("li").length === 1) { - taskItemElement.parentElement.remove(); - } else { - taskItemElement.remove(); - } - } - } else { - // 光标后文字添加到新列表中 - range.setEndAfter(taskItemElement.lastChild); - taskItemElement.insertAdjacentHTML("afterend", `
  • `); - document.querySelector("wbr").after(range.extractContents()); - } - setRangeByWbr(vditor.wysiwyg.element, range); - event.preventDefault(); - afterRenderEvent(vditor); - return; - } - - // H6 回车 解析问题 /~https://github.com/Vanessa219/vditor/issues/48 - const h6Element = hasClosestByMatchTag(range.startContainer, "H6"); - if (h6Element && range.startContainer.textContent.length === range.startOffset) { - const pTempElement = document.createElement("p"); - pTempElement.textContent = "\n"; - pTempElement.setAttribute("data-block", "0"); - range.startContainer.parentElement.insertAdjacentElement("afterend", pTempElement); - range.setStart(pTempElement, 0); - setSelectionFocus(range); - event.preventDefault(); - scrollCenter(this.element); - return; - } - scrollCenter(this.element); - }); } } diff --git a/src/ts/wysiwyg/processKeydown.ts b/src/ts/wysiwyg/processKeydown.ts index 9ea7aeb9c..ae8d86c8a 100644 --- a/src/ts/wysiwyg/processKeydown.ts +++ b/src/ts/wysiwyg/processKeydown.ts @@ -1,45 +1,18 @@ import {Constants} from "../constants"; import {setSelectionFocus} from "../editor/setSelection"; +import {scrollCenter} from "../util/editorCommenEvent"; import { hasClosestByAttribute, hasClosestByClassName, - hasClosestByMatchTag, + hasClosestByMatchTag, hasClosestByTag, hasTopClosestByTag, } from "../util/hasClosest"; +import {processKeymap} from "../util/processKeymap"; import {afterRenderEvent} from "./afterRenderEvent"; -import {highlightToolbar} from "./highlightToolbar"; import {processCodeRender} from "./processCodeRender"; +import {setHeading} from "./setHeading"; import {setRangeByWbr} from "./setRangeByWbr"; -export const altEnterKey = (vditor: IVditor, event: KeyboardEvent, range: Range) => { - // 代码块切换到语言 /~https://github.com/Vanessa219/vditor/issues/54 - const codeBlockElement = hasClosestByAttribute(range.startContainer, "data-type", "code-block"); - if (codeBlockElement) { - (vditor.wysiwyg.popover.querySelector(".vditor-input") as HTMLElement).focus(); - event.preventDefault(); - return true; - } - - // 跳出多层 blockquote 嵌套 /~https://github.com/Vanessa219/vditor/issues/51 - const topBQElement = hasTopClosestByTag(range.startContainer, "BLOCKQUOTE"); - if (topBQElement) { - range.setStartAfter(topBQElement); - setSelectionFocus(range); - const node = document.createElement("p"); - node.setAttribute("data-block", "0"); - node.innerHTML = "\n"; - range.insertNode(node); - range.collapse(true); - setSelectionFocus(range); - highlightToolbar(vditor); - afterRenderEvent(vditor); - event.preventDefault(); - return true; - } - - return false; -}; - export const deleteKey = (vditor: IVditor, event: KeyboardEvent) => { const range = getSelection().getRangeAt(0); const startContainer = range.startContainer as HTMLElement; @@ -120,71 +93,78 @@ export const deleteKey = (vditor: IVditor, event: KeyboardEvent) => { event.preventDefault(); } } +}; - // task list - const taskItemElement = hasClosestByClassName(startContainer, "vditor-task"); - if (taskItemElement && range.collapsed && - ((startContainer.nodeType === 3 && range.startOffset === 1 && - (startContainer.previousSibling as HTMLElement).tagName === "INPUT") || - startContainer.nodeType !== 3)) { - - const previousElement = taskItemElement.previousElementSibling; - taskItemElement.querySelector("input").remove(); - if (previousElement) { - previousElement.innerHTML += "" + taskItemElement.innerHTML.trim(); - taskItemElement.remove(); - } else { - taskItemElement.parentElement.insertAdjacentHTML("beforebegin", - `

    ${taskItemElement.innerHTML.trim() || "\n"}

    `); - if (taskItemElement.nextElementSibling) { - taskItemElement.remove(); - } else { - taskItemElement.parentElement.remove(); - } +export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { + // TODO deleteKey and 上下左右遇到块预览的处理重构 + const range = getSelection().getRangeAt(0); + const startContainer = range.startContainer; + + // 表格自动完成 + const pElement = hasClosestByMatchTag(range.startContainer, "P"); + if (pElement && ((!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey && event.key === "Enter") || + (!event.metaKey && !event.ctrlKey && event.shiftKey && !event.altKey && event.key === "Enter"))) { + const pText = String.raw`${pElement.textContent}`.replace(/\\\|/g, "").trim(); + const pTextList = pText.split("|"); + if (pText.startsWith("|") && pText.endsWith("|") && pTextList.length > 3) { + let tableHeaderMD = pTextList.map(() => "---").join("|"); + tableHeaderMD = pElement.textContent + tableHeaderMD.substring(3, tableHeaderMD.length - 3) + "\n|"; + pElement.outerHTML = vditor.lute.SpinVditorDOM(tableHeaderMD); + setRangeByWbr(vditor.wysiwyg.element, range); + afterRenderEvent(vditor); + scrollCenter(vditor.wysiwyg.element); + event.preventDefault(); + return true; } - setRangeByWbr(vditor.wysiwyg.element, range); - afterRenderEvent(vditor); - event.preventDefault(); - return; } // table - const cellElement = hasClosestByMatchTag(range.startContainer, "TD") - || hasClosestByMatchTag(range.startContainer, "TH"); - if (cellElement && range.startOffset === 0) { + const cellElement = hasClosestByMatchTag(startContainer, "TD") || + hasClosestByMatchTag(startContainer, "TH"); + if (cellElement) { + // 换行或软换行:在 cell 中添加 br + if ((!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey && event.key === "Enter") || + (!event.metaKey && !event.ctrlKey && event.shiftKey && !event.altKey && event.key === "Enter")) { + const brElement = document.createElement("span"); + brElement.className = "vditor-wysiwyg__block"; + brElement.setAttribute("data-type", "html-inline"); + brElement.innerHTML = '<br />'; + processCodeRender(brElement, vditor); + range.insertNode(document.createTextNode(" ")); + range.insertNode(brElement); + range.setStartAfter(brElement.nextSibling); + range.collapse(false); + setSelectionFocus(range); + afterRenderEvent(vditor); + event.preventDefault(); + return true; + } - let previousElement = cellElement.previousElementSibling; - if (!previousElement) { - if (cellElement.parentElement.previousElementSibling) { - previousElement = cellElement.parentElement.previousElementSibling.lastElementChild; - } else if (cellElement.parentElement.parentElement.tagName === "TBODY" && - cellElement.parentElement.parentElement.previousElementSibling) { - previousElement = - cellElement.parentElement.parentElement.previousElementSibling.lastElementChild.lastElementChild; - } else { - previousElement = null; + // Backspace:光标移动到前一个 cell + if (!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey && event.key === "Backspace" + && range.startOffset === 0) { + let previousElement = cellElement.previousElementSibling; + if (!previousElement) { + if (cellElement.parentElement.previousElementSibling) { + previousElement = cellElement.parentElement.previousElementSibling.lastElementChild; + } else if (cellElement.parentElement.parentElement.tagName === "TBODY" && + cellElement.parentElement.parentElement.previousElementSibling) { + previousElement = cellElement.parentElement + .parentElement.previousElementSibling.lastElementChild.lastElementChild; + } else { + previousElement = null; + } } + if (previousElement) { + range.selectNodeContents(previousElement); + range.collapse(false); + } + event.preventDefault(); + return true; } - if (previousElement) { - range.selectNodeContents(previousElement); - range.collapse(false); - } - event.preventDefault(); - return; - } -}; -export const tabKey = (vditor: IVditor, event: KeyboardEvent) => { - const range = getSelection().getRangeAt(0); - const codeElement = hasClosestByMatchTag(range.startContainer, "CODE"); - if (event.shiftKey) { - if (codeElement) { - // TODO 代码块缩进 - } - } else { - const cellElement = hasClosestByMatchTag(range.startContainer, "TD") - || hasClosestByMatchTag(range.startContainer, "TH"); - if (cellElement) { + // tab:光标移向下一个 cell + if (event.key === "Tab") { let nextElement = cellElement.nextElementSibling; if (!nextElement) { if (cellElement.parentElement.nextElementSibling) { @@ -201,27 +181,351 @@ export const tabKey = (vditor: IVditor, event: KeyboardEvent) => { range.selectNodeContents(nextElement); range.collapse(true); } - return; + event.preventDefault(); + return true; + } + + // alt+Backspace:删除行 + if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.altKey && cellElement.tagName === "TD" + && event.key === "Backspace") { + const tbodyElement = cellElement.parentElement.parentElement; + if (cellElement.parentElement.previousElementSibling) { + range.selectNodeContents(cellElement.parentElement.previousElementSibling.lastElementChild); + } else { + range.selectNodeContents(tbodyElement.previousElementSibling.lastElementChild.lastElementChild); + } + + if (tbodyElement.childElementCount === 1) { + tbodyElement.remove(); + } else { + cellElement.parentElement.remove(); + } + + range.collapse(false); + event.preventDefault(); + afterRenderEvent(vditor); + return true; } - if (range.collapsed) { + // alt+enter: 下方新添加一行 /~https://github.com/Vanessa219/vditor/issues/46 + if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.altKey && event.key === "Enter") { + let rowHTML = ""; + for (let m = 0; m < cellElement.parentElement.childElementCount; m++) { + rowHTML += `${m === 0 ? "" : ""}`; + } + if (cellElement.tagName === "TH") { + cellElement.parentElement.parentElement.insertAdjacentHTML("afterend", + `${rowHTML}`); + } else { + cellElement.parentElement.insertAdjacentHTML("afterend", `${rowHTML}`); + } + setRangeByWbr(vditor.wysiwyg.element, range); + afterRenderEvent(vditor); + scrollCenter(vditor.wysiwyg.element); + event.preventDefault(); + return true; + } + + // alt+shift+enter: 后方新添加一列 + const tableElement = cellElement.parentElement.parentElement.parentElement as HTMLTableElement; + if (!event.metaKey && !event.ctrlKey && event.shiftKey && event.altKey && event.key === "Enter") { + let index = 0; + let previousElement = cellElement.previousElementSibling; + while (previousElement) { + index++; + previousElement = previousElement.previousElementSibling; + } + for (let i = 0; i < tableElement.rows.length; i++) { + if (i === 0) { + tableElement.rows[i].cells[index].insertAdjacentHTML("afterend", ""); + } else { + tableElement.rows[i].cells[index].insertAdjacentHTML("afterend", ""); + } + } + + afterRenderEvent(vditor); + event.preventDefault(); + return true; + } + + // alt+shift+Backspace: 删除当前列 + if (!event.metaKey && !event.ctrlKey && event.shiftKey && event.altKey && event.key === "Backspace") { + let index = 0; + let previousElement = cellElement.previousElementSibling; + while (previousElement) { + index++; + previousElement = previousElement.previousElementSibling; + } + if (cellElement.previousElementSibling || cellElement.nextElementSibling) { + range.selectNodeContents(cellElement.previousElementSibling || cellElement.nextElementSibling); + range.collapse(true); + } + for (let i = 0; i < tableElement.rows.length; i++) { + if (tableElement.rows.length === 1) { + tableElement.remove(); + } else { + tableElement.rows[i].cells[index].remove(); + } + } + afterRenderEvent(vditor); + event.preventDefault(); + return true; + } + } + + const codeRenderElement = hasClosestByClassName(startContainer, "vditor-wysiwyg__block"); + if (codeRenderElement) { + // esc: 退出编辑,仅展示渲染 + if (event.key === "Escape") { + vditor.wysiwyg.popover.style.display = "none"; + (codeRenderElement.firstElementChild as HTMLElement).style.display = "none"; + vditor.wysiwyg.element.blur(); + event.preventDefault(); + return true; + } + // alt+enter: 代码块切换到语言 /~https://github.com/Vanessa219/vditor/issues/54 + if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.altKey && event.key === "Enter" && + codeRenderElement.getAttribute("data-type") === "code-block") { + (vditor.wysiwyg.popover.querySelector(".vditor-input") as HTMLElement).focus(); + event.preventDefault(); + return true; + } + + // 行级代码块中 command + a,近对当前代码块进行全选 + if (startContainer.parentElement.tagName === "CODE" && codeRenderElement.getAttribute("data-block") === "0") { + if (processKeymap("⌘-a", event, () => { + range.selectNodeContents(startContainer.parentElement); + })) { + return true; + } + } + + // 换行 + if (!event.metaKey && !event.ctrlKey && !event.altKey && event.key === "Enter" && + codeRenderElement.getAttribute("data-block") === "0") { + if (!codeRenderElement.firstElementChild.firstElementChild.textContent.endsWith("\n")) { + codeRenderElement.firstElementChild.firstElementChild.insertAdjacentText("beforeend", "\n"); + } + range.insertNode(document.createTextNode("\n")); + range.collapse(false); + afterRenderEvent(vditor); + processCodeRender(codeRenderElement, vditor); + event.preventDefault(); + return true; + } + + // tab + if (event.key === "Tab" && !event.shiftKey && range.collapsed && + codeRenderElement.getAttribute("data-block") === "0") { range.insertNode(document.createTextNode(vditor.options.tab)); range.collapse(false); + afterRenderEvent(vditor); + processCodeRender(codeRenderElement, vditor); + event.preventDefault(); + return true; + } + + // TODO shift + tab, shift and 选中文字 + } + + const topBQElement = hasTopClosestByTag(startContainer, "BLOCKQUOTE"); + if (topBQElement && !event.metaKey && !event.ctrlKey && !event.shiftKey && event.altKey && event.key === "Enter") { + // alt+enter: 跳出多层 blockquote 嵌套 /~https://github.com/Vanessa219/vditor/issues/51 + range.setStartAfter(topBQElement); + setSelectionFocus(range); + const node = document.createElement("p"); + node.setAttribute("data-block", "0"); + node.innerHTML = "\n"; + range.insertNode(node); + range.collapse(true); + setSelectionFocus(range); + afterRenderEvent(vditor); + scrollCenter(vditor.wysiwyg.element); + event.preventDefault(); + return true; + } + + // h1-h6 + const headingElement = hasClosestByTag(startContainer, "H"); + if (headingElement) { + if (headingElement.tagName === "H6" && startContainer.textContent.length === range.startOffset && + !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey && event.key === "Enter") { + // enter: H6 回车解析问题 /~https://github.com/Vanessa219/vditor/issues/48 + const pTempElement = document.createElement("p"); + pTempElement.textContent = "\n"; + pTempElement.setAttribute("data-block", "0"); + startContainer.parentElement.insertAdjacentElement("afterend", pTempElement); + range.setStart(pTempElement, 0); + setSelectionFocus(range); + afterRenderEvent(vditor); + scrollCenter(vditor.wysiwyg.element); + event.preventDefault(); + return true; + } + + // enter++: 标题变大 + if (processKeymap("⌘-=", event, () => { + const index = parseInt((headingElement as HTMLElement).tagName.substr(1), 10) - 1; + if (index < 1) { + return; + } + setHeading(vditor, `h${index}`); + afterRenderEvent(vditor); + event.preventDefault(); + })) { + return true; + } + + // enter++: 标题变小 + if (processKeymap("⌘--", event, () => { + const index = parseInt((headingElement as HTMLElement).tagName.substr(1), 10) + 1; + if (index > 6) { + return; + } + setHeading(vditor, `h${index}`); + afterRenderEvent(vditor); + event.preventDefault(); + })) { + return true; + } + } + + // task list + const taskItemElement = hasClosestByClassName(startContainer, "vditor-task"); + if (taskItemElement) { + // Backspace: 在选择框前进行删除 + if (event.key === "Backspace" && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey + && range.collapsed && ((startContainer.nodeType === 3 && range.startOffset === 1 && + (startContainer.previousSibling as HTMLElement).tagName === "INPUT") || + startContainer.nodeType !== 3)) { + const previousElement = taskItemElement.previousElementSibling; + taskItemElement.querySelector("input").remove(); + if (previousElement) { + previousElement.innerHTML += "" + taskItemElement.innerHTML.trim(); + taskItemElement.remove(); + } else { + taskItemElement.parentElement.insertAdjacentHTML("beforebegin", + `

    ${taskItemElement.innerHTML.trim() || "\n"}

    `); + if (taskItemElement.nextElementSibling) { + taskItemElement.remove(); + } else { + taskItemElement.parentElement.remove(); + } + } + setRangeByWbr(vditor.wysiwyg.element, range); + afterRenderEvent(vditor); + event.preventDefault(); + return true; + } + + if (event.key === "Enter" && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) { + if (taskItemElement.lastChild.textContent.trim() === "") { + if (taskItemElement.nextElementSibling) { + // 用段落隔断 + let afterHTML = ""; + let beforeHTML = ""; + let isAfter = false; + taskItemElement.parentElement.querySelectorAll("li").forEach((liElement) => { + if (liElement.isEqualNode(taskItemElement)) { + isAfter = true; + } else { + if (isAfter) { + afterHTML += liElement.outerHTML; + } else { + beforeHTML += liElement.outerHTML; + } + } + }); + if (beforeHTML) { + beforeHTML = ``; + } + taskItemElement.parentElement.outerHTML = `${beforeHTML}

    \n

    `; + } else { + // 变成段落 + taskItemElement.parentElement.insertAdjacentHTML("afterend", `

    \n

    `); + if (taskItemElement.parentElement.querySelectorAll("li").length === 1) { + taskItemElement.parentElement.remove(); + } else { + taskItemElement.remove(); + } + } + } else { + // 光标后文字添加到新列表中 + range.setEndAfter(taskItemElement.lastChild); + taskItemElement.insertAdjacentHTML("afterend", `
  • `); + document.querySelector("wbr").after(range.extractContents()); + } + setRangeByWbr(vditor.wysiwyg.element, range); + afterRenderEvent(vditor); + scrollCenter(vditor.wysiwyg.element); + event.preventDefault(); + return true; + } + } + + // 删除有子工具栏的块 + if (processKeymap("⌘-⇧-x", event, () => { + const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="remove"]'); + if (itemElement) { + itemElement.click(); + } + })) { + return true; + } + + // 在有子工具栏的块后插入行 + if (processKeymap("⌘-⇧-e", event, () => { + const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="insert-after"]') + || vditor.wysiwyg.popover.querySelector('[data-type="indent"]'); + if (itemElement) { + itemElement.click(); + } + })) { + return true; + } + + // 在有子工具栏的块前插入行 + if (processKeymap("⌘-⇧-s", event, () => { + const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="insert-before"]') + || vditor.wysiwyg.popover.querySelector('[data-type="outdent"]'); + if (itemElement) { + itemElement.click(); + } + })) { + return true; + } + + // tab 处理,需放在 cell tab 处理之后 + if (event.key === "Tab") { + if (event.shiftKey) { + // TODO shift+tab } else { - if (codeElement) { - // TODO 代码块缩进 + if (range.collapsed) { + range.insertNode(document.createTextNode(vditor.options.tab)); + range.collapse(false); + if (codeRenderElement) { + processCodeRender(codeRenderElement, vditor); + } } else { range.extractContents(); range.insertNode(document.createTextNode(vditor.options.tab)); range.collapse(false); } } + afterRenderEvent(vditor); + event.preventDefault(); + return true; } - const blockRenderElement = hasClosestByClassName(range.startContainer, "vditor-wysiwyg__block"); - if (blockRenderElement) { - processCodeRender(blockRenderElement, vditor); + if (!event.metaKey && !event.ctrlKey && event.shiftKey && !event.altKey && event.key === "Enter") { + range.insertNode(document.createTextNode("\n")); + range.collapse(false); + setSelectionFocus(range); + afterRenderEvent(vditor); + scrollCenter(vditor.wysiwyg.element); + event.preventDefault(); + return true; } - afterRenderEvent(vditor); + return false; }; diff --git a/tslint.json b/tslint.json index 28d597754..c6ad0edbe 100644 --- a/tslint.json +++ b/tslint.json @@ -13,7 +13,8 @@ "limit": 120, "ignore-pattern": "match" } - ] + ], + "prefer-for-of": false }, "rulesDirectory": [] }