From cafe1f96ef91c9bf5822c8274bd5b3e6eaec2723 Mon Sep 17 00:00:00 2001 From: Michael Naumov Date: Thu, 12 Sep 2024 22:41:38 -0600 Subject: [PATCH] Ensure RenameDeleteHandler is executed only once --- src/obsidian/App.ts | 25 ++++++++++++++++++++++ src/obsidian/RenameDeleteHandler.ts | 33 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/obsidian/App.ts b/src/obsidian/App.ts index 86faa6a..ae9a80b 100644 --- a/src/obsidian/App.ts +++ b/src/obsidian/App.ts @@ -43,3 +43,28 @@ export function getApp(): App { throw new Error('Obsidian app not found'); } + +interface ObsidianDevUtilsStateWrapper { + obsidianDevUtilsState: Record; +} + +/** + * Wrapper type for storing shared state in the Obsidian app. + */ +export class ValueWrapper { + public constructor(public value: T) { } +} + +/** + * Retrieves or creates a shared state wrapper object for a given key in the Obsidian app. + * + * @param app - The Obsidian app instance. + * @param key - The key to store or retrieve the shared state. + * @param defaultValue - The default value to use if the shared state does not exist. + * @returns The ValueWrapper object that stores the shared state. + */ +export function getObsidianDevUtilsState(app: App, key: string, defaultValue: T): ValueWrapper { + const sharedStateWrapper = app as Partial; + const sharedState = sharedStateWrapper.obsidianDevUtilsState ??= {}; + return (sharedState[key] ??= new ValueWrapper(defaultValue)) as ValueWrapper; +} diff --git a/src/obsidian/RenameDeleteHandler.ts b/src/obsidian/RenameDeleteHandler.ts index 820cc5f..d3d494b 100644 --- a/src/obsidian/RenameDeleteHandler.ts +++ b/src/obsidian/RenameDeleteHandler.ts @@ -18,6 +18,7 @@ import { join, relative } from '../Path.ts'; +import { getObsidianDevUtilsState } from './App.ts'; import { getAttachmentFolderPath } from './AttachmentPath.ts'; import { extractLinkFile, @@ -70,21 +71,49 @@ export interface RenameDeleteHandlerSettings { * @returns void */ export function registerRenameDeleteHandlers(plugin: Plugin, settingsBuilder: () => RenameDeleteHandlerSettings): void { + const renameDeleteHandlerPluginIds = getRenameDeleteHandlerPluginIds(plugin.app); + const pluginId = plugin.manifest.id; + + if (renameDeleteHandlerPluginIds.length > 0) { + console.warn(`Plugin ${pluginId} is registering a rename/delete handler, but it is already registered by plugins: ${renameDeleteHandlerPluginIds.join(', ')}. The handler for ${pluginId} will be skipped until all previous plugins are disabled.`); + } + + renameDeleteHandlerPluginIds.push(pluginId); + plugin.register(() => { + renameDeleteHandlerPluginIds.remove(pluginId); + }); + const app = plugin.app; const renameDeleteHandler = new RenameDeleteHandler(app, settingsBuilder); plugin.registerEvent( app.vault.on('delete', (file) => { + if (!shouldInvokeHandler(app, pluginId, 'Delete')) { + return; + } invokeAsyncSafely(renameDeleteHandler.handleDelete(file)); }) ); plugin.registerEvent( app.vault.on('rename', (file, oldPath) => { + if (!shouldInvokeHandler(app, pluginId, 'Rename')) { + return; + } invokeAsyncSafely(renameDeleteHandler.handleRename(file, oldPath)); }) ); } +function shouldInvokeHandler(app: App, pluginId: string, handlerType: string): boolean { + const renameDeleteHandlerPluginIds = getRenameDeleteHandlerPluginIds(app); + const mainPluginId = renameDeleteHandlerPluginIds[0]; + if (mainPluginId !== pluginId) { + console.warn(`${handlerType} handler for plugin ${pluginId} is skipped, because it is handled by plugin ${mainPluginId ?? '(none)'}`); + return false; + } + return true; +} + class RenameDeleteHandler { public constructor(private app: App, private settingsBuilder: () => RenameDeleteHandlerSettings) { } @@ -361,3 +390,7 @@ class RenameDeleteHandler { return backlinks; } } + +function getRenameDeleteHandlerPluginIds(app: App): string[] { + return getObsidianDevUtilsState(app, 'renameDeleteHandlerPluginIds', []).value; +}