diff --git a/src/ts/ir/processKeydown.ts b/src/ts/ir/processKeydown.ts index d7f7a863c..e1e4d708a 100644 --- a/src/ts/ir/processKeydown.ts +++ b/src/ts/ir/processKeydown.ts @@ -1,10 +1,9 @@ import {Constants} from "../constants"; import {isCtrl} from "../util/compatibility"; import {scrollCenter} from "../util/editorCommenEvent"; -import {fixList, fixMarkdown, fixTab, fixTable} from "../util/fixBrowserBehavior"; +import {fixBlockquote, fixCodeBlock, fixList, fixMarkdown, fixTab, fixTable} from "../util/fixBrowserBehavior"; import {hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest"; import {getSelectPosition, setRangeByWbr} from "../util/selection"; -import {processAfterRender} from "./process"; export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { vditor.ir.composingLock = event.isComposing; @@ -52,6 +51,10 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { if (fixList(range, vditor, pElement, event)) { return true; } + // blockquote + if (fixBlockquote(vditor, range, event, pElement)) { + return true; + } } // 代码块 @@ -59,44 +62,6 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { if (preRenderElement && preRenderElement.tagName === "PRE") { const codeRenderElement = preRenderElement.firstChild as HTMLElement; const codePosition = getSelectPosition(codeRenderElement, range); - // 换行 - if (!isCtrl(event) && !event.altKey && event.key === "Enter") { - if (!codeRenderElement.textContent.endsWith("\n")) { - codeRenderElement.insertAdjacentText("beforeend", "\n"); - } - range.insertNode(document.createTextNode("\n")); - range.collapse(false); - processAfterRender(vditor); - scrollCenter(vditor.ir.element); - event.preventDefault(); - return true; - } - - // tab - if (vditor.options.tab && event.key === "Tab" && !event.shiftKey && range.toString() === "") { - range.insertNode(document.createTextNode(vditor.options.tab)); - range.collapse(false); - processAfterRender(vditor); - event.preventDefault(); - return true; - } - - // TODO shift + tab, shift and 选中文字 - - if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey) { - if ((codePosition.start === 0 || - (codePosition.start === 1 && codeRenderElement.innerText === "\n")) // 空代码块,光标在 \n 后 - && range.toString() === "") { - // Backspace: 光标位于第零个字符,仅删除代码块标签 - preRenderElement.parentElement.outerHTML = - `

${codeRenderElement.innerHTML}

`; - setRangeByWbr(vditor.ir.element, range); - processAfterRender(vditor); - event.preventDefault(); - return true; - } - } - // 数学公式上无元素,按上或左将添加新块 if ((event.key === "ArrowUp" || event.key === "ArrowLeft") && codeRenderElement.getAttribute("data-type") === "math-block" @@ -123,7 +88,10 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { } event.preventDefault(); return true; + } + if (fixCodeBlock(vditor, event, preRenderElement, range)) { + return true; } } // 代码块语言 @@ -156,45 +124,6 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { return true; } - // blockquote - const blockquoteElement = hasClosestByMatchTag(startContainer, "BLOCKQUOTE"); - if (blockquoteElement && range.toString() === "") { - if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey && - getSelectPosition(blockquoteElement, range).start === 0) { - // Backspace: 光标位于引用中的第零个字符,仅删除引用标签 - range.insertNode(document.createElement("wbr")); - blockquoteElement.outerHTML = blockquoteElement.innerHTML; - setRangeByWbr(vditor.ir.element, range); - processAfterRender(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") { - // 空 P - isEmpty = true; - pElement.remove(); - } else if (pElement.innerHTML.endsWith("\n\n") && - getSelectPosition(pElement, 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.ir.element, range); - processAfterRender(vditor); - event.preventDefault(); - return true; - } - } - } - if (fixTab(vditor, range, event)) { return true; } diff --git a/src/ts/util/fixBrowserBehavior.ts b/src/ts/util/fixBrowserBehavior.ts index a908f5628..dd6a4e434 100644 --- a/src/ts/util/fixBrowserBehavior.ts +++ b/src/ts/util/fixBrowserBehavior.ts @@ -2,10 +2,11 @@ import {processAfterRender} from "../ir/process"; import {afterRenderEvent} from "../wysiwyg/afterRenderEvent"; import {isCtrl} from "./compatibility"; import {scrollCenter} from "./editorCommenEvent"; -import {hasClosestByMatchTag} from "./hasClosest"; +import {hasClosestBlock, hasClosestByMatchTag} from "./hasClosest"; import {getLastNode} from "./hasClosest"; import {matchHotKey} from "./hotKey"; import {getSelectPosition, setRangeByWbr} from "./selection"; +import {Constants} from "../constants"; // 光标设置到前一个表格中 const goPreviousCell = (cellElement: HTMLElement, range: Range, isSelected = true) => { @@ -127,9 +128,8 @@ export const fixList = (range: Range, vditor: IVditor, pElement: HTMLElement, ev const liElement = hasClosestByMatchTag(startContainer, "LI"); if (liElement) { if (!isCtrl(event) && !event.altKey && event.key === "Enter" && - (event.shiftKey // 软换行 - // fix li 中有多个 P 时,在第一个 P 中换行会在下方生成新的 li - || (!event.shiftKey && pElement && liElement.contains(pElement) && pElement.nextElementSibling))) { + // 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"); @@ -479,3 +479,109 @@ export const fixTable = (vditor: IVditor, event: KeyboardEvent, range: Range) => } 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, 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.insertNode(document.createTextNode("\n")); + range.collapse(false); + execAfterRender(vditor); + scrollCenter(vditor[vditor.currentMode].element); + event.preventDefault(); + return true; + } + return false +} + +export const fixBlockquote = (vditor: IVditor, range: Range, event: KeyboardEvent, pElement: HTMLElement) => { + 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, 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") { + // 空 P + isEmpty = true; + pElement.remove(); + } else if (pElement.innerHTML.endsWith("\n\n") && + getSelectPosition(pElement, range).start === pElement.textContent.length - 1) { + // 软换行 + pElement.innerHTML = pElement.innerHTML.substr(0, pElement.innerHTML.length - 2); + isEmpty = true; + } + if (isEmpty) { + if (vditor.currentMode === "wysiwyg") { + (vditor.wysiwyg.popover.querySelector('[data-type="insert-after"]') as HTMLElement).click(); + event.preventDefault(); + return true; + } else { + // 需添加零宽字符,否则的话无法记录 undo + blockquoteElement.insertAdjacentHTML("afterend", `

${Constants.ZWSP}\n

`); + setRangeByWbr(vditor[vditor.currentMode].element, range); + processAfterRender(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; + } + } + return false; +} diff --git a/src/ts/wysiwyg/processKeydown.ts b/src/ts/wysiwyg/processKeydown.ts index 9896aecfb..5e578abb7 100644 --- a/src/ts/wysiwyg/processKeydown.ts +++ b/src/ts/wysiwyg/processKeydown.ts @@ -1,7 +1,7 @@ import {Constants} from "../constants"; import {isCtrl} from "../util/compatibility"; import {scrollCenter} from "../util/editorCommenEvent"; -import {fixList, fixMarkdown, fixTab, fixTable} from "../util/fixBrowserBehavior"; +import {fixBlockquote, fixCodeBlock, fixList, fixMarkdown, fixTab, fixTable} from "../util/fixBrowserBehavior"; import { getLastNode, getTopList, hasClosestBlock, hasClosestByAttribute, @@ -14,7 +14,7 @@ import {getSelectPosition, setRangeByWbr, setSelectionFocus} from "../util/selec import {afterRenderEvent} from "./afterRenderEvent"; import {listOutdent} from "./highlightToolbar"; import {nextIsCode} from "./inlineTag"; -import {processCodeRender, showCode} from "./processCodeRender"; +import {showCode} from "./processCodeRender"; import {removeHeading, setHeading} from "./setHeading"; export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { @@ -50,6 +50,10 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { if (fixList(range, vditor, pElement, event)) { return true; } + // blockquote + if (fixBlockquote(vditor, range, event, pElement)) { + return true; + } } // table @@ -68,6 +72,7 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { event.preventDefault(); return true; } + // alt+enter: 代码块切换到语言 https://github.com/Vanessa219/vditor/issues/54 if (!isCtrl(event) && !event.shiftKey && event.altKey && event.key === "Enter" && codeRenderElement.getAttribute("data-type") === "code-block") { @@ -78,56 +83,10 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { return true; } - // 行级代码块中 command + a,近对当前代码块进行全选 - if (codeRenderElement.getAttribute("data-block") === "0" && matchHotKey("⌘-A", event)) { - range.selectNodeContents(codeRenderElement.firstElementChild.firstElementChild); - event.preventDefault(); + if (codeRenderElement.getAttribute("data-block") === "0" && + fixCodeBlock(vditor, event, codeRenderElement.firstElementChild as HTMLElement, range)) { return true; } - - // 换行 - if (!isCtrl(event) && !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); - scrollCenter(vditor.wysiwyg.element); - event.preventDefault(); - return true; - } - - // tab - if (vditor.options.tab && event.key === "Tab" && !event.shiftKey && range.toString() === "" && - 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 选中文字 - - if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey - && codeRenderElement.getAttribute("data-block") === "0") { - const codePosition = getSelectPosition(codeRenderElement, range); - if ((codePosition.start === 0 || - (codePosition.start === 1 && codeRenderElement.innerText === "\n")) // 空代码块,光标在 \n 后 - && range.toString() === "") { - // Backspace: 光标位于第零个字符,仅删除代码块标签 - codeRenderElement.outerHTML = - `

${codeRenderElement.firstElementChild.firstElementChild.innerHTML}

`; - setRangeByWbr(vditor.wysiwyg.element, range); - afterRenderEvent(vditor); - event.preventDefault(); - return true; - } - } } // 顶层 blockquote @@ -155,52 +114,6 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { } } - // blockquote - const blockquoteElement = hasClosestByMatchTag(startContainer, "BLOCKQUOTE"); - if (blockquoteElement && range.toString() === "") { - if (event.key === "Backspace" && !isCtrl(event) && !event.shiftKey && !event.altKey && - getSelectPosition(blockquoteElement, range).start === 0) { - // Backspace: 光标位于引用中的第零个字符,仅删除引用标签 - range.insertNode(document.createElement("wbr")); - blockquoteElement.outerHTML = blockquoteElement.innerHTML; - setRangeByWbr(vditor.wysiwyg.element, range); - afterRenderEvent(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") { - // 空 P - isEmpty = true; - pElement.remove(); - } else if (pElement.innerHTML.endsWith("\n\n") && - getSelectPosition(pElement, range).start === pElement.textContent.length - 1) { - // 软换行 - pElement.innerHTML = pElement.innerHTML.substr(0, pElement.innerHTML.length - 2); - isEmpty = true; - } - if (isEmpty) { - (vditor.wysiwyg.popover.querySelector('[data-type="insert-after"]') as HTMLElement).click(); - event.preventDefault(); - return true; - } - } - - if (blockElement && matchHotKey("⌘-⇧-:", event)) { - // 插入 blockquote - range.insertNode(document.createElement("wbr")); - blockElement.outerHTML = `
${blockElement.outerHTML}
`; - setRangeByWbr(vditor.wysiwyg.element, range); - afterRenderEvent(vditor); - event.preventDefault(); - return true; - } - } - // h1-h6 const headingElement = hasClosestByTag(startContainer, "H"); if (headingElement && headingElement.tagName.length === 2) { @@ -429,7 +342,7 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { return true; } - // shift+enter:软换行,但 table/hr/heading 处理、cell 内换行、block render 换行、li 软换行处理单独写在上面 + // shift+enter:软换行,但 table/hr/heading 处理、cell 内换行、block render 换行处理单独写在上面 if (!isCtrl(event) && event.shiftKey && !event.altKey && event.key === "Enter") { if (["STRONG", "S", "STRONG", "I", "EM", "B"].includes(startContainer.parentElement.tagName)) { // 行内元素软换行需继续 https://github.com/Vanessa219/vditor/issues/170