From b8708f81e9d607649315c67d086a48a8e9a31be2 Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:24:47 +0100 Subject: [PATCH 1/3] Finetuning initial application of custom sort order: - console messages more accurate - DOM-based watcher for deferred File Explorer view to automatically apply custom sort when File Explorer view is actually displayed --- src/main.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/main.ts b/src/main.ts index b8c72ca..5e53a27 100644 --- a/src/main.ts +++ b/src/main.ts @@ -201,7 +201,7 @@ export default class CustomSortPlugin let fileExplorerOrError = this.getFileExplorer() if (fileExplorerOrError.e === FileExplorerStateError.DeferredView) { if (logWarning) { - this.logWarningFileExplorerDeferred() + this.logDeferredFileExplorerInfo() } return fileExplorerOrError } @@ -269,12 +269,17 @@ export default class CustomSortPlugin } } - logWarningFileExplorerDeferred() { - const msg = `${PLUGIN_ID} v${this.manifest.version}: failed to get active File Explorer view.\n` + logDeferredFileExplorerInfo() { + const msg = `${PLUGIN_ID} v${this.manifest.version}: File Explorer is not displayed yet (Obsidian deferred view detected).\n` + `Until the File Explorer is visible, the custom-sort plugin cannot apply the custom order.\n` console.warn(msg) } + logDeferredFileExplorerWatcherSetupInfo() { + const msg = `${PLUGIN_ID} v${this.manifest.version}: Set up a watcher to apply custom sort automatically when the File Explorer is displayed.\n` + console.warn(msg) + } + logWarningFileExplorerNotAvailable() { const msg = `${PLUGIN_ID} v${this.manifest.version}: failed to locate File Explorer. The 'Files' core plugin can be disabled.\n` + `Some community plugins can also disable it.\n` @@ -583,7 +588,7 @@ export default class CustomSortPlugin const plugin = this this.app.workspace.onLayoutReady(() => { setTimeout(() => { - plugin.initialDelayedApplicationOfCustomSorting.apply(this) + plugin.delayedApplicationOfCustomSorting.apply(this) }, plugin.settings.delayForInitialApplication) }) @@ -682,7 +687,30 @@ export default class CustomSortPlugin return false } - initialDelayedApplicationOfCustomSorting() { + setWatcherForDelayedFileExplorerView() { + const self = this + const fullyFledgedFileExplorerElementSelector = () => document.querySelector('[data-type="file-explorer"] .nav-files-container'); + + const mutationObserver = new MutationObserver((_, observerInstance) => { + const fullyFledgedFileExplorerElement = fullyFledgedFileExplorerElementSelector(); + if (fullyFledgedFileExplorerElement) { + observerInstance.disconnect(); + self.delayedApplicationOfCustomSorting(true) + } + }); + const workspaceElement = document.querySelector(".workspace"); + if (workspaceElement) { + mutationObserver.observe(workspaceElement, { + childList: true, + subtree: true + }); + } + } + + // Entering this method for the first time after initial delay after plugin loaded (via setTimeout()), + // and if first attempt is unsuccessful, then entering this method again from DOM watcher, when + // the File Explorer view gets transformed from delayed view into fully-fledged active view + delayedApplicationOfCustomSorting(fromDOMwatcher?: boolean) { if (!this?.isThePluginStillInstalledAndEnabled()) { console.log(`${PLUGIN_ID} v${this.manifest.version} - delayed handler skipped, plugin no longer active.`) return @@ -695,7 +723,20 @@ export default class CustomSortPlugin return } - this.switchPluginStateTo(true) + if (!fromDOMwatcher) { + // Only for the first delayed invocation: + // If file explorer is delayed, configure the watcher + // NOTE: Do not configure the watcher if the file explorer is not available + let fileExplorerOrError: FileExplorerLeafOrError = this.checkFileExplorerIsAvailableAndPatchable() + if (fileExplorerOrError.e === FileExplorerStateError.DeferredView) { + this.logDeferredFileExplorerWatcherSetupInfo() + this.setWatcherForDelayedFileExplorerView() + } else { // file explorer is available or does not exist + this.switchPluginStateTo(true) + } + } else { + this.switchPluginStateTo(true) + } } setRibbonIconToEnabled() { From e0b7c9bd6d1893346348eae1500030bbd3db49ba Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Mon, 31 Mar 2025 10:28:30 +0200 Subject: [PATCH 2/3] Minor finetuning of console log message --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 5e53a27..39b846a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -276,7 +276,7 @@ export default class CustomSortPlugin } logDeferredFileExplorerWatcherSetupInfo() { - const msg = `${PLUGIN_ID} v${this.manifest.version}: Set up a watcher to apply custom sort automatically when the File Explorer is displayed.\n` + const msg = `${PLUGIN_ID} v${this.manifest.version}: A watcher was set up to apply custom sort automatically when the File Explorer is displayed.\n` console.warn(msg) } From 22992ad4218d6f13fa44405b8126bf60dc72ea8b Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Mon, 31 Mar 2025 13:43:08 +0200 Subject: [PATCH 3/3] Finetuned DOM observer and detector of the fully-fledged File Explorer view to enable smoother auto-application of custom sorting --- src/main.ts | 66 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/src/main.ts b/src/main.ts index 39b846a..90bc329 100644 --- a/src/main.ts +++ b/src/main.ts @@ -78,11 +78,16 @@ type MonkeyAroundUninstaller = () => void type ContextMenuProvider = (item: MenuItem) => void -enum FileExplorerStateError { - DoesNotExist, +enum FileExplorerState { + DoesNotExist = 1, DeferredView } +interface FileExplorerStateError { + state: FileExplorerState + fileExplorerInDeferredState?: FileExplorerLeaf +} + type FileExplorerLeafOrError = ValueOrError export default class CustomSortPlugin @@ -188,18 +193,23 @@ export default class CustomSortPlugin if (fileExplorer) { if (fileExplorer.isDeferred) { - return fileExplorerOrError.setError(FileExplorerStateError.DeferredView) + return fileExplorerOrError.setError({ + state: FileExplorerState.DeferredView, + fileExplorerInDeferredState: fileExplorer + }) } else { return fileExplorerOrError.setValue(fileExplorer) } } else { - return fileExplorerOrError.setError(FileExplorerStateError.DoesNotExist) + return fileExplorerOrError.setError({ + state: FileExplorerState.DoesNotExist + }) } } checkFileExplorerIsAvailableAndPatchable(logWarning: boolean = true): FileExplorerLeafOrError { let fileExplorerOrError = this.getFileExplorer() - if (fileExplorerOrError.e === FileExplorerStateError.DeferredView) { + if (fileExplorerOrError.e && fileExplorerOrError.e.state === FileExplorerState.DeferredView) { if (logWarning) { this.logDeferredFileExplorerInfo() } @@ -687,22 +697,30 @@ export default class CustomSortPlugin return false } - setWatcherForDelayedFileExplorerView() { + setWatcherForDelayedFileExplorerView(fileExplorerInDeferredState?: FileExplorerLeaf) { const self = this - const fullyFledgedFileExplorerElementSelector = () => document.querySelector('[data-type="file-explorer"] .nav-files-container'); - const mutationObserver = new MutationObserver((_, observerInstance) => { - const fullyFledgedFileExplorerElement = fullyFledgedFileExplorerElementSelector(); - if (fullyFledgedFileExplorerElement) { - observerInstance.disconnect(); - self.delayedApplicationOfCustomSorting(true) - } - }); - const workspaceElement = document.querySelector(".workspace"); - if (workspaceElement) { - mutationObserver.observe(workspaceElement, { + let workspaceLeafContentElementParentToObserve: HTMLElement|Element|null|undefined = fileExplorerInDeferredState?.view?.containerEl?.parentElement + if (!workspaceLeafContentElementParentToObserve) { + // Fallback for the case when DOM is not available for deferred file explorer + // (did not happen in practice, but just in case) + workspaceLeafContentElementParentToObserve = document.querySelector(".workspace"); + } + if (workspaceLeafContentElementParentToObserve) { + const fullyFledgedFileExplorerElementSelector= + () => workspaceLeafContentElementParentToObserve.querySelector('[data-type="file-explorer"] .nav-files-container'); + + const mutationObserver = new MutationObserver((_, observerInstance) => { + const fullyFledgedFileExplorerElement = fullyFledgedFileExplorerElementSelector(); + if (fullyFledgedFileExplorerElement) { + observerInstance.disconnect(); + self.delayedApplicationOfCustomSorting(self.FROM_DOM_WATCHER) + } + }); + + mutationObserver.observe(workspaceLeafContentElementParentToObserve, { childList: true, - subtree: true + subtree: false }); } } @@ -710,6 +728,7 @@ export default class CustomSortPlugin // Entering this method for the first time after initial delay after plugin loaded (via setTimeout()), // and if first attempt is unsuccessful, then entering this method again from DOM watcher, when // the File Explorer view gets transformed from delayed view into fully-fledged active view + FROM_DOM_WATCHER: boolean = true delayedApplicationOfCustomSorting(fromDOMwatcher?: boolean) { if (!this?.isThePluginStillInstalledAndEnabled()) { console.log(`${PLUGIN_ID} v${this.manifest.version} - delayed handler skipped, plugin no longer active.`) @@ -728,10 +747,15 @@ export default class CustomSortPlugin // If file explorer is delayed, configure the watcher // NOTE: Do not configure the watcher if the file explorer is not available let fileExplorerOrError: FileExplorerLeafOrError = this.checkFileExplorerIsAvailableAndPatchable() - if (fileExplorerOrError.e === FileExplorerStateError.DeferredView) { + if (fileExplorerOrError.e && fileExplorerOrError.e.state === FileExplorerState.DeferredView) { this.logDeferredFileExplorerWatcherSetupInfo() - this.setWatcherForDelayedFileExplorerView() - } else { // file explorer is available or does not exist + this.setWatcherForDelayedFileExplorerView(fileExplorerOrError.e.fileExplorerInDeferredState) + } else if (fileExplorerOrError.e) { + // file explorer other error - does not exist + // force the plugin switch state to report error and show the error icon + this.switchPluginStateTo(true) + } + else { // file explorer is available this.switchPluginStateTo(true) } } else {