From 0285730c595074c65b050809225fa8ef0f446875 Mon Sep 17 00:00:00 2001 From: Vanessa Date: Thu, 15 Sep 2022 22:48:03 +0800 Subject: [PATCH] :sparkles: https://github.com/siyuan-note/siyuan/issues/2911 inline math --- app/src/assets/scss/_wysiwyg.scss | 2 +- app/src/protyle/toolbar/InlineMath.ts | 75 +++++++++++++++++++++++++ app/src/protyle/toolbar/ToolbarItem.ts | 2 +- app/src/protyle/toolbar/index.ts | 77 +++++++++++++------------- app/src/protyle/wysiwyg/enter.ts | 4 +- app/src/protyle/wysiwyg/keydown.ts | 6 +- app/src/protyle/wysiwyg/remove.ts | 2 +- 7 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 app/src/protyle/toolbar/InlineMath.ts diff --git a/app/src/assets/scss/_wysiwyg.scss b/app/src/assets/scss/_wysiwyg.scss index 672fa7ca4..b50dabd14 100644 --- a/app/src/assets/scss/_wysiwyg.scss +++ b/app/src/assets/scss/_wysiwyg.scss @@ -213,7 +213,7 @@ } } - span[data-type="inline-math"] { + span[data-type~="inline-math"] { user-select: text; display: inline; // https://github.com/siyuan-note/siyuan/issues/3081 test $\begin{bmatrix} a & b \\c & d \\ e & f\end{bmatrix}$ test diff --git a/app/src/protyle/toolbar/InlineMath.ts b/app/src/protyle/toolbar/InlineMath.ts new file mode 100644 index 000000000..d06495fc2 --- /dev/null +++ b/app/src/protyle/toolbar/InlineMath.ts @@ -0,0 +1,75 @@ +import {ToolbarItem} from "./ToolbarItem"; +import * as dayjs from "dayjs"; +import {updateTransaction} from "../wysiwyg/transaction"; +import {hasClosestBlock, hasClosestByAttribute} from "../util/hasClosest"; +import {hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock"; +import {focusByRange, focusByWbr} from "../util/selection"; +import {mathRender} from "../markdown/mathRender"; + +export class InlineMath extends ToolbarItem { + public element: HTMLElement; + + constructor(protyle: IProtyle, menuItem: IMenuItem) { + super(protyle, menuItem); + this.element.addEventListener("click", async (event: MouseEvent & { changedTouches: MouseEvent[] }) => { + protyle.toolbar.element.classList.add("fn__none"); + event.stopPropagation(); + + const range = protyle.toolbar.range; + const nodeElement = hasClosestBlock(range.startContainer); + if (!nodeElement) { + return; + } + + const inlineMathElement = hasClosestByAttribute(range.startContainer, "data-type", "inline-math"); + if (inlineMathElement) { + mathRender(inlineMathElement); + return; + } + + if (!["DIV", "TD", "TH"].includes(range.startContainer.parentElement.tagName) && range.startOffset === 0 && !hasPreviousSibling(range.startContainer)) { + range.setStartBefore(range.startContainer.parentElement); + } + if (!["DIV", "TD", "TH"].includes(range.endContainer.parentElement.tagName) && range.endOffset === range.endContainer.textContent.length && !hasNextSibling(range.endContainer)) { + range.setEndAfter(range.endContainer.parentElement); + } + const wbrElement = document.createElement("wbr"); + range.insertNode(wbrElement); + const html = nodeElement.outerHTML; + + const newElement = document.createElement("span"); + newElement.className = "render-node"; + newElement.setAttribute("contenteditable", "false"); + newElement.setAttribute("data-type", "inline-math"); + newElement.setAttribute("data-subtype", "math"); + newElement.setAttribute("data-content", range.toString()); + range.extractContents(); + range.insertNode(newElement); + mathRender(newElement); + nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); + updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, html); + wbrElement.remove(); + }); + } +} + +export const removeLink = (linkElement: HTMLElement, range: Range) => { + const types = linkElement.getAttribute("data-type").split(" "); + if (types.length === 1) { + const linkParentElement = linkElement.parentElement; + linkElement.outerHTML = linkElement.innerHTML + ""; + focusByWbr(linkParentElement, range); + } else { + types.find((itemType, index) => { + if ("a" === itemType) { + types.splice(index, 1); + return true; + } + }); + linkElement.setAttribute("data-type", types.join(" ")); + linkElement.removeAttribute("data-href"); + range.selectNodeContents(linkElement); + range.collapse(false); + focusByRange(range); + } +}; diff --git a/app/src/protyle/toolbar/ToolbarItem.ts b/app/src/protyle/toolbar/ToolbarItem.ts index 525e5a250..9cd4963ee 100644 --- a/app/src/protyle/toolbar/ToolbarItem.ts +++ b/app/src/protyle/toolbar/ToolbarItem.ts @@ -12,7 +12,7 @@ export class ToolbarItem { this.element.setAttribute("data-type", menuItem.name); this.element.setAttribute("aria-label", tip + hotkey); this.element.innerHTML = ``; - if (["text", "a", "block-ref"].includes(menuItem.name)) { + if (["text", "a", "block-ref", "inline-math"].includes(menuItem.name)) { return; } this.element.addEventListener(getEventName(), (event) => { diff --git a/app/src/protyle/toolbar/index.ts b/app/src/protyle/toolbar/index.ts index 43cea967c..dd75c6971 100644 --- a/app/src/protyle/toolbar/index.ts +++ b/app/src/protyle/toolbar/index.ts @@ -41,6 +41,7 @@ import {renderAssetsPreview} from "../../asset/renderAssets"; import {electronUndo} from "../undo"; import {previewTemplate} from "./util"; import {showMessage} from "../../dialog/message"; +import {InlineMath} from "./InlineMath"; export class Toolbar { public element: HTMLElement; @@ -125,7 +126,7 @@ export class Toolbar { }); const types = this.getCurrentType(); types.forEach(item => { - if (["a", "block-ref", "text", "file-annotation-ref"].includes(item)) { + if (["a", "block-ref", "text", "file-annotation-ref", "inline-math"].includes(item)) { return; } this.element.querySelector(`[data-type="${item}"]`).classList.add("protyle-toolbar__item--current"); @@ -163,15 +164,15 @@ export class Toolbar { types = types.concat((endElement.getAttribute("data-type") || "").split(" ")); } // if (range.startOffset === range.startContainer.textContent.length) { - // const nextSibling = hasNextSibling(range.startContainer) as Element; - // if (nextSibling && nextSibling.nodeType !== 3) { - // types = types.concat((nextSibling.getAttribute("data-type") || "").split(" ")); - // } + // const nextSibling = hasNextSibling(range.startContainer) as Element; + // if (nextSibling && nextSibling.nodeType !== 3) { + // types = types.concat((nextSibling.getAttribute("data-type") || "").split(" ")); + // } // } else if (range.endOffset === 0) { // const previousSibling = hasPreviousSibling(range.startContainer) as Element; - // if (previousSibling && previousSibling.nodeType !== 3) { - // types = types.concat((previousSibling.getAttribute("data-type") || "").split(" ")); - // } + // if (previousSibling && previousSibling.nodeType !== 3) { + // types = types.concat((previousSibling.getAttribute("data-type") || "").split(" ")); + // } // } range.cloneContents().childNodes.forEach((item: HTMLElement) => { if (item.nodeType !== 3) { @@ -195,12 +196,14 @@ export class Toolbar { case "sup": case "sub": case "kbd": - case "inline-math": menuItemObj = new ToolbarItem(protyle, menuItem); break; case "block-ref": menuItemObj = new BlockRef(protyle, menuItem); break; + case "inline-math": + menuItemObj = new InlineMath(protyle, menuItem); + break; case "|": menuItemObj = new Divider(); break; @@ -488,12 +491,12 @@ export class Toolbar { // return; // } let startElement = this.range.startContainer as Element; - if (this.range.startContainer.nodeType === 3) { - startElement = this.range.startContainer.parentElement; - if (startElement.getAttribute("data-type") === "virtual-block-ref" && !["DIV", "TD", "TH"].includes(startElement.parentElement.tagName)) { - startElement = startElement.parentElement; - } - } + // if (this.range.startContainer.nodeType === 3) { + // startElement = this.range.startContainer.parentElement; + // if (startElement.getAttribute("data-type") === "virtual-block-ref" && !["DIV", "TD", "TH"].includes(startElement.parentElement.tagName)) { + // startElement = startElement.parentElement; + // } + // } // table 选中处理 const tableElement = hasClosestByAttribute(startElement, "data-type", "NodeTable"); @@ -523,19 +526,19 @@ export class Toolbar { } } - if (this.range.toString() === "" && action === "range" && getSelectionOffset(startElement, protyle.wysiwyg.element).end === startElement.textContent.length && - this.range.startContainer.nodeType === 3 && !this.range.startContainer.parentElement.getAttribute("contenteditable") && - types.length > 0) { - // 跳出行内元素 - const textNode = document.createTextNode(Constants.ZWSP); - this.range.startContainer.parentElement.after(textNode); - this.range.selectNodeContents(textNode); - this.range.collapse(false); - if (types.includes(type)) { - // 如果不是同一种行内元素,需进行后续的渲染操作 - return; - } - } + // if (this.range.toString() === "" && action === "range" && getSelectionOffset(startElement, protyle.wysiwyg.element).end === startElement.textContent.length && + // this.range.startContainer.nodeType === 3 && !this.range.startContainer.parentElement.getAttribute("contenteditable") && + // types.length > 0) { + // // 跳出行内元素 + // const textNode = document.createTextNode(Constants.ZWSP); + // this.range.startContainer.parentElement.after(textNode); + // this.range.selectNodeContents(textNode); + // this.range.collapse(false); + // if (types.includes(type)) { + // // 如果不是同一种行内元素,需进行后续的渲染操作 + // return; + // } + // } // if (types.length > 0 && types.includes("link") && action === "range") { // // 链接快捷键不应取消,应该显示链接信息 // linkMenu(protyle, this.range.startContainer.parentElement); @@ -653,15 +656,15 @@ export class Toolbar { // this.range.selectNodeContents(refNode); // hintRef(refText, protyle, true); // break; - case "inline-math": - newElement = document.createElement("span"); - newElement.className = "render-node"; - newElement.setAttribute("contenteditable", "false"); - newElement.setAttribute("data-type", "inline-math"); - newElement.setAttribute("data-subtype", "math"); - newElement.setAttribute("data-content", startText + selectContents.textContent + endText); - mathRender(newElement); - break; + // case "inline-math": + // newElement = document.createElement("span"); + // newElement.className = "render-node"; + // newElement.setAttribute("contenteditable", "false"); + // newElement.setAttribute("data-type", "inline-math"); + // newElement.setAttribute("data-subtype", "math"); + // newElement.setAttribute("data-content", startText + selectContents.textContent + endText); + // mathRender(newElement); + // break; } // if (newElement) { // this.range.insertNode(newElement); diff --git a/app/src/protyle/wysiwyg/enter.ts b/app/src/protyle/wysiwyg/enter.ts index d553f529f..9eadc4c08 100644 --- a/app/src/protyle/wysiwyg/enter.ts +++ b/app/src/protyle/wysiwyg/enter.ts @@ -152,7 +152,7 @@ const listEnter = (protyle: IProtyle, blockElement: HTMLElement, range: Range) = selectNode.firstChild.remove(); } // https://github.com/siyuan-note/siyuan/issues/3850 - if (editableElement?.lastElementChild?.getAttribute("data-type") === "inline-math" && + if (editableElement?.lastElementChild?.getAttribute("data-type").indexOf("inline-math") > -1 && !hasNextSibling(editableElement?.lastElementChild)) { editableElement.insertAdjacentText("beforeend", "\n"); } @@ -390,7 +390,7 @@ export const enter = (blockElement: HTMLElement, range: Range, protyle: IProtyle selectNode.firstChild.remove(); } // https://github.com/siyuan-note/siyuan/issues/3850 - if (editableElement?.lastElementChild?.getAttribute("data-type") === "inline-math" && + if (editableElement?.lastElementChild?.getAttribute("data-type").indexOf("inline-math") > -1 && !hasNextSibling(editableElement?.lastElementChild)) { editableElement.insertAdjacentText("beforeend", "\n"); } diff --git a/app/src/protyle/wysiwyg/keydown.ts b/app/src/protyle/wysiwyg/keydown.ts index 43c4bbe30..1cbcbc861 100644 --- a/app/src/protyle/wysiwyg/keydown.ts +++ b/app/src/protyle/wysiwyg/keydown.ts @@ -535,11 +535,11 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => { // https://github.com/siyuan-note/siyuan/issues/5185 if (range.startOffset === 0 && range.startContainer.nodeType === 3) { const previousSibling = hasPreviousSibling(range.startContainer) as HTMLElement; - if (previousSibling && previousSibling.nodeType !== 3 && previousSibling.getAttribute("data-type") === "inline-math") { + if (previousSibling && previousSibling.nodeType !== 3 && previousSibling.getAttribute("data-type").indexOf("inline-math") > -1) { protyle.toolbar.showRender(protyle, previousSibling); return; } else if (!previousSibling && range.startContainer.parentElement.previousSibling.isSameNode(range.startContainer.parentElement.previousElementSibling) && - range.startContainer.parentElement.previousElementSibling.getAttribute("data-type") === "inline-math") { + range.startContainer.parentElement.previousElementSibling.getAttribute("data-type").indexOf("inline-math") > -1) { protyle.toolbar.showRender(protyle, range.startContainer.parentElement.previousElementSibling); return; } @@ -1185,7 +1185,7 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => { } if (matchHotKey(menuItem.hotkey, event)) { protyle.toolbar.range = getEditorRange(protyle.wysiwyg.element); - if (["text", "a", "block-ref"].includes(menuItem.name)) { + if (["text", "a", "block-ref", "inline-math"].includes(menuItem.name)) { protyle.toolbar.element.querySelector(`[data-type="${menuItem.name}"]`).dispatchEvent(new CustomEvent("block-ref" === menuItem.name ? getEventName() : "click")); } else { protyle.toolbar.setInlineMark(protyle, menuItem.name, "range"); diff --git a/app/src/protyle/wysiwyg/remove.ts b/app/src/protyle/wysiwyg/remove.ts index 21291bef5..58fd50660 100644 --- a/app/src/protyle/wysiwyg/remove.ts +++ b/app/src/protyle/wysiwyg/remove.ts @@ -442,7 +442,7 @@ export const removeBlock = (protyle: IProtyle, blockElement: Element, range: Ran // 非空块 range.setEndAfter(editableElement.lastChild); // 数学公式会车后再删除 https://github.com/siyuan-note/siyuan/issues/3850 - if (previousLastEditElement?.lastElementChild?.getAttribute("data-type") === "inline-math") { + if (previousLastEditElement?.lastElementChild?.getAttribute("data-type").indexOf("inline-math") > -1) { const lastSibling = hasNextSibling(previousLastEditElement?.lastElementChild); if (lastSibling && lastSibling.textContent === "\n") { lastSibling.remove();