UNPKG

vditor

Version:

♏ 易于使用的 Markdown 编辑器,为适配不同的应用场景而生

203 lines (195 loc) 10.2 kB
import {isCtrl} from "../util/compatibility"; import {fixTab} from "../util/fixBrowserBehavior"; import {hasClosestByAttribute} from "../util/hasClosest"; import {hasClosestByTag} from "../util/hasClosestByHeadings"; import {getEditorRange, getSelectPosition} from "../util/selection"; import {inputEvent} from "./inputEvent"; import {processAfterRender, processPreviousMarkers} from "./process"; export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { vditor.sv.composingLock = event.isComposing; if (event.isComposing) { return false; } if (event.key.indexOf("Arrow") === -1 && event.key !== "Meta" && event.key !== "Control" && event.key !== "Alt" && event.key !== "Shift" && event.key !== "CapsLock" && event.key !== "Escape" && !/^F\d{1,2}$/.test(event.key)) { vditor.undo.recordFirstPosition(vditor, event); } // 仅处理以下快捷键操作 if (event.key !== "Enter" && event.key !== "Tab" && event.key !== "Backspace" && event.key.indexOf("Arrow") === -1 && !isCtrl(event) && event.key !== "Escape") { return false; } const range = getEditorRange(vditor); let startContainer = range.startContainer; if (range.startContainer.nodeType !== 3 && (range.startContainer as HTMLElement).tagName === "DIV") { startContainer = range.startContainer.childNodes[range.startOffset - 1]; } const textElement = hasClosestByAttribute(startContainer, "data-type", "text"); // blockquote let blockquoteMarkerElement = hasClosestByAttribute(startContainer, "data-type", "blockquote-marker"); if (!blockquoteMarkerElement && range.startOffset === 0 && textElement && textElement.previousElementSibling && textElement.previousElementSibling.getAttribute("data-type") === "blockquote-marker") { blockquoteMarkerElement = textElement.previousElementSibling as HTMLElement; } // 回车逐个删除 blockquote marker 标记 if (blockquoteMarkerElement) { if (event.key === "Enter" && !isCtrl(event) && !event.altKey && blockquoteMarkerElement.nextElementSibling.textContent.trim() === "" && getSelectPosition(blockquoteMarkerElement, vditor.sv.element, range).start === blockquoteMarkerElement.textContent.length) { if (blockquoteMarkerElement.previousElementSibling?.getAttribute("data-type") === "padding") { // 列表中存在多行 BQ 时,标记回车需跳出列表 blockquoteMarkerElement.previousElementSibling.setAttribute("data-action", "enter-remove"); } blockquoteMarkerElement.remove(); processAfterRender(vditor); event.preventDefault(); return true; } } // list item const listMarkerElement = hasClosestByAttribute(startContainer, "data-type", "li-marker") as HTMLElement; const taskMarkerElement = hasClosestByAttribute(startContainer, "data-type", "task-marker") as HTMLElement; let listLastMarkerElement = listMarkerElement; if (!listLastMarkerElement) { if (taskMarkerElement && taskMarkerElement.nextElementSibling.getAttribute("data-type") !== "task-marker") { listLastMarkerElement = taskMarkerElement; } } if (!listLastMarkerElement && range.startOffset === 0 && textElement && textElement.previousElementSibling && (textElement.previousElementSibling.getAttribute("data-type") === "li-marker" || textElement.previousElementSibling.getAttribute("data-type") === "task-marker")) { listLastMarkerElement = textElement.previousElementSibling as HTMLElement; } if (listLastMarkerElement) { const startIndex = getSelectPosition(listLastMarkerElement, vditor.sv.element, range).start; const isTask = listLastMarkerElement.getAttribute("data-type") === "task-marker"; let listFirstMarkerElement = listLastMarkerElement; if (isTask) { listFirstMarkerElement = listLastMarkerElement.previousElementSibling.previousElementSibling .previousElementSibling as HTMLElement; } if (startIndex === listLastMarkerElement.textContent.length) { // 回车清空列表标记符 if (event.key === "Enter" && !isCtrl(event) && !event.altKey && !event.shiftKey && listLastMarkerElement.nextElementSibling.textContent.trim() === "") { if (listFirstMarkerElement.previousElementSibling?.getAttribute("data-type") === "padding") { listFirstMarkerElement.previousElementSibling.remove(); inputEvent(vditor); } else { if (isTask) { listFirstMarkerElement.remove(); listLastMarkerElement.previousElementSibling.previousElementSibling.remove(); listLastMarkerElement.previousElementSibling.remove(); } listLastMarkerElement.nextElementSibling.remove(); listLastMarkerElement.remove(); processAfterRender(vditor); } event.preventDefault(); return true; } // 第一个 marker 后 tab 进行缩进 if (event.key === "Tab") { if (event.shiftKey) { if (listFirstMarkerElement.previousElementSibling.getAttribute("data-type") === "padding") { listFirstMarkerElement.previousElementSibling.remove(); } } else { listFirstMarkerElement.insertAdjacentHTML("beforebegin", `<span data-type="padding">${listFirstMarkerElement.textContent.replace(/\S/g, " ")}</span>`); } if (/^\d/.test(listFirstMarkerElement.textContent)) { listFirstMarkerElement.textContent = listFirstMarkerElement.textContent.replace(/^\d{1,}/, "1"); range.selectNodeContents(listLastMarkerElement.firstChild); range.collapse(false); } inputEvent(vditor); event.preventDefault(); return true; } } } // tab if (fixTab(vditor, range, event)) { return true; } const blockElement = hasClosestByAttribute(startContainer, "data-block", "0"); const spanElement = hasClosestByTag(startContainer, "SPAN"); // 回车 if (event.key === "Enter" && !isCtrl(event) && !event.altKey && !event.shiftKey && blockElement) { let isFirst = false; const newLineMatch = blockElement.textContent.match(/^\n+/); if (getSelectPosition(blockElement, vditor.sv.element).start <= (newLineMatch ? newLineMatch[0].length : 0)) { // 允许段落开始换行 isFirst = true; } let newLineText = "\n"; if (spanElement) { if (spanElement.previousElementSibling?.getAttribute("data-action") === "enter-remove") { // https://github.com/Vanessa219/vditor/issues/596 spanElement.previousElementSibling.remove(); processAfterRender(vditor); event.preventDefault(); return true; } else { newLineText += processPreviousMarkers(spanElement); } } range.insertNode(document.createTextNode(newLineText)); range.collapse(false); if (blockElement && blockElement.textContent.trim() !== "" && !isFirst) { inputEvent(vditor); } else { processAfterRender(vditor); } event.preventDefault(); return true; } // 删除后光标前有 newline 的处理 if (event.key === "Backspace" && !isCtrl(event) && !event.altKey && !event.shiftKey) { if (spanElement && spanElement.previousElementSibling?.getAttribute("data-type") === "newline" && getSelectPosition(spanElement, vditor.sv.element, range).start === 1 && // 飘号的处理需在 inputEvent 中,否则上下飘号对不齐 spanElement.getAttribute("data-type").indexOf("code-block-") === -1) { // 光标在每一行的第一个字符后 range.setStart(spanElement, 0); range.extractContents(); if (spanElement.textContent.trim() !== "") { inputEvent(vditor); } else { processAfterRender(vditor); } event.preventDefault(); return true; } // 每一段第一个字符前 if (blockElement && getSelectPosition(blockElement, vditor.sv.element, range).start === 0 && blockElement.previousElementSibling) { range.extractContents(); let previousLastElement = blockElement.previousElementSibling.lastElementChild; if (previousLastElement.getAttribute("data-type") === "newline") { previousLastElement.remove(); previousLastElement = blockElement.previousElementSibling.lastElementChild; } // 场景:末尾无法删除 [```\ntext\n```\n\n] if (previousLastElement.getAttribute("data-type") !== "newline") { previousLastElement.insertAdjacentHTML("afterend", blockElement.innerHTML); blockElement.remove(); } if (blockElement.textContent.trim() !== "" && !blockElement.previousElementSibling?.querySelector('[data-type="code-block-open-marker"]')) { inputEvent(vditor); } else { if (previousLastElement.getAttribute("data-type") !== "newline") { // https://github.com/Vanessa219/vditor/issues/597 range.selectNodeContents(previousLastElement.lastChild); range.collapse(false); } processAfterRender(vditor); } event.preventDefault(); return true; } } return false; };