diff --git a/src/main.ts b/src/main.ts index b8c72ca..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,20 +193,25 @@ 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.logWarningFileExplorerDeferred() + this.logDeferredFileExplorerInfo() } return fileExplorerOrError } @@ -269,12 +279,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}: A watcher was set up 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 +598,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 +697,39 @@ export default class CustomSortPlugin return false } - initialDelayedApplicationOfCustomSorting() { + setWatcherForDelayedFileExplorerView(fileExplorerInDeferredState?: FileExplorerLeaf) { + const self = this + + 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: false + }); + } + } + + // 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.`) return @@ -695,7 +742,25 @@ 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 && fileExplorerOrError.e.state === FileExplorerState.DeferredView) { + this.logDeferredFileExplorerWatcherSetupInfo() + 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 { + this.switchPluginStateTo(true) + } } setRibbonIconToEnabled() {