From 2fa7bce535beb783c4b79b7adb8a71b0a9826d66 Mon Sep 17 00:00:00 2001 From: 4Source <38220764+4Source@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:02:22 +0100 Subject: [PATCH 1/5] Update License year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 98458a9..85ab9f7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 4Source +Copyright (c) 2025 4Source Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a7adffdac5ed7d8269a21a7f6ee56498f12940a4 Mon Sep 17 00:00:00 2001 From: 4Source <38220764+4Source@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:22:52 +0100 Subject: [PATCH 2/5] Use monkey-around to patch functions --- package-lock.json | 11 ++- package.json | 3 +- src/constants.ts | 6 ++ src/main.ts | 182 +++++++++++++++------------------------------- 4 files changed, 76 insertions(+), 126 deletions(-) create mode 100644 src/constants.ts diff --git a/package-lock.json b/package-lock.json index 062c23d..c0b62eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "favorites", - "version": "0.0.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "favorites", - "version": "0.0.1", + "version": "1.0.0", "license": "MIT", "devDependencies": { "@stylistic/eslint-plugin": "^3.1.0", @@ -19,6 +19,7 @@ "eslint": "^8.57.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "monkey-around": "^3.0.0", "obsidian": "latest", "semver": "^7.6.0", "ts-jest": "^29.1.2", @@ -5048,6 +5049,12 @@ "node": "*" } }, + "node_modules/monkey-around": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/monkey-around/-/monkey-around-3.0.0.tgz", + "integrity": "sha512-jL6uY2lEAoaHxZep1cNRkCZjoIWY4g5VYCjriEWmcyHU7w8NU1+JH57xE251UVTohK0lCxMjv0ZV4ByDLIXEpw==", + "dev": true + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index efb9898..2177e11 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "eslint": "^8.57.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "monkey-around": "^3.0.0", "obsidian": "latest", "semver": "^7.6.0", "ts-jest": "^29.1.2", @@ -33,4 +34,4 @@ "typescript": "4.7.4" }, "version": "1.0.0" -} \ No newline at end of file +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..d673d01 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,6 @@ +export const PLUGIN_ID = 'favorite'; +export const MONKEY_KEY_MODAL_OPEN = `${PLUGIN_ID}-Modal-open`; +export const MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS = `${PLUGIN_ID}-CommunityPluginModal-updateItems`; +export const MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS = `${PLUGIN_ID}-CommunityPluginModal-showItems`; +export const MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS = `${PLUGIN_ID}-ThemePluginModal-updateItems`; +export const MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS = `${PLUGIN_ID}-ThemePluginModal-showItems`; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 58fae08..27a57d1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,44 +1,39 @@ import { CommunityItem, CommunityModal, Modal, Plugin } from 'obsidian'; +import { dedupe, around } from 'monkey-around'; +import { MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_MODAL_OPEN, MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS } from './constants'; export default class FavoritesPlugin extends Plugin { favoritePlugins?: string[]; favoriteThemes?: string[]; communityPluginModalPrototype?: CommunityModal; communityThemesModalPrototype?: CommunityModal; + uninstallModalOpen: () => void; + uninstallPluginBrowserModalUpdateItems: () => void; + uninstallPluginBrowserModalShowItem: () => void; + uninstallThemeBrowserModalUpdateItems: () => void; + uninstallThemeBrowserModalShowItem: () => void; async onload() { await this.loadSettings(); - if (!Modal.prototype.originalOpen) { - Modal.prototype.originalOpen = Modal.prototype.open; - const originalModalOpen = Modal.prototype.open; + // eslint-disable-next-line @typescript-eslint/no-this-alias + const plugin = this; - Modal.prototype.open = (function (originalModalOpen, plugin) { - return function () { - originalModalOpen?.apply(this); + this.uninstallModalOpen = around(Modal.prototype, { + open(oldMethod) { + return dedupe(MONKEY_KEY_MODAL_OPEN, oldMethod, function (...args) { + const result = oldMethod && oldMethod.apply(this, args); // Check the opened Modal is the Community plugin browse modal if (this.containerEl?.querySelector('.mod-community-plugin')) { - // Store the Community Plugin Browse Modal prototype to be able to reset to the original - plugin.communityPluginModalPrototype = this.constructor.prototype; - - // Check the plugin updateItems is already overwritten - if (!this.constructor.prototype.originalUpdateItems) { - // Save original method - this.constructor.prototype.originalUpdateItems = this.constructor.prototype.updateItems; - const originalUpdateItems = this.constructor.prototype.updateItems; - if (!this.constructor.prototype.originalUpdateItems) { - console.error('Favorites plugin: Failed to access original updateItems method!'); - return; - } - - // Overwrite method with additional functionality - this.constructor.prototype.updateItems = (function (originalUpdateItems, plugin) { - return function () { + // Patch updateItems method + plugin.uninstallPluginBrowserModalUpdateItems = around(this.constructor.prototype, { + updateItems(oldMethod) { + return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { // Load the favorite plugins plugin.favoritePlugins = JSON.parse(localStorage.getItem('favorite-plugins') || '[]'); - const result = originalUpdateItems.apply(this); + const result = oldMethod && oldMethod.apply(this, args); // Add to the favorite plugins a tag to visualize it for the user if (plugin.favoritePlugins) { @@ -53,27 +48,18 @@ export default class FavoritesPlugin extends Plugin { } return result; - }; - })(originalUpdateItems, plugin); - } + }); + }, + }); - // Check the plugin showItem is already overwritten - if (!this.constructor.prototype.originalShowItem) { - // Save original method - this.constructor.prototype.originalShowItem = this.constructor.prototype.showItem; - const originalShowItem = this.constructor.prototype.showItem; - if (!this.constructor.prototype.originalShowItem) { - console.error('Favorites: Failed to access original showItem method!'); - return; - } - - // Overwrite method with additional functionality - this.constructor.prototype.showItem = (function (originalShowItem, plugin) { - return function (...args: CommunityItem[]) { + // Patch showItem method + plugin.uninstallPluginBrowserModalShowItem = around(this.constructor.prototype, { + showItem(oldMethod) { + return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { // Load the favorite plugins plugin.favoritePlugins = JSON.parse(localStorage.getItem('favorite-plugins') || '[]'); - return originalShowItem.apply(this, args).then(async (r: void) => { + return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0]; const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0]; const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0]; @@ -108,33 +94,21 @@ export default class FavoritesPlugin extends Plugin { return r; }); - }; - })(originalShowItem, plugin); - } + }); + }, + }); } // Check the opened Modal is the Community themes browse modal if (this.containerEl?.querySelector('.mod-community-theme')) { - // Store the Community Theme Browse Modal prototype to be able to reset to the original - plugin.communityThemesModalPrototype = this.constructor.prototype; - - // Check the theme updateItems is already overwritten - if (!this.constructor.prototype.originalUpdateItems) { - // Save original method - this.constructor.prototype.originalUpdateItems = this.constructor.prototype.updateItems; - const originalUpdateItems = this.constructor.prototype.updateItems; - if (!this.constructor.prototype.originalUpdateItems) { - console.error('Favorites: Failed to access original updateItems method!'); - return; - } - - // Overwrite method with additional functionality - this.constructor.prototype.updateItems = (function (originalUpdateItems, plugin) { - return function () { + // Patch updateItems method + plugin.uninstallThemeBrowserModalUpdateItems = around(this.constructor.prototype, { + updateItems(oldMethod) { + return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { // Load the favorite themes plugin.favoriteThemes = JSON.parse(localStorage.getItem('favorite-themes') || '[]'); - const result = originalUpdateItems.apply(this); + const result = oldMethod && oldMethod.apply(this, args); // Ignore the default theme if (this.selectedItemId === '') { @@ -154,27 +128,18 @@ export default class FavoritesPlugin extends Plugin { } return result; - }; - })(originalUpdateItems, plugin); - } + }); + }, + }); - // Check the theme showItem is already overwritten - if (!this.constructor.prototype.originalShowItem) { - // Save original method - this.constructor.prototype.originalShowItem = this.constructor.prototype.showItem; - const originalShowItem = this.constructor.prototype.showItem; - if (!this.constructor.prototype.originalShowItem) { - console.error('Favorites: Failed to access original showItem method!'); - return; - } + // Patch showItems method + plugin.uninstallThemeBrowserModalShowItem = around(this.constructor.prototype, { + showItem(oldMethod) { + return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { + // Load the favorite themes + plugin.favoriteThemes = JSON.parse(localStorage.getItem('favorite-themes') || '[]'); - // Overwrite method with additional functionality - this.constructor.prototype.showItem = (function (originalShowItem, plugin) { - // Load the favorite themes - plugin.favoriteThemes = JSON.parse(localStorage.getItem('favorite-themes') || '[]'); - - return function (...args: CommunityItem[]) { - return originalShowItem.apply(this, args).then(async (r: void) => { + return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { // Ignore the default theme if (this.selectedItemId === '') { return; @@ -214,22 +179,17 @@ export default class FavoritesPlugin extends Plugin { return r; }); - }; - })(originalShowItem, plugin); - } + }); + }, + }); } - // Restore the original Modal open function as soon as the CommunityPluginModal and CommunityThemesModal is opened once - if (Modal.prototype.originalOpen && plugin.communityPluginModalPrototype && plugin.communityThemesModalPrototype) { - Modal.prototype.open = Modal.prototype.originalOpen; - delete Modal.prototype.originalOpen; - } - }; - })(originalModalOpen, this); - } - else { - console.warn('Favorites plugin: Modal prototype already has originalOpen, this is not intended!'); - } + return result; + }); + }, + }); + + // TODO: Redraw modals if local storage 'favorite-plugins' or 'favorite-themes' changed /* * TODO: Add command to save and reload the favorites list @@ -239,36 +199,12 @@ export default class FavoritesPlugin extends Plugin { } async onunload() { - // Restore the original Modal open function - - if (Modal.prototype.originalOpen) { - Modal.prototype.open = Modal.prototype.originalOpen; - delete Modal.prototype.originalOpen; - } - - // Restore the original plugin updateItems function - if (this.communityPluginModalPrototype && this.communityPluginModalPrototype.originalUpdateItems) { - this.communityPluginModalPrototype.updateItems = this.communityPluginModalPrototype.originalUpdateItems; - delete this.communityPluginModalPrototype.originalUpdateItems; - } - - // Restore the original plugin showItem function - if (this.communityPluginModalPrototype && this.communityPluginModalPrototype.originalShowItem) { - this.communityPluginModalPrototype.showItem = this.communityPluginModalPrototype.originalShowItem; - delete this.communityPluginModalPrototype.originalShowItem; - } - - // Restore the original theme updateItems function - if (this.communityThemesModalPrototype && this.communityThemesModalPrototype.originalUpdateItems) { - this.communityThemesModalPrototype.updateItems = this.communityThemesModalPrototype.originalUpdateItems; - delete this.communityThemesModalPrototype.originalUpdateItems; - } - - // Restore the original theme showItem function - if (this.communityThemesModalPrototype && this.communityThemesModalPrototype.originalShowItem) { - this.communityThemesModalPrototype.showItem = this.communityThemesModalPrototype.originalShowItem; - delete this.communityThemesModalPrototype.originalShowItem; - } + // Restore the original functions + this.uninstallModalOpen(); + this.uninstallPluginBrowserModalUpdateItems(); + this.uninstallPluginBrowserModalShowItem(); + this.uninstallThemeBrowserModalUpdateItems(); + this.uninstallThemeBrowserModalShowItem(); } async loadSettings() { From 251efe585aea1d7b311ea298a67d55007f7a3aef Mon Sep 17 00:00:00 2001 From: 4Source <38220764+4Source@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:55:59 +0100 Subject: [PATCH 3/5] Depending on build type localStorage key --- esbuild.config.mjs | 4 ++++ src/main.ts | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index be2cafe..9557da9 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -38,6 +38,10 @@ const context = await esbuild.context({ sourcemap: prod ? false : "inline", treeShaking: true, outfile: "main.js", + define: { + 'process.env.FAVORITE_PLUGINS_KEY': JSON.stringify(prod ? 'favorite-plugins' : 'dev-favorite-plugins'), + 'process.env.FAVORITE_THEMES_KEY': JSON.stringify(prod ? 'favorite-themes' : 'dev-favorite-themes') + } }); if (prod) { diff --git a/src/main.ts b/src/main.ts index 27a57d1..b51a63e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,8 @@ import { dedupe, around } from 'monkey-around'; import { MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_MODAL_OPEN, MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS } from './constants'; export default class FavoritesPlugin extends Plugin { + pluginsKey: string; + themesKey: string; favoritePlugins?: string[]; favoriteThemes?: string[]; communityPluginModalPrototype?: CommunityModal; @@ -16,6 +18,10 @@ export default class FavoritesPlugin extends Plugin { async onload() { await this.loadSettings(); + this.pluginsKey = process.env.FAVORITE_PLUGINS_KEY || ""; + this.themesKey = process.env.FAVORITE_THEMES_KEY || ""; + console.log(this.pluginsKey, this.themesKey) + // eslint-disable-next-line @typescript-eslint/no-this-alias const plugin = this; @@ -31,9 +37,9 @@ export default class FavoritesPlugin extends Plugin { updateItems(oldMethod) { return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { // Load the favorite plugins - plugin.favoritePlugins = JSON.parse(localStorage.getItem('favorite-plugins') || '[]'); + plugin.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]'); - const result = oldMethod && oldMethod.apply(this, args); + const result = oldMethod && oldMethod.apply(this); // Add to the favorite plugins a tag to visualize it for the user if (plugin.favoritePlugins) { @@ -57,7 +63,7 @@ export default class FavoritesPlugin extends Plugin { showItem(oldMethod) { return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { // Load the favorite plugins - plugin.favoritePlugins = JSON.parse(localStorage.getItem('favorite-plugins') || '[]'); + plugin.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]'); return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0]; @@ -106,9 +112,9 @@ export default class FavoritesPlugin extends Plugin { updateItems(oldMethod) { return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { // Load the favorite themes - plugin.favoriteThemes = JSON.parse(localStorage.getItem('favorite-themes') || '[]'); + plugin.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); - const result = oldMethod && oldMethod.apply(this, args); + const result = oldMethod && oldMethod.apply(this); // Ignore the default theme if (this.selectedItemId === '') { @@ -137,7 +143,7 @@ export default class FavoritesPlugin extends Plugin { showItem(oldMethod) { return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { // Load the favorite themes - plugin.favoriteThemes = JSON.parse(localStorage.getItem('favorite-themes') || '[]'); + plugin.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { // Ignore the default theme @@ -214,19 +220,19 @@ export default class FavoritesPlugin extends Plugin { async saveSettings() { if (this.favoritePlugins) { if (this.favoritePlugins.length > 0) { - localStorage.setItem('favorite-plugins', JSON.stringify(this.favoritePlugins)); + localStorage.setItem(this.pluginsKey, JSON.stringify(this.favoritePlugins)); } else { - localStorage.removeItem('favorite-plugins'); + localStorage.removeItem(this.pluginsKey); } } if (this.favoriteThemes) { if (this.favoriteThemes.length > 0) { - localStorage.setItem('favorite-themes', JSON.stringify(this.favoriteThemes)); + localStorage.setItem(this.themesKey, JSON.stringify(this.favoriteThemes)); } else { - localStorage.removeItem('favorite-themes'); + localStorage.removeItem(this.themesKey); } } } From 43e9da8dab944c36612bb292444e8f9c34f7e1ec Mon Sep 17 00:00:00 2001 From: 4Source <38220764+4Source@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:03:41 +0100 Subject: [PATCH 4/5] Add debug messages --- src/main.ts | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main.ts b/src/main.ts index b51a63e..ae54457 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,17 +25,21 @@ export default class FavoritesPlugin extends Plugin { // eslint-disable-next-line @typescript-eslint/no-this-alias const plugin = this; + console.debug('Patch Modal.open') this.uninstallModalOpen = around(Modal.prototype, { open(oldMethod) { return dedupe(MONKEY_KEY_MODAL_OPEN, oldMethod, function (...args) { + console.debug('Call Modal.open') const result = oldMethod && oldMethod.apply(this, args); // Check the opened Modal is the Community plugin browse modal if (this.containerEl?.querySelector('.mod-community-plugin')) { // Patch updateItems method + console.debug('Patch PluginBrowserModal.updateItems') plugin.uninstallPluginBrowserModalUpdateItems = around(this.constructor.prototype, { updateItems(oldMethod) { return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { + console.debug('Call PluginBrowserModal.updateItems') // Load the favorite plugins plugin.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]'); @@ -59,9 +63,11 @@ export default class FavoritesPlugin extends Plugin { }); // Patch showItem method + console.debug('Patch PluginBrowserModal.showItem') plugin.uninstallPluginBrowserModalShowItem = around(this.constructor.prototype, { showItem(oldMethod) { return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { + console.debug('Call PluginBrowserModal.showItem') // Load the favorite plugins plugin.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]'); @@ -83,6 +89,7 @@ export default class FavoritesPlugin extends Plugin { } plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { + console.log('PluginBrowserModal clicked Favorite') if (isFavorite) { plugin.favoritePlugins?.remove(this.selectedItemId); } @@ -108,9 +115,11 @@ export default class FavoritesPlugin extends Plugin { // Check the opened Modal is the Community themes browse modal if (this.containerEl?.querySelector('.mod-community-theme')) { // Patch updateItems method + console.debug('Patch ThemeBrowserModal.updateItems') plugin.uninstallThemeBrowserModalUpdateItems = around(this.constructor.prototype, { updateItems(oldMethod) { return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { + console.debug('Call ThemeBrowserModal.updateItems') // Load the favorite themes plugin.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); @@ -139,9 +148,11 @@ export default class FavoritesPlugin extends Plugin { }); // Patch showItems method + console.debug('Patch ThemeBrowserModal.showItem') plugin.uninstallThemeBrowserModalShowItem = around(this.constructor.prototype, { showItem(oldMethod) { return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { + console.debug('Call ThemeBrowserModal.showItem') // Load the favorite themes plugin.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); @@ -168,6 +179,7 @@ export default class FavoritesPlugin extends Plugin { } plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { + console.log('ThemeBrowserModal clicked Favorite') if (isFavorite) { plugin.favoriteThemes?.remove(this.selectedItemId); } @@ -206,11 +218,26 @@ export default class FavoritesPlugin extends Plugin { async onunload() { // Restore the original functions - this.uninstallModalOpen(); - this.uninstallPluginBrowserModalUpdateItems(); - this.uninstallPluginBrowserModalShowItem(); - this.uninstallThemeBrowserModalUpdateItems(); - this.uninstallThemeBrowserModalShowItem(); + if (this.uninstallModalOpen) { + console.debug('Uninstall Modal.open') + this.uninstallModalOpen(); + } + if (this.uninstallPluginBrowserModalUpdateItems) { + console.debug('Uninstall PluginBrowserModal.updateItem') + this.uninstallPluginBrowserModalUpdateItems(); + } + if (this.uninstallPluginBrowserModalShowItem) { + console.debug('Uninstall PluginBrowserModal.showItem') + this.uninstallPluginBrowserModalShowItem(); + } + if (this.uninstallThemeBrowserModalUpdateItems) { + console.debug('Uninstall ThemeBrowserModal.updateItem') + this.uninstallThemeBrowserModalUpdateItems(); + } + if (this.uninstallThemeBrowserModalShowItem) { + console.debug('Uninstall ThemeBrowserModal.showItem') + this.uninstallThemeBrowserModalShowItem(); + } } async loadSettings() { From 1113ceab27060b6d3c431b4ae932f927385626a9 Mon Sep 17 00:00:00 2001 From: 4Source <38220764+4Source@users.noreply.github.com> Date: Fri, 31 Oct 2025 17:41:21 +0100 Subject: [PATCH 5/5] Fix uninstall --- src/main.ts | 402 +++++++++++++++++++++++++++++----------------------- 1 file changed, 222 insertions(+), 180 deletions(-) diff --git a/src/main.ts b/src/main.ts index ae54457..da922f5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,203 +9,235 @@ export default class FavoritesPlugin extends Plugin { favoriteThemes?: string[]; communityPluginModalPrototype?: CommunityModal; communityThemesModalPrototype?: CommunityModal; - uninstallModalOpen: () => void; - uninstallPluginBrowserModalUpdateItems: () => void; - uninstallPluginBrowserModalShowItem: () => void; - uninstallThemeBrowserModalUpdateItems: () => void; - uninstallThemeBrowserModalShowItem: () => void; + uninstallModalOpen?: () => void; + uninstallPluginBrowserModalUpdateItems?: () => void; + uninstallPluginBrowserModalShowItem?: () => void; + uninstallThemeBrowserModalUpdateItems?: () => void; + uninstallThemeBrowserModalShowItem?: () => void; async onload() { await this.loadSettings(); - this.pluginsKey = process.env.FAVORITE_PLUGINS_KEY || ""; - this.themesKey = process.env.FAVORITE_THEMES_KEY || ""; - console.log(this.pluginsKey, this.themesKey) + this.pluginsKey = process.env.FAVORITE_PLUGINS_KEY || ''; + this.themesKey = process.env.FAVORITE_THEMES_KEY || ''; + console.debug(`Plugins key: ${this.pluginsKey} Themes key: ${this.themesKey}`); // eslint-disable-next-line @typescript-eslint/no-this-alias const plugin = this; - console.debug('Patch Modal.open') - this.uninstallModalOpen = around(Modal.prototype, { - open(oldMethod) { - return dedupe(MONKEY_KEY_MODAL_OPEN, oldMethod, function (...args) { - console.debug('Call Modal.open') - const result = oldMethod && oldMethod.apply(this, args); + if (!this.uninstallModalOpen) { + console.debug('Patch Modal.open'); + this.uninstallModalOpen = around(Modal.prototype, { + open(oldMethod) { + return dedupe(MONKEY_KEY_MODAL_OPEN, oldMethod, function (...args) { + console.debug('Call Modal.open'); + const result = oldMethod && oldMethod.apply(this, args); - // Check the opened Modal is the Community plugin browse modal - if (this.containerEl?.querySelector('.mod-community-plugin')) { - // Patch updateItems method - console.debug('Patch PluginBrowserModal.updateItems') - plugin.uninstallPluginBrowserModalUpdateItems = around(this.constructor.prototype, { - updateItems(oldMethod) { - return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { - console.debug('Call PluginBrowserModal.updateItems') - // Load the favorite plugins - plugin.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]'); + // Check the opened Modal is the Community plugin browse modal + if (this.containerEl?.querySelector('.mod-community-plugin')) { + // Patch updateItems method + if (!plugin.uninstallPluginBrowserModalUpdateItems) { + console.debug('Patch PluginBrowserModal.updateItems'); + plugin.uninstallPluginBrowserModalUpdateItems = around(this.constructor.prototype, { + updateItems(oldMethod) { + return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { + console.debug('Call PluginBrowserModal.updateItems'); - const result = oldMethod && oldMethod.apply(this); + // Load the favorite plugins + plugin.favoritePlugins = JSON.parse(localStorage.getItem(plugin.pluginsKey) || '[]'); - // Add to the favorite plugins a tag to visualize it for the user - if (plugin.favoritePlugins) { - plugin.favoritePlugins.forEach(id => { - if (this.items && this.items[id]?.nameEl) { - this.items[id].nameEl.createSpan({ - cls: 'flair', - text: 'favorite', + const result = oldMethod && oldMethod.apply(this); + + // Add to the favorite plugins a tag to visualize it for the user + if (plugin.favoritePlugins) { + plugin.favoritePlugins.forEach(id => { + if (this.items && this.items[id]?.nameEl) { + this.items[id].nameEl.createSpan({ + cls: 'flair', + text: 'favorite', + }); + } }); } + + return result; }); - } - - return result; + }, }); - }, - }); + } + else { + console.warn('PluginBrowserModal.updateItems already patched!'); + } - // Patch showItem method - console.debug('Patch PluginBrowserModal.showItem') - plugin.uninstallPluginBrowserModalShowItem = around(this.constructor.prototype, { - showItem(oldMethod) { - return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { - console.debug('Call PluginBrowserModal.showItem') - // Load the favorite plugins - plugin.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]'); + // Patch showItem method + if (!plugin.uninstallPluginBrowserModalShowItem) { + console.debug('Patch PluginBrowserModal.showItem'); + plugin.uninstallPluginBrowserModalShowItem = around(this.constructor.prototype, { + showItem(oldMethod) { + return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { + console.debug('Call PluginBrowserModal.showItem'); - return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { - const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0]; - const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0]; - const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0]; + // Load the favorite plugins + plugin.favoritePlugins = JSON.parse(localStorage.getItem(plugin.pluginsKey) || '[]'); - const isFavorite = plugin.favoritePlugins?.contains(this.selectedItemId); + return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { + const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0]; + const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0]; + const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0]; - // Add to the favorite plugins a tag to visualize it for the user - if (isFavorite) { - if (infoNameEl) { - infoNameEl.createSpan({ - cls: 'flair', - text: 'favorite', - }); - } - } + const isFavorite = plugin.favoritePlugins?.contains(this.selectedItemId); - plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { - console.log('PluginBrowserModal clicked Favorite') - if (isFavorite) { - plugin.favoritePlugins?.remove(this.selectedItemId); - } - else { - plugin.favoritePlugins?.push(this.selectedItemId); - } + // Add to the favorite plugins a tag to visualize it for the user + if (isFavorite) { + if (infoNameEl) { + infoNameEl.createSpan({ + cls: 'flair', + text: 'favorite', + }); + } + } - plugin.saveSettings(); + plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { + if (isFavorite) { + plugin.favoritePlugins?.remove(this.selectedItemId); + } + else { + plugin.favoritePlugins?.push(this.selectedItemId); + } - // Redraw - infoEl.detach(); - this.showItem(this.items[this.selectedItemId]); - this.update(); - })); + plugin.saveSettings(); - return r; - }); - }); - }, - }); - } + // Redraw + infoEl.detach(); + this.showItem(this.items[this.selectedItemId]); + this.update(); + })); - // Check the opened Modal is the Community themes browse modal - if (this.containerEl?.querySelector('.mod-community-theme')) { - // Patch updateItems method - console.debug('Patch ThemeBrowserModal.updateItems') - plugin.uninstallThemeBrowserModalUpdateItems = around(this.constructor.prototype, { - updateItems(oldMethod) { - return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { - console.debug('Call ThemeBrowserModal.updateItems') - // Load the favorite themes - plugin.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); - - const result = oldMethod && oldMethod.apply(this); - - // Ignore the default theme - if (this.selectedItemId === '') { - return; - } - - // Add to the favorite themes a tag to visualize it for the user - if (plugin.favoriteThemes) { - plugin.favoriteThemes.forEach(id => { - if (this.items && this.items[id]?.nameEl) { - this.items[id].nameEl.createSpan({ - cls: 'flair', - text: 'favorite', - }); - } + return r; + }); }); - } - - return result; + }, }); - }, - }); + } + else { + console.warn('PluginBrowserModal.showItem already patched!'); + } + } - // Patch showItems method - console.debug('Patch ThemeBrowserModal.showItem') - plugin.uninstallThemeBrowserModalShowItem = around(this.constructor.prototype, { - showItem(oldMethod) { - return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { - console.debug('Call ThemeBrowserModal.showItem') - // Load the favorite themes - plugin.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); + // Check the opened Modal is the Community themes browse modal + if (this.containerEl?.querySelector('.mod-community-theme')) { + // Patch updateItems method + if (!plugin.uninstallThemeBrowserModalUpdateItems) { + console.debug('Patch ThemeBrowserModal.updateItems'); + plugin.uninstallThemeBrowserModalUpdateItems = around(this.constructor.prototype, { + updateItems(oldMethod) { + return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () { + console.debug('Call ThemeBrowserModal.updateItems'); - return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { - // Ignore the default theme - if (this.selectedItemId === '') { - return; - } + // Load the favorite themes + plugin.favoriteThemes = JSON.parse(localStorage.getItem(plugin.themesKey) || '[]'); - const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0]; - const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0]; - const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0]; + const result = oldMethod && oldMethod.apply(this); - const isFavorite = plugin.favoriteThemes?.contains(this.selectedItemId); + // Ignore the default theme + if (this.selectedItemId === '') { + return result; + } - // Add to the favorite themes a tag to visualize it for the user - if (isFavorite) { - if (infoNameEl) { - infoNameEl.createSpan({ - cls: 'flair', - text: 'favorite', + // Add to the favorite themes a tag to visualize it for the user + if (plugin.favoriteThemes) { + plugin.favoriteThemes.forEach(id => { + if (this.items && this.items[id]?.nameEl) { + this.items[id].nameEl.createSpan({ + cls: 'flair', + text: 'favorite', + }); + } }); } - } - plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { - console.log('ThemeBrowserModal clicked Favorite') - if (isFavorite) { - plugin.favoriteThemes?.remove(this.selectedItemId); - } - else { - plugin.favoriteThemes?.push(this.selectedItemId); - } - - plugin.saveSettings(); - - // Redraw - infoEl.detach(); - this.showItem(this.items[this.selectedItemId]); - this.update(); - })); - - return r; - }); + return result; + }); + }, }); - }, - }); - } + } + else { + console.warn('ThemeBrowserModal.updateItems already patched!'); + } - return result; - }); - }, - }); + // Patch showItems method + if (!plugin.uninstallThemeBrowserModalShowItem) { + console.debug('Patch ThemeBrowserModal.showItem'); + plugin.uninstallThemeBrowserModalShowItem = around(this.constructor.prototype, { + showItem(oldMethod) { + return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, oldMethod, async function (...args: CommunityItem[]) { + console.debug('Call ThemeBrowserModal.showItem'); + + // Load the favorite themes + plugin.favoriteThemes = JSON.parse(localStorage.getItem(plugin.themesKey) || '[]'); + + return oldMethod && oldMethod.apply(this, args).then(async (r: void) => { + // Ignore the default theme + if (this.selectedItemId === '') { + return r; + } + + const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0]; + const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0]; + const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0]; + + const isFavorite = plugin.favoriteThemes?.contains(this.selectedItemId); + + // Add to the favorite themes a tag to visualize it for the user + if (isFavorite) { + if (infoNameEl) { + infoNameEl.createSpan({ + cls: 'flair', + text: 'favorite', + }); + } + } + + plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { + if (isFavorite) { + plugin.favoriteThemes?.remove(this.selectedItemId); + } + else { + plugin.favoriteThemes?.push(this.selectedItemId); + } + + plugin.saveSettings(); + + // Redraw + infoEl.detach(); + this.showItem(this.items[this.selectedItemId]); + this.update(); + })); + + return r; + }); + }); + }, + }); + } + else { + console.warn('ThemeBrowserModal.showItem already patched!'); + } + } + + if (plugin.uninstallModalOpen && plugin.uninstallPluginBrowserModalUpdateItems && plugin.uninstallPluginBrowserModalShowItem && plugin.uninstallThemeBrowserModalUpdateItems && plugin.uninstallThemeBrowserModalShowItem) { + console.debug('Uninstall Modal.open'); + plugin.uninstallModalOpen(); + plugin.uninstallModalOpen = undefined; + } + return result; + }); + }, + }); + } + else { + console.warn('Modal.open already patched!'); + } // TODO: Redraw modals if local storage 'favorite-plugins' or 'favorite-themes' changed @@ -217,26 +249,36 @@ export default class FavoritesPlugin extends Plugin { } async onunload() { - // Restore the original functions - if (this.uninstallModalOpen) { - console.debug('Uninstall Modal.open') - this.uninstallModalOpen(); + try { + // Restore the original functions + if (this.uninstallModalOpen) { + console.debug('Uninstall Modal.open'); + this.uninstallModalOpen(); + this.uninstallModalOpen = undefined; + } + if (this.uninstallPluginBrowserModalUpdateItems) { + console.debug('Uninstall PluginBrowserModal.updateItem'); + this.uninstallPluginBrowserModalUpdateItems(); + this.uninstallPluginBrowserModalUpdateItems = undefined; + } + if (this.uninstallPluginBrowserModalShowItem) { + console.debug('Uninstall PluginBrowserModal.showItem'); + this.uninstallPluginBrowserModalShowItem(); + this.uninstallPluginBrowserModalShowItem = undefined; + } + if (this.uninstallThemeBrowserModalUpdateItems) { + console.debug('Uninstall ThemeBrowserModal.updateItem'); + this.uninstallThemeBrowserModalUpdateItems(); + this.uninstallThemeBrowserModalUpdateItems = undefined; + } + if (this.uninstallThemeBrowserModalShowItem) { + console.debug('Uninstall ThemeBrowserModal.showItem'); + this.uninstallThemeBrowserModalShowItem(); + this.uninstallThemeBrowserModalShowItem = undefined; + } } - if (this.uninstallPluginBrowserModalUpdateItems) { - console.debug('Uninstall PluginBrowserModal.updateItem') - this.uninstallPluginBrowserModalUpdateItems(); - } - if (this.uninstallPluginBrowserModalShowItem) { - console.debug('Uninstall PluginBrowserModal.showItem') - this.uninstallPluginBrowserModalShowItem(); - } - if (this.uninstallThemeBrowserModalUpdateItems) { - console.debug('Uninstall ThemeBrowserModal.updateItem') - this.uninstallThemeBrowserModalUpdateItems(); - } - if (this.uninstallThemeBrowserModalShowItem) { - console.debug('Uninstall ThemeBrowserModal.showItem') - this.uninstallThemeBrowserModalShowItem(); + catch (e) { + console.error(e); } }