import {Dialog} from "../../../dialog"; import {App} from "../../../index"; import {upDownHint} from "../../../util/upDownHint"; import {updateHotkeyTip} from "../../../protyle/util/compatibility"; import {isMobile} from "../../../util/functions"; import {Constants} from "../../../constants"; import {Editor} from "../../../editor"; /// #if MOBILE import {getCurrentEditor} from "../../../mobile/editor"; import {popSearch} from "../../../mobile/menu/search"; /// #else import {getActiveTab, getDockByType} from "../../../layout/tabUtil"; import {Custom} from "../../../layout/dock/Custom"; import {getAllModels} from "../../../layout/getAll"; import {Files} from "../../../layout/dock/Files"; import {Search} from "../../../search"; import {openSearch} from "../../../search/spread"; /// #endif import {addEditorToDatabase, addFilesToDatabase} from "../../../protyle/render/av/addToDatabase"; import {hasClosestBlock, hasClosestByClassName, hasTopClosestByTag} from "../../../protyle/util/hasClosest"; import {onlyProtyleCommand} from "./protyle"; import {globalCommand} from "./global"; import {getDisplayName, getNotebookName, getTopPaths, movePathTo, moveToPath, pathPosix} from "../../../util/pathName"; import {hintMoveBlock} from "../../../protyle/hint/extend"; import {fetchSyncPost} from "../../../util/fetch"; import {focusByRange} from "../../../protyle/util/selection"; export const commandPanel = (app: App) => { const range = getSelection().rangeCount > 0 ? getSelection().getRangeAt(0) : undefined; const dialog = new Dialog({ width: isMobile() ? "92vw" : "80vw", height: isMobile() ? "80vh" : "70vh", title: window.siyuan.languages.commandPanel, content: `
↑/↓ ${window.siyuan.languages.searchTip1} ${window.siyuan.languages.enterKey}/${window.siyuan.languages.click} ${window.siyuan.languages.confirm} Esc ${window.siyuan.languages.close}
`, destroyCallback() { if (range) { focusByRange(range); } }, }); dialog.element.setAttribute("data-key", Constants.DIALOG_COMMANDPANEL); const listElement = dialog.element.querySelector("#commands"); let html = ""; Object.keys(window.siyuan.config.keymap.general).forEach((key) => { let keys; /// #if MOBILE keys = ["addToDatabase", "fileTree", "outline", "bookmark", "tag", "dailyNote", "inbox", "backlinks", "dataHistory", "editReadonly", "enter", "enterBack", "globalSearch", "lockScreen", "mainMenu", "move", "newFile", "recentDocs", "replace", "riffCard", "search", "selectOpen1", "syncNow"]; /// #else keys = ["addToDatabase", "fileTree", "outline", "bookmark", "tag", "dailyNote", "inbox", "backlinks", "graphView", "globalGraph", "closeAll", "closeLeft", "closeOthers", "closeRight", "closeTab", "closeUnmodified", "config", "dataHistory", "editReadonly", "enter", "enterBack", "globalSearch", "goBack", "goForward", "goToEditTabNext", "goToEditTabPrev", "goToTab1", "goToTab2", "goToTab3", "goToTab4", "goToTab5", "goToTab6", "goToTab7", "goToTab8", "goToTab9", "goToTabNext", "goToTabPrev", "lockScreen", "mainMenu", "move", "newFile", "recentDocs", "replace", "riffCard", "search", "selectOpen1", "syncNow", "splitLR", "splitMoveB", "splitMoveR", "splitTB", "tabToWindow", "stickSearch", "toggleDock", "unsplitAll", "unsplit", "recentClosed"]; /// #if !BROWSER keys.push("toggleWin"); /// #endif /// #endif if (keys.includes(key)) { html += `
  • ${window.siyuan.languages[key]} ${updateHotkeyTip(window.siyuan.config.keymap.general[key].custom)}
  • `; } }); Object.keys(window.siyuan.config.keymap.editor.general).forEach((key) => { if (["switchReadonly", "switchAdjust"].includes(key)) { html += `
  • ${window.siyuan.languages[key]} ${updateHotkeyTip(window.siyuan.config.keymap.editor.general[key].custom)}
  • `; } }); listElement.insertAdjacentHTML("beforeend", html); app.plugins.forEach(plugin => { plugin.commands.forEach(command => { const liElement = document.createElement("li"); liElement.classList.add("b3-list-item"); liElement.innerHTML = `${plugin.displayName}: ${command.langText || plugin.i18n[command.langKey]} ${updateHotkeyTip(command.customHotkey)}`; liElement.addEventListener("click", (event) => { if (command.callback) { command.callback(); } else if (command.globalCallback) { command.globalCallback(); } dialog.destroy(); event.preventDefault(); event.stopPropagation(); }); listElement.insertAdjacentElement("beforeend", liElement); }); }); if (listElement.childElementCount === 0) { const liElement = document.createElement("li"); liElement.classList.add("b3-list-item", "b3-list-item--focus"); liElement.innerHTML = `${window.siyuan.languages._kernel[122]}`; liElement.addEventListener("click", () => { dialog.destroy(); }); listElement.insertAdjacentElement("beforeend", liElement); } else { listElement.firstElementChild.classList.add("b3-list-item--focus"); } const inputElement = dialog.element.querySelector(".b3-text-field") as HTMLInputElement; inputElement.focus(); listElement.addEventListener("click", (event: KeyboardEvent) => { const liElement = hasClosestByClassName(event.target as HTMLElement, "b3-list-item"); if (liElement) { const command = liElement.getAttribute("data-command"); if (command) { execByCommand({command, app, previousRange: range}); dialog.destroy(); event.preventDefault(); event.stopPropagation(); } } }); inputElement.addEventListener("keydown", (event: KeyboardEvent) => { event.stopPropagation(); if (event.isComposing) { return; } upDownHint(listElement, event); if (event.key === "Enter") { const currentElement = listElement.querySelector(".b3-list-item--focus"); if (currentElement) { const command = currentElement.getAttribute("data-command"); if (command) { execByCommand({command, app, previousRange: range}); } else { currentElement.dispatchEvent(new CustomEvent("click")); } } dialog.destroy(); } else if (event.key === "Escape") { dialog.destroy(); } }); inputElement.addEventListener("compositionend", () => { filterList(inputElement, listElement); }); inputElement.addEventListener("input", (event: InputEvent) => { if (event.isComposing) { return; } event.stopPropagation(); filterList(inputElement, listElement); }); }; const filterList = (inputElement: HTMLInputElement, listElement: Element) => { const inputValue = inputElement.value.toLowerCase(); listElement.querySelector(".b3-list-item--focus")?.classList.remove("b3-list-item--focus"); let hasFocus = false; Array.from(listElement.children).forEach((element: HTMLElement) => { const elementValue = element.querySelector(".b3-list-item__text").textContent.toLowerCase(); const command = element.dataset.command; if (inputValue.indexOf(elementValue) > -1 || elementValue.indexOf(inputValue) > -1 || inputValue.indexOf(command) > -1 || command?.indexOf(inputValue) > -1) { if (!hasFocus) { element.classList.add("b3-list-item--focus"); } hasFocus = true; element.classList.remove("fn__none"); } else { element.classList.add("fn__none"); } }); }; export const execByCommand = async (options: { command: string, app?: App, previousRange?: Range, protyle?: IProtyle, fileLiElements?: Element[] }) => { if (globalCommand(options.command, options.app)) { return; } const isFileFocus = document.querySelector(".layout__tab--active")?.classList.contains("sy__file"); let protyle = options.protyle; /// #if MOBILE if (!protyle) { protyle = getCurrentEditor().protyle; options.previousRange = protyle.toolbar.range; } /// #endif const range: Range = options.previousRange || (getSelection().rangeCount > 0 ? getSelection().getRangeAt(0) : document.createRange()); let fileLiElements = options.fileLiElements; if (!isFileFocus && !protyle) { if (range) { window.siyuan.dialogs.find(item => { if (item.editors) { Object.keys(item.editors).find(key => { if (item.editors[key].protyle.element.contains(range.startContainer)) { protyle = item.editors[key].protyle; return true; } }); if (protyle) { return true; } } }); } const activeTab = getActiveTab(); if (!protyle && activeTab) { if (activeTab.model instanceof Editor) { protyle = activeTab.model.editor.protyle; } else if (activeTab.model instanceof Search) { if (activeTab.model.element.querySelector("#searchUnRefPanel").classList.contains("fn__none")) { protyle = activeTab.model.editors.edit.protyle; } else { protyle = activeTab.model.editors.unRefEdit.protyle; } } else if (activeTab.model instanceof Custom && activeTab.model.editors?.length > 0) { if (range) { activeTab.model.editors.find(item => { if (item.protyle.element.contains(range.startContainer)) { protyle = item.protyle; return true; } }); } } } else if (!protyle) { if (!protyle && range) { window.siyuan.blockPanels.find(item => { item.editors.find(editorItem => { if (editorItem.protyle.element.contains(range.startContainer)) { protyle = editorItem.protyle; return true; } }); if (protyle) { return true; } }); } const models = getAllModels(); if (!protyle) { models.backlink.find(item => { if (item.element.classList.contains("layout__tab--active")) { if (range) { item.editors.find(editor => { if (editor.protyle.element.contains(range.startContainer)) { protyle = editor.protyle; return true; } }); } if (!protyle && item.editors.length > 0) { protyle = item.editors[0].protyle; } return true; } }); } if (!protyle) { models.editor.find(item => { if (item.parent.headElement.classList.contains("item--focus")) { protyle = item.editor.protyle; return true; } }); } } } // only protyle if (!isFileFocus && protyle && onlyProtyleCommand({ command: options.command, previousRange: range, protyle })) { return; } if (isFileFocus && !fileLiElements) { const dockFile = getDockByType("file"); if (!dockFile) { return false; } const files = dockFile.data.file as Files; fileLiElements = Array.from(files.element.querySelectorAll(".b3-list-item--focus")); } // 全局命令,在没有 protyle 和文件树没聚焦的情况下执行 if ((!protyle && !isFileFocus) || (isFileFocus && (!fileLiElements || fileLiElements.length === 0)) || (isMobile() && !document.getElementById("empty").classList.contains("fn__none"))) { if (options.command === "replace") { /// #if MOBILE popSearch(options.app, {hasReplace: true, page: 1}); /// #else openSearch({ app: options.app, hotkey: Constants.DIALOG_REPLACE, key: range.toString() }); /// #endif } else if (options.command === "search") { /// #if MOBILE popSearch(options.app, {hasReplace: false, page: 1}); /// #else openSearch({ app: options.app, hotkey: Constants.DIALOG_SEARCH, key: range.toString() }); /// #endif } return; } // protyle and file tree switch (options.command) { case "replace": if (!isFileFocus) { /// #if MOBILE const response = await fetchSyncPost("/api/filetree/getHPathByPath", { notebook: protyle.notebookId, path: protyle.path.endsWith(".sy") ? protyle.path : protyle.path + ".sy" }); popSearch(options.app, { page: 1, hasReplace: true, hPath: pathPosix().join(getNotebookName(protyle.notebookId), response.data), idPath: [pathPosix().join(protyle.notebookId, protyle.path)] }); /// #else openSearch({ app: options.app, hotkey: Constants.DIALOG_REPLACE, key: range.toString(), notebookId: protyle.notebookId, searchPath: protyle.path }); /// #endif } else { /// #if !MOBILE const topULElement = hasTopClosestByTag(fileLiElements[0], "UL"); if (!topULElement) { return false; } const notebookId = topULElement.getAttribute("data-url"); const pathString = fileLiElements[0].getAttribute("data-path"); const isFile = fileLiElements[0].getAttribute("data-type") === "navigation-file"; if (isFile) { openSearch({ app: options.app, hotkey: Constants.DIALOG_REPLACE, notebookId: notebookId, searchPath: getDisplayName(pathString, false, true) }); } else { openSearch({ app: options.app, hotkey: Constants.DIALOG_REPLACE, notebookId: notebookId, }); } /// #endif } break; case "search": if (!isFileFocus) { /// #if MOBILE const response = await fetchSyncPost("/api/filetree/getHPathByPath", { notebook: protyle.notebookId, path: protyle.path.endsWith(".sy") ? protyle.path : protyle.path + ".sy" }); popSearch(options.app, { page: 1, hasReplace: false, hPath: pathPosix().join(getNotebookName(protyle.notebookId), response.data), idPath: [pathPosix().join(protyle.notebookId, protyle.path)] }); /// #else openSearch({ app: options.app, hotkey: Constants.DIALOG_SEARCH, key: range.toString(), notebookId: protyle.notebookId, searchPath: protyle.path }); /// #endif } else { /// #if !MOBILE const topULElement = hasTopClosestByTag(fileLiElements[0], "UL"); if (!topULElement) { return false; } const notebookId = topULElement.getAttribute("data-url"); const pathString = fileLiElements[0].getAttribute("data-path"); const isFile = fileLiElements[0].getAttribute("data-type") === "navigation-file"; if (isFile) { openSearch({ app: options.app, hotkey: Constants.DIALOG_SEARCH, notebookId: notebookId, searchPath: getDisplayName(pathString, false, true) }); } else { openSearch({ app: options.app, hotkey: Constants.DIALOG_SEARCH, notebookId: notebookId, }); } /// #endif } break; case "addToDatabase": if (!isFileFocus) { addEditorToDatabase(protyle, range); } else { addFilesToDatabase(fileLiElements); } break; case "move": if (!isFileFocus) { const nodeElement = hasClosestBlock(range.startContainer); if (protyle.title?.editElement.contains(range.startContainer) || !nodeElement || window.siyuan.menus.menu.element.getAttribute("data-name") === Constants.MENU_TITLE) { movePathTo((toPath, toNotebook) => { moveToPath([protyle.path], toNotebook[0], toPath[0]); }, [protyle.path], range); } else if (nodeElement && range && protyle.element.contains(range.startContainer)) { let selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); if (selectElements.length === 0) { selectElements = [nodeElement]; } movePathTo((toPath) => { hintMoveBlock(toPath[0], selectElements, protyle); }); } } else { const paths = getTopPaths(fileLiElements); movePathTo((toPath, toNotebook) => { moveToPath(paths, toNotebook[0], toPath[0]); }, paths); } break; } };