/// #if !BROWSER import {shell} from "electron"; /// #endif import {newFile} from "../util/newFile"; import {getDockByType} from "../layout/util"; import {confirmDialog} from "../dialog/confirmDialog"; import {getSearch, isMobile} from "../util/functions"; import {getDisplayName, isLocalPath, movePathTo} from "../util/pathName"; import {MenuItem} from "./Menu"; import {hasClosestByClassName} from "../protyle/util/hasClosest"; import {saveExport} from "../protyle/export"; import {writeText} from "../protyle/util/compatibility"; import {fetchPost} from "../util/fetch"; import {hideMessage, showMessage} from "../dialog/message"; import {Dialog} from "../dialog"; import {getAllModels} from "../layout/getAll"; import {focusBlock, focusByRange, getEditorRange} from "../protyle/util/selection"; import {setPosition} from "../util/setPosition"; import {updateTransaction} from "../protyle/wysiwyg/transaction"; import {Bookmark} from "../layout/dock/Bookmark"; import {rename} from "../editor/rename"; import {openAsset, openBy} from "../editor/util"; import {matchHotKey} from "../protyle/util/hotKey"; import * as dayjs from "dayjs"; import {Constants} from "../constants"; const bindAttrInput = (inputElement: HTMLInputElement, confirmElement: Element) => { inputElement.addEventListener("keydown", (event) => { if (event.isComposing) { return; } if (matchHotKey("⌘↩", event)) { confirmElement.dispatchEvent(new CustomEvent("click")); event.stopPropagation(); event.preventDefault(); } }); }; export const openWechatNotify = (nodeElement: Element) => { const id = nodeElement.getAttribute("data-node-id"); const range = getEditorRange(nodeElement); const reminder = nodeElement.getAttribute("custom-reminder-wechat"); let reminderFormat = ""; if (reminder) { reminderFormat = dayjs(reminder).format("YYYY-MM-DDTHH:mm"); } const dialog = new Dialog({ width: isMobile() ? "80vw" : "50vw", title: window.siyuan.languages.wechatReminder, content: `
${window.siyuan.languages.notifyTime}
${window.siyuan.languages.wechatTip}
`, destroyCallback() { focusByRange(range); } }); const btnsElement = dialog.element.querySelectorAll(".b3-button"); btnsElement[0].addEventListener("click", () => { dialog.destroy(); }); btnsElement[1].addEventListener("click", () => { if (btnsElement[1].getAttribute("disabled")) { return; } btnsElement[1].setAttribute("disabled", "disabled"); fetchPost("/api/block/setBlockReminder", {id, timed: "0"}, () => { nodeElement.removeAttribute("custom-reminder-wechat"); dialog.destroy(); }); }); btnsElement[2].addEventListener("click", () => { const date = dialog.element.querySelector("input").value; if (date) { if (new Date(date) <= new Date()) { showMessage(window.siyuan.languages.reminderTip); return; } if (btnsElement[2].getAttribute("disabled")) { return; } btnsElement[2].setAttribute("disabled", "disabled"); const timed = dayjs(date).format("YYYYMMDDHHmmss"); fetchPost("/api/block/setBlockReminder", {id, timed}, () => { nodeElement.setAttribute("custom-reminder-wechat", timed); dialog.destroy(); }); } else { showMessage(window.siyuan.languages.notEmpty); } }); }; export const openFileWechatNotify = (protyle: IProtyle) => { fetchPost("/api/block/getDocInfo", { id: protyle.block.rootID }, (response) => { const reminder = response.data.ial["custom-reminder-wechat"]; let reminderFormat = ""; if (reminder) { reminderFormat = dayjs(reminder).format("YYYY-MM-DDTHH:mm"); } const dialog = new Dialog({ width: isMobile() ? "80vw" : "50vw", title: window.siyuan.languages.wechatReminder, content: `
${window.siyuan.languages.notifyTime}
${window.siyuan.languages.wechatTip}
` }); const btnsElement = dialog.element.querySelectorAll(".b3-button"); btnsElement[0].addEventListener("click", () => { dialog.destroy(); }); btnsElement[1].addEventListener("click", () => { fetchPost("/api/block/setBlockReminder", {id: protyle.block.rootID, timed: "0"}, () => { dialog.destroy(); }); }); btnsElement[2].addEventListener("click", () => { const date = dialog.element.querySelector("input").value; if (date) { if (new Date(date) <= new Date()) { showMessage(window.siyuan.languages.reminderTip); return; } fetchPost("/api/block/setBlockReminder", { id: protyle.block.rootID, timed: dayjs(date).format("YYYYMMDDHHmmss") }, () => { dialog.destroy(); }); } else { showMessage(window.siyuan.languages.notEmpty); } }); }); }; export const openFileAttr = (attrs: IObject, id: string, focusName = "bookmark") => { let customHTML = ""; let notifyHTML = ""; Object.keys(attrs).forEach(item => { if (item === "custom-reminder-wechat") { notifyHTML = `
`; } else if (item.indexOf("custom") > -1) { customHTML += `
`; } }); const dialog = new Dialog({ width: isMobile() ? "80vw" : "50vw", title: window.siyuan.languages.attr, content: `
${window.siyuan.languages.bookmark}
${notifyHTML} ${customHTML}
`, }); (dialog.element.querySelector('.b3-text-field[data-name="bookmark"]') as HTMLInputElement).value = attrs.bookmark || ""; (dialog.element.querySelector('.b3-text-field[data-name="name"]') as HTMLInputElement).value = attrs.name || ""; (dialog.element.querySelector('.b3-text-field[data-name="alias"]') as HTMLInputElement).value = attrs.alias || ""; const removeAttrs: string[] = []; dialog.element.addEventListener("click", (event) => { const target = event.target as HTMLElement; const actionElement = hasClosestByClassName(target, "block__icon"); if (!actionElement) { return; } switch (actionElement.getAttribute("data-action")) { case "remove": if (actionElement.parentElement.firstElementChild.tagName === "SPAN") { removeAttrs.push(actionElement.parentElement.querySelector("textarea").getAttribute("data-name")); } actionElement.parentElement.nextElementSibling.remove(); actionElement.parentElement.remove(); break; case "bookmark": fetchPost("/api/attr/getBookmarkLabels", {}, (response) => { window.siyuan.menus.menu.remove(); if (response.data.length === 0) { window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.emptyContent, type: "readonly", }).element); } else { response.data.forEach((item: string) => { window.siyuan.menus.menu.append(new MenuItem({ label: item, click() { actionElement.parentElement.querySelector("input").value = item; } }).element); }); } window.siyuan.menus.menu.element.classList.remove("fn__none"); setPosition(window.siyuan.menus.menu.element, event.clientX, event.clientY + 16); }); break; } }); const btnsElement = dialog.element.querySelectorAll(".b3-button"); btnsElement[0].addEventListener("click", () => { btnsElement[0].insertAdjacentHTML("beforebegin", `
`); const inputElements = dialog.element.querySelectorAll(".b3-text-field") as NodeListOf; inputElements[inputElements.length - 2].focus(); bindAttrInput(inputElements[inputElements.length - 1], btnsElement[2]); bindAttrInput(inputElements[inputElements.length - 2], btnsElement[2]); }); btnsElement[1].addEventListener("click", () => { dialog.destroy(); }); btnsElement[2].addEventListener("click", () => { let nodeAttrHTML = ""; let errorTip = ""; const attrsResult: IObject = {}; dialog.element.querySelectorAll(".fn__flex-1").forEach((item: HTMLInputElement) => { const name = item.getAttribute("data-name") || ("custom-" + (item.previousElementSibling.previousElementSibling as HTMLInputElement).value); if (item.value.trim()) { if (!/^[0-9a-zA-Z\-]*$/.test(name.replace("custom-", "")) || name === "custom-") { errorTip += name.replace("custom-", "") + ", "; return; } attrsResult[name] = item.value; const escapeHTML = Lute.EscapeHTMLStr(item.value); if (name === "bookmark") { nodeAttrHTML += `
${escapeHTML}
`; } else if (name === "name") { nodeAttrHTML += `
${escapeHTML}
`; } else if (name === "alias") { nodeAttrHTML += `
${escapeHTML}
`; } else if (name === "memo") { nodeAttrHTML += `
`; } } }); if (errorTip) { showMessage(errorTip.substr(0, errorTip.length - 2) + " " + window.siyuan.languages.invalid); } getAllModels().editor.forEach(item => { if (item.editor.protyle.block.rootID === id) { const refElement = item.editor.protyle.title.element.querySelector(".protyle-attr--refcount"); if (refElement) { nodeAttrHTML += refElement.outerHTML; } item.editor.protyle.title.element.querySelector(".protyle-attr").innerHTML = nodeAttrHTML; item.editor.protyle.wysiwyg.renderCustom(attrsResult); } }); fetchPost("/api/attr/resetBlockAttrs", {id, attrs: attrsResult}, () => { if (attrsResult.bookmark !== attrs.bookmark) { const bookmark = getDockByType("bookmark").data.bookmark; if (bookmark instanceof Bookmark) { bookmark.update(); } } }); dialog.destroy(); }); dialog.element.querySelectorAll(".b3-text-field").forEach((item: HTMLInputElement) => { if (focusName === item.getAttribute("data-name")) { item.focus(); } bindAttrInput(item, btnsElement[2]); }); }; export const openAttr = (nodeElement: Element, protyle: IProtyle, focusName = "bookmark") => { if (nodeElement.getAttribute("data-type") === "NodeThematicBreak") { return; } const id = nodeElement.getAttribute("data-node-id"); const range = getEditorRange(nodeElement); fetchPost("/api/attr/getBlockAttrs", {id}, (response) => { let customHTML = ""; let notifyHTML = ""; Object.keys(response.data).forEach(item => { if (item === "custom-reminder-wechat") { notifyHTML = `
`; } else if (item.indexOf("custom") > -1 && item !== "custom-reminder-wechat") { customHTML += `
`; } }); const dialog = new Dialog({ width: isMobile() ? "80vw" : "50vw", title: window.siyuan.languages.attr, content: `
${window.siyuan.languages.bookmark}
${notifyHTML} ${customHTML}
`, destroyCallback() { focusByRange(range); } }); (dialog.element.querySelector('.b3-text-field[data-name="bookmark"]') as HTMLInputElement).value = response.data.bookmark || ""; (dialog.element.querySelector('.b3-text-field[data-name="name"]') as HTMLInputElement).value = response.data.name || ""; (dialog.element.querySelector('.b3-text-field[data-name="alias"]') as HTMLInputElement).value = response.data.alias || ""; const removeAttrs: string[] = []; dialog.element.addEventListener("click", (event) => { const target = event.target as HTMLElement; const actionElement = hasClosestByClassName(target, "block__icon"); if (!actionElement) { return; } switch (actionElement.getAttribute("data-action")) { case "remove": if (actionElement.parentElement.firstElementChild.tagName === "SPAN") { removeAttrs.push(actionElement.parentElement.querySelector("textarea").getAttribute("data-name")); } actionElement.parentElement.nextElementSibling.remove(); actionElement.parentElement.remove(); break; case "bookmark": fetchPost("/api/attr/getBookmarkLabels", {}, (response) => { window.siyuan.menus.menu.remove(); if (response.data.length === 0) { window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.emptyContent, type: "readonly", }).element); } else { response.data.forEach((item: string) => { window.siyuan.menus.menu.append(new MenuItem({ label: item, click() { actionElement.parentElement.querySelector("input").value = item; } }).element); }); } window.siyuan.menus.menu.element.classList.remove("fn__none"); setPosition(window.siyuan.menus.menu.element, event.clientX, event.clientY + 16); }); break; } }); const btnsElement = dialog.element.querySelectorAll(".b3-button"); btnsElement[0].addEventListener("click", () => { btnsElement[0].insertAdjacentHTML("beforebegin", `
`); const inputElements = dialog.element.querySelectorAll(".b3-text-field") as NodeListOf; inputElements[inputElements.length - 2].focus(); bindAttrInput(inputElements[inputElements.length - 1], btnsElement[2]); bindAttrInput(inputElements[inputElements.length - 2], btnsElement[2]); }); btnsElement[1].addEventListener("click", () => { dialog.destroy(); }); btnsElement[2].addEventListener("click", () => { let nodeAttrHTML = ""; const oldHTML = nodeElement.outerHTML; let errorTip = ""; dialog.element.querySelectorAll(".fn__flex-1").forEach((item: HTMLInputElement) => { const name = item.getAttribute("data-name") || ("custom-" + (item.previousElementSibling.previousElementSibling as HTMLInputElement).value); if (item.value.trim()) { if (!/^[0-9a-zA-Z\-]*$/.test(name.replace("custom-", "")) || name === "custom-") { errorTip += name.replace("custom-", "") + ", "; return; } if (removeAttrs.includes(name)) { removeAttrs.find((rmAttr, index) => { if (rmAttr === name) { removeAttrs.splice(index, 1); return true; } }); } const escapeHTML = Lute.EscapeHTMLStr(item.value); nodeElement.setAttribute(name, escapeHTML); if (name === "bookmark") { if (escapeHTML !== response.data.bookmark && !isMobile()) { const bookmark = getDockByType("bookmark").data.bookmark; if (bookmark instanceof Bookmark) { setTimeout(() => { bookmark.update(); }, 219); } } nodeAttrHTML += `
${escapeHTML}
`; } else if (name === "name") { nodeAttrHTML += `
${escapeHTML}
`; } else if (name === "alias") { nodeAttrHTML += `
${escapeHTML}
`; } else if (name === "memo") { nodeAttrHTML += `
`; } } else { nodeElement.removeAttribute(name); } }); removeAttrs.forEach(item => { nodeElement.removeAttribute(item); }); if (errorTip) { showMessage(errorTip.substr(0, errorTip.length - 2) + " " + window.siyuan.languages.invalid); } const refElement = nodeElement.lastElementChild.querySelector(".protyle-attr--refcount"); if (refElement) { nodeAttrHTML += refElement.outerHTML; } nodeElement.lastElementChild.innerHTML = nodeAttrHTML + Constants.ZWSP; updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML); dialog.destroy(); }); dialog.element.querySelectorAll(".b3-text-field").forEach((item: HTMLInputElement) => { if (focusName === item.getAttribute("data-name")) { item.focus(); } bindAttrInput(item, btnsElement[2]); }); }); }; export const copySubMenu = (id: string, name: string, accelerator = true, focusElement?: Element) => { return [ { icon: "iconGraph", accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyBlockRef.custom : undefined, label: window.siyuan.languages.copyBlockRef, click: () => { if (name) { writeText(`((${id} "${name}"))`); } else { fetchPost("/api/block/getRefText", {id}, (response) => { writeText(`((${id} '${response.data}'))`); }); } if (focusElement) { focusBlock(focusElement); } } }, { icon: "iconSQL", label: window.siyuan.languages.copyBlockEmbed, accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyBlockEmbed.custom : undefined, click: () => { writeText(`{{select * from blocks where id='${id}'}}`); if (focusElement) { focusBlock(focusElement); } } }, { icon: "iconSiYuan", label: window.siyuan.languages.copyProtocol, accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyProtocol.custom : undefined, click: () => { writeText(`siyuan://blocks/${id}`); if (focusElement) { focusBlock(focusElement); } } }, { label: window.siyuan.languages.copyHPath, accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyHPath.custom : undefined, click: () => { fetchPost("/api/filetree/getHPathByID", { id }, (response) => { writeText(response.data); }); } }, { label: window.siyuan.languages.copy + " ID", click: () => { writeText(id); if (focusElement) { focusBlock(focusElement); } } } ]; }; export const exportMd = (id: string) => { return new MenuItem({ label: window.siyuan.languages.export, type: "submenu", icon: "iconUpload", submenu: [ { label: window.siyuan.languages.template, icon: "iconMarkdown", click: () => { fetchPost("/api/template/docSaveAsTemplate", { id, overwrite: false }, response => { if (response.code === 1) { // 重名 confirmDialog(window.siyuan.languages.export, window.siyuan.languages.exportTplTip, () => { fetchPost("/api/template/docSaveAsTemplate", { id, overwrite: true }); }); return; } showMessage(window.siyuan.languages.exportTplSucc); }); } }, { label: "Markdown", icon: "iconMarkdown", click: () => { const msgId = showMessage(window.siyuan.languages.exporting, -1); fetchPost("/api/export/exportMd", { id, }, response => { hideMessage(msgId); if (window.siyuan.config.system.container === "ios") { window.location.href = response.data.zip; } else if (window.siyuan.config.system.container === "android" && window.JSAndroid) { window.JSAndroid.openExternal(response.data.zip); } else { window.open(response.data.zip); } }); } }, { label: "SiYuan .sy.zip", icon: "iconSiYuan", click: () => { const msgId = showMessage(window.siyuan.languages.exporting, -1); fetchPost("/api/export/exportSY", { id, }, response => { hideMessage(msgId); if (window.siyuan.config.system.container === "ios") { window.location.href = response.data.zip; } else if (window.siyuan.config.system.container === "android" && window.JSAndroid) { window.JSAndroid.openExternal(response.data.zip); } else { window.open(response.data.zip); } }); } }, /// #if !BROWSER { label: "PDF", icon: "iconPDF", click: () => { saveExport({type: "pdf", id}); } }, { label: "HTML (SiYuan)", icon: "iconHTML5", click: () => { saveExport({type: "html", id}); } }, { label: "HTML (Markdown)", icon: "iconHTML5", click: () => { saveExport({type: "htmlmd", id}); } }, { label: "Word .docx", icon: "iconExact", click: () => { saveExport({type: "word", id}); } } /// #endif ] }).element; }; export const openMenu = (src: string, onlyMenu = false) => { const submenu = []; if (isLocalPath(src)) { submenu.push({ label: window.siyuan.languages.insertRight, accelerator: "Click", click() { openAsset(src.trim(), parseInt(getSearch("page", src)), "right"); } }); /// #if !BROWSER submenu.push({ label: window.siyuan.languages.useDefault, accelerator: "⇧Click", click() { openBy(src, "app"); } }); submenu.push({ label: window.siyuan.languages.showInFolder, accelerator: "⌘Click", click: () => { openBy(src, "folder"); } }); /// #endif } else { submenu.push({ label: window.siyuan.languages.useBrowserView, accelerator: "Click", click: () => { /// #if !BROWSER shell.openExternal(src).catch((e) => { console.log("openExternal error:" + e); }); /// #else if (window.siyuan.config.system.container === "ios") { window.location.href = src; } else { window.open(src); } /// #endif } }); } if (onlyMenu) { return submenu; } window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.openBy, submenu }).element); }; export const newFileMenu = (notebookId?: string, path?: string, open?: boolean) => { return new MenuItem({ icon: "iconFile", label: window.siyuan.languages.newFile, click: () => { newFile(notebookId, path, open); } }).element; }; export const deleteMenu = (notebookId: string, name: string, pathString: string) => { return new MenuItem({ icon: "iconTrashcan", label: window.siyuan.languages.delete, accelerator: "⌦", click: () => { fetchPost("/api/block/getDocInfo", { id: getDisplayName(pathString, true, true) }, (response) => { let tip = `${window.siyuan.languages.confirmDelete} ${name}?`; if (response.data.subFileCount > 0) { tip = `${window.siyuan.languages.confirmDelete} ${name} ${window.siyuan.languages.andSubFile.replace("x", response.data.subFileCount)}?`; } confirmDialog(window.siyuan.languages.delete, tip, () => { fetchPost("/api/filetree/removeDoc", { notebook: notebookId, path: pathString }); }); }); } }).element; }; export const renameMenu = (options: { path: string notebookId: string name: string, type: "notebook" | "file" }) => { return new MenuItem({ accelerator: window.siyuan.config.keymap.editor.general.rename.custom, label: window.siyuan.languages.rename, click: () => { rename(options); } }).element; }; export const movePathToMenu = (notebookId: string, path: string) => { return new MenuItem({ label: window.siyuan.languages.move, icon: "iconMove", accelerator: window.siyuan.config.keymap.general.move.custom, click() { movePathTo(notebookId, path); } }).element; };