diff --git a/src/lib/acode.js b/src/lib/acode.js index ea024037e..fe195b2bc 100644 --- a/src/lib/acode.js +++ b/src/lib/acode.js @@ -44,6 +44,7 @@ import helpers from "utils/helpers"; import KeyboardEvent from "utils/keyboardEvent"; import Url from "utils/Url"; import constants from "./constants"; +import { onPluginLoadCallback, onPluginsLoadCompleteCallback, LOADED_PLUGINS } from "lib/loadPlugins"; export default class Acode { #modules = {}; @@ -62,6 +63,7 @@ export default class Acode { }, }, ]; + #pluginWatchers = {} constructor() { const encodingsModule = { @@ -78,7 +80,7 @@ export default class Acode { list: themes.list, update: themes.update, // Deprecated, not supported anymore - apply: () => {}, + apply: () => { }, }; const sidebarAppsModule = { @@ -285,21 +287,21 @@ export default class Acode { */ installPlugin(pluginId, installerPluginName) { return new Promise((resolve, reject) => { - confirm( - strings.install, - `Do you want to install plugin '${pluginId}'${installerPluginName ? ` requested by ${installerPluginName}` : ""}?`, - ) - .then((confirmation) => { - if (!confirmation) { - reject(new Error("User cancelled installation")); + fsOperation(Url.join(PLUGIN_DIR, pluginId)) + .exists() + .then((isPluginExists) => { + if (isPluginExists) { + reject(new Error("Plugin already installed")); return; } - fsOperation(Url.join(PLUGIN_DIR, pluginId)) - .exists() - .then((isPluginExists) => { - if (isPluginExists) { - reject(new Error("Plugin already installed")); + confirm( + strings.install, + `Do you want to install plugin '${pluginId}'${installerPluginName ? ` requested by ${installerPluginName}` : ""}?`, + ) + .then((confirmation) => { + if (!confirmation) { + reject(new Error("User cancelled installation")); return; } @@ -398,6 +400,31 @@ export default class Acode { }); } + [onPluginLoadCallback](pluginId) { + if (this.#pluginWatchers[pluginId]) { + this.#pluginWatchers[pluginId].resolve(); + delete this.#pluginWatchers[pluginId]; + } + } + + [onPluginsLoadCompleteCallback]() { + for (const key in this.#pluginWatchers) { + this.#pluginWatchers[key].reject(); + } + } + + waitForPlugin(pluginId) { + return new Promise((resolve, reject) => { + if (LOADED_PLUGINS.has(pluginId)) { + return resolve(true); + } + + this.#pluginWatchers[pluginId] = { + resolve, reject + } + }); + } + get exitAppMessage() { const numFiles = editorManager.hasUnsavedFiles(); if (numFiles) { diff --git a/src/lib/loadPlugins.js b/src/lib/loadPlugins.js index 415a15944..e1a51abb3 100644 --- a/src/lib/loadPlugins.js +++ b/src/lib/loadPlugins.js @@ -24,11 +24,15 @@ const THEME_IDENTIFIERS = new Set([ "acode.plugin.extra_syntax_highlights", ]); +export const onPluginLoadCallback = Symbol("onPluginLoadCallback"); +export const onPluginsLoadCompleteCallback = Symbol("onPluginsLoadCompleteCallback"); + +export const LOADED_PLUGINS = new Set(); + export default async function loadPlugins(loadOnlyTheme = false) { const plugins = await fsOperation(PLUGIN_DIR).lsDir(); const results = []; const failedPlugins = []; - const loadedPlugins = new Set(); if (plugins.length > 0) { toast(strings["loading plugins"]); @@ -42,7 +46,7 @@ export default async function loadPlugins(loadOnlyTheme = false) { // Only load theme plugins matching current theme pluginsToLoad = plugins.filter((pluginDir) => { const pluginId = Url.basename(pluginDir.url); - return isThemePlugin(pluginId) && !loadedPlugins.has(pluginId); + return isThemePlugin(pluginId) && !LOADED_PLUGINS.has(pluginId); }); } else { // Load non-theme plugins that aren't loaded yet and are enabled @@ -50,7 +54,7 @@ export default async function loadPlugins(loadOnlyTheme = false) { const pluginId = Url.basename(pluginDir.url); return ( !isThemePlugin(pluginId) && - !loadedPlugins.has(pluginId) && + !LOADED_PLUGINS.has(pluginId) && enabledMap[pluginId] !== true ); }); @@ -74,7 +78,10 @@ export default async function loadPlugins(loadOnlyTheme = false) { try { await loadPlugin(pluginId); - loadedPlugins.add(pluginId); + LOADED_PLUGINS.add(pluginId); + + acode[onPluginLoadCallback](pluginId); + results.push(true); } catch (error) { console.error(`Error loading plugin ${pluginId}:`, error); @@ -82,9 +89,11 @@ export default async function loadPlugins(loadOnlyTheme = false) { results.push(false); } }); - + await Promise.allSettled(loadPromises); + acode[onPluginsLoadCompleteCallback](pluginId); + if (failedPlugins.length > 0) { setTimeout(() => { cleanupFailedPlugins(failedPlugins).catch((error) => { @@ -95,6 +104,7 @@ export default async function loadPlugins(loadOnlyTheme = false) { return results.filter(Boolean).length; } + function isThemePlugin(pluginId) { // Convert to lowercase for case-insensitive matching const id = pluginId.toLowerCase();