diff --git a/app/src/menus/protyle.ts b/app/src/menus/protyle.ts index 9e1edeaf6..d24beee12 100644 --- a/app/src/menus/protyle.ts +++ b/app/src/menus/protyle.ts @@ -32,6 +32,7 @@ import {removeFoldHeading} from "../protyle/util/heading"; import {lineNumberRender} from "../protyle/markdown/highlightRender"; import * as dayjs from "dayjs"; import {blockRender} from "../protyle/markdown/blockRender"; +import {isLocalPath} from "../util/pathName"; export const refMenu = (protyle: IProtyle, element: HTMLElement) => { const nodeElement = hasClosestBlock(element); @@ -535,6 +536,133 @@ export const imgMenu = (protyle: IProtyle, range: Range, assetElement: HTMLEleme window.siyuan.menus.menu.element.querySelector("input").focus(); }; +export const linkMenu = (protyle: IProtyle, linkElement: HTMLElement, focusText = false) => { + window.siyuan.menus.menu.remove(); + protyle.toolbar.isNewEmptyInline = false; + const nodeElement = hasClosestBlock(linkElement); + if (!nodeElement) { + return; + } + const id = nodeElement.getAttribute("data-node-id"); + let html = nodeElement.outerHTML; + const linkAddress = linkElement.getAttribute("data-href"); + window.siyuan.menus.menu.append(new MenuItem({ + label: `
`, + bind(element) { + const inputElement = element.querySelector("input"); + inputElement.value = linkAddress || ""; + inputElement.addEventListener("change", () => { + linkElement.setAttribute("data-href", inputElement.value); + updateTransaction(protyle, id, nodeElement.outerHTML, html); + html = nodeElement.outerHTML; + }); + inputElement.addEventListener("keydown", (event) => { + if ((event.key === "Enter" || event.key === "Escape") && !event.isComposing) { + event.preventDefault(); + event.stopPropagation(); + protyle.toolbar.range.selectNodeContents(linkElement); + protyle.toolbar.range.collapse(false); + focusByRange(protyle.toolbar.range); + window.siyuan.menus.menu.remove(); + } + }); + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + label: `
`, + bind(element) { + const inputElement = element.querySelector("input"); + inputElement.value = linkElement.textContent.replace(Constants.ZWSP, ""); + inputElement.addEventListener("change", () => { + if (!inputElement.value) { + protyle.toolbar.setInlineMark(protyle, "link", "remove"); + window.siyuan.menus.menu.remove(); + } + updateTransaction(protyle, id, nodeElement.outerHTML, html); + html = nodeElement.outerHTML; + }); + inputElement.addEventListener("compositionend", () => { + linkElement.innerHTML = Lute.EscapeHTMLStr(inputElement.value) || ""; + }); + inputElement.addEventListener("input", (event: KeyboardEvent) => { + if (!event.isComposing) { + linkElement.innerHTML = Lute.EscapeHTMLStr(inputElement.value) || ""; + } + }); + inputElement.addEventListener("keydown", (event) => { + if ((event.key === "Enter" || event.key === "Escape") && !event.isComposing) { + event.preventDefault(); + event.stopPropagation(); + protyle.toolbar.range.selectNodeContents(linkElement); + protyle.toolbar.range.collapse(false); + focusByRange(protyle.toolbar.range); + window.siyuan.menus.menu.remove(); + } + }); + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + label: `
`, + bind(element) { + const inputElement = element.querySelector("input"); + inputElement.value = Lute.UnEscapeHTMLStr(linkElement.getAttribute("data-title") || ""); + inputElement.addEventListener("change", () => { + if (inputElement.value) { + linkElement.setAttribute("data-title", Lute.EscapeHTMLStr(inputElement.value)); + } else { + linkElement.removeAttribute("data-title"); + } + updateTransaction(protyle, id, nodeElement.outerHTML, html); + html = nodeElement.outerHTML; + }); + inputElement.addEventListener("keydown", (event) => { + if ((event.key === "Enter" || event.key === "Escape") && !event.isComposing) { + event.preventDefault(); + event.stopPropagation(); + protyle.toolbar.range.selectNodeContents(linkElement); + protyle.toolbar.range.collapse(false); + focusByRange(protyle.toolbar.range); + window.siyuan.menus.menu.remove(); + } + }); + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + icon: "iconTrashcan", + label: window.siyuan.languages.remove, + click() { + protyle.toolbar.setInlineMark(protyle, "link", "remove"); + } + }).element); + if (linkAddress?.startsWith("siyuan://blocks/")) { + window.siyuan.menus.menu.append(new MenuItem({ + icon: "iconRefresh", + label: window.siyuan.languages.turnInto + " " + window.siyuan.languages.blockRef, + click() { + linkElement.setAttribute("data-subtype", "s"); + linkElement.setAttribute("data-type", "block-ref"); + linkElement.setAttribute("data-id", linkAddress?.replace("siyuan://blocks/", "")); + linkElement.removeAttribute("data-href"); + linkElement.removeAttribute("data-title"); + nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); + updateTransaction(protyle, id, nodeElement.outerHTML, html); + protyle.toolbar.range.selectNode(linkElement); + protyle.toolbar.range.collapse(false); + focusByRange(protyle.toolbar.range); + } + }).element); + } + if (isLocalPath(linkAddress)) { + openMenu(linkAddress); + } + window.siyuan.menus.menu.element.classList.remove("fn__none"); + if (focusText || protyle.lute.IsValidLinkDest(linkAddress)) { + window.siyuan.menus.menu.element.querySelectorAll("input")[1].select(); + } else { + window.siyuan.menus.menu.element.querySelector("input").select(); + } +}; + const genImageWidthMenu = (label: string, assetElement: HTMLElement, imgElement: HTMLElement, protyle: IProtyle, id: string, nodeElement: HTMLElement, html: string) => { return { label, diff --git a/app/src/protyle/toolbar/index.ts b/app/src/protyle/toolbar/index.ts index ad586828e..e146ec3f9 100644 --- a/app/src/protyle/toolbar/index.ts +++ b/app/src/protyle/toolbar/index.ts @@ -39,6 +39,7 @@ import {matchHotKey} from "../util/hotKey"; import {unicode2Emoji} from "../../emoji"; import {escapeHtml} from "../../util/escape"; import {hideElements} from "../ui/hideElements"; +import {linkMenu} from "../../menus/protyle"; export class Toolbar { public element: HTMLElement; @@ -252,7 +253,10 @@ export class Toolbar { const types = this.getCurrentType(); if (action === "add" && types.length > 0 && types.includes(type) && !focusAdd) { if (type === "link") { - this.showLink(protyle, this.range.startContainer.parentElement); + this.element.classList.add("fn__none"); + linkMenu(protyle, this.range.startContainer.parentElement); + const rect = this.range.startContainer.parentElement.getBoundingClientRect(); + setPosition(window.siyuan.menus.menu.element, rect.left, rect.top + 13, 26); } return; } @@ -312,7 +316,9 @@ export class Toolbar { } if (types.length > 0 && types.includes("link") && action === "range") { // 链接快捷键不应取消,应该显示链接信息 - this.showLink(protyle, this.range.startContainer.parentElement); + linkMenu(protyle, this.range.startContainer.parentElement); + const rect = this.range.startContainer.parentElement.getBoundingClientRect(); + setPosition(window.siyuan.menus.menu.element, rect.left, rect.top + 13, 26); return; } const wbrElement = document.createElement("wbr"); @@ -491,7 +497,9 @@ export class Toolbar { console.log(e); } if (needShowLink) { - this.showLink(protyle, newElement as HTMLElement, focusText); + linkMenu(protyle, newElement as HTMLElement, focusText); + const rect = newElement.getBoundingClientRect(); + setPosition(window.siyuan.menus.menu.element, rect.left, rect.top + 13, 26); } } } @@ -507,161 +515,6 @@ export class Toolbar { wbrElement.remove(); } - public showLink(protyle: IProtyle, linkElement: HTMLElement, focusText = false) { - const nodeElement = hasClosestBlock(linkElement); - if (!nodeElement) { - return; - } - const id = nodeElement.getAttribute("data-node-id"); - this.subElement.style.width = isMobile() ? "80vw" : Math.min(480, window.innerWidth) + "px"; - this.subElement.style.padding = ""; - this.subElement.innerHTML = `
- -
- -
- -
-
-
- - - -
`; - let preventChange = false; - let oldHTML = nodeElement.outerHTML; - this.subElement.querySelector(".b3-button--outline").addEventListener(getEventName(), (event) => { - preventChange = true; - linkElement.setAttribute("data-subtype", "s"); - linkElement.setAttribute("data-type", "block-ref"); - linkElement.setAttribute("data-id", linkElement.getAttribute("data-href")?.replace("siyuan://blocks/", "")); - linkElement.removeAttribute("data-href"); - linkElement.removeAttribute("data-title"); - nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); - updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML); - protyle.toolbar.range.selectNode(linkElement); - protyle.toolbar.range.collapse(false); - focusByRange(protyle.toolbar.range); - this.subElement.classList.add("fn__none"); - event.stopPropagation(); - }); - this.subElement.querySelector(".b3-button--cancel").addEventListener(getEventName(), (event) => { - preventChange = true; - this.setInlineMark(protyle, "link", "remove"); - this.subElement.classList.add("fn__none"); - event.stopPropagation(); - }); - const titleElements = this.subElement.querySelectorAll(".b3-text-field") as NodeListOf; - titleElements[0].value = linkElement.getAttribute("data-href") || ""; - titleElements[1].value = linkElement.textContent.replace(Constants.ZWSP, ""); - titleElements[2].value = Lute.UnEscapeHTMLStr(linkElement.getAttribute("data-title") || ""); - const updateChange = (event: KeyboardEvent) => { - this.isNewEmptyInline = false; - event.stopPropagation(); - if (event.isComposing) { - return; - } - window.setTimeout(() => { - if (preventChange) { - return; - } - if (titleElements[1].value === "" || titleElements[0].value === "") { - if (this.subElement.classList.contains("fn__none")) { - this.setInlineMark(protyle, "link", "remove"); - } - return; - } - linkElement.innerHTML = Lute.EscapeHTMLStr(titleElements[1].value); - if (titleElements[2].value) { - linkElement.setAttribute("data-title", Lute.EscapeHTMLStr(titleElements[2].value)); - } else { - linkElement.removeAttribute("data-title"); - } - linkElement.setAttribute("data-href", titleElements[0].value); - if (nodeElement.outerHTML !== oldHTML) { - nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); - updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML); - oldHTML = nodeElement.outerHTML; - } - if (this.subElement.classList.contains("fn__none")) { - this.range.setEnd(linkElement.lastChild, linkElement.lastChild.textContent.length); - this.range.collapse(false); - focusByRange(this.range); - } - }, Constants.TIMEOUT_SEARCH); - }; - const hideSubElement = (event: KeyboardEvent) => { - if ((event.key === "Enter" || event.key === "Escape") && !event.isComposing) { - event.stopPropagation(); - event.preventDefault(); - this.subElement.classList.add("fn__none"); - } - }; - titleElements[0].addEventListener("keydown", (event: KeyboardEvent) => { - hideSubElement(event); - }); - titleElements[1].addEventListener("keydown", (event: KeyboardEvent) => { - hideSubElement(event); - }); - titleElements[2].addEventListener("keydown", (event: KeyboardEvent) => { - hideSubElement(event); - }); - titleElements[0].addEventListener("blur", (event: KeyboardEvent) => { - updateChange(event); - }); - titleElements[1].addEventListener("blur", (event: KeyboardEvent) => { - updateChange(event); - }); - titleElements[1].addEventListener("compositionend", () => { - linkElement.innerHTML = Lute.EscapeHTMLStr(titleElements[1].value) || ""; - }); - titleElements[1].addEventListener("input", (event: KeyboardEvent) => { - if (!event.isComposing) { - linkElement.innerHTML = Lute.EscapeHTMLStr(titleElements[1].value) || ""; - } - }); - titleElements[2].addEventListener("blur", (event: KeyboardEvent) => { - updateChange(event); - }); - titleElements[2].addEventListener("compositionend", () => { - if (titleElements[2].value) { - linkElement.setAttribute("data-title", Lute.EscapeHTMLStr(titleElements[2].value) || ""); - } else { - linkElement.removeAttribute("data-title"); - } - }); - titleElements[2].addEventListener("input", (event: KeyboardEvent) => { - if (!event.isComposing) { - if (titleElements[2].value) { - linkElement.setAttribute("data-title", Lute.EscapeHTMLStr(titleElements[2].value) || ""); - } else { - linkElement.removeAttribute("data-title"); - } - } - }); - this.subElement.classList.remove("fn__none"); - const nodeRect = linkElement.getBoundingClientRect(); - setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height + 4); - this.element.classList.add("fn__none"); - if (focusText || protyle.lute.IsValidLinkDest(titleElements[0].value)) { - titleElements[1].select(); - } else { - titleElements[0].select(); - } - } - public showFileAnnotationRef(protyle: IProtyle, refElement: HTMLElement) { const nodeElement = hasClosestBlock(refElement); if (!nodeElement) { diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index 50f94abd1..8affa2868 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -20,7 +20,7 @@ import {isLocalPath, pathPosix} from "../../util/pathName"; import {genEmptyElement} from "../../block/util"; import {previewImage} from "../preview/image"; import {openGlobalSearch} from "../../search/util"; -import {contentMenu, imgMenu, refMenu, setFold, zoomOut} from "../../menus/protyle"; +import {contentMenu, imgMenu, linkMenu, refMenu, setFold, zoomOut} from "../../menus/protyle"; import * as dayjs from "dayjs"; import {dropEvent} from "../util/editorCommonEvent"; import {input} from "./input"; @@ -428,7 +428,8 @@ export class WYSIWYG { const type = target.getAttribute("data-type"); if (type === "block-ref") { refMenu(protyle, target); - setPosition(window.siyuan.menus.menu.element, event.clientX, event.clientY + 13, 26); + const rect =target.getBoundingClientRect(); + setPosition(window.siyuan.menus.menu.element, rect.left, rect.top + 13, 26); // 阻止 popover target.removeAttribute("data-type"); setTimeout(() => { @@ -441,8 +442,10 @@ export class WYSIWYG { return false; } if (type === "a") { - protyle.toolbar.showLink(protyle, target); - if (target.getAttribute("data-href").startsWith("siyuan://blocks")) { + linkMenu(protyle, target); + const rect =target.getBoundingClientRect(); + setPosition(window.siyuan.menus.menu.element, rect.left, rect.top + 13, 26); + if (target.getAttribute("data-href")?.startsWith("siyuan://blocks")) { // 阻止 popover target.setAttribute("prevent-popover", "true"); setTimeout(() => { diff --git a/app/src/protyle/wysiwyg/keydown.ts b/app/src/protyle/wysiwyg/keydown.ts index bdd710c88..9076a5027 100644 --- a/app/src/protyle/wysiwyg/keydown.ts +++ b/app/src/protyle/wysiwyg/keydown.ts @@ -34,7 +34,7 @@ import {isLocalPath} from "../../util/pathName"; import {clipboard} from "electron"; import {getCurrentWindow} from "@electron/remote"; /// #endif -import {refMenu, setFold, zoomOut} from "../../menus/protyle"; +import {linkMenu, refMenu, setFold, zoomOut} from "../../menus/protyle"; import {setPosition} from "../../util/setPosition"; import {removeEmbed} from "./removeEmbed"; import {openAttr} from "../../menus/commonMenuItem"; @@ -477,7 +477,9 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => { protyle.toolbar.showFileAnnotationRef(protyle, inlineElement); return; } else if (type === "a") { - protyle.toolbar.showLink(protyle, inlineElement); + linkMenu(protyle, inlineElement); + const rect = inlineElement.getBoundingClientRect(); + setPosition(window.siyuan.menus.menu.element, rect.left, rect.top + 13, 26); return; } } diff --git a/app/src/util/pathName.ts b/app/src/util/pathName.ts index 02d240270..f8dc8fccb 100644 --- a/app/src/util/pathName.ts +++ b/app/src/util/pathName.ts @@ -29,6 +29,9 @@ export const getDisplayName = (filePath: string, basename = true, removeSY = fal }; export const isLocalPath = (link: string) => { + if (!link) { + return false; + } return link.startsWith("assets/") || link.startsWith("file://"); }; @@ -93,7 +96,7 @@ export const movePathTo = async (notebookId: string, path: string, focus = true) k: inputElement.value }, (data) => { let fileHTML = ""; - data.data.forEach((item: { boxIcon:string, box: string, hPath: string, path: string }) => { + data.data.forEach((item: { boxIcon: string, box: string, hPath: string, path: string }) => { if (item.path === pathPosix().dirname(path) + "/" || item.path === path) { return; }