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] 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() {