diff --git a/.gitignore b/.gitignore index 5a247e1..a0ff410 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ data.json # Exclude macOS Finder (System Explorer) View States .DS_Store /yarn.lock +/.run/test.run.xml +/.run/build.run.xml diff --git a/src/custom-sort/folder-matching-rules.ts b/src/custom-sort/folder-matching-rules.ts index 55f558b..1a89c9a 100644 --- a/src/custom-sort/folder-matching-rules.ts +++ b/src/custom-sort/folder-matching-rules.ts @@ -161,7 +161,7 @@ export class FolderWildcardMatching { } } - rule = rule ?? inheritedRule + rule ??= inheritedRule } if (rule) { diff --git a/src/custom-sort/sorting-spec-processor.ts b/src/custom-sort/sorting-spec-processor.ts index 6ecf463..4ffdd10 100644 --- a/src/custom-sort/sorting-spec-processor.ts +++ b/src/custom-sort/sorting-spec-processor.ts @@ -673,7 +673,7 @@ export interface SortSpecsCollection { } const ensureCollectionHasSortSpecByPath = (collection?: SortSpecsCollection | null) => { - collection = collection ?? {} + collection ??= {} if (!collection.sortSpecByPath) { collection.sortSpecByPath = {} } @@ -681,7 +681,7 @@ const ensureCollectionHasSortSpecByPath = (collection?: SortSpecsCollection | nu } const ensureCollectionHasSortSpecByName = (collection?: SortSpecsCollection | null) => { - collection = collection ?? {} + collection ??= {} if (!collection.sortSpecByName) { collection.sortSpecByName = {} } @@ -689,7 +689,7 @@ const ensureCollectionHasSortSpecByName = (collection?: SortSpecsCollection | nu } const ensureCollectionHasSortSpecByWildcard = (collection?: SortSpecsCollection | null) => { - collection = collection ?? {} + collection ??= {} if (!collection.sortSpecByWildcard) { collection.sortSpecByWildcard = new FolderWildcardMatching((spec: CustomSortSpec) => !!spec.implicit) } diff --git a/src/main.ts b/src/main.ts index 8b0ba9f..93d46de 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,10 @@ import { + App, + CommunityPlugin, + FileExplorerLeaf, FileExplorerView, - Menu, - MenuItem, + Menu, + MenuItem, MetadataCache, Notice, Platform, @@ -10,7 +13,8 @@ import { TAbstractFile, TFile, TFolder, - Vault, WorkspaceLeaf + Vault, + WorkspaceLeaf } from 'obsidian'; import {around} from 'monkey-around'; import { @@ -26,7 +30,6 @@ import { import { CustomSortSpec } from './custom-sort/custom-sort-types'; - import { addIcons, ICON_SORT_ENABLED_ACTIVE, @@ -42,11 +45,12 @@ import { groupNameForPath } from "./utils/BookmarksCorePluginSignature"; import { - getIconFolderPlugin + getIconFolderPlugin, ObsidianIconFolder_PluginInstance, ObsidianIconFolderPlugin_getData_methodName } from "./utils/ObsidianIconFolderPluginSignature"; import { extractBasename, - lastPathComponent + lastPathComponent, + ValueOrError, } from "./utils/utils"; import { collectSortingAndGroupingTypes, @@ -62,6 +66,8 @@ import { } from "./settings"; import {CustomSortPluginAPI} from "./custom-sort-plugin"; +const PLUGIN_ID = 'custom-sort' // same as in manifest.json + const SORTSPEC_FILE_NAME: string = 'sortspec.md' const SORTINGSPEC_YAML_KEY: string = 'sorting-spec' @@ -72,6 +78,13 @@ type MonkeyAroundUninstaller = () => void type ContextMenuProvider = (item: MenuItem) => void +enum FileExplorerStateError { + DoesNotExist, + DeferredView +} + +type FileExplorerLeafOrError = ValueOrError + export default class CustomSortPlugin extends Plugin implements CustomSortPluginAPI @@ -79,12 +92,9 @@ export default class CustomSortPlugin settings: CustomSortPluginSettings statusBarItemEl: HTMLElement ribbonIconEl: HTMLElement // On small-screen mobile devices this is useless (ribbon is re-created on-the-fly) - ribbonIconStateInaccurate: boolean // each time when displayed sortSpecCache?: SortSpecsCollection | null - initialAutoOrManualSortingTriggered: boolean - - fileExplorerFolderPatched: boolean + customSortAppliedAtLeastOnce: boolean = false showNotice(message: string, timeout?: number) { if (this.settings.notificationsEnabled || (Platform.isMobile && this.settings.mobileNotificationsEnabled)) { @@ -134,7 +144,7 @@ export default class CustomSortPlugin aFile.basename === this.settings.indexNoteNameForFolderNotes || // when user configured as index aFile.name === this.settings.indexNoteNameForFolderNotes // when user configured as index.md ) { - const sortingSpecTxt: string = mCache.getCache(aFile.path)?.frontmatter?.[SORTINGSPEC_YAML_KEY] + const sortingSpecTxt: string|undefined = mCache.getCache(aFile.path)?.frontmatter?.[SORTINGSPEC_YAML_KEY] // Warning: newer Obsidian versions can return objects as well, hence the explicit check for string value if (typeof sortingSpecTxt === 'string') { anySortingSpecFound = true @@ -154,7 +164,11 @@ export default class CustomSortPlugin }) if (this.sortSpecCache) { - this.showNotice(`Parsing custom sorting specification SUCCEEDED!`) + if (anySortingSpecFound) { + this.showNotice(`Parsing custom sorting specification SUCCEEDED!`) + } else { + this.showNotice(`No custom sorting spec, will go with implicit sorting (bookmarks-based).`) + } } else { if (anySortingSpecFound) { errorMessage = errorMessage ? errorMessage : `No valid '${SORTINGSPEC_YAML_KEY}:' key(s) in YAML front matter or multiline YAML indentation error or general YAML syntax error` @@ -167,22 +181,102 @@ export default class CustomSortPlugin } } - checkFileExplorerIsAvailableAndPatchable(logWarning: boolean = true): FileExplorerView | undefined { - let fileExplorerView: FileExplorerView | undefined = this.getFileExplorer() - if (fileExplorerView && typeof fileExplorerView.requestSort === 'function') { - if (typeof fileExplorerView.getSortedFolderItems === 'function') { - return fileExplorerView + // Credits go to https://github.com/nothingislost/obsidian-bartender + getFileExplorer(): FileExplorerLeafOrError { + let fileExplorer: FileExplorerLeaf | undefined = this.app.workspace.getLeavesOfType("file-explorer")?.first() as FileExplorerLeaf; + const fileExplorerOrError: FileExplorerLeafOrError = new ValueOrError() + + if (fileExplorer) { + if (fileExplorer.isDeferred) { + return fileExplorerOrError.setError(FileExplorerStateError.DeferredView) + } else { + return fileExplorerOrError.setValue(fileExplorer) + } + } else { + return fileExplorerOrError.setError(FileExplorerStateError.DoesNotExist) + } + } + + checkFileExplorerIsAvailableAndPatchable(logWarning: boolean = true): FileExplorerLeafOrError { + let fileExplorerOrError = this.getFileExplorer() + if (fileExplorerOrError.e === FileExplorerStateError.DeferredView) { + if (logWarning) { + this.logWarningFileExplorerDeferred() + } + return fileExplorerOrError + } + if (fileExplorerOrError.v && fileExplorerOrError.v.view && typeof fileExplorerOrError.v.view.requestSort === 'function') { + if (typeof fileExplorerOrError.v.view.getSortedFolderItems === 'function') { + return fileExplorerOrError } } + // Various scenarios when File Explorer was turned off (e.g. by some other plugin) if (logWarning) { this.logWarningFileExplorerNotAvailable() } - return undefined + return fileExplorerOrError + } + + // For the idea of monkey-patching credits go to https://github.com/nothingislost/obsidian-bartender + patchFileExplorer(patchableFileExplorer: FileExplorerLeaf): FileExplorerLeaf|undefined { + let plugin = this; + const requestStandardObsidianSortAfter = (patchUninstaller: MonkeyAroundUninstaller|undefined) => { + return () => { + if (patchUninstaller) patchUninstaller() + + const fileExplorerOrError= this.checkFileExplorerIsAvailableAndPatchable() + if (fileExplorerOrError.v && fileExplorerOrError.v.view) { + fileExplorerOrError.v.view.requestSort?.() + } + } + } + + // patching file explorer might fail here because of various non-error reasons. + // That's why not showing and not logging error message here + if (patchableFileExplorer) { + const uninstallerOfFolderSortFunctionWrapper: MonkeyAroundUninstaller = around(patchableFileExplorer.view.constructor.prototype, { + getSortedFolderItems(old: any) { + return function (...args: any[]) { + // quick check for plugin status + if (plugin.settings.suspended) { + return old.call(this, ...args); + } + + const folder = args[0] + const sortingData = plugin.determineAndPrepareSortingDataForFolder(folder) + + if (sortingData.sortSpec) { + if (!plugin.customSortAppliedAtLeastOnce) { + plugin.customSortAppliedAtLeastOnce = true + setTimeout(() => { + plugin.setRibbonIconToEnabled.apply(plugin) + plugin.showNotice('Custom sort APPLIED.'); + plugin.updateStatusBar() + }) + } + return getSortedFolderItems.call(this, folder, sortingData.sortSpec, plugin.createProcessingContextForSorting(sortingData.sortingAndGroupingStats)) + } else { + return old.call(this, ...args); + } + }; + } + }) + this.register(requestStandardObsidianSortAfter(uninstallerOfFolderSortFunctionWrapper)) + return patchableFileExplorer + } else { + return undefined + } + } + + logWarningFileExplorerDeferred() { + const msg = `${PLUGIN_ID} v${this.manifest.version}: failed to get active File Explorer view.\n` + + `Until the File Explorer is visible, the custom-sort plugin cannot apply the custom order.\n` + console.warn(msg) } logWarningFileExplorerNotAvailable() { - const msg = `custom-sort v${this.manifest.version}: failed to locate File Explorer. The 'Files' core plugin can be disabled.\n` + 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` + `See the example of MAKE.md plugin: https://github.com/Make-md/makemd/issues/25\n` + `You can find there instructions on how to re-enable the File Explorer in MAKE.md plugin` @@ -190,36 +284,34 @@ export default class CustomSortPlugin } // Safe to suspend when suspended and re-enable when enabled - switchPluginStateTo(enabled: boolean, updateRibbonBtnIcon: boolean = true) { - let fileExplorerView: FileExplorerView | undefined = this.checkFileExplorerIsAvailableAndPatchable() - if (fileExplorerView && !this.fileExplorerFolderPatched) { - this.fileExplorerFolderPatched = this.patchFileExplorerFolder(fileExplorerView); - - if (!this.fileExplorerFolderPatched) { - fileExplorerView = undefined - } - } + switchPluginStateTo(enabled: boolean) { this.settings.suspended = !enabled; this.saveSettings() - let iconToSet: string + + let fileExplorerOrError: FileExplorerLeafOrError = this.checkFileExplorerIsAvailableAndPatchable(!this.settings.suspended) + const fileExplorer = fileExplorerOrError.v ? this.patchFileExplorer(fileExplorerOrError.v) : undefined + if (this.settings.suspended) { this.showNotice('Custom sort OFF'); this.sortSpecCache = null - iconToSet = ICON_SORT_SUSPENDED + setIcon(this.ribbonIconEl, ICON_SORT_SUSPENDED) + if (fileExplorer) { + fileExplorer.view.requestSort(); + } } else { this.readAndParseSortingSpec(); if (this.sortSpecCache) { - if (fileExplorerView) { - this.showNotice('Custom sort ON'); - iconToSet = ICON_SORT_ENABLED_ACTIVE + if (fileExplorer) { + this.customSortAppliedAtLeastOnce = false + fileExplorer.view.requestSort(); } else { - this.showNotice('Custom sort GENERAL PROBLEM. See console for detailed message.'); - iconToSet = ICON_SORT_SUSPENDED_GENERAL_ERROR + this.showNotice('Custom sort File Explorer view problem. See console for detailed message.'); + setIcon(this.ribbonIconEl, ICON_SORT_SUSPENDED_GENERAL_ERROR) this.settings.suspended = true this.saveSettings() } } else { - iconToSet = ICON_SORT_SUSPENDED_SYNTAX_ERROR + setIcon(this.ribbonIconEl, ICON_SORT_SUSPENDED_SYNTAX_ERROR) this.settings.suspended = true this.saveSettings() } @@ -231,32 +323,11 @@ export default class CustomSortPlugin getBookmarksPlugin(this.app, this.settings.bookmarksGroupToConsumeAsOrderingReference, ForceFlushCache) } - if (fileExplorerView) { - if (this.fileExplorerFolderPatched) { - fileExplorerView.requestSort(); - this.initialAutoOrManualSortingTriggered = true - } - } else { - if (iconToSet === ICON_SORT_ENABLED_ACTIVE) { - iconToSet = ICON_SORT_ENABLED_NOT_APPLIED - - if (updateRibbonBtnIcon) { - this.ribbonIconStateInaccurate = true - } - } - } - - if (updateRibbonBtnIcon) { - // REMARK: on small-screen mobile devices this is void, the handle to ribbon
Element is useless, - // as the ribbon (and its icons) get re-created each time when re-displayed (expanded) - setIcon(this.ribbonIconEl, iconToSet) - } - this.updateStatusBar(); } async onload() { - console.log(`loading custom-sort v${this.manifest.version}`); + console.log(`loading ${PLUGIN_ID} v${this.manifest.version}`); await this.loadSettings(); @@ -273,7 +344,7 @@ export default class CustomSortPlugin // in result, the handle to the ribbon
Element is useless this.ribbonIconEl = this.addRibbonIcon( Platform.isDesktop ? - (this.settings.suspended ? ICON_SORT_SUSPENDED : ICON_SORT_ENABLED_NOT_APPLIED) + ICON_SORT_SUSPENDED : ICON_SORT_MOBILE_INITIAL // REMARK: on small-screen mobile devices this icon stays permanent , @@ -282,10 +353,6 @@ export default class CustomSortPlugin this.switchPluginStateTo(this.settings.suspended) }); - if (!this.settings.suspended) { - this.ribbonIconStateInaccurate = true - } - this.addSettingTab(new CustomSortSettingTab(this.app, this)); this.registerEventHandlers() @@ -299,54 +366,17 @@ export default class CustomSortPlugin const plugin: CustomSortPlugin = this const m: boolean = Platform.isMobile - this.registerEvent( - // Keep in mind: this event is triggered once after app starts and then after each modification of _any_ metadata - plugin.app.metadataCache.on("resolved", () => { - if (!this.settings.suspended) { - if (!this.initialAutoOrManualSortingTriggered) { - this.readAndParseSortingSpec() - if (this.sortSpecCache) { // successful read of sorting specifications? - this.showNotice('Custom sort ON') - let fileExplorerView: FileExplorerView | undefined = this.checkFileExplorerIsAvailableAndPatchable(false) - if (fileExplorerView && !this.fileExplorerFolderPatched) { - this.fileExplorerFolderPatched = this.patchFileExplorerFolder(fileExplorerView); - - if (!this.fileExplorerFolderPatched) { - fileExplorerView = undefined - } - } - if (fileExplorerView) { - setIcon(this.ribbonIconEl, ICON_SORT_ENABLED_ACTIVE) - fileExplorerView.requestSort() - this.initialAutoOrManualSortingTriggered = true - } else { - // Remark: in this case the File Explorer will render later on with standard Obsidian sort - // and a different event will be responsible for patching it and applying the custom sort - setIcon(this.ribbonIconEl, ICON_SORT_ENABLED_NOT_APPLIED) - plugin.ribbonIconStateInaccurate = true - } - this.updateStatusBar() - } else { - this.settings.suspended = true - setIcon(this.ribbonIconEl, ICON_SORT_SUSPENDED_SYNTAX_ERROR) - this.saveSettings() - } - } - } - }) - ); - const applyCustomSortMenuItem = (item: MenuItem) => { item.setTitle(m ? 'Custom sort: apply custom sorting' : 'Apply custom sorting'); item.onClick(() => { - plugin.switchPluginStateTo(true, true) + plugin.switchPluginStateTo(true) }) }; const suspendCustomSortMenuItem = (item: MenuItem) => { item.setTitle(m ? 'Custom sort: suspend custom sorting' : 'Suspend custom sorting'); item.onClick(() => { - plugin.switchPluginStateTo(false, true) + plugin.switchPluginStateTo(false) }) }; @@ -532,28 +562,32 @@ export default class CustomSortPlugin id: 'enable-custom-sorting', name: 'Enable and apply the custom sorting, (re)parsing the sorting configuration first. Sort-on.', callback: () => { - plugin.switchPluginStateTo(true, true) + plugin.switchPluginStateTo(true) } }); this.addCommand({ id: 'suspend-custom-sorting', name: 'Suspend the custom sorting. Sort-off.', callback: () => { - plugin.switchPluginStateTo(false, true) + plugin.switchPluginStateTo(false) } }); } initialize() { + const plugin = this this.app.workspace.onLayoutReady(() => { - this.fileExplorerFolderPatched = this.patchFileExplorerFolder(); + setTimeout(() => { + plugin.initialDelayedApplicationOfCustomSorting.apply(this) + }, + plugin.settings.delayForInitialApplication) }) } determineSortSpecForFolder(folderPath: string, folderName?: string): CustomSortSpec|null|undefined { - folderName = folderName ?? lastPathComponent(folderPath) + folderName ??= lastPathComponent(folderPath) let sortSpec: CustomSortSpec | null | undefined = this.sortSpecCache?.sortSpecByPath?.[folderPath] - sortSpec = sortSpec ?? this.sortSpecCache?.sortSpecByName?.[folderName] + sortSpec ??= this.sortSpecCache?.sortSpecByName?.[folderName] if (!sortSpec && this.sortSpecCache?.sortSpecByWildcard) { // when no sorting spec found directly by folder path, check for wildcard-based match @@ -572,13 +606,6 @@ export default class CustomSortPlugin return ctx } - resetIconInaccurateStateToEnabled() { - if (this.ribbonIconStateInaccurate && this.ribbonIconEl) { - this.ribbonIconStateInaccurate = false - setIcon(this.ribbonIconEl, ICON_SORT_ENABLED_ACTIVE) - } - } - determineAndPrepareSortingDataForFolder(folder: TFolder) { let sortSpec: CustomSortSpec | null | undefined = this.determineSortSpecForFolder(folder.path, folder.name) @@ -598,58 +625,12 @@ export default class CustomSortPlugin } } - // For the idea of monkey-patching credits go to https://github.com/nothingislost/obsidian-bartender - patchFileExplorerFolder(patchableFileExplorer?: FileExplorerView): boolean { - let plugin = this; - const requestStandardObsidianSortAfter = (patchUninstaller: MonkeyAroundUninstaller|undefined) => { - return () => { - if (patchUninstaller) patchUninstaller() - - const fileExplorerView: FileExplorerView | undefined = this.checkFileExplorerIsAvailableAndPatchable(false) - if (fileExplorerView) { - fileExplorerView.requestSort() - } - } - } - - // patching file explorer might fail here because of various non-error reasons. - // That's why not showing and not logging error message here - patchableFileExplorer = patchableFileExplorer ?? this.checkFileExplorerIsAvailableAndPatchable(false) - if (patchableFileExplorer) { - const uninstallerOfFolderSortFunctionWrapper: MonkeyAroundUninstaller = around(patchableFileExplorer.constructor.prototype, { - getSortedFolderItems(old: any) { - return function (...args: any[]) { - // quick check for plugin status - if (plugin.settings.suspended) { - return old.call(this, ...args); - } - - plugin.resetIconInaccurateStateToEnabled() - - const folder = args[0] - const sortingData = plugin.determineAndPrepareSortingDataForFolder(folder) - - if (sortingData.sortSpec) { - return getSortedFolderItems.call(this, folder, sortingData.sortSpec, plugin.createProcessingContextForSorting(sortingData.sortingAndGroupingStats)) - } else { - return old.call(this, ...args); - } - }; - } - }) - this.register(requestStandardObsidianSortAfter(uninstallerOfFolderSortFunctionWrapper)) - return true - } else { - return false - } - } - orderedFolderItemsForBookmarking(folder: TFolder, bookmarksPlugin: BookmarksPluginInterface): Array { let sortSpec: CustomSortSpec | null | undefined = undefined if (!this.settings.suspended) { sortSpec = this.determineSortSpecForFolder(folder.path, folder.name) } - let uiSortOrder: string = this.getFileExplorer()?.sortOrder || ObsidianStandardDefaultSortingName + let uiSortOrder: string = this.getFileExplorer().v?.view?.sortOrder || ObsidianStandardDefaultSortingName const has: HasSortingOrGrouping = collectSortingAndGroupingTypes(sortSpec) @@ -662,19 +643,16 @@ export default class CustomSortPlugin ) } - // Credits go to https://github.com/nothingislost/obsidian-bartender - getFileExplorer(): FileExplorerView | undefined { - let fileExplorer: FileExplorerView | undefined = this.app.workspace.getLeavesOfType("file-explorer")?.first() - ?.view as unknown as FileExplorerView; - return fileExplorer; + onunload() { } - onunload() { + onUserEnable() { } updateStatusBar() { if (this.statusBarItemEl) { - this.statusBarItemEl.setText(`Custom sort:${this.settings.suspended ? 'OFF' : 'ON'}`) + let status = (!this.settings.suspended && this.customSortAppliedAtLeastOnce) ? 'ON' : 'OFF' + this.statusBarItemEl.setText(`Custom sort:${status}`) } } @@ -691,6 +669,34 @@ export default class CustomSortPlugin await this.saveData(this.settings); } + isThePluginStillInstalledAndEnabled(): boolean { + const thisPlugin: CommunityPlugin | undefined = this?.app?.plugins?.plugins?.[PLUGIN_ID] + if (thisPlugin && thisPlugin._loaded && this?.app?.plugins?.enabledPlugins?.has(PLUGIN_ID)) { + return true + } + return false + } + + initialDelayedApplicationOfCustomSorting() { + if (!this?.isThePluginStillInstalledAndEnabled()) { + console.log(`${PLUGIN_ID} v${this.manifest.version} - delayed handler skipped, plugin no longer active.`) + return + } + + // should be applied? yes (based on settings) + const shouldSortingBeApplied = !this.settings.suspended + + if (!shouldSortingBeApplied || this.customSortAppliedAtLeastOnce) { + return + } + + this.switchPluginStateTo(true) + } + + setRibbonIconToEnabled() { + setIcon(this.ribbonIconEl, ICON_SORT_ENABLED_ACTIVE) + } + // API derivedIndexNoteNameForFolderNotes: string | undefined indexNoteNameForFolderNotesDerivedFrom: any @@ -703,3 +709,4 @@ export default class CustomSortPlugin return this.derivedIndexNoteNameForFolderNotes } } + diff --git a/src/settings.ts b/src/settings.ts index 3a0d251..8e13d7c 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -13,8 +13,15 @@ export interface CustomSortPluginSettings { customSortContextSubmenu: boolean bookmarksContextMenus: boolean bookmarksGroupToConsumeAsOrderingReference: string + delayForInitialApplication: number // miliseconds } +const MILIS = 1000 +const DEFAULT_DELAY_SECONDS = 1 +const DELAY_MIN_SECONDS = 1 +const DELAY_MAX_SECONDS = 30 +const DEFAULT_DELAY = DEFAULT_DELAY_SECONDS * MILIS + export const DEFAULT_SETTINGS: CustomSortPluginSettings = { additionalSortspecFile: '', indexNoteNameForFolderNotes: '', @@ -25,7 +32,8 @@ export const DEFAULT_SETTINGS: CustomSortPluginSettings = { customSortContextSubmenu: true, automaticBookmarksIntegration: false, bookmarksContextMenus: false, - bookmarksGroupToConsumeAsOrderingReference: 'sortspec' + bookmarksGroupToConsumeAsOrderingReference: 'sortspec', + delayForInitialApplication: DEFAULT_DELAY } // On API 1.2.x+ enable the bookmarks integration by default @@ -51,7 +59,27 @@ export class CustomSortSettingTab extends PluginSettingTab { containerEl.empty(); - // containerEl.createEl('h2', {text: 'Settings for Custom File Explorer Sorting Plugin'}); + const delayDescr: DocumentFragment = sanitizeHTMLToDom( + 'Seconds to wait before applying custom ordering on plugin / app start.' + + '
' + + 'For large vaults or on mobile the value might need to be increased if plugin constantly fails to auto-apply' + + ' custom ordering on start.' + + '
' + + `Min: ${DELAY_MIN_SECONDS} sec., max. ${DELAY_MAX_SECONDS} sec.` + ) + + new Setting(containerEl) + .setName('Delay for initial automatic application of custom ordering') + .setDesc(delayDescr) + .addText(text => text + .setValue(`${this.plugin.settings.delayForInitialApplication/MILIS}`) + .onChange(async (value) => { + let delayS = parseInt(value, 10) + delayS = Number.isNaN(delayS) ? DEFAULT_DELAY_SECONDS : (delayS < DELAY_MIN_SECONDS ? DELAY_MIN_SECONDS :(delayS > DELAY_MAX_SECONDS ? DELAY_MAX_SECONDS : delayS)) + delayS = Math.round(delayS) + this.plugin.settings.delayForInitialApplication = delayS * MILIS + await this.plugin.saveSettings() + })) const additionalSortspecFileDescr: DocumentFragment = sanitizeHTMLToDom( 'A note name or note path to scan (YAML frontmatter) for sorting specification in addition to the `sortspec` notes and Folder Notes.' diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 6e9a2ee..a82f53a 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -1,6 +1,6 @@ import {PluginInstance, TFolder, WorkspaceLeaf} from "obsidian"; -// Needed to support monkey-patching of the folder sort() function +// Needed to support monkey-patching functions of FileExplorerLeaf or FileExplorerView declare module 'obsidian' { export interface ViewRegistry { @@ -46,9 +46,6 @@ declare module 'obsidian' { getPluginById(id: string): InstalledPlugin; } - interface FileExplorerFolder { - } - export interface FileExplorerView extends View { getSortedFolderItems(sortedFolder: TFolder): any[]; @@ -57,6 +54,11 @@ declare module 'obsidian' { sortOrder: string } + export interface FileExplorerLeaf extends WorkspaceLeaf { + view: FileExplorerView + get isDeferred(): boolean // since Obsidian 1.7.2 + } + interface MenuItem { setSubmenu: () => Menu; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 65d9257..b020a09 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -24,3 +24,25 @@ export function extractBasename (configEntry: string | undefined): string | unde return configEntry } } + +export class ValueOrError { + constructor(private value?: V, private error?: E) { + if (value) this.error = undefined + } + public setValue(value: V): ValueOrError { + this.value = value + this.error = undefined + return this + } + public setError(error: E): ValueOrError { + this.value = undefined + this.error = error + return this + } + public get v() { + return this.value + } + public get e() { + return this.error + } +}