import {Constants} from "../constants"; import {input as IRInput} from "../ir/input"; import {processAfterRender} from "../ir/process"; import {processAfterRender as processSVAfterRender, processPaste} from "../sv/process"; import {uploadFiles} from "../upload"; import {setHeaders} from "../upload/setHeaders"; import {afterRenderEvent} from "../wysiwyg/afterRenderEvent"; import {input} from "../wysiwyg/input"; import {isCtrl, isFirefox} from "./compatibility"; import {scrollCenter} from "./editorCommonEvent"; import { getTopList, hasClosestBlock, hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag, } from "./hasClosest"; import {getLastNode} from "./hasClosest"; import {highlightToolbar} from "./highlightToolbar"; import {matchHotKey} from "./hotKey"; import {processCodeRender, processPasteCode} from "./processCode"; import { getEditorRange, getSelectPosition, insertHTML, setRangeByWbr, setSelectionByPosition, setSelectionFocus, } from "./selection"; // https://github.com/Vanessa219/vditor/issues/508 软键盘无法删除空块 export const fixGSKeyBackspace = (event: KeyboardEvent, vditor: IVditor, startContainer: Node) => { if (event.keyCode === 229 && event.code === "" && event.key === "Unidentified" && vditor.currentMode !== "sv") { const blockElement = hasClosestBlock(startContainer); // 移动端的标点符号都显示为 299,因此需限定为空删除的条件 if (blockElement && blockElement.textContent.trim() === "") { vditor[vditor.currentMode].composingLock = true; return false; } } return true; }; // https://github.com/Vanessa219/vditor/issues/361 代码块后输入中文 export const fixCJKPosition = (range: Range, vditor: IVditor, event: KeyboardEvent) => { if (event.key === "Enter" || event.key === "Tab" || event.key === "Backspace" || event.key.indexOf("Arrow") > -1 || isCtrl(event) || event.key === "Escape" || event.shiftKey || event.altKey) { return; } const pLiElement = hasClosestByMatchTag(range.startContainer, "P") || hasClosestByMatchTag(range.startContainer, "LI"); if (pLiElement && getSelectPosition(pLiElement, vditor[vditor.currentMode].element, range).start === 0) { const zwspNode = document.createTextNode(Constants.ZWSP); range.insertNode(zwspNode); range.setStartAfter(zwspNode); } }; // https://github.com/Vanessa219/vditor/issues/381 光标在内联数学公式中无法向下移动 export const fixCursorDownInlineMath = (range: Range, key: string) => { if (key === "ArrowDown" || key === "ArrowUp") { const inlineElement = hasClosestByAttribute(range.startContainer, "data-type", "math-inline") || hasClosestByAttribute(range.startContainer, "data-type", "html-entity") || hasClosestByAttribute(range.startContainer, "data-type", "html-inline"); if (inlineElement) { if (key === "ArrowDown") { range.setStartAfter(inlineElement.parentElement); } if (key === "ArrowUp") { range.setStartBefore(inlineElement.parentElement); } } } }; export const insertEmptyBlock = (vditor: IVditor, position: InsertPosition) => { const range = getEditorRange(vditor); const blockElement = hasClosestBlock(range.startContainer); if (blockElement) { blockElement.insertAdjacentHTML(position, `

${Constants.ZWSP}\n

`); setRangeByWbr(vditor[vditor.currentMode].element, range); highlightToolbar(vditor); execAfterRender(vditor); } }; export const isFirstCell = (cellElement: HTMLElement) => { const tableElement = hasClosestByMatchTag(cellElement, "TABLE") as HTMLTableElement; if (tableElement && tableElement.rows[0].cells[0].isSameNode(cellElement)) { return tableElement; } return false; }; export const isLastCell = (cellElement: HTMLElement) => { const tableElement = hasClosestByMatchTag(cellElement, "TABLE") as HTMLTableElement; if (tableElement && tableElement.lastElementChild.lastElementChild.lastElementChild.isSameNode(cellElement)) { return tableElement; } return false; }; // 光标设置到前一个表格中 const goPreviousCell = (cellElement: HTMLElement, range: Range, isSelected = 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; } } if (previousElement) { range.selectNodeContents(previousElement); if (!isSelected) { range.collapse(false); } setSelectionFocus(range); } return previousElement; }; export const insertAfterBlock = (vditor: IVditor, event: KeyboardEvent, range: Range, element: HTMLElement, blockElement: HTMLElement) => { const position = getSelectPosition(element, vditor[vditor.currentMode].element, range); if ((event.key === "ArrowDown" && element.textContent.trimRight().substr(position.start).indexOf("\n") === -1) || (event.key === "ArrowRight" && position.start >= element.textContent.trimRight().length)) { const nextElement = blockElement.nextElementSibling; if (!nextElement || (nextElement && (nextElement.tagName === "TABLE" || nextElement.getAttribute("data-type")))) { blockElement.insertAdjacentHTML("afterend", `

${Constants.ZWSP}

`); setRangeByWbr(vditor[vditor.currentMode].element, range); } else { range.selectNodeContents(nextElement); range.collapse(true); setSelectionFocus(range); } event.preventDefault(); return true; } return false; }; export const insertBeforeBlock = (vditor: IVditor, event: KeyboardEvent, range: Range, element: HTMLElement, blockElement: HTMLElement) => { const position = getSelectPosition(element, vditor[vditor.currentMode].element, range); if ((event.key === "ArrowUp" && element.textContent.substr(0, position.start).indexOf("\n") === -1) || ((event.key === "ArrowLeft" || (event.key === "Backspace" && range.toString() === "")) && position.start === 0)) { const previousElement = blockElement.previousElementSibling; // table || code if (!previousElement || (previousElement && (previousElement.tagName === "TABLE" || previousElement.getAttribute("data-type")))) { blockElement.insertAdjacentHTML("beforebegin", `

${Constants.ZWSP}

`); setRangeByWbr(vditor[vditor.currentMode].element, range); } else { range.selectNodeContents(previousElement); range.collapse(false); setSelectionFocus(range); } event.preventDefault(); return true; } return false; }; export const listToggle = (vditor: IVditor, range: Range, type: string, cancel = true) => { const itemElement = hasClosestByMatchTag(range.startContainer, "LI"); vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => { wbr.remove(); }); range.insertNode(document.createElement("wbr")); if (cancel && itemElement) { // 取消 let pHTML = ""; for (let i = 0; i < itemElement.parentElement.childElementCount; i++) { const inputElement = itemElement.parentElement.children[i].querySelector("input"); if (inputElement) { inputElement.remove(); } pHTML += `

${itemElement.parentElement.children[i].innerHTML.trimLeft()}

`; } itemElement.parentElement.insertAdjacentHTML("beforebegin", pHTML); itemElement.parentElement.remove(); } else { if (!itemElement) { // 添加 let blockElement = hasClosestByAttribute(range.startContainer, "data-block", "0"); if (!blockElement) { vditor[vditor.currentMode].element.querySelector("wbr").remove(); blockElement = vditor[vditor.currentMode].element.querySelector("p"); blockElement.innerHTML = ""; } if (type === "check") { blockElement.insertAdjacentHTML("beforebegin", ``); blockElement.remove(); } else if (type === "list") { blockElement.insertAdjacentHTML("beforebegin", ``); blockElement.remove(); } else if (type === "ordered-list") { blockElement.insertAdjacentHTML("beforebegin", `
  1. ${blockElement.innerHTML}
`); blockElement.remove(); } } else { // 切换 if (type === "check") { itemElement.parentElement.querySelectorAll("li").forEach((item) => { item.insertAdjacentHTML("afterbegin", `${item.textContent.indexOf(" ") === 0 ? "" : " "}`); item.classList.add("vditor-task"); }); } else { if (itemElement.querySelector("input")) { itemElement.parentElement.querySelectorAll("li").forEach((item) => { item.querySelector("input").remove(); item.classList.remove("vditor-task"); }); } let element; if (type === "list") { element = document.createElement("ul"); } else { element = document.createElement("ol"); } element.innerHTML = itemElement.parentElement.innerHTML; itemElement.parentElement.parentNode.replaceChild(element, itemElement.parentElement); } } } }; export const listIndent = (vditor: IVditor, liElement: HTMLElement, range: Range) => { const previousElement = liElement.previousElementSibling; if (liElement && previousElement) { const liElements: HTMLElement[] = [liElement]; Array.from(range.cloneContents().children).forEach((item, index) => { if (item.nodeType !== 3 && liElement && item.textContent.trim() !== "" && liElement.getAttribute("data-node-id") === item.getAttribute("data-node-id")) { if (index !== 0) { liElements.push(liElement); } liElement = liElement.nextElementSibling as HTMLElement; } }); vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => { wbr.remove(); }); range.insertNode(document.createElement("wbr")); const liParentElement = previousElement.parentElement; let liHTML = ""; liElements.forEach((item: HTMLElement) => { let marker = item.getAttribute("data-marker"); if (marker.length !== 1) { marker = `1${marker.slice(-1)}`; } liHTML += `
  • ${item.innerHTML}
  • `; item.remove(); }); previousElement.insertAdjacentHTML("beforeend", `<${liParentElement.tagName} data-block="0">${liHTML}`); if (vditor.currentMode === "wysiwyg") { liParentElement.outerHTML = vditor.lute.SpinVditorDOM(liParentElement.outerHTML); } else { liParentElement.outerHTML = vditor.lute.SpinVditorIRDOM(liParentElement.outerHTML); } setRangeByWbr(vditor[vditor.currentMode].element, range); const tempTopListElement = getTopList(range.startContainer); if (tempTopListElement) { tempTopListElement.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`) .forEach((item: HTMLElement) => { processCodeRender(item, vditor); if (vditor.currentMode === "wysiwyg") { item.previousElementSibling.setAttribute("style", "display:none"); } }); } execAfterRender(vditor); highlightToolbar(vditor); } else { vditor[vditor.currentMode].element.focus(); } }; export const listOutdent = (vditor: IVditor, liElement: HTMLElement, range: Range, topListElement: HTMLElement) => { const liParentLiElement = hasClosestByMatchTag(liElement.parentElement, "LI"); if (liParentLiElement) { vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => { wbr.remove(); }); range.insertNode(document.createElement("wbr")); const liParentElement = liElement.parentElement; const liParentAfterElement = liParentElement.cloneNode() as HTMLElement; const liElements: HTMLElement[] = [liElement]; Array.from(range.cloneContents().children).forEach((item, index) => { if (item.nodeType !== 3 && liElement && item.textContent.trim() !== "" && liElement.getAttribute("data-node-id") === item.getAttribute("data-node-id")) { if (index !== 0) { liElements.push(liElement); } liElement = liElement.nextElementSibling as HTMLElement; } }); let isMatch = false; let afterHTML = ""; liParentElement.querySelectorAll("li").forEach((item) => { if (isMatch) { afterHTML += item.outerHTML; if (!item.nextElementSibling && !item.previousElementSibling) { item.parentElement.remove(); } else { item.remove(); } } if (item.isSameNode(liElements[liElements.length - 1])) { isMatch = true; } }); liElements.reverse().forEach((item) => { liParentLiElement.insertAdjacentElement("afterend", item); }); if (afterHTML) { liParentAfterElement.innerHTML = afterHTML; liElements[0].insertAdjacentElement("beforeend", liParentAfterElement); } if (vditor.currentMode === "wysiwyg") { topListElement.outerHTML = vditor.lute.SpinVditorDOM(topListElement.outerHTML); } else { topListElement.outerHTML = vditor.lute.SpinVditorIRDOM(topListElement.outerHTML); } setRangeByWbr(vditor[vditor.currentMode].element, range); const tempTopListElement = getTopList(range.startContainer); if (tempTopListElement) { tempTopListElement.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`) .forEach((item: HTMLElement) => { processCodeRender(item, vditor); if (vditor.currentMode === "wysiwyg") { item.previousElementSibling.setAttribute("style", "display:none"); } }); } execAfterRender(vditor); highlightToolbar(vditor); } else { vditor[vditor.currentMode].element.focus(); } }; export const setTableAlign = (tableElement: HTMLTableElement, type: string) => { const cell = getSelection().getRangeAt(0).startContainer.parentElement; const columnCnt = tableElement.rows[0].cells.length; const rowCnt = tableElement.rows.length; let currentColumn = 0; for (let i = 0; i < rowCnt; i++) { for (let j = 0; j < columnCnt; j++) { if (tableElement.rows[i].cells[j].isSameNode(cell)) { currentColumn = j; break; } } } for (let k = 0; k < rowCnt; k++) { tableElement.rows[k].cells[currentColumn].setAttribute("align", type); } }; export const isHrMD = (text: string) => { // - _ * const marker = text.trimRight().split("\n").pop(); if (marker === "") { return false; } if (marker.replace(/ |-/g, "") === "" || marker.replace(/ |_/g, "") === "" || marker.replace(/ |\*/g, "") === "") { if (marker.replace(/ /g, "").length > 2) { if (marker.indexOf("-") > -1 && marker.trimLeft().indexOf(" ") === -1 && text.trimRight().split("\n").length > 1) { // 满足 heading return false; } if (marker.indexOf(" ") === 0 || marker.indexOf("\t") === 0) { // 代码块 return false; } return true; } return false; } return false; }; export const isHeadingMD = (text: string) => { // - = const textArray = text.trimRight().split("\n"); text = textArray.pop(); if (text.indexOf(" ") === 0 || text.indexOf("\t") === 0) { return false; } text = text.trimLeft(); if (text === "" || textArray.length === 0) { return false; } if (text.replace(/-/g, "") === "" || text.replace(/=/g, "") === "") { return true; } return false; }; export const execAfterRender = (vditor: IVditor, options = { enableAddUndoStack: true, enableHint: false, enableInput: true, }) => { if (vditor.currentMode === "wysiwyg") { afterRenderEvent(vditor, options); } else if (vditor.currentMode === "ir") { processAfterRender(vditor, options); } else if (vditor.currentMode === "sv") { processSVAfterRender(vditor, options); } }; export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement | false, event: KeyboardEvent) => { const startContainer = range.startContainer; const liElement = hasClosestByMatchTag(startContainer, "LI"); if (liElement) { if (!isCtrl(event) && !event.altKey && event.key === "Enter" && // fix li 中有多个 P 时,在第一个 P 中换行会在下方生成新的 li (!event.shiftKey && pElement && liElement.contains(pElement) && pElement.nextElementSibling)) { if (liElement && !liElement.textContent.endsWith("\n")) { // li 结尾需 \n liElement.insertAdjacentText("beforeend", "\n"); } range.insertNode(document.createTextNode("\n\n")); range.collapse(false); execAfterRender(vditor); event.preventDefault(); return true; } if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" && !liElement.previousElementSibling && range.toString() === "" && getSelectPosition(liElement, vditor[vditor.currentMode].element, range).start === 0) { // 光标位于点和第一个字符中间时,无法删除 li 元素 if (liElement.nextElementSibling) { liElement.parentElement.insertAdjacentHTML("beforebegin", `

    ${liElement.innerHTML}

    `); liElement.remove(); } else { liElement.parentElement.outerHTML = `

    ${liElement.innerHTML}

    `; } setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); event.preventDefault(); return true; } // 空列表删除后与上一级段落对齐 if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" && liElement.textContent.trim().replace(Constants.ZWSP, "") === "" && range.toString() === "" && liElement.previousElementSibling?.tagName === "LI") { liElement.previousElementSibling.insertAdjacentText("beforeend", "\n\n"); range.selectNodeContents(liElement.previousElementSibling); range.collapse(false); liElement.remove(); setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); event.preventDefault(); return true; } if (!isCtrl(event) && !event.altKey && event.key === "Tab") { // 光标位于第一/零字符时,tab 用于列表的缩进 let isFirst = false; if (range.startOffset === 0 && ((startContainer.nodeType === 3 && !startContainer.previousSibling) || (startContainer.nodeType !== 3 && startContainer.nodeName === "LI"))) { // 有序/无序列表 isFirst = true; } else if (liElement.classList.contains("vditor-task") && range.startOffset === 1 && startContainer.previousSibling.nodeType !== 3 && (startContainer.previousSibling as HTMLElement).tagName === "INPUT") { // 任务列表 isFirst = true; } if (isFirst || range.toString() !== "") { if (event.shiftKey) { listOutdent(vditor, liElement, range, liElement.parentElement); } else { listIndent(vditor, liElement, range); } event.preventDefault(); return true; } } } return false; }; // tab 处理: block code render, table, 列表第一个字符中的 tab 处理单独写在上面 export const fixTab = (vditor: IVditor, range: Range, event: KeyboardEvent) => { if (vditor.options.tab && event.key === "Tab") { if (event.shiftKey) { // TODO shift+tab } else { if (range.toString() === "") { range.insertNode(document.createTextNode(vditor.options.tab)); range.collapse(false); } else { range.extractContents(); range.insertNode(document.createTextNode(vditor.options.tab)); range.collapse(false); } } setSelectionFocus(range); execAfterRender(vditor); event.preventDefault(); return true; } }; export const fixMarkdown = (event: KeyboardEvent, vditor: IVditor, pElement: HTMLElement | false, range: Range) => { if (!pElement) { return; } if (!isCtrl(event) && !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) { // table 自动完成 let tableHeaderMD = pTextList.map(() => "---").join("|"); tableHeaderMD = pElement.textContent + "\n" + tableHeaderMD.substring(3, tableHeaderMD.length - 3) + "\n|"; pElement.outerHTML = vditor.lute.SpinVditorDOM(tableHeaderMD); setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); scrollCenter(vditor); event.preventDefault(); return true; } // hr 渲染 if (isHrMD(pElement.innerHTML) && pElement.previousElementSibling) { // 软换行后 hr 前有内容 let pInnerHTML = ""; const innerHTMLList = pElement.innerHTML.trimRight().split("\n"); if (innerHTMLList.length > 1) { innerHTMLList.pop(); pInnerHTML = `

    ${innerHTMLList.join("\n")}

    `; } pElement.insertAdjacentHTML("afterend", `${pInnerHTML}

    \n

    `); pElement.remove(); setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); scrollCenter(vditor); event.preventDefault(); return true; } if (isHeadingMD(pElement.innerHTML)) { // heading 渲染 if (vditor.currentMode === "wysiwyg") { pElement.outerHTML = vditor.lute.SpinVditorDOM(pElement.innerHTML + '

    \n

    '); } else { pElement.outerHTML = vditor.lute.SpinVditorIRDOM(pElement.innerHTML + '

    \n

    '); } setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); scrollCenter(vditor); event.preventDefault(); return true; } } // 软换行会被切割 https://github.com/Vanessa219/vditor/issues/220 if (range.collapsed && pElement.previousElementSibling && event.key === "Backspace" && !isCtrl(event) && !event.altKey && !event.shiftKey && pElement.textContent.trimRight().split("\n").length > 1 && getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start === 0) { const lastElement = getLastNode(pElement.previousElementSibling) as HTMLElement; if (!lastElement.textContent.endsWith("\n")) { lastElement.textContent = lastElement.textContent + "\n"; } lastElement.parentElement.insertAdjacentHTML("beforeend", `${pElement.innerHTML}`); pElement.remove(); setRangeByWbr(vditor[vditor.currentMode].element, range); return false; } return false; }; export const insertRow = (vditor: IVditor, range: Range, cellElement: HTMLElement) => { let rowHTML = ""; for (let m = 0; m < cellElement.parentElement.childElementCount; m++) { rowHTML += ` `; } if (cellElement.tagName === "TH") { cellElement.parentElement.parentElement.insertAdjacentHTML("afterend", `${rowHTML}`); } else { cellElement.parentElement.insertAdjacentHTML("afterend", `${rowHTML}`); } execAfterRender(vditor); }; export const insertRowAbove = (vditor: IVditor, range: Range, cellElement: HTMLElement) => { let rowHTML = ""; for (let m = 0; m < cellElement.parentElement.childElementCount; m++) { if (cellElement.tagName === "TH") { rowHTML += ` `; } else { rowHTML += ` `; } } if (cellElement.tagName === "TH") { cellElement.parentElement.parentElement.insertAdjacentHTML("beforebegin", `${rowHTML}`); range.insertNode(document.createElement("wbr")); const theadHTML = cellElement.parentElement.innerHTML.replace(//g, "").replace(/<\/th>/g, ""); cellElement.parentElement.parentElement.nextElementSibling.insertAdjacentHTML("afterbegin", theadHTML); cellElement.parentElement.parentElement.remove(); setRangeByWbr(vditor.ir.element, range); } else { cellElement.parentElement.insertAdjacentHTML("beforebegin", `${rowHTML}`); } execAfterRender(vditor); }; export const insertColumn = (vditor: IVditor, tableElement: HTMLTableElement, cellElement: HTMLElement, type: InsertPosition = "afterend") => { 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(type, " "); } else { tableElement.rows[i].cells[index].insertAdjacentHTML(type, " "); } } execAfterRender(vditor); }; export const deleteRow = (vditor: IVditor, range: Range, cellElement: HTMLElement) => { if (cellElement.tagName === "TD") { 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); setSelectionFocus(range); execAfterRender(vditor); } }; export const deleteColumn = (vditor: IVditor, range: Range, tableElement: HTMLTableElement, cellElement: HTMLElement) => { 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++) { const cells = tableElement.rows[i].cells; if (cells.length === 1) { tableElement.remove(); highlightToolbar(vditor); break; } cells[index].remove(); } setSelectionFocus(range); execAfterRender(vditor); }; export const fixTable = (vditor: IVditor, event: KeyboardEvent, range: Range) => { const startContainer = range.startContainer; const cellElement = hasClosestByMatchTag(startContainer, "TD") || hasClosestByMatchTag(startContainer, "TH"); if (cellElement) { // 换行或软换行:在 cell 中添加 br if (!isCtrl(event) && !event.altKey && event.key === "Enter") { if (!cellElement.lastElementChild || (cellElement.lastElementChild && (!cellElement.lastElementChild.isSameNode(cellElement.lastChild) || cellElement.lastElementChild.tagName !== "BR"))) { cellElement.insertAdjacentHTML("beforeend", "
    "); } const brElement = document.createElement("br"); range.insertNode(brElement); range.setStartAfter(brElement); execAfterRender(vditor); scrollCenter(vditor); event.preventDefault(); return true; } // tab:光标移向下一个 cell if (event.key === "Tab") { if (event.shiftKey) { // shift + tab 光标移动到前一个 cell goPreviousCell(cellElement, range); event.preventDefault(); return true; } let nextElement = cellElement.nextElementSibling; if (!nextElement) { if (cellElement.parentElement.nextElementSibling) { nextElement = cellElement.parentElement.nextElementSibling.firstElementChild; } else if (cellElement.parentElement.parentElement.tagName === "THEAD" && cellElement.parentElement.parentElement.nextElementSibling) { nextElement = cellElement.parentElement.parentElement.nextElementSibling.firstElementChild.firstElementChild; } else { nextElement = null; } } if (nextElement) { range.selectNodeContents(nextElement); setSelectionFocus(range); } event.preventDefault(); return true; } const tableElement = cellElement.parentElement.parentElement.parentElement as HTMLTableElement; if (event.key === "ArrowUp") { event.preventDefault(); if (cellElement.tagName === "TH") { if (tableElement.previousElementSibling) { range.selectNodeContents(tableElement.previousElementSibling); range.collapse(false); setSelectionFocus(range); } else { insertEmptyBlock(vditor, "beforebegin"); } return true; } let m = 0; const trElement = cellElement.parentElement as HTMLTableRowElement; for (; m < trElement.cells.length; m++) { if (trElement.cells[m].isSameNode(cellElement)) { break; } } let previousElement = trElement.previousElementSibling as HTMLTableRowElement; if (!previousElement) { previousElement = trElement.parentElement.previousElementSibling.firstChild as HTMLTableRowElement; } range.selectNodeContents(previousElement.cells[m]); range.collapse(false); setSelectionFocus(range); return true; } if (event.key === "ArrowDown") { event.preventDefault(); const trElement = cellElement.parentElement as HTMLTableRowElement; if (!trElement.nextElementSibling && cellElement.tagName === "TD") { if (tableElement.nextElementSibling) { range.selectNodeContents(tableElement.nextElementSibling); range.collapse(true); setSelectionFocus(range); } else { insertEmptyBlock(vditor, "afterend"); } return true; } let m = 0; for (; m < trElement.cells.length; m++) { if (trElement.cells[m].isSameNode(cellElement)) { break; } } let nextElement = trElement.nextElementSibling as HTMLTableRowElement; if (!nextElement) { nextElement = trElement.parentElement.nextElementSibling.firstChild as HTMLTableRowElement; } range.selectNodeContents(nextElement.cells[m]); range.collapse(true); setSelectionFocus(range); return true; } // focus row input, only wysiwyg if (vditor.currentMode === "wysiwyg" && !isCtrl(event) && event.key === "Enter" && !event.shiftKey && event.altKey) { const inputElement = (vditor.wysiwyg.popover.querySelector(".vditor-input") as HTMLInputElement); inputElement.focus(); inputElement.select(); event.preventDefault(); return true; } // Backspace:光标移动到前一个 cell if (!isCtrl(event) && !event.shiftKey && !event.altKey && event.key === "Backspace" && range.startOffset === 0 && range.toString() === "") { const previousCellElement = goPreviousCell(cellElement, range, false); if (!previousCellElement && tableElement) { if (tableElement.textContent.trim() === "") { tableElement.outerHTML = `

    \n

    `; setRangeByWbr(vditor[vditor.currentMode].element, range); } else { range.setStartBefore(tableElement); range.collapse(true); } execAfterRender(vditor); } event.preventDefault(); return true; } // 上方新添加一行 if (matchHotKey("⇧⌘F", event)) { insertRowAbove(vditor, range, cellElement); event.preventDefault(); return true; } // 下方新添加一行 https://github.com/Vanessa219/vditor/issues/46 if (matchHotKey("⌘=", event)) { insertRow(vditor, range, cellElement); event.preventDefault(); return true; } // 左方新添加一列 if (matchHotKey("⇧⌘G", event)) { insertColumn(vditor, tableElement, cellElement, "beforebegin"); event.preventDefault(); return true; } // 后方新添加一列 if (matchHotKey("⇧⌘=", event)) { insertColumn(vditor, tableElement, cellElement); event.preventDefault(); return true; } // 删除当前行 if (matchHotKey("⌘-", event)) { deleteRow(vditor, range, cellElement); event.preventDefault(); return true; } // 删除当前列 if (matchHotKey("⇧⌘-", event)) { deleteColumn(vditor, range, tableElement, cellElement); event.preventDefault(); return true; } // 剧左 if (matchHotKey("⇧⌘L", event)) { if (vditor.currentMode === "ir") { setTableAlign(tableElement, "left"); execAfterRender(vditor); event.preventDefault(); return true; } else { const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="left"]'); if (itemElement) { itemElement.click(); event.preventDefault(); return true; } } } // 剧中 if (matchHotKey("⇧⌘C", event)) { if (vditor.currentMode === "ir") { setTableAlign(tableElement, "center"); execAfterRender(vditor); event.preventDefault(); return true; } else { const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="center"]'); if (itemElement) { itemElement.click(); event.preventDefault(); return true; } } } // 剧右 if (matchHotKey("⇧⌘R", event)) { if (vditor.currentMode === "ir") { setTableAlign(tableElement, "right"); execAfterRender(vditor); event.preventDefault(); return true; } else { const itemElement: HTMLElement = vditor.wysiwyg.popover.querySelector('[data-type="right"]'); if (itemElement) { itemElement.click(); event.preventDefault(); return true; } } } } return false; }; export const fixCodeBlock = (vditor: IVditor, event: KeyboardEvent, codeRenderElement: HTMLElement, range: Range) => { // 行级代码块中 command + a,近对当前代码块进行全选 if (codeRenderElement.tagName === "PRE" && matchHotKey("⌘A", event)) { range.selectNodeContents(codeRenderElement.firstElementChild); event.preventDefault(); return true; } // tab // TODO shift + tab, shift and 选中文字 if (vditor.options.tab && event.key === "Tab" && !event.shiftKey && range.toString() === "") { range.insertNode(document.createTextNode(vditor.options.tab)); range.collapse(false); execAfterRender(vditor); event.preventDefault(); return true; } // Backspace: 光标位于第零个字符,仅删除代码块标签 if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey) { const codePosition = getSelectPosition(codeRenderElement, vditor[vditor.currentMode].element, range); if ((codePosition.start === 0 || (codePosition.start === 1 && codeRenderElement.innerText === "\n")) // 空代码块,光标在 \n 后 && range.toString() === "") { codeRenderElement.parentElement.outerHTML = `

    ${codeRenderElement.firstElementChild.innerHTML}

    `; setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); event.preventDefault(); return true; } } // 换行 if (!isCtrl(event) && !event.altKey && event.key === "Enter") { if (!codeRenderElement.firstElementChild.textContent.endsWith("\n")) { codeRenderElement.firstElementChild.insertAdjacentText("beforeend", "\n"); } range.extractContents(); range.insertNode(document.createTextNode("\n")); range.collapse(false); setSelectionFocus(range); if (!isFirefox()) { if (vditor.currentMode === "wysiwyg") { input(vditor, range); } else { IRInput(vditor, range); } } scrollCenter(vditor); event.preventDefault(); return true; } return false; }; export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEvent, pElement: HTMLElement | false) => { const startContainer = range.startContainer; const blockquoteElement = hasClosestByMatchTag(startContainer, "BLOCKQUOTE"); if (blockquoteElement && range.toString() === "") { if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey && getSelectPosition(blockquoteElement, vditor[vditor.currentMode].element, range).start === 0) { // Backspace: 光标位于引用中的第零个字符,仅删除引用标签 range.insertNode(document.createElement("wbr")); blockquoteElement.outerHTML = blockquoteElement.innerHTML; setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); event.preventDefault(); return true; } if (pElement && event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey && pElement.parentElement.tagName === "BLOCKQUOTE") { // Enter: 空行回车应逐层跳出 let isEmpty = false; if (pElement.innerHTML.replace(Constants.ZWSP, "") === "\n" || pElement.innerHTML.replace(Constants.ZWSP, "") === "") { // 空 P isEmpty = true; pElement.remove(); } else if (pElement.innerHTML.endsWith("\n\n") && getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start === pElement.textContent.length - 1) { // 软换行 pElement.innerHTML = pElement.innerHTML.substr(0, pElement.innerHTML.length - 2); isEmpty = true; } if (isEmpty) { // 需添加零宽字符,否则的话无法记录 undo blockquoteElement.insertAdjacentHTML("afterend", `

    ${Constants.ZWSP}\n

    `); setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); event.preventDefault(); return true; } } const blockElement = hasClosestBlock(startContainer); if (vditor.currentMode === "wysiwyg" && blockElement && matchHotKey("⇧⌘;", event)) { // 插入 blockquote range.insertNode(document.createElement("wbr")); blockElement.outerHTML = `
    ${blockElement.outerHTML}
    `; setRangeByWbr(vditor.wysiwyg.element, range); afterRenderEvent(vditor); event.preventDefault(); return true; } if (insertAfterBlock(vditor, event, range, blockquoteElement, blockquoteElement)) { return true; } if (insertBeforeBlock(vditor, event, range, blockquoteElement, blockquoteElement)) { return true; } } return false; }; export const fixTask = (vditor: IVditor, range: Range, event: KeyboardEvent) => { const startContainer = range.startContainer; const taskItemElement = hasClosestByClassName(startContainer, "vditor-task"); if (taskItemElement) { if (matchHotKey("⇧⌘J", event)) { // ctrl + shift: toggle checked const inputElement = taskItemElement.firstElementChild as HTMLInputElement; if (inputElement.checked) { inputElement.removeAttribute("checked"); } else { inputElement.setAttribute("checked", "checked"); } execAfterRender(vditor); event.preventDefault(); return true; } // Backspace: 在选择框前进行删除 if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey && range.toString() === "" && range.startOffset === 1 && ((startContainer.nodeType === 3 && startContainer.previousSibling && (startContainer.previousSibling as HTMLElement).tagName === "INPUT") || startContainer.nodeType !== 3)) { const previousElement = taskItemElement.previousElementSibling; taskItemElement.querySelector("input").remove(); if (previousElement) { const lastNode = getLastNode(previousElement); lastNode.parentElement.insertAdjacentHTML("beforeend", "" + 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[vditor.currentMode].element, range); execAfterRender(vditor); event.preventDefault(); return true; } if (event.key === "Enter" && !isCtrl(event) && !event.shiftKey && !event.altKey) { if (taskItemElement.textContent.trim() === "") { // 当前任务列表无文字 if (hasClosestByClassName(taskItemElement.parentElement, "vditor-task")) { // 为子元素时,需进行反向缩进 const topListElement = getTopList(startContainer); if (topListElement) { listOutdent(vditor, taskItemElement, range, topListElement); } } else { // 仅有一级任务列表 if (taskItemElement.nextElementSibling) { // 任务列表下方还有元素,需要使用用段落隔断 let afterHTML = ""; let beforeHTML = ""; let isAfter = false; Array.from(taskItemElement.parentElement.children).forEach((taskItem) => { if (taskItemElement.isSameNode(taskItem)) { isAfter = true; } else { if (isAfter) { afterHTML += taskItem.outerHTML; } else { beforeHTML += taskItem.outerHTML; } } }); const parentTagName = taskItemElement.parentElement.tagName; const dataMarker = taskItemElement.parentElement.tagName === "OL" ? "" : ` data-marker="${taskItemElement.parentElement.getAttribute("data-marker")}"`; let startAttribute = ""; if (beforeHTML) { startAttribute = taskItemElement.parentElement.tagName === "UL" ? "" : ` start="1"`; beforeHTML = `<${parentTagName} data-tight="true"${dataMarker} data-block="0">${beforeHTML}`; } //

    \n

    =>

    \n

    // https://github.com/Vanessa219/vditor/issues/430 taskItemElement.parentElement.outerHTML = `${beforeHTML}

    \n

    <${parentTagName} data-tight="true"${dataMarker} data-block="0"${startAttribute}>${afterHTML}`; } else { // 任务列表下方无任务列表元素 taskItemElement.parentElement.insertAdjacentHTML("afterend", `

    \n

    `); if (taskItemElement.parentElement.querySelectorAll("li").length === 1) { // 任务列表仅有一项时,使用 p 元素替换 taskItemElement.parentElement.remove(); } else { // 任务列表有多项时,当前任务列表位于最后一项,移除该任务列表 taskItemElement.remove(); } } } } else if (startContainer.nodeType !== 3 && range.startOffset === 0 && (startContainer.firstChild as HTMLElement).tagName === "INPUT") { // 光标位于 input 之前 range.setStart(startContainer.childNodes[1], 1); } else { // 当前任务列表有文字,光标后的文字需添加到新任务列表中 range.setEndAfter(taskItemElement.lastChild); taskItemElement.insertAdjacentHTML("afterend", `
  • `); document.querySelector("wbr").after(range.extractContents()); } setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); scrollCenter(vditor); event.preventDefault(); return true; } } return false; }; export const fixDelete = (vditor: IVditor, range: Range, event: KeyboardEvent, pElement: HTMLElement | false) => { if (range.startContainer.nodeType !== 3) { // 光标位于 hr 前,hr 前有内容 const rangeElement = (range.startContainer as HTMLElement).children[range.startOffset]; if (rangeElement && rangeElement.tagName === "HR") { range.selectNodeContents(rangeElement.previousElementSibling); range.collapse(false); event.preventDefault(); return true; } } if (pElement) { const previousElement = pElement.previousElementSibling; if (previousElement && getSelectPosition(pElement, vditor[vditor.currentMode].element, range).start === 0 && ((isFirefox() && previousElement.tagName === "HR") || previousElement.tagName === "TABLE")) { if (previousElement.tagName === "TABLE") { // table 后删除 https://github.com/Vanessa219/vditor/issues/243 const lastCellElement = previousElement.lastElementChild.lastElementChild.lastElementChild; lastCellElement.innerHTML = lastCellElement.innerHTML.trimLeft() + "" + pElement.textContent.trim(); pElement.remove(); } else { // 光标位于 hr 后进行删除 previousElement.remove(); } setRangeByWbr(vditor[vditor.currentMode].element, range); execAfterRender(vditor); event.preventDefault(); return true; } } return false; }; export const fixHR = (range: Range) => { if (isFirefox() && range.startContainer.nodeType !== 3 && (range.startContainer as HTMLElement).tagName === "HR") { range.setStartBefore(range.startContainer); } }; // firefox https://github.com/Vanessa219/vditor/issues/407 export const fixFirefoxArrowUpTable = (event: KeyboardEvent, blockElement: false | HTMLElement, range: Range) => { if (!isFirefox()) { return false; } if (event.key === "ArrowUp" && blockElement && blockElement.previousElementSibling?.tagName === "TABLE") { const tableElement = blockElement.previousElementSibling as HTMLTableElement; range.selectNodeContents(tableElement.rows[tableElement.rows.length - 1].lastElementChild); range.collapse(false); event.preventDefault(); return true; } if (event.key === "ArrowDown" && blockElement && blockElement.nextElementSibling?.tagName === "TABLE") { range.selectNodeContents((blockElement.nextElementSibling as HTMLTableElement).rows[0].cells[0]); range.collapse(true); event.preventDefault(); return true; } return false; }; export const paste = async (vditor: IVditor, event: (ClipboardEvent | DragEvent) & { target: HTMLElement }, callback: { pasteCode(code: string): void, }) => { event.stopPropagation(); event.preventDefault(); let textHTML; let textPlain; let files; if ("clipboardData" in event) { textHTML = event.clipboardData.getData("text/html"); textPlain = event.clipboardData.getData("text/plain"); files = event.clipboardData.files; } else { textHTML = event.dataTransfer.getData("text/html"); textPlain = event.dataTransfer.getData("text/plain"); if (event.dataTransfer.types[0] === "Files") { files = event.dataTransfer.items; } } const renderers: { HTML2VditorDOM?: ILuteRender, HTML2VditorIRDOM?: ILuteRender, Md2VditorDOM?: ILuteRender, Md2VditorIRDOM?: ILuteRender, Md2VditorSVDOM?: ILuteRender, } = {}; const renderLinkDest: ILuteRenderCallback = (node, entering) => { if (!entering) { return ["", Lute.WalkContinue]; } const src = node.TokensStr(); if (node.__internal_object__.Parent.Type === 34 && src && src.indexOf("file://") === -1 && vditor.options.upload.linkToImgUrl) { const xhr = new XMLHttpRequest(); xhr.open("POST", vditor.options.upload.linkToImgUrl); if (vditor.options.upload.token) { xhr.setRequestHeader("X-Upload-Token", vditor.options.upload.token); } if (vditor.options.upload.withCredentials) { xhr.withCredentials = true; } setHeaders(vditor, xhr); xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { let responseText = xhr.responseText; if (vditor.options.upload.linkToImgFormat) { responseText = vditor.options.upload.linkToImgFormat(xhr.responseText); } const responseJSON = JSON.parse(responseText); if (responseJSON.code !== 0) { vditor.tip.show(responseJSON.msg); return; } const original = responseJSON.data.originalURL; if (vditor.currentMode === "sv") { vditor.sv.element.querySelectorAll(".vditor-sv__marker--link") .forEach((item: HTMLElement) => { if (item.textContent === original) { item.textContent = responseJSON.data.url; } }); } else { const imgElement: HTMLImageElement = vditor[vditor.currentMode].element.querySelector(`img[src="${original}"]`); imgElement.src = responseJSON.data.url; if (vditor.currentMode === "ir") { imgElement.previousElementSibling.previousElementSibling.innerHTML = responseJSON.data.url; } } execAfterRender(vditor); } else { vditor.tip.show(xhr.responseText); } if (vditor.options.upload.linkToImgCallback) { vditor.options.upload.linkToImgCallback(xhr.responseText); } } }; xhr.send(JSON.stringify({url: src})); } if (vditor.currentMode === "ir") { return [`${src}`, Lute.WalkContinue]; } else if (vditor.currentMode === "wysiwyg") { return ["", Lute.WalkContinue]; } else { return [`${src}`, Lute.WalkContinue]; } }; // 浏览器地址栏拷贝处理 if (textHTML.replace(/&/g, "&").replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() === `${textPlain}` || textHTML.replace(/&/g, "&").replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() === `${textPlain}`) { textHTML = ""; } // process word const doc = new DOMParser().parseFromString(textHTML, "text/html"); if (doc.body) { textHTML = doc.body.innerHTML; } vditor.wysiwyg.getComments(vditor); // process code const height = vditor[vditor.currentMode].element.scrollHeight; const code = processPasteCode(textHTML, textPlain, vditor.currentMode); const codeElement = vditor.currentMode === "sv" ? hasClosestByAttribute(event.target, "data-type", "code-block") : hasClosestByMatchTag(event.target, "CODE"); if (codeElement) { // 粘贴在代码位置 if (vditor.currentMode === "sv") { document.execCommand("insertHTML", false, textPlain.replace(/&/g, "&").replace(/ { e.removeAttribute("style"); }); tempElement.querySelectorAll(".vditor-copy").forEach((e) => { e.remove(); }); if (vditor.currentMode === "ir") { renderers.HTML2VditorIRDOM = {renderLinkDest}; vditor.lute.SetJSRenderers({renderers}); insertHTML(vditor.lute.HTML2VditorIRDOM(tempElement.innerHTML), vditor); } else if (vditor.currentMode === "wysiwyg") { renderers.HTML2VditorDOM = {renderLinkDest}; vditor.lute.SetJSRenderers({renderers}); insertHTML(vditor.lute.HTML2VditorDOM(tempElement.innerHTML), vditor); } else { renderers.Md2VditorSVDOM = {renderLinkDest}; vditor.lute.SetJSRenderers({renderers}); processPaste(vditor, vditor.lute.HTML2Md(tempElement.innerHTML).trimRight()); } vditor.outline.render(vditor); } else if (files.length > 0 && vditor.options.upload.url) { await uploadFiles(vditor, files); } else if (textPlain.trim() !== "" && files.length === 0) { if (vditor.currentMode === "ir") { renderers.Md2VditorIRDOM = {renderLinkDest}; vditor.lute.SetJSRenderers({renderers}); insertHTML(vditor.lute.Md2VditorIRDOM(textPlain), vditor); } else if (vditor.currentMode === "wysiwyg") { renderers.Md2VditorDOM = {renderLinkDest}; vditor.lute.SetJSRenderers({renderers}); insertHTML(vditor.lute.Md2VditorDOM(textPlain), vditor); } else { renderers.Md2VditorSVDOM = {renderLinkDest}; vditor.lute.SetJSRenderers({renderers}); processPaste(vditor, textPlain); } vditor.outline.render(vditor); } } if (vditor.currentMode !== "sv") { const blockElement = hasClosestBlock(getEditorRange(vditor).startContainer); if (blockElement) { // https://github.com/Vanessa219/vditor/issues/591 const range = getEditorRange(vditor); vditor[vditor.currentMode].element.querySelectorAll("wbr").forEach((wbr) => { wbr.remove(); }); range.insertNode(document.createElement("wbr")); if (vditor.currentMode === "wysiwyg") { blockElement.outerHTML = vditor.lute.SpinVditorDOM(blockElement.outerHTML); } else { blockElement.outerHTML = vditor.lute.SpinVditorIRDOM(blockElement.outerHTML); } setRangeByWbr(vditor[vditor.currentMode].element, range); } vditor[vditor.currentMode].element.querySelectorAll(`.vditor-${vditor.currentMode}__preview[data-render='2']`) .forEach((item: HTMLElement) => { processCodeRender(item, vditor); }); } vditor.wysiwyg.triggerRemoveComment(vditor); execAfterRender(vditor); if (vditor[vditor.currentMode].element.scrollHeight - height > Math.min(vditor[vditor.currentMode].element.clientHeight, window.innerHeight) / 2) { scrollCenter(vditor); } };