diff --git a/src/ts/ir/input.ts b/src/ts/ir/input.ts index 71ce12e46..33eecf150 100644 --- a/src/ts/ir/input.ts +++ b/src/ts/ir/input.ts @@ -1,5 +1,6 @@ import {getTopList, hasClosestBlock, hasClosestByClassName, hasClosestByTag} from "../util/hasClosest"; import {log} from "../util/log"; +import {isHeadingMD, isHrMD} from "../util/processMD"; import {getSelectPosition, setRangeByWbr} from "../util/selection"; import {processAfterRender, processCodeRender} from "./process"; @@ -40,7 +41,7 @@ export const input = (vditor: IVditor, range: Range) => { } } - if ((startSpace || endSpace) + if ((startSpace || endSpace || isHrMD(blockElement.innerHTML) || isHeadingMD(blockElement.innerHTML)) // insert table && !blockElement.querySelector("wbr")) { blockElement.classList.add("vditor-ir__node--expand"); diff --git a/src/ts/ir/processKeydown.ts b/src/ts/ir/processKeydown.ts index c653b6c74..a7f79b26a 100644 --- a/src/ts/ir/processKeydown.ts +++ b/src/ts/ir/processKeydown.ts @@ -2,6 +2,7 @@ import {Constants} from "../constants"; import {isCtrl} from "../util/compatibility"; import {scrollCenter} from "../util/editorCommenEvent"; import {hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest"; +import {mdKeydown} from "../util/processMD"; import {tableHotkey} from "../util/processTable"; import {getSelectPosition, setRangeByWbr} from "../util/selection"; import {processAfterRender} from "./process"; @@ -27,7 +28,6 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { const startContainer = range.startContainer; const newlineElement = hasClosestByAttribute(startContainer, "data-newline", "1"); - if (!isCtrl(event) && !event.altKey && !event.shiftKey && event.key === "Enter" && newlineElement && range.startOffset < newlineElement.textContent.length) { // 斜体、粗体、内联代码块中换行 @@ -44,6 +44,12 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { } const pElement = hasClosestByMatchTag(startContainer, "P"); + if (pElement) { + if (mdKeydown(event, vditor, pElement, range, processAfterRender)) { + return true; + } + } + // 代码块 const preRenderElement = hasClosestByClassName(startContainer, "vditor-ir__marker--pre"); if (preRenderElement && preRenderElement.tagName === "PRE") { diff --git a/src/ts/util/hasClosest.ts b/src/ts/util/hasClosest.ts index 644973ee8..7ab1092a1 100644 --- a/src/ts/util/hasClosest.ts +++ b/src/ts/util/hasClosest.ts @@ -159,3 +159,10 @@ export const getTopList = (element: Node) => { } return topListElement; }; + +export const getLastNode = (node: Node) => { + while (node && node.lastChild) { + node = node.lastChild; + } + return node; +}; diff --git a/src/ts/util/processMD.ts b/src/ts/util/processMD.ts new file mode 100644 index 000000000..4fed51074 --- /dev/null +++ b/src/ts/util/processMD.ts @@ -0,0 +1,134 @@ +import {isCtrl} from "./compatibility"; +import {scrollCenter} from "./editorCommenEvent"; +import {getLastNode} from "./hasClosest"; +import {getSelectPosition, setRangeByWbr} from "./selection"; + +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 isToC = (text: string) => { + return text.trim().toLowerCase() === "[toc]"; +}; + +export const renderToc = (editorElement: HTMLPreElement) => { + const tocElement = editorElement.querySelector('[data-type="toc-block"]'); + if (!tocElement) { + return; + } + let tocHTML = ""; + Array.from(editorElement.children).forEach((item: HTMLElement) => { + if (item.tagName.indexOf("H") === 0 && item.tagName.length === 2 && item.textContent.trim() !== "") { + const space = new Array((parseInt(item.tagName.substring(1), 10) - 1) * 2).fill(" ").join(""); + tocHTML += `${space}${item.textContent.trim()}
`; + } + }); + tocElement.innerHTML = tocHTML || "[ToC]"; +}; + +export const mdKeydown = (event: KeyboardEvent, vditor: IVditor, pElement: HTMLElement, range: Range, + afterRenderEvent: (vditor: IVditor) => void) => { + 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 + tableHeaderMD.substring(3, tableHeaderMD.length - 3) + "\n|"; + pElement.outerHTML = vditor.lute.SpinVditorDOM(tableHeaderMD); + setRangeByWbr(vditor[vditor.currentMode].element, range); + afterRenderEvent(vditor); + scrollCenter(vditor[vditor.currentMode].element); + event.preventDefault(); + return true; + } + + // hr 渲染 + if (isHrMD(pElement.innerHTML)) { + // 软换行后 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); + afterRenderEvent(vditor); + scrollCenter(vditor[vditor.currentMode].element); + event.preventDefault(); + return true; + } + + if (isHeadingMD(pElement.innerHTML)) { + // heading 渲染 + pElement.outerHTML = vditor.lute.SpinVditorDOM(pElement.innerHTML + '

\n

'); + setRangeByWbr(vditor[vditor.currentMode].element, range); + afterRenderEvent(vditor); + scrollCenter(vditor[vditor.currentMode].element); + event.preventDefault(); + return true; + } + } + + // 软换行会被切割 https://github.com/Vanessa219/vditor/issues/220 + if (pElement.previousElementSibling && event.key === "Backspace" && !isCtrl(event) && !event.altKey && + !event.shiftKey && pElement.textContent.trimRight().split("\n").length > 1 && + getSelectPosition(pElement, 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; +}; diff --git a/src/ts/wysiwyg/index.ts b/src/ts/wysiwyg/index.ts index 869892e2d..1b52e17c9 100644 --- a/src/ts/wysiwyg/index.ts +++ b/src/ts/wysiwyg/index.ts @@ -6,6 +6,7 @@ import { hasClosestBlock, hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag, } from "../util/hasClosest"; +import {isHeadingMD, isHrMD, renderToc} from "../util/processMD"; import {processPasteCode} from "../util/processPasteCode"; import {getSelectPosition, insertHTML, setRangeByWbr, setSelectionByPosition, setSelectionFocus} from "../util/selection"; import {afterRenderEvent} from "./afterRenderEvent"; @@ -13,7 +14,6 @@ import {highlightToolbar} from "./highlightToolbar"; import {getRenderElementNextNode, modifyPre} from "./inlineTag"; import {input} from "./input"; import {processCodeRender, showCode} from "./processCodeRender"; -import {isHeadingMD, isHrMD, renderToc} from "./processMD"; class WYSIWYG { public element: HTMLPreElement; diff --git a/src/ts/wysiwyg/inlineTag.ts b/src/ts/wysiwyg/inlineTag.ts index 6db6e0b1c..464bebd1b 100644 --- a/src/ts/wysiwyg/inlineTag.ts +++ b/src/ts/wysiwyg/inlineTag.ts @@ -79,13 +79,6 @@ export const getRenderElementNextNode = (blockCodeElement: HTMLElement) => { return nextNode.nextSibling; }; -export const getLastNode = (node: Node) => { - while (node && node.lastChild) { - node = node.lastChild; - } - return node; -}; - export const splitElement = (range: Range) => { const previousHTML = getPreviousHTML(range.startContainer); const nextHTML = getNextHTML(range.startContainer); diff --git a/src/ts/wysiwyg/input.ts b/src/ts/wysiwyg/input.ts index bd8e0c447..92699f9e8 100644 --- a/src/ts/wysiwyg/input.ts +++ b/src/ts/wysiwyg/input.ts @@ -5,11 +5,11 @@ import { hasClosestByTag, } from "../util/hasClosest"; import {log} from "../util/log"; +import {isToC, renderToc} from "../util/processMD"; import {setRangeByWbr} from "../util/selection"; import {afterRenderEvent} from "./afterRenderEvent"; import {previoueIsEmptyA} from "./inlineTag"; import {processCodeRender} from "./processCodeRender"; -import {isToC, renderToc} from "./processMD"; export const input = (vditor: IVditor, range: Range, event?: InputEvent) => { let blockElement = hasClosestBlock(range.startContainer); diff --git a/src/ts/wysiwyg/processKeydown.ts b/src/ts/wysiwyg/processKeydown.ts index 133d0fa57..4c1f2207b 100644 --- a/src/ts/wysiwyg/processKeydown.ts +++ b/src/ts/wysiwyg/processKeydown.ts @@ -2,19 +2,20 @@ import {Constants} from "../constants"; import {isCtrl} from "../util/compatibility"; import {scrollCenter} from "../util/editorCommenEvent"; import { + getLastNode, getTopList, hasClosestBlock, hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag, hasClosestByTag, hasTopClosestByTag, } from "../util/hasClosest"; import {matchHotKey} from "../util/hotKey"; +import {mdKeydown} from "../util/processMD"; import {tableHotkey} from "../util/processTable"; import {getSelectPosition, setRangeByWbr, setSelectionFocus} from "../util/selection"; import {afterRenderEvent} from "./afterRenderEvent"; import {listOutdent} from "./highlightToolbar"; -import {getLastNode, nextIsCode} from "./inlineTag"; +import {nextIsCode} from "./inlineTag"; import {processCodeRender, showCode} from "./processCodeRender"; -import {isHeadingMD, isHrMD} from "./processMD"; import {removeHeading, setHeading} from "./setHeading"; export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { @@ -43,64 +44,8 @@ export const processKeydown = (vditor: IVditor, event: KeyboardEvent) => { // md 处理 const pElement = hasClosestByMatchTag(startContainer, "P"); if (pElement) { - 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 + 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; - } - - // hr 渲染 - if (isHrMD(pElement.innerHTML)) { - // 软换行后 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.wysiwyg.element, range); - afterRenderEvent(vditor); - scrollCenter(vditor.wysiwyg.element); - event.preventDefault(); - return true; - } - - if (isHeadingMD(pElement.innerHTML)) { - // heading 渲染 - pElement.outerHTML = vditor.lute.SpinVditorDOM(pElement.innerHTML + '

\n

'); - setRangeByWbr(vditor.wysiwyg.element, range); - afterRenderEvent(vditor); - scrollCenter(vditor.wysiwyg.element); - event.preventDefault(); - return true; - } - } - - // 软换行会被切割 https://github.com/Vanessa219/vditor/issues/220 - if (pElement.previousElementSibling && event.key === "Backspace" && !isCtrl(event) && !event.altKey && - !event.shiftKey && pElement.textContent.trimRight().split("\n").length > 1 && - getSelectPosition(pElement, 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.wysiwyg.element, range); + if (mdKeydown(event, vditor, pElement, range, afterRenderEvent)) { + return true; } } diff --git a/src/ts/wysiwyg/processMD.ts b/src/ts/wysiwyg/processMD.ts deleted file mode 100644 index 0a394473d..000000000 --- a/src/ts/wysiwyg/processMD.ts +++ /dev/null @@ -1,64 +0,0 @@ -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 isToC = (text: string) => { - return text.trim().toLowerCase() === "[toc]"; -}; - -export const renderToc = (editorElement: HTMLPreElement) => { - const tocElement = editorElement.querySelector('[data-type="toc-block"]'); - if (!tocElement) { - return; - } - let tocHTML = ""; - Array.from(editorElement.children).forEach((item: HTMLElement) => { - if (item.tagName.indexOf("H") === 0 && item.tagName.length === 2 && item.textContent.trim() !== "") { - const space = new Array((parseInt(item.tagName.substring(1), 10) - 1) * 2).fill(" ").join(""); - tocHTML += `${space}${item.textContent.trim()}
`; - } - }); - tocElement.innerHTML = tocHTML || "[ToC]"; -}; diff --git a/src/ts/wysiwyg/setHeading.ts b/src/ts/wysiwyg/setHeading.ts index 904733837..c606bcded 100644 --- a/src/ts/wysiwyg/setHeading.ts +++ b/src/ts/wysiwyg/setHeading.ts @@ -1,6 +1,6 @@ import {hasClosestBlock} from "../util/hasClosest"; +import {renderToc} from "../util/processMD"; import {getEditorRange, setRangeByWbr} from "../util/selection"; -import {renderToc} from "./processMD"; export const setHeading = (vditor: IVditor, tagName: string) => { const range = getEditorRange(vditor.wysiwyg.element);