#188 - Group of changes addressing compatibility issued with Obsidian 1.7.2 and newer

This commit is contained in:
SebastianMC 2025-01-06 02:25:07 +01:00
parent 2c2053d853
commit ae0d2f5280
6 changed files with 217 additions and 172 deletions

View File

@ -161,7 +161,7 @@ export class FolderWildcardMatching<SortingSpec> {
} }
} }
rule = rule ?? inheritedRule rule ??= inheritedRule
} }
if (rule) { if (rule) {

View File

@ -673,7 +673,7 @@ export interface SortSpecsCollection {
} }
const ensureCollectionHasSortSpecByPath = (collection?: SortSpecsCollection | null) => { const ensureCollectionHasSortSpecByPath = (collection?: SortSpecsCollection | null) => {
collection = collection ?? {} collection ??= {}
if (!collection.sortSpecByPath) { if (!collection.sortSpecByPath) {
collection.sortSpecByPath = {} collection.sortSpecByPath = {}
} }
@ -681,7 +681,7 @@ const ensureCollectionHasSortSpecByPath = (collection?: SortSpecsCollection | nu
} }
const ensureCollectionHasSortSpecByName = (collection?: SortSpecsCollection | null) => { const ensureCollectionHasSortSpecByName = (collection?: SortSpecsCollection | null) => {
collection = collection ?? {} collection ??= {}
if (!collection.sortSpecByName) { if (!collection.sortSpecByName) {
collection.sortSpecByName = {} collection.sortSpecByName = {}
} }
@ -689,7 +689,7 @@ const ensureCollectionHasSortSpecByName = (collection?: SortSpecsCollection | nu
} }
const ensureCollectionHasSortSpecByWildcard = (collection?: SortSpecsCollection | null) => { const ensureCollectionHasSortSpecByWildcard = (collection?: SortSpecsCollection | null) => {
collection = collection ?? {} collection ??= {}
if (!collection.sortSpecByWildcard) { if (!collection.sortSpecByWildcard) {
collection.sortSpecByWildcard = new FolderWildcardMatching<CustomSortSpec>((spec: CustomSortSpec) => !!spec.implicit) collection.sortSpecByWildcard = new FolderWildcardMatching<CustomSortSpec>((spec: CustomSortSpec) => !!spec.implicit)
} }

View File

@ -1,4 +1,5 @@
import { import {
FileExplorerLeaf,
FileExplorerView, FileExplorerView,
Menu, Menu,
MenuItem, MenuItem,
@ -10,7 +11,8 @@ import {
TAbstractFile, TAbstractFile,
TFile, TFile,
TFolder, TFolder,
Vault, WorkspaceLeaf Vault,
WorkspaceLeaf
} from 'obsidian'; } from 'obsidian';
import {around} from 'monkey-around'; import {around} from 'monkey-around';
import { import {
@ -26,7 +28,6 @@ import {
import { import {
CustomSortSpec CustomSortSpec
} from './custom-sort/custom-sort-types'; } from './custom-sort/custom-sort-types';
import { import {
addIcons, addIcons,
ICON_SORT_ENABLED_ACTIVE, ICON_SORT_ENABLED_ACTIVE,
@ -46,7 +47,8 @@ import {
} from "./utils/ObsidianIconFolderPluginSignature"; } from "./utils/ObsidianIconFolderPluginSignature";
import { import {
extractBasename, extractBasename,
lastPathComponent lastPathComponent,
ValueOrError,
} from "./utils/utils"; } from "./utils/utils";
import { import {
collectSortingAndGroupingTypes, collectSortingAndGroupingTypes,
@ -72,6 +74,13 @@ type MonkeyAroundUninstaller = () => void
type ContextMenuProvider = (item: MenuItem) => void type ContextMenuProvider = (item: MenuItem) => void
enum FileExplorerStateError {
DoesNotExist,
DeferredView
}
type FileExplorerLeafOrError = ValueOrError<FileExplorerLeaf,FileExplorerStateError>
export default class CustomSortPlugin export default class CustomSortPlugin
extends Plugin extends Plugin
implements CustomSortPluginAPI implements CustomSortPluginAPI
@ -79,12 +88,10 @@ export default class CustomSortPlugin
settings: CustomSortPluginSettings settings: CustomSortPluginSettings
statusBarItemEl: HTMLElement statusBarItemEl: HTMLElement
ribbonIconEl: HTMLElement // On small-screen mobile devices this is useless (ribbon is re-created on-the-fly) 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 sortSpecCache?: SortSpecsCollection | null
initialAutoOrManualSortingTriggered: boolean initialAutoOrManualSortingTriggered: boolean
customSortAppliedAtLeastOnce: boolean = false
fileExplorerFolderPatched: boolean
showNotice(message: string, timeout?: number) { showNotice(message: string, timeout?: number) {
if (this.settings.notificationsEnabled || (Platform.isMobile && this.settings.mobileNotificationsEnabled)) { if (this.settings.notificationsEnabled || (Platform.isMobile && this.settings.mobileNotificationsEnabled)) {
@ -134,7 +141,7 @@ export default class CustomSortPlugin
aFile.basename === this.settings.indexNoteNameForFolderNotes || // when user configured as index aFile.basename === this.settings.indexNoteNameForFolderNotes || // when user configured as index
aFile.name === this.settings.indexNoteNameForFolderNotes // when user configured as index.md 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 // Warning: newer Obsidian versions can return objects as well, hence the explicit check for string value
if (typeof sortingSpecTxt === 'string') { if (typeof sortingSpecTxt === 'string') {
anySortingSpecFound = true anySortingSpecFound = true
@ -154,7 +161,11 @@ export default class CustomSortPlugin
}) })
if (this.sortSpecCache) { if (this.sortSpecCache) {
if (anySortingSpecFound) {
this.showNotice(`Parsing custom sorting specification SUCCEEDED!`) this.showNotice(`Parsing custom sorting specification SUCCEEDED!`)
} else {
this.showNotice(`Implicit custom sorting specification consumed.`)
}
} else { } else {
if (anySortingSpecFound) { 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` 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,19 +178,99 @@ export default class CustomSortPlugin
} }
} }
checkFileExplorerIsAvailableAndPatchable(logWarning: boolean = true): FileExplorerView | undefined { // Credits go to https://github.com/nothingislost/obsidian-bartender
let fileExplorerView: FileExplorerView | undefined = this.getFileExplorer() getFileExplorer(): FileExplorerLeafOrError {
if (fileExplorerView && typeof fileExplorerView.requestSort === 'function') { let fileExplorer: FileExplorerLeaf | undefined = this.app.workspace.getLeavesOfType("file-explorer")?.first() as FileExplorerLeaf;
if (typeof fileExplorerView.getSortedFolderItems === 'function') { const fileExplorerOrError: FileExplorerLeafOrError = new ValueOrError()
return fileExplorerView
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) // Various scenarios when File Explorer was turned off (e.g. by some other plugin)
if (logWarning) { if (logWarning) {
this.logWarningFileExplorerNotAvailable() this.logWarningFileExplorerNotAvailable()
} }
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 return undefined
} }
}
logWarningFileExplorerDeferred() {
const msg = `custom-sort 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() { logWarningFileExplorerNotAvailable() {
const msg = `custom-sort v${this.manifest.version}: failed to locate File Explorer. The 'Files' core plugin can be disabled.\n` const msg = `custom-sort v${this.manifest.version}: failed to locate File Explorer. The 'Files' core plugin can be disabled.\n`
@ -190,36 +281,35 @@ export default class CustomSortPlugin
} }
// Safe to suspend when suspended and re-enable when enabled // Safe to suspend when suspended and re-enable when enabled
switchPluginStateTo(enabled: boolean, updateRibbonBtnIcon: boolean = true) { switchPluginStateTo(enabled: boolean) {
let fileExplorerView: FileExplorerView | undefined = this.checkFileExplorerIsAvailableAndPatchable()
if (fileExplorerView && !this.fileExplorerFolderPatched) {
this.fileExplorerFolderPatched = this.patchFileExplorerFolder(fileExplorerView);
if (!this.fileExplorerFolderPatched) {
fileExplorerView = undefined
}
}
this.settings.suspended = !enabled; this.settings.suspended = !enabled;
this.saveSettings() 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) { if (this.settings.suspended) {
this.showNotice('Custom sort OFF'); this.showNotice('Custom sort OFF');
this.sortSpecCache = null this.sortSpecCache = null
iconToSet = ICON_SORT_SUSPENDED setIcon(this.ribbonIconEl, ICON_SORT_SUSPENDED)
if (fileExplorer) {
fileExplorer.view.requestSort();
}
} else { } else {
this.readAndParseSortingSpec(); this.readAndParseSortingSpec();
if (this.sortSpecCache) { if (this.sortSpecCache) {
if (fileExplorerView) { if (fileExplorer) {
this.showNotice('Custom sort ON'); this.initialAutoOrManualSortingTriggered = true
iconToSet = ICON_SORT_ENABLED_ACTIVE this.customSortAppliedAtLeastOnce = false
fileExplorer.view.requestSort();
} else { } else {
this.showNotice('Custom sort GENERAL PROBLEM. See console for detailed message.'); this.showNotice('Custom sort File Explorer view problem. See console for detailed message.');
iconToSet = ICON_SORT_SUSPENDED_GENERAL_ERROR setIcon(this.ribbonIconEl, ICON_SORT_SUSPENDED_GENERAL_ERROR)
this.settings.suspended = true this.settings.suspended = true
this.saveSettings() this.saveSettings()
} }
} else { } else {
iconToSet = ICON_SORT_SUSPENDED_SYNTAX_ERROR setIcon(this.ribbonIconEl, ICON_SORT_SUSPENDED_SYNTAX_ERROR)
this.settings.suspended = true this.settings.suspended = true
this.saveSettings() this.saveSettings()
} }
@ -231,27 +321,6 @@ export default class CustomSortPlugin
getBookmarksPlugin(this.app, this.settings.bookmarksGroupToConsumeAsOrderingReference, ForceFlushCache) 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 <div> Element is useless,
// as the ribbon (and its icons) get re-created each time when re-displayed (expanded)
setIcon(this.ribbonIconEl, iconToSet)
}
this.updateStatusBar(); this.updateStatusBar();
} }
@ -273,7 +342,7 @@ export default class CustomSortPlugin
// in result, the handle to the ribbon <div> Element is useless // in result, the handle to the ribbon <div> Element is useless
this.ribbonIconEl = this.addRibbonIcon( this.ribbonIconEl = this.addRibbonIcon(
Platform.isDesktop ? 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 ICON_SORT_MOBILE_INITIAL // REMARK: on small-screen mobile devices this icon stays permanent
, ,
@ -282,10 +351,6 @@ export default class CustomSortPlugin
this.switchPluginStateTo(this.settings.suspended) this.switchPluginStateTo(this.settings.suspended)
}); });
if (!this.settings.suspended) {
this.ribbonIconStateInaccurate = true
}
this.addSettingTab(new CustomSortSettingTab(this.app, this)); this.addSettingTab(new CustomSortSettingTab(this.app, this));
this.registerEventHandlers() this.registerEventHandlers()
@ -299,54 +364,17 @@ export default class CustomSortPlugin
const plugin: CustomSortPlugin = this const plugin: CustomSortPlugin = this
const m: boolean = Platform.isMobile 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) => { const applyCustomSortMenuItem = (item: MenuItem) => {
item.setTitle(m ? 'Custom sort: apply custom sorting' : 'Apply custom sorting'); item.setTitle(m ? 'Custom sort: apply custom sorting' : 'Apply custom sorting');
item.onClick(() => { item.onClick(() => {
plugin.switchPluginStateTo(true, true) plugin.switchPluginStateTo(true)
}) })
}; };
const suspendCustomSortMenuItem = (item: MenuItem) => { const suspendCustomSortMenuItem = (item: MenuItem) => {
item.setTitle(m ? 'Custom sort: suspend custom sorting' : 'Suspend custom sorting'); item.setTitle(m ? 'Custom sort: suspend custom sorting' : 'Suspend custom sorting');
item.onClick(() => { item.onClick(() => {
plugin.switchPluginStateTo(false, true) plugin.switchPluginStateTo(false)
}) })
}; };
@ -532,28 +560,32 @@ export default class CustomSortPlugin
id: 'enable-custom-sorting', id: 'enable-custom-sorting',
name: 'Enable and apply the custom sorting, (re)parsing the sorting configuration first. Sort-on.', name: 'Enable and apply the custom sorting, (re)parsing the sorting configuration first. Sort-on.',
callback: () => { callback: () => {
plugin.switchPluginStateTo(true, true) plugin.switchPluginStateTo(true)
} }
}); });
this.addCommand({ this.addCommand({
id: 'suspend-custom-sorting', id: 'suspend-custom-sorting',
name: 'Suspend the custom sorting. Sort-off.', name: 'Suspend the custom sorting. Sort-off.',
callback: () => { callback: () => {
plugin.switchPluginStateTo(false, true) plugin.switchPluginStateTo(false)
} }
}); });
} }
initialize() { initialize() {
const plugin = this
this.app.workspace.onLayoutReady(() => { this.app.workspace.onLayoutReady(() => {
this.fileExplorerFolderPatched = this.patchFileExplorerFolder(); setTimeout(() => {
plugin.initialDelayedApplicationOfCustomSorting.apply(this)
},
plugin.settings.delayForInitialApplication)
}) })
} }
determineSortSpecForFolder(folderPath: string, folderName?: string): CustomSortSpec|null|undefined { determineSortSpecForFolder(folderPath: string, folderName?: string): CustomSortSpec|null|undefined {
folderName = folderName ?? lastPathComponent(folderPath) folderName ??= lastPathComponent(folderPath)
let sortSpec: CustomSortSpec | null | undefined = this.sortSpecCache?.sortSpecByPath?.[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) { if (!sortSpec && this.sortSpecCache?.sortSpecByWildcard) {
// when no sorting spec found directly by folder path, check for wildcard-based match // when no sorting spec found directly by folder path, check for wildcard-based match
@ -572,13 +604,6 @@ export default class CustomSortPlugin
return ctx return ctx
} }
resetIconInaccurateStateToEnabled() {
if (this.ribbonIconStateInaccurate && this.ribbonIconEl) {
this.ribbonIconStateInaccurate = false
setIcon(this.ribbonIconEl, ICON_SORT_ENABLED_ACTIVE)
}
}
determineAndPrepareSortingDataForFolder(folder: TFolder) { determineAndPrepareSortingDataForFolder(folder: TFolder) {
let sortSpec: CustomSortSpec | null | undefined = this.determineSortSpecForFolder(folder.path, folder.name) let sortSpec: CustomSortSpec | null | undefined = this.determineSortSpecForFolder(folder.path, folder.name)
@ -598,58 +623,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<TAbstractFile> { orderedFolderItemsForBookmarking(folder: TFolder, bookmarksPlugin: BookmarksPluginInterface): Array<TAbstractFile> {
let sortSpec: CustomSortSpec | null | undefined = undefined let sortSpec: CustomSortSpec | null | undefined = undefined
if (!this.settings.suspended) { if (!this.settings.suspended) {
sortSpec = this.determineSortSpecForFolder(folder.path, folder.name) 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) const has: HasSortingOrGrouping = collectSortingAndGroupingTypes(sortSpec)
@ -662,19 +641,16 @@ export default class CustomSortPlugin
) )
} }
// Credits go to https://github.com/nothingislost/obsidian-bartender onunload() {
getFileExplorer(): FileExplorerView | undefined {
let fileExplorer: FileExplorerView | undefined = this.app.workspace.getLeavesOfType("file-explorer")?.first()
?.view as unknown as FileExplorerView;
return fileExplorer;
} }
onunload() { onUserEnable() {
} }
updateStatusBar() { updateStatusBar() {
if (this.statusBarItemEl) { 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 +667,25 @@ export default class CustomSortPlugin
await this.saveData(this.settings); await this.saveData(this.settings);
} }
initialDelayedApplicationOfCustomSorting() {
if (!this) {
return // sanity check - plugin removed from the system?
}
// 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 // API
derivedIndexNoteNameForFolderNotes: string | undefined derivedIndexNoteNameForFolderNotes: string | undefined
indexNoteNameForFolderNotesDerivedFrom: any indexNoteNameForFolderNotesDerivedFrom: any
@ -703,3 +698,4 @@ export default class CustomSortPlugin
return this.derivedIndexNoteNameForFolderNotes return this.derivedIndexNoteNameForFolderNotes
} }
} }

View File

@ -13,8 +13,15 @@ export interface CustomSortPluginSettings {
customSortContextSubmenu: boolean customSortContextSubmenu: boolean
bookmarksContextMenus: boolean bookmarksContextMenus: boolean
bookmarksGroupToConsumeAsOrderingReference: string 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 = { export const DEFAULT_SETTINGS: CustomSortPluginSettings = {
additionalSortspecFile: '', additionalSortspecFile: '',
indexNoteNameForFolderNotes: '', indexNoteNameForFolderNotes: '',
@ -25,7 +32,8 @@ export const DEFAULT_SETTINGS: CustomSortPluginSettings = {
customSortContextSubmenu: true, customSortContextSubmenu: true,
automaticBookmarksIntegration: false, automaticBookmarksIntegration: false,
bookmarksContextMenus: false, bookmarksContextMenus: false,
bookmarksGroupToConsumeAsOrderingReference: 'sortspec' bookmarksGroupToConsumeAsOrderingReference: 'sortspec',
delayForInitialApplication: DEFAULT_DELAY
} }
// On API 1.2.x+ enable the bookmarks integration by default // On API 1.2.x+ enable the bookmarks integration by default
@ -51,7 +59,24 @@ export class CustomSortSettingTab extends PluginSettingTab {
containerEl.empty(); 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.'
+ '<br>'
+ 'For large vaults or on mobile the value might need to be increased if plugin constantly fails to auto-apply'
+ ' custom ordering on start.'
)
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_MIN_SECONDS : delayS))
this.plugin.settings.delayForInitialApplication = delayS * MILIS
await this.plugin.saveSettings()
}))
const additionalSortspecFileDescr: DocumentFragment = sanitizeHTMLToDom( 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.' 'A note name or note path to scan (YAML frontmatter) for sorting specification in addition to the `sortspec` notes and Folder Notes.'

10
src/types/types.d.ts vendored
View File

@ -1,6 +1,6 @@
import {PluginInstance, TFolder, WorkspaceLeaf} from "obsidian"; 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' { declare module 'obsidian' {
export interface ViewRegistry { export interface ViewRegistry {
@ -46,9 +46,6 @@ declare module 'obsidian' {
getPluginById(id: string): InstalledPlugin; getPluginById(id: string): InstalledPlugin;
} }
interface FileExplorerFolder {
}
export interface FileExplorerView extends View { export interface FileExplorerView extends View {
getSortedFolderItems(sortedFolder: TFolder): any[]; getSortedFolderItems(sortedFolder: TFolder): any[];
@ -57,6 +54,11 @@ declare module 'obsidian' {
sortOrder: string sortOrder: string
} }
export interface FileExplorerLeaf extends WorkspaceLeaf {
view: FileExplorerView
get isDeferred(): boolean // since Obsidian 1.7.2
}
interface MenuItem { interface MenuItem {
setSubmenu: () => Menu; setSubmenu: () => Menu;
} }

View File

@ -24,3 +24,25 @@ export function extractBasename (configEntry: string | undefined): string | unde
return configEntry return configEntry
} }
} }
export class ValueOrError<V,E> {
constructor(private value?: V, private error?: E) {
if (value) this.error = undefined
}
public setValue(value: V): ValueOrError<V,E> {
this.value = value
this.error = undefined
return this
}
public setError(error: E): ValueOrError<V,E> {
this.value = undefined
this.error = error
return this
}
public get v() {
return this.value
}
public get e() {
return this.error
}
}