diff --git a/app/src/boot/onGetConfig.ts b/app/src/boot/onGetConfig.ts index a6b4d640f..ea572c154 100644 --- a/app/src/boot/onGetConfig.ts +++ b/app/src/boot/onGetConfig.ts @@ -85,7 +85,7 @@ const hasKeymap = (keymap: Record, key1: "general" | "edito return match; }; -export const onGetConfig = (isStart: boolean, app:App) => { +export const onGetConfig = (isStart: boolean, app: App) => { const matchKeymap1 = matchKeymap(Constants.SIYUAN_KEYMAP.general, "general"); const matchKeymap2 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.general, "editor", "general"); const matchKeymap3 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.insert, "editor", "insert"); @@ -135,7 +135,7 @@ export const onGetConfig = (isStart: boolean, app:App) => { fetchPost("/api/system/getEmojiConf", {}, response => { window.siyuan.emojis = response.data as IEmoji[]; try { - JSONToLayout(isStart); + JSONToLayout(app, isStart); openChangelog(); } catch (e) { resetLayout(); diff --git a/app/src/card/newCardTab.ts b/app/src/card/newCardTab.ts index 1ee21a27f..ad16398f4 100644 --- a/app/src/card/newCardTab.ts +++ b/app/src/card/newCardTab.ts @@ -14,7 +14,7 @@ export const newCardModel = (options: { }) => { let editor: Protyle; const custom = new Custom({ - type: "card", + type: "siyuan-card", tab: options.tab, data: options.data, init() { diff --git a/app/src/card/openCard.ts b/app/src/card/openCard.ts index 53d7f1753..ec2754094 100644 --- a/app/src/card/openCard.ts +++ b/app/src/card/openCard.ts @@ -12,6 +12,7 @@ import {MenuItem} from "../menus/Menu"; import {escapeHtml} from "../util/escape"; /// #if !MOBILE import {openFile} from "../editor/util"; +import {newCardModel} from "./newCardTab"; /// #endif import {getDisplayName, movePathTo} from "../util/pathName"; @@ -21,9 +22,9 @@ export const genCardHTML = (options: { blocks: ICard[], isTab: boolean }) => { - let iconsHTML:string; + let iconsHTML: string; /// #if MOBILE - iconsHTML=`
+ iconsHTML = `
${window.siyuan.languages.riffCard}
1/${options.blocks.length}
@@ -31,7 +32,7 @@ export const genCardHTML = (options: {
`; /// #else - iconsHTML=`
+ iconsHTML = `
${options.isTab ? '
' : `
@@ -205,11 +206,16 @@ export const bindCardEvent = (options: { if (sticktabElement) { openFile({ position: "right", - customData:{ - cardType: filterElement.getAttribute("data-cardtype") as TCardType, - id: filterElement.getAttribute("data-id"), - title: options.title - } + custom: { + icon: "iconRiffCard", + title: window.siyuan.languages.spaceRepetition, + data: { + cardType: filterElement.getAttribute("data-cardtype") as TCardType, + id: filterElement.getAttribute("data-id"), + title: options.title + }, + fn: newCardModel + }, }); if (options.dialog) { options.dialog.destroy(); diff --git a/app/src/editor/util.ts b/app/src/editor/util.ts index c801b95d8..0352383fe 100644 --- a/app/src/editor/util.ts +++ b/app/src/editor/util.ts @@ -26,7 +26,6 @@ import {countBlockWord, countSelectWord} from "../layout/status"; import {showMessage} from "../dialog/message"; import {objEquals} from "../util/functions"; import {resize} from "../protyle/util/resize"; -import {newCardModel} from "../card/newCardTab"; import {Search} from "../search"; export const openFileById = async (options: { @@ -96,9 +95,9 @@ export const openFile = (options: IOpenFileOptions) => { } return; } - } else if (options.customData) { + } else if (options.custom) { const custom = allModels.custom.find((item) => { - if (objEquals(item.data, options.customData)) { + if (objEquals(item.data, options.custom.data)) { if (!pdfIsLoading(item.parent.parent.element)) { item.parent.parent.switchTab(item.parent.headElement); item.parent.parent.showHeading(); @@ -384,14 +383,14 @@ const newTab = (options: IOpenFileOptions) => { } }); } - } else if (options.customData) { + } else if (options.custom) { tab = new Tab({ - icon: "iconRiffCard", - title: window.siyuan.languages.spaceRepetition, + icon: options.custom.icon, + title: options.custom.title, callback(tab) { - tab.addModel(newCardModel({ + tab.addModel(options.custom.fn({ tab, - data: options.customData + data: options.custom.data })); setPanelFocus(tab.panelElement.parentElement.parentElement); } diff --git a/app/src/index.ts b/app/src/index.ts index 1f928ffa5..11f6d3628 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -166,7 +166,7 @@ export class App { getLocalStorage(() => { fetchGet(`/appearance/langs/${window.siyuan.config.appearance.lang}.json?v=${Constants.SIYUAN_VERSION}`, (lauguages) => { window.siyuan.languages = lauguages; - window.siyuan.menus = new Menus(); + window.siyuan.menus = new Menus(siyuanApp); bootSync(); fetchPost("/api/setting/getCloudUser", {}, userResponse => { window.siyuan.user = userResponse.data; diff --git a/app/src/layout/Wnd.ts b/app/src/layout/Wnd.ts index 18af80a13..ac9294f93 100644 --- a/app/src/layout/Wnd.ts +++ b/app/src/layout/Wnd.ts @@ -34,8 +34,10 @@ import {isWindow} from "../util/functions"; import {hideAllElements} from "../protyle/ui/hideElements"; import {focusByOffset, getSelectionOffset} from "../protyle/util/selection"; import {Custom} from "./dock/Custom"; +import {App} from "../index"; export class Wnd { + private app: App; public id: string; public parent?: Layout; public element: HTMLElement; @@ -43,8 +45,9 @@ export class Wnd { public children: Tab[] = []; public resize?: TDirection; - constructor(resize?: TDirection, parentType?: TLayout) { + constructor(app: App, resize?: TDirection, parentType?: TLayout) { this.id = genUUID(); + this.app = app; this.resize = resize; this.element = document.createElement("div"); this.element.classList.add("fn__flex-1", "fn__flex"); @@ -223,7 +226,7 @@ export class Wnd { /// #if !BROWSER if (!oldTab) { // 从主窗口拖拽到页签新窗口 if (wnd instanceof Wnd) { - JSONToCenter(tabData, wnd); + JSONToCenter(app, tabData, wnd); oldTab = wnd.children[wnd.children.length - 1]; ipcRenderer.send(Constants.SIYUAN_SEND_WINDOWS, {cmd: "closetab", data: tabData.id}); it.querySelector("li[data-clone='true']").remove(); @@ -332,7 +335,7 @@ export class Wnd { let oldTab = getInstanceById(tabData.id) as Tab; /// #if !BROWSER if (!oldTab) { // 从主窗口拖拽到页签新窗口 - JSONToCenter(tabData, this); + JSONToCenter(app, tabData, this); oldTab = this.children[this.children.length - 1]; ipcRenderer.send(Constants.SIYUAN_SEND_WINDOWS, {cmd: "closetab", data: tabData.id}); getCurrentWindow().focus(); @@ -596,7 +599,7 @@ export class Wnd { }).element); }); window.siyuan.menus.menu.element.setAttribute("data-name", "tabList"); - const rect = target.getBoundingClientRect(); + const rect = target.getBoundingClientRect(); window.siyuan.menus.menu.popup({ x: rect.left + rect.width, y: rect.top + rect.height, @@ -738,7 +741,7 @@ export class Wnd { return; } /// #endif - const wnd = new Wnd(); + const wnd = new Wnd(this.app); window.siyuan.layout.centerLayout.addWnd(wnd); wnd.addTab(newCenterEmptyTab()); } @@ -844,7 +847,7 @@ export class Wnd { // 场景:没有打开的文档,点击标签面板打开 return this; } - const wnd = new Wnd(direction); + const wnd = new Wnd(this.app, direction); if (direction === this.parent.direction) { this.parent.addWnd(wnd, this.id); } else if (this.parent.children.length === 1) { diff --git a/app/src/layout/dock/Custom.ts b/app/src/layout/dock/Custom.ts index a7e419420..b01a87e84 100644 --- a/app/src/layout/dock/Custom.ts +++ b/app/src/layout/dock/Custom.ts @@ -5,21 +5,23 @@ import {Model} from "../Model"; export class Custom extends Model { private element: Element; public data: any; + public type: string; public init: () => void; public destroy: () => void; public resize: () => void; public update: () => void; constructor(options: { + type: string, tab: Tab, data: any, destroy?: () => void, resize?: () => void, update?: () => void, - type: string, // 同一类型的唯一标识 init: () => void }) { super({id: options.tab.id}); + this.type = options.type; if (window.siyuan.config.fileTree.openFilesUseCurrentTab) { options.tab.headElement.classList.add("item--unupdate"); } diff --git a/app/src/layout/util.ts b/app/src/layout/util.ts index 46de19de6..47d89d05a 100644 --- a/app/src/layout/util.ts +++ b/app/src/layout/util.ts @@ -36,6 +36,7 @@ import {openHistory} from "../history/history"; import {Custom} from "./dock/Custom"; import {newCardModel} from "../card/newCardTab"; import {openRecentDocs} from "../business/openRecentDocs"; +import {App} from "../index"; export const setPanelFocus = (element: Element) => { if (element.classList.contains("layout__tab--active") || element.classList.contains("layout__wnd--active")) { @@ -237,7 +238,7 @@ const JSONToDock = (json: any) => { window.siyuan.layout.bottomDock = new Dock({position: "Bottom", data: json.bottom}); }; -export const JSONToCenter = (json: ILayoutJSON, layout?: Layout | Wnd | Tab | Model, isStart = false) => { +export const JSONToCenter = (app: App, json: ILayoutJSON, layout?: Layout | Wnd | Tab | Model, isStart = false) => { let child: Layout | Wnd | Tab | Model; if (json.instance === "Layout") { if (!layout) { @@ -258,7 +259,7 @@ export const JSONToCenter = (json: ILayoutJSON, layout?: Layout | Wnd | Tab | Mo (layout as Layout).addLayout(child); } } else if (json.instance === "Wnd") { - child = new Wnd(json.resize, (layout as Layout).type); + child = new Wnd(app, json.resize, (layout as Layout).type); (layout as Layout).addWnd(child); if (json.width) { child.element.classList.remove("fn__flex-1"); @@ -338,15 +339,27 @@ export const JSONToCenter = (json: ILayoutJSON, layout?: Layout | Wnd | Tab | Mo config: json.config })); } else if (json.instance === "Custom") { - (layout as Tab).addModel(newCardModel({ - tab: (layout as Tab), - data: json.data - })); + if (json.customModelType === "siyuan-card") { + (layout as Tab).addModel(newCardModel({ + tab: (layout as Tab), + data: json.customModelData + })); + } else { + app.plugins.find(item => { + if (item.models[json.customModelType]) { + (layout as Tab).addModel(item.models[json.customModelType]({ + tab: (layout as Tab), + data: json.customModelData + })); + return true; + } + }); + } } if (json.children) { if (Array.isArray(json.children)) { json.children.forEach((item: any, index: number) => { - JSONToCenter(item, layout ? child : window.siyuan.layout.layout, isStart); + JSONToCenter(app, item, layout ? child : window.siyuan.layout.layout, isStart); if (item.instance === "Tab" && index === (json.children as ILayoutJSON[]).length - 1) { const activeTabElement = (child as Wnd).headersElement.querySelector('[data-init-active="true"]') as HTMLElement; if (activeTabElement) { @@ -361,13 +374,13 @@ export const JSONToCenter = (json: ILayoutJSON, layout?: Layout | Wnd | Tab | Mo } }); } else { - JSONToCenter(json.children, child, isStart); + JSONToCenter(app, json.children, child, isStart); } } }; -export const JSONToLayout = (isStart: boolean) => { - JSONToCenter(window.siyuan.config.uiLayout.layout, undefined, isStart); +export const JSONToLayout = (app: App, isStart: boolean) => { + JSONToCenter(app, window.siyuan.config.uiLayout.layout, undefined, isStart); JSONToDock(window.siyuan.config.uiLayout); // 启动时不打开页签,需要移除没有钉住的页签 if (window.siyuan.config.fileTree.closeTabsOnStart && isStart) { @@ -472,7 +485,8 @@ export const layoutToJSON = (layout: Layout | Wnd | Tab | Model, json: any) => { json.config = layout.config; } else if (layout instanceof Custom) { json.instance = "Custom"; - json.data = layout.data; + json.customModelType = layout.type; + json.customModelData = layout.data; } if (layout instanceof Layout || layout instanceof Wnd) { @@ -568,7 +582,7 @@ export const resizeTabs = () => { }, 200); }; -export const copyTab = (tab: Tab) => { +export const copyTab = (app: App, tab: Tab) => { return new Tab({ icon: tab.icon, docIcon: tab.docIcon, @@ -620,10 +634,23 @@ export const copyTab = (tab: Tab) => { config: tab.model.config }); } else if (tab.model instanceof Custom) { - model = newCardModel({ - tab, - data: tab.model.data - }); + const custom = tab.model as Custom; + if (custom.type === "siyuan-card") { + model = newCardModel({ + tab, + data: custom.data + }); + } else { + app.plugins.find(item => { + if (item.models[custom.type]) { + model = item.models[custom.type]({ + tab, + data: custom.data + }); + return true; + } + }); + } } else if (!tab.model && tab.headElement) { const initData = JSON.parse(tab.headElement.getAttribute("data-initdata") || "{}"); model = new Editor({ diff --git a/app/src/menus/index.ts b/app/src/menus/index.ts index f66efd8a9..bdd89f46e 100644 --- a/app/src/menus/index.ts +++ b/app/src/menus/index.ts @@ -8,12 +8,13 @@ import {initTabMenu} from "./tab"; /// #endif import {Menu} from "./Menu"; import {hasTopClosestByTag} from "../protyle/util/hasClosest"; +import {App} from "../index"; export class Menus { public menu: Menu; - constructor() { + constructor(app: App) { this.menu = new Menu(); /// #if !MOBILE window.addEventListener("contextmenu", (event) => { @@ -23,7 +24,7 @@ export class Menus { const dataType = target.getAttribute("data-type"); if (dataType === "tab-header") { this.unselect(); - initTabMenu((getInstanceById(target.getAttribute("data-id")) as Tab)).popup({ + initTabMenu(app, (getInstanceById(target.getAttribute("data-id")) as Tab)).popup({ x: event.clientX, y: event.clientY }); diff --git a/app/src/menus/tab.ts b/app/src/menus/tab.ts index 47545bd2b..7220a195d 100644 --- a/app/src/menus/tab.ts +++ b/app/src/menus/tab.ts @@ -6,6 +6,7 @@ import {copyTab, resizeTabs} from "../layout/util"; import {openNewWindow} from "../window/openNewWindow"; /// #endif import {copySubMenu} from "./commonMenuItem"; +import {App} from "../index"; const closeMenu = (tab: Tab) => { const allTabs: Tab[] = []; @@ -115,12 +116,12 @@ const closeMenu = (tab: Tab) => { window.siyuan.menus.menu.append(new MenuItem({type: "separator"}).element); }; -const splitSubMenu = (tab: Tab) => { +const splitSubMenu = (app: App, tab: Tab) => { const subMenus: IMenu[] = [{ icon: "iconSplitLR", label: window.siyuan.languages.splitLR, click: () => { - tab.parent.split("lr").addTab(copyTab(tab)); + tab.parent.split("lr").addTab(copyTab(app, tab)); } }]; if (tab.parent.children.length > 1) { @@ -140,7 +141,7 @@ const splitSubMenu = (tab: Tab) => { icon: "iconSplitTB", label: window.siyuan.languages.splitTB, click: () => { - tab.parent.split("tb").addTab(copyTab(tab)); + tab.parent.split("tb").addTab(copyTab(app, tab)); } }); @@ -160,12 +161,12 @@ const splitSubMenu = (tab: Tab) => { return subMenus; }; -export const initTabMenu = (tab: Tab) => { +export const initTabMenu = (app: App, tab: Tab) => { window.siyuan.menus.menu.remove(); closeMenu(tab); window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.split, - submenu: splitSubMenu(tab) + submenu: splitSubMenu(app, tab) }).element); const model = tab.model; let rootId: string; diff --git a/app/src/mobile/index.ts b/app/src/mobile/index.ts index 4651723d4..a55081aa1 100644 --- a/app/src/mobile/index.ts +++ b/app/src/mobile/index.ts @@ -64,7 +64,7 @@ class App { getLocalStorage(() => { fetchGet(`/appearance/langs/${window.siyuan.config.appearance.lang}.json?v=${Constants.SIYUAN_VERSION}`, (lauguages) => { window.siyuan.languages = lauguages; - window.siyuan.menus = new Menus(); + window.siyuan.menus = new Menus(siyuanApp); document.title = window.siyuan.languages.siyuanNote; bootSync(); loadAssets(confResponse.data.conf.appearance); diff --git a/app/src/plugin/API.ts b/app/src/plugin/API.ts index c29aa0c9d..e797c6eac 100644 --- a/app/src/plugin/API.ts +++ b/app/src/plugin/API.ts @@ -6,6 +6,8 @@ import {MenuItem} from "../menus/Menu"; import {Menu as SiyuanMenu} from "../menus/Menu"; import {fetchGet, fetchPost, fetchSyncPost} from "../util/fetch"; import {isMobile} from "../util/functions"; +import {Custom} from "../layout/dock/Custom"; +import {openFile} from "../editor/util"; export class Menu { private menu: SiyuanMenu; @@ -73,8 +75,9 @@ export const API = { fetchPost, fetchSyncPost, fetchGet, - Plugin: Plugin, + isMobile, + openTab: openFile, + Plugin, Dialog, Menu, - isMobile }; diff --git a/app/src/plugin/index.ts b/app/src/plugin/index.ts index 7c90224e3..e8e2a5454 100644 --- a/app/src/plugin/index.ts +++ b/app/src/plugin/index.ts @@ -2,12 +2,15 @@ import {App} from "../index"; import {EventBus} from "./EventBus"; import {fetchPost} from "../util/fetch"; import {isMobile, isWindow} from "../util/functions"; +import {Custom} from "../layout/dock/Custom"; +import {Tab} from "../layout/Tab"; export class Plugin { public i18n: IObject; public eventBus: EventBus; public data: any; public name: string; + public models: { [key: string]: (options: { tab: Tab, data: any }) => Custom } = {}; constructor(options: { app: App, @@ -89,4 +92,24 @@ export class Plugin { }); }); } + + public createTab(options: { + type: string, + destroy?: () => void, + resize?: () => void, + update?: () => void, + init: () => void + }) { + 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, + }); + return this.models[type2]; + } } diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 2f036f003..90c08022f 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -291,11 +291,8 @@ declare interface ILayoutJSON extends ILayoutOptions { rootId?: string active?: boolean pin?: boolean - data?: { - cardType: TCardType, - id: string, - title?: string - } + customModelData?: any + customModelType?: string config?: ISearchOption children?: ILayoutJSON[] | ILayoutJSON } @@ -310,7 +307,13 @@ declare interface IDockTab { declare interface IOpenFileOptions { searchData?: ISearchOption, // 搜索必填 - customData?: any, // card 必填 + // card 和自定义页签 必填 + custom?: { + title: string, + icon: string, + data?: any + fn?: (options: { tab: import("../layout/Tab").Tab, data: any }) => import("../layout/Model").Model, + } assetPath?: string, // asset 必填 fileName?: string, // file 必填 rootIcon?: string, // 文档图标 diff --git a/app/src/window/index.ts b/app/src/window/index.ts index d6520ce8d..76123bcfd 100644 --- a/app/src/window/index.ts +++ b/app/src/window/index.ts @@ -131,7 +131,7 @@ class App { getLocalStorage(() => { fetchGet(`/appearance/langs/${window.siyuan.config.appearance.lang}.json?v=${Constants.SIYUAN_VERSION}`, (lauguages) => { window.siyuan.languages = lauguages; - window.siyuan.menus = new Menus(); + window.siyuan.menus = new Menus(siyuanApp); fetchPost("/api/setting/getCloudUser", {}, userResponse => { window.siyuan.user = userResponse.data; loadPlugins(siyuanApp); diff --git a/app/src/window/init.ts b/app/src/window/init.ts index f1eb1da40..611dc4747 100644 --- a/app/src/window/init.ts +++ b/app/src/window/init.ts @@ -11,7 +11,7 @@ import {getSearch} from "../util/functions"; import {initWindow} from "../boot/onGetConfig"; import {App} from "../index"; -export const init = (app:App) => { +export const init = (app: App) => { webFrame.setZoomFactor(window.siyuan.storage[Constants.LOCAL_ZOOM]); globalShortcut(app); fetchPost("/api/system/getEmojiConf", {}, response => { @@ -19,13 +19,13 @@ export const init = (app:App) => { const layout = JSON.parse(sessionStorage.getItem("layout") || "{}"); if (layout.layout) { - JSONToCenter(layout.layout); + JSONToCenter(app, layout.layout); window.siyuan.layout.centerLayout = window.siyuan.layout.layout; return; } const tabJSON = JSON.parse(getSearch("json")); tabJSON.active = true; - JSONToCenter({ + JSONToCenter(app, { direction: "lr", resize: "lr", size: "auto",