From 4e10a71d0c6968cf893e86864f150c2dce60eb34 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen <78434827+TCOTC@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:11:53 +0800 Subject: [PATCH] :art: Unregister plugin event bus on unload https://github.com/siyuan-note/siyuan/issues/16910 ; expose registerCustomEventBus on window.siyuan --- app/src/asset/index.ts | 5 +- app/src/boot/globalEvent/command/global.ts | 2 +- app/src/boot/globalEvent/keydown.ts | 2 +- app/src/card/openCard.ts | 19 ++-- app/src/dialog/processSystem.ts | 27 +++--- app/src/editor/openLink.ts | 13 ++- app/src/editor/util.ts | 5 +- app/src/index.ts | 8 +- app/src/layout/dock/Files.ts | 9 +- app/src/layout/dock/Inbox.ts | 19 ++-- app/src/menus/navigation.ts | 57 +++++------- app/src/menus/protyle.ts | 100 +++++++++------------ app/src/menus/workspace.ts | 2 +- app/src/mobile/editor.ts | 9 +- app/src/mobile/index.ts | 6 +- app/src/mobile/menu/index.ts | 2 +- app/src/mobile/util/MobileBackFoward.ts | 7 +- app/src/mobile/util/keyboardToolbar.ts | 9 +- app/src/mobile/util/onMessage.ts | 13 ++- app/src/plugin/EventBus.ts | 74 ++++++++++++--- app/src/plugin/uninstall.ts | 7 +- app/src/protyle/breadcrumb/index.ts | 19 ++-- app/src/protyle/gutter/index.ts | 38 ++++---- app/src/protyle/header/openTitleMenu.ts | 19 ++-- app/src/protyle/render/av/action.ts | 21 ++--- app/src/protyle/toolbar/index.ts | 39 +++----- app/src/protyle/util/destroy.ts | 7 +- app/src/protyle/util/onGet.ts | 13 ++- app/src/protyle/util/paste.ts | 89 +++++++++--------- app/src/protyle/util/setEditMode.ts | 5 +- app/src/protyle/wysiwyg/index.ts | 9 +- app/src/search/util.ts | 11 ++- app/src/types/index.d.ts | 4 + app/src/window/index.ts | 8 +- 34 files changed, 326 insertions(+), 351 deletions(-) diff --git a/app/src/asset/index.ts b/app/src/asset/index.ts index b937feb74f4..7b185b3037d 100644 --- a/app/src/asset/index.ts +++ b/app/src/asset/index.ts @@ -16,6 +16,7 @@ import {setStorageVal, updateHotkeyTip} from "../protyle/util/compatibility"; import {App} from "../index"; import {clearOBG} from "../layout/dock/util"; import {getDisplayName} from "../util/pathName"; +import {emitToEventBus} from "../plugin/EventBus"; export class Asset extends Model { public path: string; @@ -35,9 +36,7 @@ export class Asset extends Model { this.element.addEventListener("click", (event) => { clearOBG(); setPanelFocus(this.element.parentElement.parentElement); - this.app.plugins.forEach(item => { - item.eventBus.emit("click-pdf", {event}); - }); + emitToEventBus("click-pdf", {event}); }); if (typeof this.pdfId === "string") { this.getPdfId(() => { diff --git a/app/src/boot/globalEvent/command/global.ts b/app/src/boot/globalEvent/command/global.ts index ccd6de44e8d..6acc008db89 100644 --- a/app/src/boot/globalEvent/command/global.ts +++ b/app/src/boot/globalEvent/command/global.ts @@ -432,7 +432,7 @@ export const globalCommand = (command: string, app: App) => { setReadOnly(!window.siyuan.config.editor.readOnly); return true; case "lockScreen": - lockScreen(app); + lockScreen(); return true; case "newFile": newFile({ diff --git a/app/src/boot/globalEvent/keydown.ts b/app/src/boot/globalEvent/keydown.ts index c772a9a2984..8a57113a3bd 100644 --- a/app/src/boot/globalEvent/keydown.ts +++ b/app/src/boot/globalEvent/keydown.ts @@ -1340,7 +1340,7 @@ export const windowKeyDown = (app: App, event: KeyboardEvent) => { return; } if (matchHotKey(window.siyuan.config.keymap.general.lockScreen.custom, event)) { - lockScreen(app); + lockScreen(); event.preventDefault(); return; } diff --git a/app/src/card/openCard.ts b/app/src/card/openCard.ts index 247dd6a33c8..2e9f14d4072 100644 --- a/app/src/card/openCard.ts +++ b/app/src/card/openCard.ts @@ -26,6 +26,7 @@ import {updateCardHV} from "./util"; import {showMessage} from "../dialog/message"; import {Menu} from "../plugin/Menu"; import {transaction} from "../protyle/wysiwyg/transaction"; +import {emitToEventBus} from "../plugin/EventBus"; const genCardCount = (cardsData: ICardData, allIndex = 0) => { let newIndex = 0; @@ -695,7 +696,7 @@ export const bindCardEvent = async (options: { element.previousElementSibling.textContent = currentCard.nextDues[btnIndex - 1]; }); actionElements[1].classList.remove("fn__none"); - emitEvent(options.app, currentCard, type); + emitEvent(currentCard, type); return; } } else if (type === "-2") { // 上一步 @@ -708,7 +709,7 @@ export const bindCardEvent = async (options: { index, cardsData: options.cardsData }); - emitEvent(options.app, options.cardsData.cards[index + 1], type); + emitEvent(options.cardsData.cards[index + 1], type); } return; } @@ -737,7 +738,7 @@ export const bindCardEvent = async (options: { notebook: docId, reviewedCards: options.cardsData.cards }, async (result) => { - emitEvent(options.app, options.cardsData.cards[index - 1], type); + emitEvent(options.cardsData.cards[index - 1], type); index = 0; options.cardsData = result.data; for (let i = 0; i < options.app.plugins.length; i++) { @@ -768,19 +769,17 @@ export const bindCardEvent = async (options: { index, cardsData: options.cardsData }); - emitEvent(options.app, options.cardsData.cards[index - 1], type); + emitEvent(options.cardsData.cards[index - 1], type); }); } }); return editor; }; -const emitEvent = (app: App, card: ICard, type: string) => { - app.plugins.forEach(item => { - item.eventBus.emit("click-flashcard-action", { - type, - card - }); +const emitEvent = (card: ICard, type: string) => { + emitToEventBus("click-flashcard-action", { + type, + card }); }; diff --git a/app/src/dialog/processSystem.ts b/app/src/dialog/processSystem.ts index 016d6c6369a..81b9d4851c2 100644 --- a/app/src/dialog/processSystem.ts +++ b/app/src/dialog/processSystem.ts @@ -24,7 +24,7 @@ import {hideAllElements, hideElements} from "../protyle/ui/hideElements"; import {App} from "../index"; import {saveScroll} from "../protyle/scroll/saveScroll"; import {isInAndroid, isInHarmony, isInIOS, setStorageVal} from "../protyle/util/compatibility"; -import {Plugin} from "../plugin"; +import {emitToEventBus} from "../plugin/EventBus"; const updateTitle = (rootID: string, tab: Tab, protyle?: IProtyle) => { fetchPost("/api/block/getDocInfo", { @@ -233,13 +233,11 @@ export const setDefRefCount = (data: { } }; -export const lockScreen = (app: App) => { +export const lockScreen = () => { if (window.siyuan.config.readonly) { return; } - app.plugins.forEach(item => { - item.eventBus.emit("lock-screen"); - }); + emitToEventBus("lock-screen"); /// #if BROWSER fetchPost("/api/system/logoutAuth", {}, () => { redirectToCheckAuth(); @@ -551,7 +549,7 @@ export const downloadProgress = (data: { id: string, percent: number }) => { } }; -export const processSync = (data?: IWebSocketData, plugins?: Plugin[]) => { +export const processSync = (data?: IWebSocketData) => { /// #if MOBILE const menuSyncUseElement = document.querySelector("#menuSyncNow use"); const barSyncUseElement = document.querySelector("#toolbarSync use"); @@ -578,6 +576,7 @@ export const processSync = (data?: IWebSocketData, plugins?: Plugin[]) => { } else if (data.code === 1) { // success menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudSucc"); barSyncUseElement.setAttribute("xlink:href", "#iconCloudSucc"); + document.getElementById("toolbarSync").classList.add("fn__none"); } /// #else const iconElement = document.querySelector("#barSync"); @@ -607,13 +606,11 @@ export const processSync = (data?: IWebSocketData, plugins?: Plugin[]) => { useElement.setAttribute("xlink:href", "#iconCloudSucc"); } /// #endif - plugins.forEach((item) => { - if (data.code === 0) { - item.eventBus.emit("sync-start", data); - } else if (data.code === 1) { - item.eventBus.emit("sync-end", data); - } else if (data.code === 2) { - item.eventBus.emit("sync-fail", data); - } - }); + if (data.code === 0) { + emitToEventBus("sync-start", data); + } else if (data.code === 1) { + emitToEventBus("sync-end", data); + } else if (data.code === 2) { + emitToEventBus("sync-fail", data); + } }; diff --git a/app/src/editor/openLink.ts b/app/src/editor/openLink.ts index ecf0d033b63..3760fe3d971 100644 --- a/app/src/editor/openLink.ts +++ b/app/src/editor/openLink.ts @@ -13,6 +13,7 @@ import {App} from "../index"; import {fetchPost} from "../util/fetch"; import {checkFold} from "../util/noRelyPCFunction"; import {openMobileFileById} from "../mobile/editor"; +import {emitToEventBus} from "../plugin/EventBus"; export const processSYLink = (app: App, url: string) => { let urlObj: URL; @@ -82,13 +83,11 @@ export const processSYLink = (app: App, url: string) => { ipcRenderer.send(Constants.SIYUAN_CMD, "show"); /// #endif } - app.plugins.forEach(plugin => { - plugin.eventBus.emit("open-siyuan-url-block", { - url, - id, - focus, - exist: existResponse.data, - }); + emitToEventBus("open-siyuan-url-block", { + url, + id, + focus, + exist: existResponse.data, }); }); return true; diff --git a/app/src/editor/util.ts b/app/src/editor/util.ts index 86b7922c806..fcb69981a48 100644 --- a/app/src/editor/util.ts +++ b/app/src/editor/util.ts @@ -36,6 +36,7 @@ import {preventScroll} from "../protyle/scroll/preventScroll"; import {clearOBG} from "../layout/dock/util"; import {Model} from "../layout/Model"; import {hideElements} from "../protyle/ui/hideElements"; +import {emitToEventBus} from "../plugin/EventBus"; export const openFileById = async (options: { app: App, @@ -597,9 +598,7 @@ export const updatePanelByEditor = (options: { } } } - options.protyle.app.plugins.forEach(item => { - item.eventBus.emit("switch-protyle", {protyle: options.protyle}); - }); + emitToEventBus("switch-protyle", {protyle: options.protyle}); } // 切换页签或关闭所有页签时,需更新对应的面板 const models = getAllModels(); diff --git a/app/src/index.ts b/app/src/index.ts index 3c6a1a9271d..32d7732a1bf 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -41,6 +41,7 @@ import {Tag} from "./layout/dock/Tag"; import {updateControlAlt} from "./protyle/util/hotKey"; import {updateAppearance} from "./config/util/updateAppearance"; import {renderSnippet} from "./config/util/snippets"; +import {registerCustomEventBus, emitToEventBus} from "./plugin/EventBus"; export class App { public plugins: import("./plugin").Plugin[] = []; @@ -65,14 +66,13 @@ export class App { closedTabs: [], ctrlIsPressed: false, altIsPressed: false, + registerCustomEventBus, ws: new Model({ app: this, id: genUUID(), type: "main", msgCallback: (data) => { - this.plugins.forEach((plugin) => { - plugin.eventBus.emit("ws-main", data); - }); + emitToEventBus("ws-main", data); if (data) { switch (data.cmd) { case "setAppearance": @@ -173,7 +173,7 @@ export class App { transactionError(); break; case "syncing": - processSync(data, this.plugins); + processSync(data); break; case "backgroundtask": progressBackgroundTask(data.data.tasks); diff --git a/app/src/layout/dock/Files.ts b/app/src/layout/dock/Files.ts index 7401a6cac26..fcda191180a 100644 --- a/app/src/layout/dock/Files.ts +++ b/app/src/layout/dock/Files.ts @@ -28,6 +28,7 @@ import {ipcRenderer} from "electron"; /// #endif import {hideTooltip, showTooltip} from "../../dialog/tooltip"; import {selectOpenTab} from "./util"; +import {emitToEventBus} from "../../plugin/EventBus"; export class Files extends Model { public element: HTMLElement; @@ -57,9 +58,7 @@ export class Files extends Model { break; case "mount": this.onMount(data); - options.app.plugins.forEach((item) => { - item.eventBus.emit("opened-notebook", data); - }); + emitToEventBus("opened-notebook", data); break; case "createnotebook": setNoteBook((notebooks) => { @@ -81,9 +80,7 @@ export class Files extends Model { break; case "unmount": this.onRemove(data); - options.app.plugins.forEach((item) => { - item.eventBus.emit("closed-notebook", data); - }); + emitToEventBus("closed-notebook", data); break; case "removeDoc": this.onRemove(data); diff --git a/app/src/layout/dock/Inbox.ts b/app/src/layout/dock/Inbox.ts index 009301f8561..ca847f9671f 100644 --- a/app/src/layout/dock/Inbox.ts +++ b/app/src/layout/dock/Inbox.ts @@ -294,17 +294,14 @@ ${data.shorthandContent} } }).element); } - if (this.app.plugins) { - emitOpenMenu({ - plugins: this.app.plugins, - type: "open-menu-inbox", - detail: { - ids, - element: itemElement || detailsElement, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-inbox", + detail: { + ids, + element: itemElement || detailsElement, + }, + separatorPosition: "top", + }); window.siyuan.menus.menu.popup({x: event.clientX, y: event.clientY + 16}); } diff --git a/app/src/menus/navigation.ts b/app/src/menus/navigation.ts index fa4b7f26ea8..66aa817b19e 100644 --- a/app/src/menus/navigation.ts +++ b/app/src/menus/navigation.ts @@ -185,17 +185,14 @@ const initMultiMenu = (selectItemElements: NodeListOf, app: App) => { } }] }).element); - if (app.plugins) { - emitOpenMenu({ - plugins: app.plugins, - type: "open-menu-doctree", - detail: { - elements: selectItemElements, - type: "docs" - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-doctree", + detail: { + elements: selectItemElements, + type: "docs" + }, + separatorPosition: "top", + }); return window.siyuan.menus.menu; }; @@ -417,17 +414,14 @@ export const initNavigationMenu = (app: App, liElement: HTMLElement) => { } }] }).element); - if (app.plugins) { - emitOpenMenu({ - plugins: app.plugins, - type: "open-menu-doctree", - detail: { - elements: selectItemElements, - type: "notebook" - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-doctree", + detail: { + elements: selectItemElements, + type: "notebook" + }, + separatorPosition: "top", + }); return window.siyuan.menus.menu; }; @@ -712,17 +706,14 @@ export const initFileMenu = (app: App, notebookId: string, pathString: string, l } genImportMenu(notebookId, pathString); window.siyuan.menus.menu.append(exportMd(id)); - if (app.plugins) { - emitOpenMenu({ - plugins: app.plugins, - type: "open-menu-doctree", - detail: { - elements: selectItemElements, - type: "doc" - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-doctree", + detail: { + elements: selectItemElements, + type: "doc" + }, + separatorPosition: "top", + }); window.siyuan.menus.menu.element.setAttribute("data-from", Constants.MENU_FROM_DOC_TREE_MORE_DOC); return window.siyuan.menus.menu; }; diff --git a/app/src/menus/protyle.ts b/app/src/menus/protyle.ts index 23eb2683293..4df71707472 100644 --- a/app/src/menus/protyle.ts +++ b/app/src/menus/protyle.ts @@ -302,17 +302,14 @@ export const fileAnnotationRefMenu = (protyle: IProtyle, refElement: HTMLElement } }).element); - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "open-menu-fileannotationref", - detail: { - protyle, - element: refElement, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-fileannotationref", + detail: { + protyle, + element: refElement, + }, + separatorPosition: "top", + }); /// #if MOBILE window.siyuan.menus.menu.fullscreen(); /// #else @@ -665,17 +662,14 @@ export const refMenu = (protyle: IProtyle, element: HTMLElement) => { } }).element); } - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "open-menu-blockref", - detail: { - protyle, - element: element, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-blockref", + detail: { + protyle, + element: element, + }, + separatorPosition: "top", + }); /// #if MOBILE window.siyuan.menus.menu.fullscreen(); @@ -906,18 +900,15 @@ export const contentMenu = (protyle: IProtyle, nodeElement: Element) => { } } /// #endif - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "open-menu-content", - detail: { - protyle, - range, - element: nodeElement, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-content", + detail: { + protyle, + range, + element: nodeElement, + }, + separatorPosition: "top", + }); }; export const enterBack = (protyle: IProtyle, id: string) => { @@ -1428,17 +1419,14 @@ export const imgMenu = (protyle: IProtyle, range: Range, assetElement: HTMLEleme } /// #endif } - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "open-menu-image", - detail: { - protyle, - element: assetElement, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-image", + detail: { + protyle, + element: assetElement, + }, + separatorPosition: "top", + }); /// #if MOBILE window.siyuan.menus.menu.fullscreen(); /// #else @@ -1708,9 +1696,8 @@ style="margin:4px 0;width: ${isMobile() ? "100%" : "360px"}" class="b3-text-fiel } } - if (!protyle.disabled && protyle?.app?.plugins) { + if (!protyle.disabled) { emitOpenMenu({ - plugins: protyle.app.plugins, type: "open-menu-link", detail: { protyle, @@ -1902,17 +1889,14 @@ export const tagMenu = (protyle: IProtyle, tagElement: HTMLElement) => { } }).element); - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "open-menu-tag", - detail: { - protyle, - element: tagElement, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-tag", + detail: { + protyle, + element: tagElement, + }, + separatorPosition: "top", + }); /// #if MOBILE window.siyuan.menus.menu.fullscreen(); diff --git a/app/src/menus/workspace.ts b/app/src/menus/workspace.ts index 707662035ea..7bf458e12e7 100644 --- a/app/src/menus/workspace.ts +++ b/app/src/menus/workspace.ts @@ -520,7 +520,7 @@ export const workspaceMenu = (app: App, rect: DOMRect) => { icon: "iconLock", accelerator: window.siyuan.config.keymap.general.lockScreen.custom, click: () => { - lockScreen(app); + lockScreen(); } }).element); window.siyuan.menus.menu.append(new MenuItem({ diff --git a/app/src/mobile/editor.ts b/app/src/mobile/editor.ts index c2491847097..29b246b04f5 100644 --- a/app/src/mobile/editor.ts +++ b/app/src/mobile/editor.ts @@ -14,6 +14,7 @@ import {setStorageVal} from "../protyle/util/compatibility"; import {showMessage} from "../dialog/message"; import {App} from "../index"; import {getDocByScroll, saveScroll} from "../protyle/scroll/saveScroll"; +import {emitToEventBus} from "../plugin/EventBus"; export const getCurrentEditor = () => { return window.siyuan.mobile.popEditor || window.siyuan.mobile.editor; @@ -91,9 +92,7 @@ export const openMobileFileById = (app: App, id: string, action: TProtyleAction[ scrollAttr: window.siyuan.storage[Constants.LOCAL_FILEPOSITION][data.data.rootID], mergedOptions: protyleOptions, cb() { - app.plugins.forEach(item => { - item.eventBus.emit("switch-protyle", {protyle: window.siyuan.mobile.editor.protyle}); - }); + emitToEventBus("switch-protyle", {protyle: window.siyuan.mobile.editor.protyle}); } }); } else { @@ -108,9 +107,7 @@ export const openMobileFileById = (app: App, id: string, action: TProtyleAction[ action, scrollPosition, afterCB() { - app.plugins.forEach(item => { - item.eventBus.emit("switch-protyle", {protyle: window.siyuan.mobile.editor.protyle}); - }); + emitToEventBus("switch-protyle", {protyle: window.siyuan.mobile.editor.protyle}); } }); }); diff --git a/app/src/mobile/index.ts b/app/src/mobile/index.ts index ea6a5520ad2..bbb22d56fda 100644 --- a/app/src/mobile/index.ts +++ b/app/src/mobile/index.ts @@ -38,6 +38,7 @@ import {processIOSPurchaseResponse} from "../util/iOSPurchase"; import {updateControlAlt} from "../protyle/util/hotKey"; import {nbsp2space} from "../protyle/util/normalizeText"; import {callMobileAppShowKeyboard, canInput, setWebViewFocusable} from "./util/mobileAppUtil"; +import {emitToEventBus, registerCustomEventBus} from "../plugin/EventBus"; class App { public plugins: import("../plugin").Plugin[] = []; @@ -69,14 +70,13 @@ class App { inbox: null, } }, + registerCustomEventBus, ws: new Model({ app: this, id: genUUID(), type: "main", msgCallback: (data) => { - this.plugins.forEach((plugin) => { - plugin.eventBus.emit("ws-main", data); - }); + emitToEventBus("ws-main", data); onMessage(this, data); } }) diff --git a/app/src/mobile/menu/index.ts b/app/src/mobile/menu/index.ts index 007e4044fba..138df416dbe 100644 --- a/app/src/mobile/menu/index.ts +++ b/app/src/mobile/menu/index.ts @@ -253,7 +253,7 @@ export const initRightMenu = (app: App) => { event.stopPropagation(); break; } else if (target.id === "menuLock") { - lockScreen(app); + lockScreen(); event.preventDefault(); event.stopPropagation(); break; diff --git a/app/src/mobile/util/MobileBackFoward.ts b/app/src/mobile/util/MobileBackFoward.ts index e8ccd8fda12..57343f0ff34 100644 --- a/app/src/mobile/util/MobileBackFoward.ts +++ b/app/src/mobile/util/MobileBackFoward.ts @@ -13,6 +13,7 @@ import {showMessage} from "../../dialog/message"; import {getCurrentEditor} from "../editor"; import {avRender} from "../../protyle/render/av/render"; import {setTitle} from "../../dialog/processSystem"; +import {emitToEventBus} from "../../plugin/EventBus"; const forwardStack: IBackStack[] = []; @@ -104,10 +105,8 @@ const focusStack = (backStack: IBackStack) => { protyle.contentElement.scrollTop = backStack.scrollTop; }, Constants.TIMEOUT_LOAD); - protyle.app.plugins.forEach(item => { - item.eventBus.emit("switch-protyle", {protyle}); - item.eventBus.emit("loaded-protyle-static", {protyle}); - }); + emitToEventBus("switch-protyle", {protyle}); + emitToEventBus("loaded-protyle-static", {protyle}); exitFocusElement.classList.add("fn__none"); }); }; diff --git a/app/src/mobile/util/keyboardToolbar.ts b/app/src/mobile/util/keyboardToolbar.ts index f4c020e8aed..65ac38df16a 100644 --- a/app/src/mobile/util/keyboardToolbar.ts +++ b/app/src/mobile/util/keyboardToolbar.ts @@ -15,6 +15,7 @@ import {softEnter} from "../../protyle/wysiwyg/enter"; import {isInAndroid, isInEdge, isInHarmony} from "../../protyle/util/compatibility"; import {tabCodeBlock} from "../../protyle/wysiwyg/codeBlock"; import {callMobileAppShowKeyboard, canInput, keyboardLockUntil} from "./mobileAppUtil"; +import {emitToEventBus} from "../../plugin/EventBus"; let renderKeyboardToolbarTimeout: number; let showUtil = false; @@ -426,9 +427,7 @@ export const showKeyboardToolbar = () => { if (editor.protyle.wysiwyg.element.contains(range.startContainer)) { editor.protyle.element.parentElement.style.paddingBottom = "48px"; } - editor.protyle.app.plugins.forEach(item => { - item.eventBus.emit("mobile-keyboard-show"); - }); + emitToEventBus("mobile-keyboard-show"); } setTimeout(() => { const contentElement = hasClosestByClassName(range.startContainer, "protyle-content", true); @@ -460,9 +459,7 @@ export const hideKeyboardToolbar = () => { const editor = getCurrentEditor(); if (editor) { editor.protyle.element.parentElement.style.paddingBottom = ""; - editor.protyle.app.plugins.forEach(item => { - item.eventBus.emit("mobile-keyboard-hide"); - }); + emitToEventBus("mobile-keyboard-hide"); } const modelElement = document.getElementById("model"); if (modelElement.style.transform === "translateY(0px)") { diff --git a/app/src/mobile/util/onMessage.ts b/app/src/mobile/util/onMessage.ts index fc42bc294d3..534ed19b825 100644 --- a/app/src/mobile/util/onMessage.ts +++ b/app/src/mobile/util/onMessage.ts @@ -69,22 +69,19 @@ export const onMessage = (app: App, data: IWebSocketData) => { case "readonly": window.siyuan.config.editor.readOnly = data.data; break; - case"progress": + case "progress": progressLoading(data); break; - case"syncing": - processSync(data, app.plugins); - if (data.code === 1) { - document.getElementById("toolbarSync").classList.add("fn__none"); - } + case "syncing": + processSync(data); break; case "openFileById": openMobileFileById(app, data.data.id); break; - case"txerr": + case "txerr": transactionError(); break; - case"statusbar": + case "statusbar": if (!document.querySelector("#keyboardToolbar").classList.contains("fn__none") || window.siyuan.config.appearance.hideStatusBar) { return; diff --git a/app/src/plugin/EventBus.ts b/app/src/plugin/EventBus.ts index 40ea3f9f800..bc6047ccaaf 100644 --- a/app/src/plugin/EventBus.ts +++ b/app/src/plugin/EventBus.ts @@ -1,40 +1,94 @@ import {MenuItem, subMenu} from "../menus/Menu"; +type ListenerEntry = { + type: TEventBus, + listener: (event: CustomEvent) => void, + /** 仅 once 注册时有值,触发后条目会从 listenerEntries 移除 */ + wrapper?: (event: CustomEvent) => void, +}; + export class EventBus { - private eventTarget: EventTarget; + private name: string; + private eventTarget: Comment; + private listenerEntries: ListenerEntry[] = []; constructor(name = "") { + this.name = name; this.eventTarget = document.appendChild(document.createComment(name)); } + emit(type: TEventBus, detail?: DetailType) { + return this.eventTarget.dispatchEvent(new CustomEvent(type, {detail, cancelable: true})); + } + on(type: TEventBus, listener: (event: CustomEvent) => void) { - this.eventTarget.addEventListener(type, listener); + if (this.listenerEntries.some((e) => e.type === type && e.listener === listener)) { + console.warn("[" + this.name + "] Listener already registered", {type, listener}); + return; + } + this.eventTarget.addEventListener(type, listener as EventListener); + this.listenerEntries.push({type, listener}); } once(type: TEventBus, listener: (event: CustomEvent) => void) { - this.eventTarget.addEventListener(type, listener, {once: true}); + if (this.listenerEntries.some((e) => e.type === type && e.listener === listener)) { + console.warn("[" + this.name + "] Listener already registered", {type, listener}); + return; + } + const wrapper = (event: CustomEvent) => { + listener(event); + const idx = this.listenerEntries.findIndex((e) => e.wrapper === wrapper); + if (idx !== -1) this.listenerEntries.splice(idx, 1); + }; + this.eventTarget.addEventListener(type, wrapper as EventListener, {once: true}); + this.listenerEntries.push({type, listener, wrapper}); } off(type: TEventBus, listener: (event: CustomEvent) => void) { - this.eventTarget.removeEventListener(type, listener); + const idx = this.listenerEntries.findIndex((e) => e.type === type && e.listener === listener); + if (idx !== -1) { + const entry = this.listenerEntries[idx]; + this.eventTarget.removeEventListener(type, (entry.wrapper ?? entry.listener) as EventListener); + this.listenerEntries.splice(idx, 1); + } } - emit(type: TEventBus, detail?: DetailType) { - return this.eventTarget.dispatchEvent(new CustomEvent(type, {detail, cancelable: true})); + // https://github.com/siyuan-note/siyuan/issues/16910 + destroy() { + this.listenerEntries.forEach(({type, listener, wrapper}) => { + this.eventTarget.removeEventListener(type, (wrapper ?? listener) as EventListener); + }); + this.listenerEntries.length = 0; // 清空数组避免外部引用 + this.eventTarget.remove(); + const idx = customEventBuses.indexOf(this); + if (idx !== -1) customEventBuses.splice(idx, 1); } } +export const emitToEventBus = (type: TEventBus, detail?: any) => { + // 避免在遍历过程中数组被修改导致插件被跳过 + const plugins = [...window.siyuan.ws.app.plugins]; + const buses = [...customEventBuses]; + plugins.forEach((p) => p.eventBus.emit(type, detail)); + buses.forEach((bus) => bus.emit(type, detail)); +}; + +export const customEventBuses: EventBus[] = []; + +export const registerCustomEventBus = (name?: string): EventBus => { + const bus = new EventBus(name ?? "custom-event-bus-" + (customEventBuses.length + 1)); + customEventBuses.push(bus); + return bus; +}; + export const emitOpenMenu = (options: { - plugins: import("./index").Plugin[], type: TEventBus, detail: any, separatorPosition?: "top" | "bottom", }) => { const pluginSubMenu = new subMenu(); options.detail.menu = pluginSubMenu; - options.plugins.forEach((plugin) => { - plugin.eventBus.emit(options.type, options.detail); - }); + emitToEventBus(options.type, options.detail); if (pluginSubMenu.menus.length > 0) { if (options.separatorPosition === "top") { window.siyuan.menus.menu.append(new MenuItem({id: "separator_pluginTop", type: "separator"}).element); diff --git a/app/src/plugin/uninstall.ts b/app/src/plugin/uninstall.ts index 0e19e4c3246..2bad79cb71c 100644 --- a/app/src/plugin/uninstall.ts +++ b/app/src/plugin/uninstall.ts @@ -70,12 +70,7 @@ export const uninstall = (app: App, name: string, isReload: boolean) => { }); /// #endif // rm listen - Array.from(document.childNodes).find(item => { - if (item.nodeType === 8 && item.textContent === name) { - item.remove(); - return true; - } - }); + plugin.eventBus.destroy(); // rm plugin app.plugins.splice(index, 1); // rm icons diff --git a/app/src/protyle/breadcrumb/index.ts b/app/src/protyle/breadcrumb/index.ts index 306da017882..7316fa50ef3 100644 --- a/app/src/protyle/breadcrumb/index.ts +++ b/app/src/protyle/breadcrumb/index.ts @@ -543,17 +543,14 @@ ${padHTML} }).element); } /// #endif - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "open-menu-breadcrumbmore", - detail: { - protyle, - data: response.data.stat, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-breadcrumbmore", + detail: { + protyle, + data: response.data.stat, + }, + separatorPosition: "top", + }); window.siyuan.menus.menu.append(new MenuItem({id: "separator_2", type: "separator"}).element); window.siyuan.menus.menu.append(new MenuItem({ id: "docInfo", diff --git a/app/src/protyle/gutter/index.ts b/app/src/protyle/gutter/index.ts index c0714d0dfae..3990f283f1c 100644 --- a/app/src/protyle/gutter/index.ts +++ b/app/src/protyle/gutter/index.ts @@ -923,17 +923,14 @@ export class Gutter { }).element); } - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "click-blockicon", - detail: { - protyle, - blockElements: selectsElement, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "click-blockicon", + detail: { + protyle, + blockElements: selectsElement, + }, + separatorPosition: "top", + }); return window.siyuan.menus.menu; } @@ -2116,17 +2113,14 @@ export class Gutter { window.siyuan.menus.menu.append(new MenuItem({id: "separator_5", type: "separator"}).element); } - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "click-blockicon", - detail: { - protyle, - blockElements: [nodeElement] - }, - separatorPosition: "bottom", - }); - } + emitOpenMenu({ + type: "click-blockicon", + detail: { + protyle, + blockElements: [nodeElement] + }, + separatorPosition: "bottom", + }); let updateHTML = nodeElement.getAttribute("updated") || ""; if (updateHTML) { diff --git a/app/src/protyle/header/openTitleMenu.ts b/app/src/protyle/header/openTitleMenu.ts index aac3caa81e3..fc3335c6b14 100644 --- a/app/src/protyle/header/openTitleMenu.ts +++ b/app/src/protyle/header/openTitleMenu.ts @@ -277,17 +277,14 @@ export const openTitleMenu = (protyle: IProtyle, position: IPosition, from: stri window.siyuan.menus.menu.append(exportMd(protyle.block.showAll ? protyle.block.id : protyle.block.rootID)); window.siyuan.menus.menu.append(new MenuItem({id: "separator_4", type: "separator"}).element); - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "click-editortitleicon", - detail: { - protyle, - data: response.data, - }, - separatorPosition: "bottom", - }); - } + emitOpenMenu({ + type: "click-editortitleicon", + detail: { + protyle, + data: response.data, + }, + separatorPosition: "bottom", + }); window.siyuan.menus.menu.append(new MenuItem({ id: "updateAndCreatedAt", iconHTML: "", diff --git a/app/src/protyle/render/av/action.ts b/app/src/protyle/render/av/action.ts index 783da905386..067d2cc69fb 100644 --- a/app/src/protyle/render/av/action.ts +++ b/app/src/protyle/render/av/action.ts @@ -756,18 +756,15 @@ ${window.siyuan.languages[avType === "table" ? "insertRowAfter" : "insertItemAft submenu: editAttrSubmenu }); } - if (protyle?.app?.plugins) { - emitOpenMenu({ - plugins: protyle.app.plugins, - type: "open-menu-av", - detail: { - protyle, - element: blockElement, - selectRowElements: rowElements, - }, - separatorPosition: "top", - }); - } + emitOpenMenu({ + type: "open-menu-av", + detail: { + protyle, + element: blockElement, + selectRowElements: rowElements, + }, + separatorPosition: "top", + }); menu.open(position); return true; }; diff --git a/app/src/protyle/toolbar/index.ts b/app/src/protyle/toolbar/index.ts index e92ea669b3e..225319eff31 100644 --- a/app/src/protyle/toolbar/index.ts +++ b/app/src/protyle/toolbar/index.ts @@ -46,6 +46,7 @@ import {confirmDialog} from "../../dialog/confirmDialog"; import {paste, pasteAsPlainText, pasteEscaped} from "../util/paste"; import {escapeHtml} from "../../util/escape"; import {resizeSide} from "../../history/resizeSide"; +import {emitToEventBus} from "../../plugin/EventBus"; export class Toolbar { public element: HTMLElement; @@ -1223,13 +1224,11 @@ export class Toolbar { if (!protyle.disabled) { textElement.select(); } - protyle.app.plugins.forEach(item => { - item.eventBus.emit("open-noneditableblock", { - protyle, - toolbar: this, - blockElement: nodeElement, - renderElement, - }); + emitToEventBus("open-noneditableblock", { + protyle, + toolbar: this, + blockElement: nodeElement, + renderElement, }); } @@ -1254,11 +1253,7 @@ export class Toolbar { let hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort(); const eventDetail = {languages: hljsLanguages, type: "init", listElement}; - if (protyle.app && protyle.app.plugins) { - protyle.app.plugins.forEach((plugin: any) => { - plugin.eventBus.emit("code-language-update", eventDetail); - }); - } + emitToEventBus("code-language-update", eventDetail); hljsLanguages = eventDetail.languages; hljsLanguages.forEach((item) => { @@ -1328,11 +1323,7 @@ export class Toolbar { } const eventDetail = {languages: value ? matchLanguages : hljsLanguages, type: "match", value, listElement}; - if (protyle.app && protyle.app.plugins) { - protyle.app.plugins.forEach((plugin: any) => { - plugin.eventBus.emit("code-language-update", eventDetail); - }); - } + emitToEventBus("code-language-update", eventDetail); matchLanguages = eventDetail.languages; if (value) { @@ -1807,15 +1798,11 @@ ${item.name} private updateLanguage(languageElements: HTMLElement[], protyle: IProtyle, selectedLang: string) { const currentLang = selectedLang === window.siyuan.languages.clear ? "" : selectedLang; - if (protyle.app && protyle.app.plugins) { - protyle.app.plugins.forEach((plugin: any) => { - plugin.eventBus.emit("code-language-change", { - language: currentLang, - languageElements, - protyle: protyle - }); - }); - } + emitToEventBus("code-language-change", { + language: currentLang, + languageElements, + protyle: protyle + }); if (!Constants.SIYUAN_RENDER_CODE_LANGUAGES.includes(currentLang)) { window.siyuan.storage[Constants.LOCAL_CODELANG] = currentLang; diff --git a/app/src/protyle/util/destroy.ts b/app/src/protyle/util/destroy.ts index a635e3ab759..04a933a033e 100644 --- a/app/src/protyle/util/destroy.ts +++ b/app/src/protyle/util/destroy.ts @@ -1,5 +1,6 @@ import {hideElements} from "../ui/hideElements"; import {isSupportCSSHL} from "../render/searchMarkRender"; +import {emitToEventBus} from "../../plugin/EventBus"; export const destroy = (protyle: IProtyle) => { if (!protyle) { @@ -29,9 +30,7 @@ export const destroy = (protyle: IProtyle) => { protyle.ws.send("closews", {}); }, 10240); } - protyle.app.plugins.forEach(item => { - item.eventBus.emit("destroy-protyle", { - protyle, - }); + emitToEventBus("destroy-protyle", { + protyle, }); }; diff --git a/app/src/protyle/util/onGet.ts b/app/src/protyle/util/onGet.ts index 783522e4ff3..c7b6277bfb3 100644 --- a/app/src/protyle/util/onGet.ts +++ b/app/src/protyle/util/onGet.ts @@ -20,6 +20,7 @@ import {hideTooltip} from "../../dialog/tooltip"; import {stickyRow} from "../render/av/row"; import {getContenteditableElement} from "../wysiwyg/getBlock"; import {activeBlur} from "../../mobile/util/keyboardToolbar"; +import {emitToEventBus} from "../../plugin/EventBus"; export const onGet = (options: { data: IWebSocketData, @@ -273,11 +274,9 @@ const setHTML = (options: { }); protyle.options.defIds = []; if (options.action.includes(Constants.CB_GET_APPEND) || options.action.includes(Constants.CB_GET_BEFORE)) { - protyle.app.plugins.forEach(item => { - item.eventBus.emit("loaded-protyle-dynamic", { - protyle, - position: options.action.includes(Constants.CB_GET_APPEND) ? "afterend" : "beforebegin" - }); + emitToEventBus("loaded-protyle-dynamic", { + protyle, + position: options.action.includes(Constants.CB_GET_APPEND) ? "afterend" : "beforebegin" }); return; } @@ -322,9 +321,7 @@ const setHTML = (options: { }); } - protyle.app.plugins.forEach(item => { - item.eventBus.emit("loaded-protyle-static", {protyle}); - }); + emitToEventBus("loaded-protyle-static", {protyle}); }; export const disabledForeverProtyle = (protyle: IProtyle) => { diff --git a/app/src/protyle/util/paste.ts b/app/src/protyle/util/paste.ts index 3ecaab890e9..7993dcdbbbe 100644 --- a/app/src/protyle/util/paste.ts +++ b/app/src/protyle/util/paste.ts @@ -17,6 +17,7 @@ import {getCalloutInfo, getContenteditableElement} from "../wysiwyg/getBlock"; import {clearBlockElement} from "./clear"; import {removeZWJ} from "./normalizeText"; import {base64ToURL} from "../../util/image"; +import {customEventBuses} from "../../plugin/EventBus"; export const getTextStar = (blockElement: HTMLElement, contentOnly = false) => { const dataType = blockElement.dataset.type; @@ -222,24 +223,26 @@ export const restoreLuteMarkdownSyntax = (protyle: IProtyle) => { }; const readLocalFile = async (protyle: IProtyle, localFiles: ILocalFiles[]) => { - if (protyle && protyle.app && protyle.app.plugins) { - for (let i = 0; i < protyle.app.plugins.length; i++) { - const response: { localFiles: ILocalFiles[] } = await new Promise((resolve) => { - const emitResult = protyle.app.plugins[i].eventBus.emit("paste", { - protyle, - resolve, - textHTML: "", - textPlain: "", - siyuanHTML: "", - localFiles - }); - if (emitResult) { - resolve(undefined); - } + const eventBuses = [ + ...protyle.app.plugins.map((p) => p.eventBus), + ...customEventBuses + ]; + for (let i = 0; i < eventBuses.length; i++) { + const response: { localFiles: ILocalFiles[] } = await new Promise((resolve) => { + const emitResult = eventBuses[i].emit("paste", { + protyle, + resolve, + textHTML: "", + textPlain: "", + siyuanHTML: "", + localFiles }); - if (response?.localFiles) { - localFiles = response.localFiles; + if (emitResult) { + resolve(undefined); } + }); + if (response?.localFiles) { + localFiles = response.localFiles; } } uploadLocalFiles(localFiles, protyle, true); @@ -323,34 +326,36 @@ export const paste = async (protyle: IProtyle, event: (ClipboardEvent | DragEven textHTML = Lute.Sanitize(textHTML); } - if (protyle && protyle.app && protyle.app.plugins) { - for (let i = 0; i < protyle.app.plugins.length; i++) { - const response: IObject & { files: FileList } = await new Promise((resolve) => { - const emitResult = protyle.app.plugins[i].eventBus.emit("paste", { - protyle, - resolve, - textHTML, - textPlain, - siyuanHTML, - files - }); - if (emitResult) { - resolve(undefined); - } + const eventBuses = [ + ...protyle.app.plugins.map((p) => p.eventBus), + ...customEventBuses + ]; + for (let i = 0; i < eventBuses.length; i++) { + const response: IObject & { files: FileList } = await new Promise((resolve) => { + const emitResult = eventBuses[i].emit("paste", { + protyle, + resolve, + textHTML, + textPlain, + siyuanHTML, + files }); - - if (response?.textHTML) { - textHTML = response.textHTML; - } - if (response?.textPlain) { - textPlain = response.textPlain; - } - if (response?.siyuanHTML) { - siyuanHTML = response.siyuanHTML; - } - if (response?.files) { - files = response.files as FileList; + if (emitResult) { + resolve(undefined); } + }); + + if (response?.textHTML) { + textHTML = response.textHTML; + } + if (response?.textPlain) { + textPlain = response.textPlain; + } + if (response?.siyuanHTML) { + siyuanHTML = response.siyuanHTML; + } + if (response?.files) { + files = response.files as FileList; } } diff --git a/app/src/protyle/util/setEditMode.ts b/app/src/protyle/util/setEditMode.ts index ad4792a7c1b..5afbe9aca5d 100644 --- a/app/src/protyle/util/setEditMode.ts +++ b/app/src/protyle/util/setEditMode.ts @@ -2,6 +2,7 @@ import {hideElements} from "../ui/hideElements"; import {getAllModels} from "../../layout/getAll"; import {updateOutline} from "../../editor/util"; import {resize} from "./resize"; +import {emitToEventBus} from "../../plugin/EventBus"; export const setEditMode = (protyle: IProtyle, type: TEditorMode) => { if (type === "preview") { @@ -38,7 +39,5 @@ export const setEditMode = (protyle: IProtyle, type: TEditorMode) => { resize(protyle); } hideElements(["gutterOnly", "toolbar", "select", "hint", "util"], protyle); - protyle.app.plugins.forEach(item => { - item.eventBus.emit("switch-protyle-mode", {protyle}); - }); + emitToEventBus("switch-protyle-mode", {protyle}); }; diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index 3ffc21d8b0e..3da6fecf66d 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -103,6 +103,7 @@ import {clearSelect} from "../util/clear"; import {chartRender} from "../render/chartRender"; import {updateCalloutType} from "./callout"; import {nbsp2space, removeZWJ} from "../util/normalizeText"; +import {emitToEventBus} from "../../plugin/EventBus"; export class WYSIWYG { public lastHTMLs: { [key: string]: string } = {}; @@ -2610,11 +2611,9 @@ export class WYSIWYG { this.preventClick = false; return; } - protyle.app.plugins.forEach(item => { - item.eventBus.emit("click-editorcontent", { - protyle, - event - }); + emitToEventBus("click-editorcontent", { + protyle, + event }); hideElements(["hint", "util"], protyle); const ctrlIsPressed = isOnlyMeta(event); diff --git a/app/src/search/util.ts b/app/src/search/util.ts index f0449096a40..94dfd397445 100644 --- a/app/src/search/util.ts +++ b/app/src/search/util.ts @@ -48,6 +48,7 @@ import {highlightById} from "../util/highlightById"; import {getSelectionOffset} from "../protyle/util/selection"; import {electronUndo} from "../protyle/undo"; import {getContenteditableElement} from "../protyle/wysiwyg/getBlock"; +import {emitToEventBus} from "../plugin/EventBus"; export const openGlobalSearch = (app: App, text: string, replace: boolean, searchData?: Config.IUILayoutTabSearchConfig) => { text = text.trim(); @@ -1308,12 +1309,10 @@ export const inputEvent = (element: Element, config: Config.IUILayoutTabSearchCo element.querySelector("#searchList").scrollTo(0, 0); const previousElement = element.querySelector('[data-type="previous"]'); const nextElement = element.querySelector('[data-type="next"]'); - edit.protyle?.app.plugins.forEach(item => { - item.eventBus.emit("input-search", { - protyle: edit, - config, - searchElement: searchInputElement, - }); + emitToEventBus("input-search", { + protyle: edit, + config, + searchElement: searchInputElement, }); const searchResultElement = element.querySelector("#searchResult"); if (config.query === "" && (!config.idPath || config.idPath.length === 0)) { diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index f682403f9fe..3a93a8e9402 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -571,6 +571,10 @@ interface ISiyuan { * 是否在发布服务下访问 */ isPublish?: boolean; + /** + * 供 JS 代码片段注册事件总线 + */ + registerCustomEventBus: (name?: string) => import("../plugin/EventBus").EventBus; } interface IOperation { diff --git a/app/src/window/index.ts b/app/src/window/index.ts index 5716e267b9c..075d84a19f0 100644 --- a/app/src/window/index.ts +++ b/app/src/window/index.ts @@ -29,6 +29,7 @@ import {reloadEmoji} from "../emoji"; import {updateControlAlt} from "../protyle/util/hotKey"; import {updateAppearance} from "../config/util/updateAppearance"; import {renderSnippet} from "../config/util/snippets"; +import {emitToEventBus, registerCustomEventBus} from "../plugin/EventBus"; class App { public plugins: import("../plugin").Plugin[] = []; @@ -48,14 +49,13 @@ class App { closedTabs: [], ctrlIsPressed: false, altIsPressed: false, + registerCustomEventBus, ws: new Model({ app: this, id: genUUID(), type: "main", msgCallback: (data) => { - this.plugins.forEach((plugin) => { - plugin.eventBus.emit("ws-main", data); - }); + emitToEventBus("ws-main", data); if (data) { switch (data.cmd) { case "setAppearance": @@ -143,7 +143,7 @@ class App { transactionError(); break; case "syncing": - processSync(data, this.plugins); + processSync(data); break; case "backgroundtask": progressBackgroundTask(data.data.tasks);