diff --git a/app/src/boot/globalShortcut.ts b/app/src/boot/globalShortcut.ts index 48acc4098..ce54aaffa 100644 --- a/app/src/boot/globalShortcut.ts +++ b/app/src/boot/globalShortcut.ts @@ -76,7 +76,7 @@ const switchDialogEvent = (event: MouseEvent, switchDialog: Dialog) => { if (currentType === "riffCard") { openCard(); } else { - getDockByType(currentType as TDockType).toggleModel(currentType as TDockType, true); + getDockByType(currentType).toggleModel(currentType, true); } } else { const currentId = target.getAttribute("data-id"); @@ -369,7 +369,7 @@ export const globalShortcut = (app: App) => { if (currentType === "riffCard") { openCard(); } else { - getDockByType(currentType as TDockType).toggleModel(currentType as TDockType, true); + getDockByType(currentType).toggleModel(currentType, true); } if (document.activeElement) { (document.activeElement as HTMLElement).blur(); @@ -492,8 +492,8 @@ export const globalShortcut = (app: App) => { getAllDocks().forEach((item, index) => { dockHtml += `
  • - ${window.siyuan.languages[item.hotkeyLangId]} - ${updateHotkeyTip(window.siyuan.config.keymap.general[item.hotkeyLangId].custom)} + ${item.title} + ${updateHotkeyTip(item.hotkey || "")}
  • `; }); dockHtml = dockHtml + ""; @@ -623,7 +623,7 @@ export const globalShortcut = (app: App) => { return; } const matchDock = getAllDocks().find(item => { - if (matchHotKey(window.siyuan.config.keymap.general[item.hotkeyLangId].custom, event)) { + if (matchHotKey(item.hotkey, event)) { getDockByType(item.type).toggleModel(item.type); if (document.activeElement) { (document.activeElement as HTMLElement).blur(); @@ -751,15 +751,15 @@ export const globalShortcut = (app: App) => { event.preventDefault(); let activeTabElement = document.querySelector(".layout__tab--active"); if (activeTabElement && activeTabElement.getBoundingClientRect().width > 0) { - let type: TDockType; + let type = ""; Array.from(activeTabElement.classList).find(item => { if (item.startsWith("sy__")) { - type = item.replace("sy__", "") as TDockType; + type = item.replace("sy__", ""); return true; } }); if (type) { - getDockByType(type).toggleModel(type, false, true); + getDockByType(type)?.toggleModel(type, false, true); } return; } @@ -932,7 +932,7 @@ const dialogArrow = (element: HTMLElement, event: KeyboardEvent) => { if (currentType === "riffCard") { openCard(); } else { - getDockByType(currentType as TDockType).toggleModel(currentType as TDockType, true); + getDockByType(currentType).toggleModel(currentType, true); } } else { openFileById({ diff --git a/app/src/business/openRecentDocs.ts b/app/src/business/openRecentDocs.ts index 9fc6a2ba1..c56e0fe77 100644 --- a/app/src/business/openRecentDocs.ts +++ b/app/src/business/openRecentDocs.ts @@ -33,8 +33,8 @@ ${unicode2Emoji(item.icon || Constants.SIYUAN_IMAGE_FILE, false, "b3-list-item__ getAllDocks().forEach((item, index) => { dockHtml += `
  • - ${window.siyuan.languages[item.hotkeyLangId]} - ${updateHotkeyTip(window.siyuan.config.keymap.general[item.hotkeyLangId].custom)} + ${item.title} + ${updateHotkeyTip(item.hotkey || "")}
  • `; }); dockHtml = dockHtml + ""; diff --git a/app/src/card/newCardTab.ts b/app/src/card/newCardTab.ts index ad16398f4..a8d8e8982 100644 --- a/app/src/card/newCardTab.ts +++ b/app/src/card/newCardTab.ts @@ -3,6 +3,7 @@ import {Custom} from "../layout/dock/Custom"; import {bindCardEvent, genCardHTML} from "./openCard"; import {fetchPost} from "../util/fetch"; import {Protyle} from "../protyle"; +import {setPanelFocus} from "../layout/util"; export const newCardModel = (options: { tab: Tab, @@ -13,7 +14,7 @@ export const newCardModel = (options: { } }) => { let editor: Protyle; - const custom = new Custom({ + const customObj = new Custom({ type: "siyuan-card", tab: options.tab, data: options.data, @@ -66,5 +67,8 @@ export const newCardModel = (options: { }); } }); - return custom; + customObj.element.addEventListener("click", () => { + setPanelFocus(customObj.element.parentElement.parentElement); + }); + return customObj; }; diff --git a/app/src/layout/dock/Backlink.ts b/app/src/layout/dock/Backlink.ts index 95c8ced76..edf96668f 100644 --- a/app/src/layout/dock/Backlink.ts +++ b/app/src/layout/dock/Backlink.ts @@ -325,10 +325,6 @@ export class Backlink extends Model { }); this.searchBacklinks(true); - - if (this.type === "pin") { - setPanelFocus(this.element); - } } private setFocus() { diff --git a/app/src/layout/dock/Bookmark.ts b/app/src/layout/dock/Bookmark.ts index e540ae7ef..2d54983e7 100644 --- a/app/src/layout/dock/Bookmark.ts +++ b/app/src/layout/dock/Bookmark.ts @@ -146,7 +146,6 @@ export class Bookmark extends Model { }); this.update(); - setPanelFocus(this.element); } public update() { diff --git a/app/src/layout/dock/Custom.ts b/app/src/layout/dock/Custom.ts index b01a87e84..e5c7bfde5 100644 --- a/app/src/layout/dock/Custom.ts +++ b/app/src/layout/dock/Custom.ts @@ -1,9 +1,8 @@ import {Tab} from "../Tab"; -import {setPanelFocus} from "../util"; import {Model} from "../Model"; export class Custom extends Model { - private element: Element; + public element: Element; public data: any; public type: string; public init: () => void; @@ -27,9 +26,6 @@ export class Custom extends Model { } this.element = options.tab.panelElement; this.data = options.data; - this.element.addEventListener("click", () => { - setPanelFocus(this.element.parentElement.parentElement); - }); this.init = options.init; this.destroy = options.destroy; this.resize = options.resize; diff --git a/app/src/layout/dock/Files.ts b/app/src/layout/dock/Files.ts index 972ff6162..2e2ea03e4 100644 --- a/app/src/layout/dock/Files.ts +++ b/app/src/layout/dock/Files.ts @@ -602,7 +602,6 @@ export class Files extends Model { newElement.classList.remove("dragover", "dragover__bottom", "dragover__top"); }); this.init(); - setPanelFocus(this.element.parentElement); if (window.siyuan.config.openHelp) { // 需等待链接建立,不能放在 ongetconfig 中 mountHelp(); diff --git a/app/src/layout/dock/Graph.ts b/app/src/layout/dock/Graph.ts index b0cd5a32a..4b1d8ce25 100644 --- a/app/src/layout/dock/Graph.ts +++ b/app/src/layout/dock/Graph.ts @@ -354,9 +354,6 @@ export class Graph extends Model { }); }); this.searchGraph(options.type !== "global"); - if (this.type !== "local") { - setPanelFocus(this.element); - } } private reset(conf: IGraphCommon & ({ dailyNote: boolean } | { minRefs: number, dailyNote: boolean })) { diff --git a/app/src/layout/dock/Inbox.ts b/app/src/layout/dock/Inbox.ts index 63bbd7e85..d2e990380 100644 --- a/app/src/layout/dock/Inbox.ts +++ b/app/src/layout/dock/Inbox.ts @@ -163,9 +163,6 @@ export class Inbox extends Model { } }); this.update(); - /// #if !MOBILE - setPanelFocus(this.element); - /// #endif } private back() { diff --git a/app/src/layout/dock/Outline.ts b/app/src/layout/dock/Outline.ts index 70c3cf787..6b3d7ca92 100644 --- a/app/src/layout/dock/Outline.ts +++ b/app/src/layout/dock/Outline.ts @@ -169,10 +169,6 @@ export class Outline extends Model { }, response => { this.update(response); }); - - if (this.type === "pin") { - setPanelFocus(options.tab.panelElement); - } } public updateDocTitle(ial?: IObject) { diff --git a/app/src/layout/dock/Tag.ts b/app/src/layout/dock/Tag.ts index 4ce255934..8bcb3e9b0 100644 --- a/app/src/layout/dock/Tag.ts +++ b/app/src/layout/dock/Tag.ts @@ -181,7 +181,6 @@ export class Tag extends Model { } }); this.update(); - setPanelFocus(this.element); } public update() { diff --git a/app/src/layout/dock/index.ts b/app/src/layout/dock/index.ts index c7e837701..e4f5d2b74 100644 --- a/app/src/layout/dock/index.ts +++ b/app/src/layout/dock/index.ts @@ -15,16 +15,26 @@ import {Protyle} from "../../protyle"; import {Backlink} from "./Backlink"; import {resetFloatDockSize} from "./util"; import {hasClosestByClassName} from "../../protyle/util/hasClosest"; +import {App} from "../../index"; +import {Plugin} from "../../plugin"; export class Dock { public element: HTMLElement; public layout: Layout; private position: TDockPosition; + private app: App; public resizeElement: HTMLElement; public pin = true; public data: { [key: string]: Model | boolean }; - constructor(options: { data: { pin: boolean, data: IDockTab[][] }, position: TDockPosition }) { + constructor(options: { + app: App, + data: { + pin: boolean, + data: IDockTab[][] + }, + position: TDockPosition + }) { switch (options.position) { case "Left": this.layout = window.siyuan.layout.layout.children[0].children[0] as Layout; @@ -45,6 +55,7 @@ export class Dock { this.layout.element.insertAdjacentHTML("beforeend", "
    "); break; } + this.app = options.app; this.element = document.getElementById("dock" + options.position); const dockClass = options.position === "Bottom" ? ' class="fn__flex"' : ""; this.element.innerHTML = `
    `; @@ -77,13 +88,13 @@ export class Dock { this.resizeElement.classList.add("fn__none"); } else { activeElements.forEach(item => { - this.toggleModel(item.getAttribute("data-type") as TDockType, true); + this.toggleModel(item.getAttribute("data-type"), true); }); } this.element.addEventListener("click", (event) => { let target = event.target as HTMLElement; while (target && !target.isEqualNode(this.element)) { - const type = target.getAttribute("data-type") as TDockType; + const type = target.getAttribute("data-type"); if (type) { this.toggleModel(type, false, true); event.preventDefault(); @@ -256,7 +267,7 @@ export class Dock { this.layout.element.querySelector(".layout__tab--active")?.classList.remove("layout__tab--active"); } - public toggleModel(type: TDockType, show = false, close = false) { + public toggleModel(type: string, show = false, close = false) { if (!type) { return; } @@ -389,10 +400,27 @@ export class Dock { } }); break; + default: + tab = new Tab({ + callback: (tab: Tab) => { + let customModel; + this.app.plugins.find((item: Plugin) => { + if (item.docks[type]) { + customModel = item.docks[type].model({tab}); + return true; + } + }); + if (customModel) { + tab.addModel(customModel); + } + } + }); + break; } wnd.addTab(tab); target.setAttribute("data-id", tab.id); this.data[type] = tab.model; + setPanelFocus(tab.panelElement); } else { // tab 切换 Array.from(wnd.element.querySelector(".layout-tab-container").children).forEach(item => { @@ -482,7 +510,7 @@ export class Dock { public add(index: number, sourceElement: Element) { sourceElement.setAttribute("data-height", ""); sourceElement.setAttribute("data-width", ""); - const type = sourceElement.getAttribute("data-type") as TDockType; + const type = sourceElement.getAttribute("data-type"); const sourceDock = getDockByType(type); if (sourceDock.element.querySelectorAll(".dock__item").length === 2) { sourceDock.element.classList.add("fn__none"); @@ -569,7 +597,7 @@ export class Dock { private genButton(data: IDockTab[], index: number) { let html = ""; data.forEach(item => { - html += ` + html += ` `; this.data[item.type] = true; diff --git a/app/src/layout/status.ts b/app/src/layout/status.ts index d63c2462b..c15837b4f 100644 --- a/app/src/layout/status.ts +++ b/app/src/layout/status.ts @@ -126,7 +126,7 @@ export const initStatus = (isWindow = false) => { event.stopPropagation(); break; } else if (target.classList.contains("b3-menu__item")) { - const type = target.getAttribute("data-type") as TDockType; + const type = target.getAttribute("data-type"); getDockByType(type).toggleModel(type); if (type === "file" && getSelection().rangeCount > 0) { const range = getSelection().getRangeAt(0); diff --git a/app/src/layout/util.ts b/app/src/layout/util.ts index 97d466e41..27c636e97 100644 --- a/app/src/layout/util.ts +++ b/app/src/layout/util.ts @@ -55,9 +55,9 @@ export const setPanelFocus = (element: Element) => { element.classList.add("layout__wnd--active"); } else { element.classList.add("layout__tab--active"); - ["file", "inbox", "backlink", "tag", "bookmark", "graph", "globalGraph", "outline"].find(item => { - if (element.classList.contains("sy__" + item)) { - document.querySelector(`.dock__item[data-type="${item}"]`).classList.add("dock__item--activefocus"); + Array.from(element.classList).find(item => { + if (item.startsWith("sy__")) { + document.querySelector(`.dock__item[data-type="${item.substring(4)}"]`).classList.add("dock__item--activefocus"); return true; } }); @@ -71,7 +71,7 @@ export const setPanelFocus = (element: Element) => { } }; -export const getDockByType = (type: TDockType) => { +export const getDockByType = (type: string) => { if (!window.siyuan.layout.leftDock) { return undefined; } @@ -154,14 +154,16 @@ const dockToJSON = (dock: Dock) => { const data: IDockTab[] = []; dock.element.querySelectorAll(`span[data-index="${index}"]`).forEach(item => { data.push({ - type: item.getAttribute("data-type") as TDockType, + type: item.getAttribute("data-type"), size: { height: parseInt(item.getAttribute("data-height")), width: parseInt(item.getAttribute("data-width")), }, + title: item.getAttribute("data-title"), show: item.classList.contains("dock__item--active"), icon: item.querySelector("use").getAttribute("xlink:href").substring(1), - hotkeyLangId: item.getAttribute("data-hotkeylangid") + hotkey: item.getAttribute("data-hotkey") || "", + hotkeyLangId: item.getAttribute("data-hotkeyLangId") || "" }); }); return data; @@ -237,11 +239,56 @@ export const exportLayout = (options: { }); }; -const JSONToDock = (json: any) => { +const pushPluginDock = (app: App, dockItem: IDockTab[], position: TPluginDockPosition) => { + const needPushData: { [key: string]: IPluginDockTab } = {} + app.plugins.forEach((pluginItem) => { + let isExist = false; + dockItem.forEach(existSubItem => { + if (Object.keys(pluginItem.docks).includes(existSubItem.type)) { + isExist = true; + } + }) + if (!isExist) { + Object.keys(pluginItem.docks).forEach(pluginDockKey => { + if (pluginItem.docks[pluginDockKey].config.position === position) { + needPushData[pluginDockKey] = pluginItem.docks[pluginDockKey].config; + } + }) + } + }) + dockItem.forEach(existSubItem => { + if (existSubItem.hotkeyLangId) { + existSubItem.title = window.siyuan.languages[existSubItem.hotkeyLangId] + existSubItem.hotkey = window.siyuan.config.keymap.general[existSubItem.hotkeyLangId].custom + } + }) + Object.keys(needPushData).forEach(key => { + const item = needPushData[key]; + dockItem.push({ + type: key, + size: item.size, + show: false, + icon: item.icon, + hotkey: item.hotkey || "", + title: item.title, + }); + }) +} + +const JSONToDock = (json: any, app: App) => { + json.left.data.forEach((existItem: IDockTab[], index: number) => { + pushPluginDock(app, existItem, index === 0 ? "LeftTop" : "LeftBottom"); + }); + json.right.data.forEach((existItem: IDockTab[], index: number) => { + pushPluginDock(app, existItem, index === 0 ? "RightTop" : "RightBottom"); + }); + json.bottom.data.forEach((existItem: IDockTab[], index: number) => { + pushPluginDock(app, existItem, index === 0 ? "BottomLeft" : "BottomRight"); + }); window.siyuan.layout.centerLayout = window.siyuan.layout.layout.children[0].children[1] as Layout; - window.siyuan.layout.leftDock = new Dock({position: "Left", data: json.left}); - window.siyuan.layout.rightDock = new Dock({position: "Right", data: json.right}); - window.siyuan.layout.bottomDock = new Dock({position: "Bottom", data: json.bottom}); + window.siyuan.layout.leftDock = new Dock({position: "Left", data: json.left, app}); + window.siyuan.layout.rightDock = new Dock({position: "Right", data: json.right, app}); + window.siyuan.layout.bottomDock = new Dock({position: "Bottom", data: json.bottom, app}); }; export const JSONToCenter = (app: App, json: ILayoutJSON, layout?: Layout | Wnd | Tab | Model, isStart = false) => { @@ -387,7 +434,7 @@ export const JSONToCenter = (app: App, json: ILayoutJSON, layout?: Layout | Wnd export const JSONToLayout = (app: App, isStart: boolean) => { JSONToCenter(app, window.siyuan.config.uiLayout.layout, undefined, isStart); - JSONToDock(window.siyuan.config.uiLayout); + JSONToDock(window.siyuan.config.uiLayout, app); // 启动时不打开页签,需要移除没有钉住的页签 if (window.siyuan.config.fileTree.closeTabsOnStart && isStart) { getAllTabs().forEach(item => { diff --git a/app/src/menus/workspace.ts b/app/src/menus/workspace.ts index fcc03d3c3..6e99fc3f3 100644 --- a/app/src/menus/workspace.ts +++ b/app/src/menus/workspace.ts @@ -58,8 +58,8 @@ export const workspaceMenu = (app:App, rect: DOMRect) => { getAllDocks().forEach(item => { dockMenu.push({ icon: item.icon, - accelerator: window.siyuan.config.keymap.general[item.hotkeyLangId].custom, - label: window.siyuan.languages[item.hotkeyLangId], + accelerator: item.hotkey, + label: item.title, click() { getDockByType(item.type).toggleModel(item.type); } diff --git a/app/src/plugin/API.ts b/app/src/plugin/API.ts index 1a85b77da..ea28b232d 100644 --- a/app/src/plugin/API.ts +++ b/app/src/plugin/API.ts @@ -9,6 +9,7 @@ import {isMobile} from "../util/functions"; /// #if !MOBILE import {openFile} from "../editor/util"; /// #endif +import {updateHotkeyTip} from "../protyle/util/compatibility"; export class Menu { private menu: SiyuanMenu; @@ -82,6 +83,7 @@ openTab = openFile; export const API = { confirm: confirmDialog, showMessage, + adaptHotkey: updateHotkeyTip, fetchPost, fetchSyncPost, fetchGet, diff --git a/app/src/plugin/index.ts b/app/src/plugin/index.ts index 935d4b778..dd4b88e45 100644 --- a/app/src/plugin/index.ts +++ b/app/src/plugin/index.ts @@ -6,6 +6,8 @@ import {isMobile, isWindow} from "../util/functions"; import {Custom} from "../layout/dock/Custom"; /// #endif import {Tab} from "../layout/Tab"; +import {getDockByType, setPanelFocus} from "../layout/util"; +import {hasClosestByAttribute} from "../protyle/util/hasClosest"; export class Plugin { public i18n: IObject; @@ -17,6 +19,14 @@ export class Plugin { [key: string]: (options: { tab: Tab, data: any }) => Custom /// #endif } = {}; + public docks: { + /// #if !MOBILE + [key: string]: { + config: IPluginDockTab, + model: (options: { tab: Tab }) => Custom + } + /// #endif + } = {}; constructor(options: { app: App, @@ -99,7 +109,7 @@ export class Plugin { }); } - public createTab(options: { + public addTab(options: { type: string, destroy?: () => void, resize?: () => void, @@ -108,16 +118,59 @@ export class Plugin { }) { /// #if !MOBILE const type2 = this.name + options.type; - this.models[type2] = (arg: { data: any, tab: Tab }) => new Custom({ - tab: arg.tab, - type: type2, - data: arg.data, - init: options.init, - destroy: options.destroy, - resize: options.resize, - update: options.update, - }); + this.models[type2] = (arg: { data: any, tab: Tab }) => { + const customObj = new Custom({ + tab: arg.tab, + type: type2, + data: arg.data, + init: options.init, + destroy: options.destroy, + resize: options.resize, + update: options.update, + }); + customObj.element.addEventListener("click", () => { + setPanelFocus(customObj.element.parentElement.parentElement); + }); + return customObj; + }; return this.models[type2]; /// #endif } + + public addDock(options: { + config: IPluginDockTab, + data: any, + type: string, + destroy?: () => void, + resize?: () => void, + update?: () => void, + init: () => void + }) { + /// #if !MOBILE + const type2 = this.name + options.type; + this.docks[type2] = { + config: options.config, + model: (arg: { tab: Tab }) => { + const customObj = new Custom({ + tab: arg.tab, + type: type2, + data: options.data, + init: options.init, + destroy: options.destroy, + resize: options.resize, + update: options.update, + }) + customObj.element.addEventListener("click", (event: MouseEvent) => { + setPanelFocus(customObj.element); + if (hasClosestByAttribute(event.target as HTMLElement, "data-type", "min")) { + getDockByType(type2).toggleModel(type2); + } + }); + customObj.element.classList.add("sy__" + type2); + return customObj + } + }; + return this.docks[type2]; + /// #endif + } } diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index b019dc669..2868ea487 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -2,16 +2,7 @@ type TLayout = "normal" | "bottom" | "left" | "right" | "center" type TSearchFilter = "mathBlock" | "table" | "blockquote" | "superBlock" | "paragraph" | "document" | "heading" | "list" | "listItem" | "codeBlock" | "htmlBlock" type TDirection = "lr" | "tb" -type TDockType = - "file" - | "outline" - | "bookmark" - | "tag" - | "graph" - | "globalGraph" - | "backlink" - | "backlinkOld" - | "inbox" +type TPluginDockPosition = "LeftTop" | "LeftBottom" | "RightTop" | "RightBottom" | "BottomLeft" | "BottomRight" type TDockPosition = "Left" | "Right" | "Bottom" type TWS = "main" | "filetree" | "protyle" type TEditorMode = "preview" | "wysiwyg" @@ -39,7 +30,7 @@ interface Window { dataLayer: any[] siyuan: ISiyuan webkit: any - html2canvas: (element: Element, opitons: {useCORS: boolean}) => Promise; + html2canvas: (element: Element, opitons: { useCORS: boolean }) => Promise; JSAndroid: { returnDesktop(): void openExternal(url: string): void @@ -298,11 +289,21 @@ declare interface ILayoutJSON extends ILayoutOptions { } declare interface IDockTab { - type: TDockType; + type: string; size: { width: number, height: number } show: boolean icon: string - hotkeyLangId: string + title: string + hotkey?: string + hotkeyLangId?: string // 常量中无法存变量 +} + +declare interface IPluginDockTab { + position: TPluginDockPosition, + size: { width: number, height: number }, + icon: string, + hotkey?: string, + title: string, } declare interface IOpenFileOptions {