diff --git a/src/custom-sort/custom-sort-types.ts b/src/custom-sort/custom-sort-types.ts index 8a65ab0..1a7df75 100644 --- a/src/custom-sort/custom-sort-types.ts +++ b/src/custom-sort/custom-sort-types.ts @@ -1,3 +1,5 @@ +import {Plugin} from "obsidian"; + export enum CustomSortGroupType { Outsiders, // Not belonging to any of other groups MatchAll, // like a wildard *, used in connection with foldersOnly or filesOnly. The difference between the MatchAll and Outsiders is @@ -5,6 +7,7 @@ export enum CustomSortGroupType { ExactPrefix, // ... while the Outsiders captures items which didn't match any of other defined groups ExactSuffix, ExactHeadAndTail, // Like W...n or Un...ed, which is shorter variant of typing the entire title + HasMetadataField // Notes (or folder's notes) containing a specific metadata field } export enum CustomSortOrder { @@ -18,6 +21,7 @@ export enum CustomSortOrder { byCreatedTimeAdvanced, byCreatedTimeReverse, byCreatedTimeReverseAdvanced, + byMetadataField, standardObsidian, // Let the folder sorting be in hands of Obsidian, whatever user selected in the UI default = alphabetical } @@ -44,7 +48,8 @@ export interface CustomSortGroup { secondaryOrder?: CustomSortOrder filesOnly?: boolean matchFilenameWithExt?: boolean - foldersOnly?: boolean, + foldersOnly?: boolean + metadataFieldName?: string } export interface CustomSortSpec { @@ -55,4 +60,5 @@ export interface CustomSortSpec { outsidersFilesGroupIdx?: number outsidersFoldersGroupIdx?: number itemsToHide?: Set + plugin?: Plugin // to hand over the access to App instance to the sorting engine } diff --git a/src/custom-sort/custom-sort.ts b/src/custom-sort/custom-sort.ts index 69578be..5223626 100644 --- a/src/custom-sort/custom-sort.ts +++ b/src/custom-sort/custom-sort.ts @@ -1,4 +1,4 @@ -import {requireApiVersion, TAbstractFile, TFile, TFolder} from 'obsidian'; +import {MetadataCache, requireApiVersion, TAbstractFile, TFile, TFolder} from 'obsidian'; import {CustomSortGroup, CustomSortGroupType, CustomSortOrder, CustomSortSpec} from "./custom-sort-types"; import {isDefined} from "../utils/utils"; @@ -33,6 +33,7 @@ let Sorters: { [key in CustomSortOrder]: SorterFn } = { [CustomSortOrder.byCreatedTimeAdvanced]: (a: FolderItemForSorting, b: FolderItemForSorting) => a.ctimeNewest - b.ctimeNewest, [CustomSortOrder.byCreatedTimeReverse]: (a: FolderItemForSorting, b: FolderItemForSorting) => (a.isFolder && b.isFolder) ? Collator(a.sortString, b.sortString) : (b.ctimeOldest - a.ctimeOldest), [CustomSortOrder.byCreatedTimeReverseAdvanced]: (a: FolderItemForSorting, b: FolderItemForSorting) => b.ctimeOldest - a.ctimeOldest, + [CustomSortOrder.byMetadataField]: (a: FolderItemForSorting, b: FolderItemForSorting) => Collator(a.sortString, b.sortString), // This is a fallback entry which should not be used - the plugin code should refrain from custom sorting at all [CustomSortOrder.standardObsidian]: (a: FolderItemForSorting, b: FolderItemForSorting) => Collator(a.sortString, b.sortString), @@ -70,6 +71,7 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus let groupIdx: number let determined: boolean = false let matchedGroup: string | null | undefined + let sortString: string | undefined const aFolder: boolean = isFolder(entry) const aFile: boolean = !aFolder const entryAsTFile: TFile = entry as TFile @@ -144,10 +146,24 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus matchedGroup = group.regexSpec?.normalizerFn(match[1]); } } - break; + break + case CustomSortGroupType.HasMetadataField: + if (group.metadataFieldName) { + const mCache: MetadataCache | undefined = spec.plugin?.app.metadataCache + if (mCache) { + // For folders - scan metadata of 'folder note' + const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md` + const metadataValue: string | undefined = mCache.getCache(notePathToScan)?.frontmatter?.[group.metadataFieldName] + if (metadataValue) { + determined = true + sortString = metadataValue + } + } + } + break case CustomSortGroupType.MatchAll: determined = true; - break; + break } if (determined) { break; @@ -171,7 +187,7 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus return { // idx of the matched group or idx of Outsiders group or the largest index (= groups count+1) groupIdx: determinedGroupIdx, - sortString: matchedGroup ? (matchedGroup + '//' + entry.name) : entry.name, + sortString: sortString ?? (matchedGroup ? (matchedGroup + '//' + entry.name) : entry.name), matchGroup: matchedGroup ?? undefined, isFolder: aFolder, folder: aFolder ? (entry as TFolder) : undefined, @@ -255,7 +271,7 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[] return itemForSorting }) - // Finally, for advanced sorting by modified date, for some of the folders the modified date has to be determined + // Finally, for advanced sorting by modified date, for some folders the modified date has to be determined determineFolderDatesIfNeeded(folderItems, sortingSpec, sortingGroupsCardinality) folderItems.sort(function (itA: FolderItemForSorting, itB: FolderItemForSorting) { diff --git a/src/custom-sort/sorting-spec-processor.spec.ts b/src/custom-sort/sorting-spec-processor.spec.ts index 0871221..2a48e67 100644 --- a/src/custom-sort/sorting-spec-processor.spec.ts +++ b/src/custom-sort/sorting-spec-processor.spec.ts @@ -24,6 +24,12 @@ target-folder: tricky folder / /: +:::: tricky folder 2 +/: metadata: + > modified +metadata: Pages + > a-z + :::: Conceptual model /: Entities % @@ -71,6 +77,12 @@ target-folder: tricky folder /folders /:files +target-folder: tricky folder 2 +/:files metadata: + > modified +% metadata: Pages + > a-z + :::: Conceptual model /:files Entities % @@ -142,6 +154,25 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = { outsidersFoldersGroupIdx: 0, targetFoldersPaths: ['tricky folder'] }, + "tricky folder 2": { + groups: [{ + filesOnly: true, + metadataFieldName: 'sort-index-value', + order: CustomSortOrder.byModifiedTimeReverse, + type: CustomSortGroupType.HasMetadataField + }, { + metadataFieldName: 'Pages', + order: CustomSortOrder.alphabeticalReverse, + type: CustomSortGroupType.HasMetadataField + }, { + order: CustomSortOrder.alphabetical, + type: CustomSortGroupType.Outsiders + }], + outsidersGroupIdx: 2, + targetFoldersPaths: [ + 'tricky folder 2' + ] + }, "Conceptual model": { groups: [{ exactText: "Entities", diff --git a/src/custom-sort/sorting-spec-processor.ts b/src/custom-sort/sorting-spec-processor.ts index 4ac55a3..c690256 100644 --- a/src/custom-sort/sorting-spec-processor.ts +++ b/src/custom-sort/sorting-spec-processor.ts @@ -173,6 +173,10 @@ const AnyTypeGroupLexeme: string = '%' // See % as a combination of / and : const HideItemShortLexeme: string = '--%' // See % as a combination of / and : const HideItemVerboseLexeme: string = '/--hide:' +const MetadataFieldIndicatorLexeme: string = 'metadata:' + +const DEFAULT_METADATA_FIELD_FOR_SORTING: string = 'sort-index-value' + const CommentPrefix: string = '//' interface SortingGroupType { @@ -380,6 +384,12 @@ const stripWildcardPatternSuffix = (path: string): [path: string, priority: numb ] } +// Simplistic +const extractIdentifier = (text: string, defaultResult?: string): string | undefined => { + const identifier: string = text.trim().split(' ')?.[0]?.trim() + return identifier ? identifier : defaultResult +} + const ADJACENCY_ERROR: string = "Numerical sorting symbol must not be directly adjacent to a wildcard because of potential performance problem. An additional explicit separator helps in such case." export class SortingSpecProcessor { @@ -925,13 +935,28 @@ export class SortingSpecProcessor { matchFilenameWithExt: spec.matchFilenameWithExt // Doesn't make sense for matching, yet for multi-match } // theoretically could match the sorting of matched files } else { - // For non-three dots single text line assume exact match group - return { - type: CustomSortGroupType.ExactName, - exactText: spec.arraySpec[0], - filesOnly: spec.filesOnly, - foldersOnly: spec.foldersOnly, - matchFilenameWithExt: spec.matchFilenameWithExt + // prototyping - only detect the presence of metadata: lexem + if (theOnly.startsWith(MetadataFieldIndicatorLexeme)) { + const metadataFieldName: string | undefined = extractIdentifier( + theOnly.substring(MetadataFieldIndicatorLexeme.length), + DEFAULT_METADATA_FIELD_FOR_SORTING + ) + return { + type: CustomSortGroupType.HasMetadataField, + metadataFieldName: metadataFieldName, + filesOnly: spec.filesOnly, + foldersOnly: spec.foldersOnly, + matchFilenameWithExt: spec.matchFilenameWithExt + } + } else { + // For non-three dots single text line assume exact match group + return { + type: CustomSortGroupType.ExactName, + exactText: theOnly, + filesOnly: spec.filesOnly, + foldersOnly: spec.foldersOnly, + matchFilenameWithExt: spec.matchFilenameWithExt + } } } } diff --git a/src/main.ts b/src/main.ts index edf7ef2..ebd79dc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -292,6 +292,7 @@ export default class CustomSortPlugin extends Plugin { } } if (sortSpec) { + sortSpec.plugin = plugin return folderSort.call(this, sortSpec, ...args); } else { return old.call(this, ...args);