From 60a60dfb72da7766ca5a3f265ab60770e53c77aa Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:11:29 +0200 Subject: [PATCH] #156 - improved support for index-note-based folder notes - implementation completed, basic tests done - needs code review with a fresh head - needs more regression tests of affected sorting methods --- src/custom-sort-plugin.ts | 7 +++++ src/custom-sort/custom-sort.ts | 52 +++++++++++++++++++++++----------- src/main.ts | 28 +++++++++++++++--- src/test/unit/utils.spec.ts | 20 ++++++++++++- src/utils/utils.ts | 8 ++++++ 5 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 src/custom-sort-plugin.ts diff --git a/src/custom-sort-plugin.ts b/src/custom-sort-plugin.ts new file mode 100644 index 0000000..858189f --- /dev/null +++ b/src/custom-sort-plugin.ts @@ -0,0 +1,7 @@ +import { + Plugin +} from 'obsidian' + +export interface CustomSortPluginAPI extends Plugin { + indexNoteBasename(): string|undefined +} diff --git a/src/custom-sort/custom-sort.ts b/src/custom-sort/custom-sort.ts index 686a252..dc4bb9f 100644 --- a/src/custom-sort/custom-sort.ts +++ b/src/custom-sort/custom-sort.ts @@ -34,10 +34,11 @@ import { import { BookmarksPluginInterface } from "../utils/BookmarksCorePluginSignature"; +import {CustomSortPluginAPI} from "../custom-sort-plugin"; export interface ProcessingContext { // For internal transient use - plugin?: Plugin // to hand over the access to App instance to the sorting engine + plugin?: CustomSortPluginAPI // to hand over the access to App instance to the sorting engine _mCache?: MetadataCache starredPluginInstance?: Starred_PluginInstance bookmarksPluginInstance?: BookmarksPluginInterface, @@ -371,6 +372,15 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string): return [false, undefined, undefined] } +const mdataValueFromFMCaches = (mdataFieldName: string, fc?: FrontMatterCache, fcPrio?: FrontMatterCache): any => { + let prioValue = undefined + if (fcPrio) { + prioValue = fcPrio?.[mdataFieldName] + } + + return prioValue ?? fc?.[mdataFieldName] +} + export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: ProcessingContext): FolderItemForSorting { let groupIdx: number let determined: boolean = false @@ -468,16 +478,18 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus if (ctx?._mCache) { // For folders - scan metadata of 'folder note' in same-name-as-folder mode const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md` - const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter - const hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName) + let frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter + let hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName) // For folders, if index-based folder note mode, scan the index file, giving it the priority - !!! Non trivial part: - need to know the folder-index name, this is hidden - !!! in plugin settings, not exposed - refactoring is a must. - if (aFolder && ctx?.plugin?.s) { - + if (aFolder) { + const indexNoteBasename = ctx?.plugin?.indexNoteBasename() + if (indexNoteBasename) { + frontMatterCache = ctx._mCache.getCache(`${entry.path}/${indexNoteBasename}.md`)?.frontmatter + hasMetadata = hasMetadata || frontMatterCache?.hasOwnProperty(group.withMetadataFieldName) + } } - if (hasMetadata || folderIndexNoteHasMetadata) { + if (hasMetadata) { determined = true } } @@ -561,17 +573,23 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus if (ctx?._mCache) { // For folders - scan metadata of 'folder note' // and if index-based folder note mode, scan the index file, giving it the priority - !!! Non trivial part: - need to know the folder-index-note name, this is hidden - !!! in plugin settings, not exposed - refactoring is a must. - !!! - !!! Then challenging part - how to easily rewrite the below code to handle scanning - !!! of two frontMatterCaches for folders and give the priority to folder-index-note based cache, if configured const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md` const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter - if (isPrimaryOrderByMetadata) metadataValueToSortBy = frontMatterCache?.[group?.byMetadataField || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING] - if (isSecondaryOrderByMetadata) metadataValueSecondaryToSortBy = frontMatterCache?.[group?.byMetadataFieldSecondary || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING] - if (isDerivedPrimaryByMetadata) metadataValueDerivedPrimaryToSortBy = frontMatterCache?.[spec.byMetadataField || DEFAULT_METADATA_FIELD_FOR_SORTING] - if (isDerivedSecondaryByMetadata) metadataValueDerivedSecondaryToSortBy = frontMatterCache?.[spec.byMetadataFieldSecondary || DEFAULT_METADATA_FIELD_FOR_SORTING] + let prioFrontMatterCache: FrontMatterCache | undefined = undefined + if (aFolder) { + const indexNoteBasename = ctx?.plugin?.indexNoteBasename() + if (indexNoteBasename) { + prioFrontMatterCache = ctx._mCache.getCache(`${entry.path}/${indexNoteBasename}.md`)?.frontmatter + } + } + if (isPrimaryOrderByMetadata) metadataValueToSortBy = + mdataValueFromFMCaches (group?.byMetadataField || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache) + if (isSecondaryOrderByMetadata) metadataValueSecondaryToSortBy = + mdataValueFromFMCaches (group?.byMetadataFieldSecondary || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache) + if (isDerivedPrimaryByMetadata) metadataValueDerivedPrimaryToSortBy = + mdataValueFromFMCaches (spec.byMetadataField || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache) + if (isDerivedSecondaryByMetadata) metadataValueDerivedSecondaryToSortBy = + mdataValueFromFMCaches (spec.byMetadataFieldSecondary || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache) } } } diff --git a/src/main.ts b/src/main.ts index 60c4fcb..62564c1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -50,8 +50,13 @@ import { getBookmarksPlugin, groupNameForPath } from "./utils/BookmarksCorePluginSignature"; -import {getIconFolderPlugin} from "./utils/ObsidianIconFolderPluginSignature"; -import {lastPathComponent} from "./utils/utils"; +import { + getIconFolderPlugin +} from "./utils/ObsidianIconFolderPluginSignature"; +import { + extractBasename, + lastPathComponent +} from "./utils/utils"; import { collectSortingAndGroupingTypes, hasOnlyByBookmarkOrStandardObsidian, @@ -64,6 +69,7 @@ import { DEFAULT_SETTING_FOR_1_2_0_UP, DEFAULT_SETTINGS } from "./settings"; +import {CustomSortPluginAPI} from "./custom-sort-plugin"; const SORTSPEC_FILE_NAME: string = 'sortspec.md' const SORTINGSPEC_YAML_KEY: string = 'sorting-spec' @@ -75,7 +81,10 @@ type MonkeyAroundUninstaller = () => void type ContextMenuProvider = (item: MenuItem) => void -export default class CustomSortPlugin extends Plugin { +export default class CustomSortPlugin + extends Plugin + implements CustomSortPluginAPI +{ settings: CustomSortPluginSettings statusBarItemEl: HTMLElement ribbonIconEl: HTMLElement // On small-screen mobile devices this is useless (ribbon is re-created on-the-fly) @@ -725,5 +734,16 @@ export default class CustomSortPlugin extends Plugin { async saveSettings() { await this.saveData(this.settings); } -} + // API + derivedIndexNoteNameForFolderNotes: string | undefined + indexNoteNameForFolderNotesDerivedFrom: any + + indexNoteBasename(): string | undefined { + if (!(this.indexNoteNameForFolderNotesDerivedFrom === this.settings.indexNoteNameForFolderNotes)) { + this.derivedIndexNoteNameForFolderNotes = extractBasename(this.settings.indexNoteNameForFolderNotes) + this.indexNoteNameForFolderNotesDerivedFrom = this.settings.indexNoteNameForFolderNotes + } + return this.derivedIndexNoteNameForFolderNotes + } +} diff --git a/src/test/unit/utils.spec.ts b/src/test/unit/utils.spec.ts index d03ba24..1f65045 100644 --- a/src/test/unit/utils.spec.ts +++ b/src/test/unit/utils.spec.ts @@ -1,6 +1,7 @@ import { lastPathComponent, - extractParentFolderPath + extractParentFolderPath, + extractBasename } from "../../utils/utils"; describe('lastPathComponent and extractParentFolderPath', () => { @@ -25,3 +26,20 @@ describe('lastPathComponent and extractParentFolderPath', () => { } ) }) + +describe('extractBasenane', () => { + const params: Array<(string|undefined)[]> = [ + // Obvious + ['index', 'index'], + ['index.md', 'index'], + // Edge cases + ['',''], + [undefined,undefined], + ['.','.'], + ['.md',''], + ['.md.md','.md'] + ]; + it.each(params)('>%s< should become %s', (s: string|undefined, out: string|undefined) => { + expect(extractBasename(s)).toBe(out) + }) +}) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4e767d5..65d9257 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -16,3 +16,11 @@ export function extractParentFolderPath(path: string): string { const lastPathSeparatorIdx = (path ?? '').lastIndexOf('/') return lastPathSeparatorIdx > 0 ? path.substring(0, lastPathSeparatorIdx) : '' } + +export function extractBasename (configEntry: string | undefined): string | undefined { + if (typeof configEntry === 'string' && configEntry.endsWith('.md')) { + return configEntry.slice(0, -'.md'.length) + } else { + return configEntry + } +}