From c92851250119afea26817746b0e04688bca35cb8 Mon Sep 17 00:00:00 2001 From: Vanessa Date: Fri, 16 Sep 2022 09:41:37 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20https://github.com/siyuan-note/siy?= =?UTF-8?q?uan/issues/2911=20table=20=E4=B8=AD=E7=9A=84=E5=85=83=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/assets/scss/_typography.scss | 27 +++ app/src/assets/scss/_wysiwyg.scss | 38 +-- app/src/protyle/toolbar/index.ts | 342 ++++----------------------- 3 files changed, 79 insertions(+), 328 deletions(-) diff --git a/app/src/assets/scss/_typography.scss b/app/src/assets/scss/_typography.scss index 4fb669725..cceaa591c 100644 --- a/app/src/assets/scss/_typography.scss +++ b/app/src/assets/scss/_typography.scss @@ -44,21 +44,48 @@ vertical-align: bottom; } + span[data-type~="sup"], + span[data-type~="sub"] { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; + } + + span[data-type~="sup"] { + top: -0.5em; + } + + span[data-type~="sub"] { + bottom: -0.25em; + } + em, span[data-type~="em"] { + font-style: italic; color: var(--b3-protyle-inline-em-color); } s, span[data-type~="s"] { color: var(--b3-protyle-inline-s-color); + text-decoration: line-through; } strong, span[data-type~="strong"] { + font-weight: bold; color: var(--b3-protyle-inline-strong-color); } + 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 + // https://ld246.com/article/1626937851892 test $\mu_p$ test + // https://ld246.com/article/1636204769623 test $\rightarrow$ test + } + mark, span[data-type~="mark"] { background-color: var(--b3-protyle-inline-mark-background); diff --git a/app/src/assets/scss/_wysiwyg.scss b/app/src/assets/scss/_wysiwyg.scss index b50dabd14..01856cb7d 100644 --- a/app/src/assets/scss/_wysiwyg.scss +++ b/app/src/assets/scss/_wysiwyg.scss @@ -169,35 +169,7 @@ } } - span[data-type~="strong"] { - font-weight: bold; - } - - span[data-type~="em"] { - font-style: italic; - } - - span[data-type~="sup"], - span[data-type~="sub"] { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; - } - - span[data-type~="sup"] { - top: -0.5em; - } - - span[data-type~="sub"] { - bottom: -0.25em; - } - - span[data-type~="s"] { - text-decoration: line-through; - } - - span[data-type="tag"] { + span[data-type~="tag"] { border-bottom: 1px solid var(--b3-protyle-inline-tag-color); color: var(--b3-protyle-inline-tag-color); transition: var(--b3-transition); @@ -213,14 +185,6 @@ } } - 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 - // https://ld246.com/article/1626937851892 test $\mu_p$ test - // https://ld246.com/article/1636204769623 test $\rightarrow$ test - } - span[data-type="search-mark"] { background-color: var(--b3-protyle-inline-mark-background); color: var(--b3-protyle-inline-mark-color); diff --git a/app/src/protyle/toolbar/index.ts b/app/src/protyle/toolbar/index.ts index dd75c6971..de3315520 100644 --- a/app/src/protyle/toolbar/index.ts +++ b/app/src/protyle/toolbar/index.ts @@ -144,7 +144,7 @@ export class Toolbar { if (!startElement || startElement.nodeType === 3) { return []; } - if (!["DIV", "TD", "TH"].includes(startElement.tagName)) { + if (!["DIV", "TD", "TH", "TR"].includes(startElement.tagName)) { if (range.startContainer.textContent.length === range.startOffset && !hasNextSibling(range.startContainer)) { // 光标在 span 结尾不算 type,否则如在粗体后 ctrl+b 就无法继续使用粗体了 } else { @@ -160,7 +160,7 @@ export class Toolbar { if (!endElement || endElement.nodeType === 3) { return []; } - if (!["DIV", "TD", "TH"].includes(endElement.tagName) && !startElement.isSameNode(endElement)) { + if (!["DIV", "TD", "TH", "TR"].includes(endElement.tagName) && !startElement.isSameNode(endElement)) { types = types.concat((endElement.getAttribute("data-type") || "").split(" ")); } // if (range.startOffset === range.startContainer.textContent.length) { @@ -176,10 +176,16 @@ export class Toolbar { // } range.cloneContents().childNodes.forEach((item: HTMLElement) => { if (item.nodeType !== 3) { - types = types.concat(item.getAttribute("data-type").split(" ")); + types = types.concat((item.getAttribute("data-type") || "").split(" ")); } }); types = [...new Set(types)]; + types.find((item, index) => { + if (item === "") { + types.splice(index, 1); + return true; + } + }); return types; } @@ -220,23 +226,6 @@ export class Toolbar { return menuItemObj.element; } - private pushNode(newNodes: Node[], element: Element | DocumentFragment) { - element.childNodes.forEach((item: Element) => { - if (item.nodeType !== 3 && ( - (item.getAttribute("data-type") === "inline-math" && item.textContent !== "") || - item.tagName === "BR" || item.getAttribute("data-type") === "backslash" - )) { - // 软换行、数学公式、转移符不能消失 - newNodes.push(item.cloneNode(true)); - } else { - if (item.textContent === "") { - return; - } - newNodes.push(document.createTextNode(item.textContent)); - } - }); - } - // 合并多个 text 为一个 text private mergeNode(nodes: NodeListOf) { for (let i = 0; i < nodes.length; i++) { @@ -266,12 +255,40 @@ export class Toolbar { } const rangeTypes = this.getCurrentType(); const selectText = this.range.toString(); + + // table 选中处理 + const tableElement = hasClosestByAttribute(this.range.startContainer, "data-type", "NodeTable"); + if (selectText !== "" && tableElement && this.range.commonAncestorContainer.nodeType !== 3) { + const parentTag = (this.range.commonAncestorContainer as Element).tagName; + if (parentTag !== "TH" && parentTag !== "TD") { + const startCellElement = hasClosestByMatchTag(this.range.startContainer, "TD") || hasClosestByMatchTag(this.range.startContainer, "TH"); + const endCellElement = hasClosestByMatchTag(this.range.endContainer, "TD") || hasClosestByMatchTag(this.range.endContainer, "TH"); + if (!startCellElement && !endCellElement) { + const cellElement = tableElement.querySelector("th") || tableElement.querySelector("td"); + this.range.setStartBefore(cellElement.firstChild); + this.range.setEndAfter(cellElement.lastChild); + } else if (startCellElement && + // 不能包含自身元素,否则对 cell 中的部分文字两次高亮后就会选中整个 cell。 https://github.com/siyuan-note/siyuan/issues/3649 第二点 + !startCellElement.contains(this.range.endContainer)) { + const cloneRange = this.range.cloneRange(); + this.range.setEndAfter(startCellElement.lastChild); + if (this.range.toString() === "" && endCellElement) { + this.range.setEnd(cloneRange.endContainer, cloneRange.endOffset); + this.range.setStartBefore(endCellElement.lastChild); + } + if (this.range.toString() === "") { + return; + } + } + } + } + let previousElement: HTMLElement; let nextElement: HTMLElement; let previousIndex: number; let nextIndex: number; const previousSibling = hasPreviousSibling(this.range.startContainer); - if (!["DIV", "TD", "TH"].includes(this.range.startContainer.parentElement.tagName)) { + if (!["DIV", "TD", "TH", "TR"].includes(this.range.startContainer.parentElement.tagName)) { if (this.range.startOffset === 0 && !previousSibling) { previousElement = this.range.startContainer.parentElement.previousSibling as HTMLElement; this.range.setStartBefore(this.range.startContainer.parentElement); @@ -283,7 +300,7 @@ export class Toolbar { previousElement = previousSibling as HTMLElement; } const nextSibling = hasNextSibling(this.range.endContainer); - if (!["DIV", "TD", "TH"].includes(this.range.endContainer.parentElement.tagName)) { + if (!["DIV", "TD", "TH", "TR"].includes(this.range.endContainer.parentElement.tagName)) { if (this.range.endOffset === this.range.endContainer.textContent.length && !nextSibling) { nextElement = this.range.endContainer.parentElement.nextSibling as HTMLElement; this.range.setEndAfter(this.range.endContainer.parentElement); @@ -300,7 +317,7 @@ export class Toolbar { const contents = this.range.extractContents(); this.mergeNode(contents.childNodes); const actionBtn = action === "toolbar" ? this.element.querySelector(`[data-type="${type}"]`) : undefined; - let newNodes: Node[] = []; + const newNodes: Node[] = []; if (actionBtn?.classList.contains("protyle-toolbar__item--current") || ( action === "range" && rangeTypes.length > 0 && rangeTypes.includes(type) && (!textObj || textObj.type === "remove") )) { @@ -325,7 +342,7 @@ export class Toolbar { } } contents.childNodes.forEach((item: HTMLElement, index) => { - if (item.nodeType !== 3) { + if (item.nodeType !== 3 && item.tagName !== "BR") { const types = item.getAttribute("data-type").split(" "); types.find((itemType, index) => { if (type === itemType) { @@ -407,10 +424,12 @@ export class Toolbar { hasSameTextStyle(item, nextElement, textObj)) { nextIndex = item.textContent.length; nextElement.innerHTML = item.innerHTML + nextElement.innerHTML; - } else { + } else if (item.tagName !== 'BR') { item.setAttribute("data-type", types.join(" ")); setFontStyle(item, textObj); newNodes.push(item); + } else { + newNodes.push(item); } } }); @@ -446,8 +465,10 @@ export class Toolbar { } else if (newNodes.length > 0) { if (newNodes[0].firstChild) { this.range.setStart(newNodes[0].firstChild, 0); - } else { + } else if (newNodes[0].nodeType === 3) { this.range.setStart(newNodes[0], 0); + } else { + this.range.setStartBefore(newNodes[0]); } } else if (nextElement) { // aaa**bbb** 选中 aaa 加粗 @@ -459,8 +480,11 @@ export class Toolbar { const lastNewNode = newNodes[newNodes.length - 1]; if (lastNewNode.lastChild) { this.range.setEnd(lastNewNode.lastChild, lastNewNode.lastChild.textContent.length); - } else { + } else if (lastNewNode.nodeType === 3) { this.range.setEnd(lastNewNode, lastNewNode.textContent.length); + } else { + // eg: 表格中有3行时,选中第二行三级,多次加粗会增加换行 + this.range.setEndAfter(lastNewNode); } } else if (previousElement) { // **aaa**bbb 选中 bbb 加粗 @@ -472,270 +496,6 @@ export class Toolbar { wbrElement.remove(); } - public async setInlineMark1(protyle: IProtyle, type: string, action: "remove" | "add" | "range" | "toolbar", focusAdd = false) { - const nodeElement = hasClosestBlock(this.range.startContainer); - if (!nodeElement) { - return; - } - const types = this.getCurrentType(); - // if (action === "add" && types.length > 0 && types.includes(type) && !focusAdd) { - // if (type === "link") { - // this.element.classList.add("fn__none"); - // linkMenu(protyle, this.range.startContainer.parentElement); - // } - // return; - // } - // 对已有字体样式的文字再次添加字体样式 - // if (focusAdd && action === "add" && types.includes("text") && this.range.startContainer.nodeType === 3 && - // this.range.startContainer.parentNode.isSameNode(this.range.endContainer.parentNode)) { - // 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; - // } - // } - - // table 选中处理 - const tableElement = hasClosestByAttribute(startElement, "data-type", "NodeTable"); - if (this.range.toString() !== "" && tableElement && this.range.commonAncestorContainer.nodeType !== 3) { - const parentTag = (this.range.commonAncestorContainer as Element).tagName; - if (parentTag !== "TH" && parentTag !== "TD") { - const startCellElement = hasClosestByMatchTag(startElement, "TD") || hasClosestByMatchTag(startElement, "TH"); - const endCellElement = hasClosestByMatchTag(this.range.endContainer, "TD") || hasClosestByMatchTag(this.range.endContainer, "TH"); - if (!startCellElement && !endCellElement) { - const cellElement = tableElement.querySelector("th") || tableElement.querySelector("td"); - this.range.setStartBefore(cellElement.firstChild); - this.range.setEndAfter(cellElement.lastChild); - startElement = cellElement; - } else if (startCellElement && - // 不能包含自身元素,否则对 cell 中的部分文字两次高亮后就会选中整个 cell。 https://github.com/siyuan-note/siyuan/issues/3649 第二点 - !startCellElement.contains(this.range.endContainer)) { - const cloneRange = this.range.cloneRange(); - this.range.setEndAfter(startCellElement.lastChild); - if (this.range.toString() === "" && endCellElement) { - this.range.setEnd(cloneRange.endContainer, cloneRange.endOffset); - this.range.setStartBefore(endCellElement.lastChild); - } - if (this.range.toString() === "") { - 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); - // return; - // } - const wbrElement = document.createElement("wbr"); - this.range.insertNode(wbrElement); - this.range.setStartAfter(wbrElement); - const html = nodeElement.outerHTML; - const actionBtn = action === "toolbar" ? this.element.querySelector(`[data-type="${type}"]`) : undefined; - // 光标前标签移除 - const newNodes: Node[] = []; - let startText = ""; - if (!["DIV", "TD", "TH"].includes(startElement.tagName)) { - startText = startElement.textContent; - this.pushNode(newNodes, startElement); - startElement.remove(); - } - // 光标后标签移除 - let endText = ""; - let endClone; - if (!this.range.startContainer.isSameNode(this.range.endContainer)) { - let endElement = this.range.endContainer as HTMLElement; - if (this.range.endContainer.nodeType === 3) { - endElement = this.range.endContainer.parentElement; - } - if (endElement.getAttribute("data-type") === "virtual-block-ref" && !["DIV", "TD", "TH"].includes(endElement.parentElement.tagName)) { - endElement = endElement.parentElement; - } - if (!["DIV", "TD", "TH"].includes(endElement.tagName)) { - endClone = endElement; - } - } - const selectContents = this.range.extractContents(); - this.pushNode(newNodes, selectContents); - if (endClone) { - endText = endClone.textContent; - this.pushNode(newNodes, endClone); - endClone.remove(); - } - if ((action === "toolbar" && actionBtn.classList.contains("protyle-toolbar__item--current")) || - action === "remove" || (action === "range" && types.length > 0 && types.includes(type))) { - // 移除 - if (type === "inline-math" && newNodes.length === 1) { - const textNode = document.createTextNode(newNodes[0].textContent); - this.range.insertNode(textNode); - this.range.selectNodeContents(textNode); - } else { - // newNodes.forEach((item, index) => { - // this.range.insertNode(item); - // if (index !== newNodes.length - 1) { - // this.range.collapse(false); - // } else { - // this.range.setEnd(item, item.textContent.length); - // } - // }); - // if (newNodes.length > 0) { - // this.range.setStart(newNodes[0], 0); - // } - } - // focusByRange(this.range); - // this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => { - // item.classList.remove("protyle-toolbar__item--current"); - // }); - } else { - if (newNodes.length === 0) { - newNodes.push(document.createTextNode(Constants.ZWSP)); - } - // 添加 - let newElement: Element; - const refText = startText + selectContents.textContent + endText; - const refNode = document.createTextNode(refText); - switch (type) { - // case "bold": - // newElement = document.createElement("strong"); - // break; - // case "underline": - // newElement = document.createElement("u"); - // break; - // case "italic": - // newElement = document.createElement("em"); - // break; - // case "strike": - // newElement = document.createElement("s"); - // break; - // case "inline-code": - // newElement = document.createElement("code"); - // break; - // case "mark": - // newElement = document.createElement("mark"); - // break; - // case "sup": - // newElement = document.createElement("sup"); - // break; - // case "sub": - // newElement = document.createElement("sub"); - // break; - // case "kbd": - // newElement = document.createElement("kbd"); - // break; - // case "tag": - // newElement = document.createElement("span"); - // newElement.setAttribute("data-type", "tag"); - // break; - // case "link": - // newElement = document.createElement("span"); - // newElement.setAttribute("data-type", "a"); - // break; - // case "blockRef": - // if (refText === "") { - // wbrElement.remove(); - // return; - // } - // this.range.insertNode(refNode); - // 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; - } - // if (newElement) { - // this.range.insertNode(newElement); - // } - if (type === "inline-math") { - this.range.setStartAfter(newElement); - this.range.collapse(true); - if (startText + selectContents.textContent + endText === "") { - this.showRender(protyle, newElement as HTMLElement); - } else { - focusByRange(this.range); - } - this.element.classList.add("fn__none"); - } else if (type !== "blockRef") { - newNodes.forEach(item => { - newElement.append(item); - }); - if (newElement.textContent === Constants.ZWSP) { - this.isNewEmptyInline = true; - this.range.setStart(newElement.firstChild, 1); - this.range.collapse(true); - } else { - if (!hasPreviousSibling(newElement)) { - // 列表内斜体后的最后一个字符无法选中 https://ld246.com/article/1629787455575 - const nextSibling = hasNextSibling(newElement); - if (nextSibling && nextSibling.nodeType === 3) { - const textContent = nextSibling.textContent; - nextSibling.textContent = ""; - nextSibling.textContent = textContent; - } - } - this.range.setStart(newElement.firstChild, 0); - this.range.setEnd(newElement.lastChild, newElement.lastChild.textContent.length); - focusByRange(this.range); - } - // if (type === "link") { - // let needShowLink = true; - // let focusText = false; - // try { - // const clipText = await navigator.clipboard.readText(); - // // 选中链接时需忽略剪切板内容 https://ld246.com/article/1643035329737 - // if (protyle.lute.IsValidLinkDest(this.range.toString().trim())) { - // (newElement as HTMLElement).setAttribute("data-href", this.range.toString().trim()); - // needShowLink = false; - // } else if (protyle.lute.IsValidLinkDest(clipText)) { - // (newElement as HTMLElement).setAttribute("data-href", clipText); - // if (newElement.textContent.replace(Constants.ZWSP, "") !== "") { - // needShowLink = false; - // } - // focusText = true; - // } - // } catch (e) { - // console.log(e); - // } - // if (needShowLink) { - // linkMenu(protyle, newElement as HTMLElement, focusText); - // } - // } - } - // if (actionBtn) { - // this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => { - // item.classList.remove("protyle-toolbar__item--current"); - // }); - // actionBtn.classList.add("protyle-toolbar__item--current"); - // } - } - // nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); - // updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, html); - // wbrElement.remove(); - } - public showFileAnnotationRef(protyle: IProtyle, refElement: HTMLElement) { const nodeElement = hasClosestBlock(refElement); if (!nodeElement) {