#188 - Group of changes addressing compatibility issued with Obsidian 1.7.2 and newer
This commit is contained in:
parent
2c2053d853
commit
ae0d2f5280
|
@ -161,7 +161,7 @@ export class FolderWildcardMatching<SortingSpec> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rule = rule ?? inheritedRule
|
rule ??= inheritedRule
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rule) {
|
if (rule) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
316
src/main.ts
316
src/main.ts
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.'
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue