|
|
@ -1,4 +1,7 @@
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
|
|
|
|
App,
|
|
|
|
|
|
|
|
CommunityPlugin,
|
|
|
|
|
|
|
|
FileExplorerLeaf,
|
|
|
|
FileExplorerView,
|
|
|
|
FileExplorerView,
|
|
|
|
Menu,
|
|
|
|
Menu,
|
|
|
|
MenuItem,
|
|
|
|
MenuItem,
|
|
|
@ -10,7 +13,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 +30,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,
|
|
|
@ -42,11 +45,12 @@ import {
|
|
|
|
groupNameForPath
|
|
|
|
groupNameForPath
|
|
|
|
} from "./utils/BookmarksCorePluginSignature";
|
|
|
|
} from "./utils/BookmarksCorePluginSignature";
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
getIconFolderPlugin
|
|
|
|
getIconFolderPlugin, ObsidianIconFolder_PluginInstance, ObsidianIconFolderPlugin_getData_methodName
|
|
|
|
} from "./utils/ObsidianIconFolderPluginSignature";
|
|
|
|
} from "./utils/ObsidianIconFolderPluginSignature";
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
extractBasename,
|
|
|
|
extractBasename,
|
|
|
|
lastPathComponent
|
|
|
|
lastPathComponent,
|
|
|
|
|
|
|
|
ValueOrError,
|
|
|
|
} from "./utils/utils";
|
|
|
|
} from "./utils/utils";
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
collectSortingAndGroupingTypes,
|
|
|
|
collectSortingAndGroupingTypes,
|
|
|
@ -62,6 +66,8 @@ import {
|
|
|
|
} from "./settings";
|
|
|
|
} from "./settings";
|
|
|
|
import {CustomSortPluginAPI} from "./custom-sort-plugin";
|
|
|
|
import {CustomSortPluginAPI} from "./custom-sort-plugin";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const PLUGIN_ID = 'custom-sort' // same as in manifest.json
|
|
|
|
|
|
|
|
|
|
|
|
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
|
|
|
|
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
|
|
|
|
const SORTINGSPEC_YAML_KEY: string = 'sorting-spec'
|
|
|
|
const SORTINGSPEC_YAML_KEY: string = 'sorting-spec'
|
|
|
|
|
|
|
|
|
|
|
@ -72,6 +78,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 +92,9 @@ 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
|
|
|
|
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 +144,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 +164,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(`No custom sorting spec, will go with implicit sorting (bookmarks-based).`)
|
|
|
|
|
|
|
|
}
|
|
|
|
} 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,22 +181,102 @@ 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 = `${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() {
|
|
|
|
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`
|
|
|
|
+ `Some community plugins can also disable it.\n`
|
|
|
|
+ `See the example of MAKE.md plugin: https://github.com/Make-md/makemd/issues/25\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`
|
|
|
|
+ `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
|
|
|
|
// 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.customSortAppliedAtLeastOnce = false
|
|
|
|
iconToSet = ICON_SORT_ENABLED_ACTIVE
|
|
|
|
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,32 +323,11 @@ 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async onload() {
|
|
|
|
async onload() {
|
|
|
|
console.log(`loading custom-sort v${this.manifest.version}`);
|
|
|
|
console.log(`loading ${PLUGIN_ID} v${this.manifest.version}`);
|
|
|
|
|
|
|
|
|
|
|
|
await this.loadSettings();
|
|
|
|
await this.loadSettings();
|
|
|
|
|
|
|
|
|
|
|
@ -273,7 +344,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 +353,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 +366,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 +562,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 +606,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 +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<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 +643,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 +669,34 @@ export default class CustomSortPlugin
|
|
|
|
await this.saveData(this.settings);
|
|
|
|
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
|
|
|
|
// API
|
|
|
|
derivedIndexNoteNameForFolderNotes: string | undefined
|
|
|
|
derivedIndexNoteNameForFolderNotes: string | undefined
|
|
|
|
indexNoteNameForFolderNotesDerivedFrom: any
|
|
|
|
indexNoteNameForFolderNotesDerivedFrom: any
|
|
|
@ -703,3 +709,4 @@ export default class CustomSortPlugin
|
|
|
|
return this.derivedIndexNoteNameForFolderNotes
|
|
|
|
return this.derivedIndexNoteNameForFolderNotes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|