From 901ea823cbba711af5cf2deb1e201dd4f41b09ca Mon Sep 17 00:00:00 2001 From: Vanessa Date: Fri, 4 Aug 2023 14:16:47 +0800 Subject: [PATCH] :art: fix https://github.com/siyuan-note/siyuan/issues/8829 --- app/appearance/icons/ant/icon.js | 3 + app/appearance/icons/index.html | 6 ++ app/appearance/icons/material/icon.js | 3 + app/src/assets/scss/component/_menu.scss | 5 +- app/src/assets/scss/protyle/_toolbar.scss | 3 +- app/src/menus/protyle.ts | 47 ++------- app/src/protyle/toolbar/index.ts | 110 +++++++++++++++++++++- app/src/protyle/util/paste.ts | 39 +++++++- 8 files changed, 174 insertions(+), 42 deletions(-) diff --git a/app/appearance/icons/ant/icon.js b/app/appearance/icons/ant/icon.js index 3fdc3b39e..c9f828612 100644 --- a/app/appearance/icons/ant/icon.js +++ b/app/appearance/icons/ant/icon.js @@ -1,5 +1,8 @@ document.body.insertAdjacentHTML('afterbegin', ` + + + diff --git a/app/appearance/icons/index.html b/app/appearance/icons/index.html index aa0e31214..654c43c82 100644 --- a/app/appearance/icons/index.html +++ b/app/appearance/icons/index.html @@ -28,6 +28,12 @@

SiYuan

+
+ + + + iconPaste +
diff --git a/app/appearance/icons/material/icon.js b/app/appearance/icons/material/icon.js index a0a35e8fb..ae531762f 100644 --- a/app/appearance/icons/material/icon.js +++ b/app/appearance/icons/material/icon.js @@ -1,5 +1,8 @@ document.body.insertAdjacentHTML('afterbegin', ` + + + diff --git a/app/src/assets/scss/component/_menu.scss b/app/src/assets/scss/component/_menu.scss index 312f03734..618ecdad6 100644 --- a/app/src/assets/scss/component/_menu.scss +++ b/app/src/assets/scss/component/_menu.scss @@ -93,11 +93,14 @@ &__title { border-bottom: .5px solid var(--b3-theme-background-light); - line-height: 48px; padding: 0 8px; display: flex; background-color: var(--b3-theme-background); height: 48.5px; + + & > .b3-menu__label { + line-height: 32.5px; + } } &__separator { diff --git a/app/src/assets/scss/protyle/_toolbar.scss b/app/src/assets/scss/protyle/_toolbar.scss index a1ebcb60d..10ee60669 100644 --- a/app/src/assets/scss/protyle/_toolbar.scss +++ b/app/src/assets/scss/protyle/_toolbar.scss @@ -14,7 +14,8 @@ background-color: transparent; height: 29px; box-sizing: border-box; - font-size: 0; + font-size: 10px; + white-space: nowrap; padding: 1px 6px; &:focus { diff --git a/app/src/menus/protyle.ts b/app/src/menus/protyle.ts index 253e436e9..d0037622d 100644 --- a/app/src/menus/protyle.ts +++ b/app/src/menus/protyle.ts @@ -23,7 +23,7 @@ import {copyPlainText, readText, setStorageVal, writeText} from "../protyle/util import {preventScroll} from "../protyle/scroll/preventScroll"; import {onGet} from "../protyle/util/onGet"; import {getAllModels} from "../layout/getAll"; -import {pasteAsPlainText, pasteText} from "../protyle/util/paste"; +import {pasteAsPlainText, pasteEscaped, pasteText} from "../protyle/util/paste"; /// #if !MOBILE import {openFileById, updateBacklinkGraph} from "../editor/util"; import {openGlobalSearch} from "../search/util"; @@ -411,6 +411,9 @@ export const refMenu = (protyle: IProtyle, element: HTMLElement) => { export const contentMenu = (protyle: IProtyle, nodeElement: Element) => { const range = getEditorRange(nodeElement); window.siyuan.menus.menu.remove(); + /// #if MOBILE + protyle.toolbar.showContent(protyle, range, nodeElement); + /// #else if (range.toString() !== "" || (range.cloneContents().childNodes[0] as HTMLElement)?.classList?.contains("emoji")) { window.siyuan.menus.menu.append(new MenuItem({ icon: "iconCopy", @@ -464,6 +467,7 @@ export const contentMenu = (protyle: IProtyle, nodeElement: Element) => { if (!protyle.disabled) { window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.paste, + icon: "iconPaste", accelerator: "⌘V", async click() { if (document.queryCommandSupported("paste")) { @@ -488,46 +492,14 @@ export const contentMenu = (protyle: IProtyle, nodeElement: Element) => { }).element); window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.pasteEscaped, - async click() { - try { - // * _ [ ] ! \ ` < > & ~ { } ( ) = # $ ^ | - let clipText = await readText(); - // https://github.com/siyuan-note/siyuan/issues/5446 - // A\B\C\D\ - // E - // task-blog-2~default~baiduj 无法原义粘贴含有 `~foo~` 的文本 https://github.com/siyuan-note/siyuan/issues/5523 - - // 这里必须多加一个反斜杆,因为 Lute 在进行 Markdown 嵌套节点转换平铺标记节点时会剔除 Backslash 节点, - // 多加入的一个反斜杆会作为文本节点保留下来,后续 Spin 时刚好用于转义标记符 https://github.com/siyuan-note/siyuan/issues/6341 - clipText = clipText.replace(/\\/g, "\\\\\\\\") - .replace(/\*/g, "\\\\\\*") - .replace(/\_/g, "\\\\\\_") - .replace(/\[/g, "\\\\\\[") - .replace(/\]/g, "\\\\\\]") - .replace(/\!/g, "\\\\\\!") - .replace(/\`/g, "\\\\\\`") - .replace(/\/g, "\\\\\\>") - .replace(/\&/g, "\\\\\\&") - .replace(/\~/g, "\\\\\\~") - .replace(/\{/g, "\\\\\\{") - .replace(/\}/g, "\\\\\\}") - .replace(/\(/g, "\\\\\\(") - .replace(/\)/g, "\\\\\\)") - .replace(/\=/g, "\\\\\\=") - .replace(/\#/g, "\\\\\\#") - .replace(/\$/g, "\\\\\\$") - .replace(/\^/g, "\\\\\\^") - .replace(/\|/g, "\\\\\\|"); - pasteText(protyle, clipText, nodeElement); - } catch (e) { - console.log(e); - } + click() { + pasteEscaped(protyle, nodeElement); } }).element); } window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.selectAll, + icon: "iconSelect", accelerator: "⌘A", click() { selectAll(protyle, nodeElement, range); @@ -544,6 +516,7 @@ export const contentMenu = (protyle: IProtyle, nodeElement: Element) => { }).element); } } + /// #endif if (protyle?.app?.plugins) { emitOpenMenu({ plugins: protyle.app.plugins, @@ -669,7 +642,7 @@ export const imgMenu = (protyle: IProtyle, range: Range, assetElement: HTMLEleme iconHTML: "", label: ``, bind(element) { - element.querySelector("textarea").addEventListener("input", (event:InputEvent) => { + element.querySelector("textarea").addEventListener("input", (event: InputEvent) => { if (event.isComposing) { return; } diff --git a/app/src/protyle/toolbar/index.ts b/app/src/protyle/toolbar/index.ts index d6b52e6d7..be4113aba 100644 --- a/app/src/protyle/toolbar/index.ts +++ b/app/src/protyle/toolbar/index.ts @@ -6,7 +6,7 @@ import { focusByRange, focusByWbr, getEditorRange, - getSelectionPosition, + getSelectionPosition, selectAll, setFirstNodeRange, setLastNodeRange } from "../util/selection"; @@ -15,7 +15,7 @@ import {Link} from "./Link"; import {setPosition} from "../../util/setPosition"; import {updateTransaction} from "../wysiwyg/transaction"; import {Constants} from "../../constants"; -import {openByMobile, setStorageVal} from "../util/compatibility"; +import {copyPlainText, openByMobile, readText, setStorageVal} from "../util/compatibility"; import {upDownHint} from "../../util/upDownHint"; import {highlightRender} from "../render/highlightRender"; import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock"; @@ -45,6 +45,7 @@ import {mathRender} from "../render/mathRender"; import {linkMenu} from "../../menus/protyle"; import {addScript} from "../util/addScript"; import {confirmDialog} from "../../dialog/confirmDialog"; +import {pasteAsPlainText, pasteEscaped, pasteText} from "../util/paste"; export class Toolbar { public element: HTMLElement; @@ -835,6 +836,7 @@ export class Toolbar { const autoHeight = () => { textElement.style.height = textElement.scrollHeight + "px"; if (isMobile()) { + setPosition(this.subElement, 0, 0); return; } if (this.subElement.firstElementChild.getAttribute("data-drag") === "true") { @@ -1258,6 +1260,8 @@ export class Toolbar { /// #if !MOBILE const nodeRect = languageElement.getBoundingClientRect(); setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height); + /// #else + setPosition(this.subElement, 0, 0); /// #endif this.element.classList.add("fn__none"); inputElement.select(); @@ -1434,6 +1438,8 @@ export class Toolbar { const rangePosition = getSelectionPosition(nodeElement, range); setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT); (this.subElement.firstElementChild as HTMLElement).style.maxHeight = Math.min(window.innerHeight * 0.8, window.innerHeight - this.subElement.getBoundingClientRect().top) - 16 + "px"; + /// #else + setPosition(this.subElement, 0, 0); /// #endif }); } @@ -1499,6 +1505,8 @@ export class Toolbar { /// #if !MOBILE const rangePosition = getSelectionPosition(nodeElement, range); setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT); + /// #else + setPosition(this.subElement, 0, 0); /// #endif }); } @@ -1605,6 +1613,8 @@ export class Toolbar { /// #if !MOBILE const rangePosition = getSelectionPosition(nodeElement, range); setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT); + /// #else + setPosition(this.subElement, 0, 0); /// #endif this.element.classList.add("fn__none"); inputElement.select(); @@ -1621,4 +1631,100 @@ export class Toolbar { this.subElement.querySelector(".b3-list--background").innerHTML = html; }); } + + public showContent(protyle: IProtyle, range: Range, nodeElement: Element) { + this.range = range; + hideElements(["hint"], protyle); + + this.subElement.style.width = "auto"; + this.subElement.style.padding = "0 8px"; + let html = "" + const hasCopy = range.toString() !== "" || (range.cloneContents().childNodes[0] as HTMLElement)?.classList?.contains("emoji"); + if (hasCopy) { + html += `` + if (!protyle.disabled) { + html += ` +` + } + } + if (!protyle.disabled) { + html += ` +` + } + if (hasCopy || !protyle.disabled) { + html += `` + } + this.subElement.innerHTML = `
${html}
`; + this.subElement.lastElementChild.addEventListener("click", async (event) => { + const btnElemen = hasClosestByClassName(event.target as HTMLElement, "protyle-toolbar__item"); + if (!btnElemen) { + return + } + const action = btnElemen.getAttribute("data-action"); + if (action === "copy") { + focusByRange(getEditorRange(nodeElement)); + document.execCommand("copy"); + this.subElement.classList.add("fn__none"); + } else if (action === "cut") { + focusByRange(getEditorRange(nodeElement)); + document.execCommand("cut"); + this.subElement.classList.add("fn__none"); + } else if (action === "delete") { + const currentRange = getEditorRange(nodeElement); + currentRange.insertNode(document.createElement("wbr")); + const oldHTML = nodeElement.outerHTML; + currentRange.extractContents(); + focusByWbr(nodeElement, currentRange); + focusByRange(currentRange); + updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, oldHTML); + this.subElement.classList.add("fn__none"); + } else if (action === "paste") { + if (document.queryCommandSupported("paste")) { + document.execCommand("paste"); + } else { + try { + const clipText = await readText(); + pasteText(protyle, clipText, nodeElement); + } catch (e) { + console.log(e); + } + } + this.subElement.classList.add("fn__none"); + } else if (action === "select") { + selectAll(protyle, nodeElement, range); + this.subElement.classList.add("fn__none"); + } else if (action === "copyPlainText") { + focusByRange(getEditorRange(nodeElement)); + const cloneContents = getSelection().getRangeAt(0).cloneContents(); + cloneContents.querySelectorAll('[data-type="backslash"]').forEach(item => { + item.firstElementChild.remove(); + }); + copyPlainText(cloneContents.textContent); + this.subElement.classList.add("fn__none"); + } else if (action === "pasteAsPlainText") { + focusByRange(getEditorRange(nodeElement)); + pasteAsPlainText(protyle); + this.subElement.classList.add("fn__none"); + } else if (action === "pasteEscaped") { + pasteEscaped(protyle, nodeElement); + this.subElement.classList.add("fn__none"); + } else if (action === "back") { + this.subElement.lastElementChild.innerHTML = html; + } else if (action === "more") { + this.subElement.lastElementChild.innerHTML = ` +
+ +
+ +
+` + setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT); + } + }); + this.subElement.classList.remove("fn__none"); + this.subElementCloseCB = undefined; + this.element.classList.add("fn__none"); + const rangePosition = getSelectionPosition(nodeElement, range); + setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT); + } } diff --git a/app/src/protyle/util/paste.ts b/app/src/protyle/util/paste.ts index 9d39a3612..2d04cefce 100644 --- a/app/src/protyle/util/paste.ts +++ b/app/src/protyle/util/paste.ts @@ -1,7 +1,7 @@ import {Constants} from "../../constants"; import {uploadFiles, uploadLocalFiles} from "../upload"; import {processPasteCode, processRender} from "./processCode"; -import {writeText} from "./compatibility"; +import {readText, writeText} from "./compatibility"; /// #if !BROWSER import {clipboard} from "electron"; /// #endif @@ -16,6 +16,43 @@ import {scrollCenter} from "../../util/highlightById"; import {hideElements} from "../ui/hideElements"; import {avRender} from "../render/av/render"; +export const pasteEscaped = async (protyle:IProtyle, nodeElement:Element) => { + try { + // * _ [ ] ! \ ` < > & ~ { } ( ) = # $ ^ | + let clipText = await readText(); + // https://github.com/siyuan-note/siyuan/issues/5446 + // A\B\C\D\ + // E + // task-blog-2~default~baiduj 无法原义粘贴含有 `~foo~` 的文本 https://github.com/siyuan-note/siyuan/issues/5523 + + // 这里必须多加一个反斜杆,因为 Lute 在进行 Markdown 嵌套节点转换平铺标记节点时会剔除 Backslash 节点, + // 多加入的一个反斜杆会作为文本节点保留下来,后续 Spin 时刚好用于转义标记符 https://github.com/siyuan-note/siyuan/issues/6341 + clipText = clipText.replace(/\\/g, "\\\\\\\\") + .replace(/\*/g, "\\\\\\*") + .replace(/\_/g, "\\\\\\_") + .replace(/\[/g, "\\\\\\[") + .replace(/\]/g, "\\\\\\]") + .replace(/\!/g, "\\\\\\!") + .replace(/\`/g, "\\\\\\`") + .replace(/\/g, "\\\\\\>") + .replace(/\&/g, "\\\\\\&") + .replace(/\~/g, "\\\\\\~") + .replace(/\{/g, "\\\\\\{") + .replace(/\}/g, "\\\\\\}") + .replace(/\(/g, "\\\\\\(") + .replace(/\)/g, "\\\\\\)") + .replace(/\=/g, "\\\\\\=") + .replace(/\#/g, "\\\\\\#") + .replace(/\$/g, "\\\\\\$") + .replace(/\^/g, "\\\\\\^") + .replace(/\|/g, "\\\\\\|"); + pasteText(protyle, clipText, nodeElement); + } catch (e) { + console.log(e); + } +} + const filterClipboardHint = (protyle: IProtyle, textPlain: string) => { let needRender = true; protyle.options.hint.extend.find(item => {