#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
This commit is contained in:
SebastianMC 2024-09-02 00:11:29 +02:00
parent 605d5026a6
commit 60a60dfb72
5 changed files with 93 additions and 22 deletions

View File

@ -0,0 +1,7 @@
import {
Plugin
} from 'obsidian'
export interface CustomSortPluginAPI extends Plugin {
indexNoteBasename(): string|undefined
}

View File

@ -34,10 +34,11 @@ import {
import { import {
BookmarksPluginInterface BookmarksPluginInterface
} from "../utils/BookmarksCorePluginSignature"; } from "../utils/BookmarksCorePluginSignature";
import {CustomSortPluginAPI} from "../custom-sort-plugin";
export interface ProcessingContext { export interface ProcessingContext {
// For internal transient use // 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 _mCache?: MetadataCache
starredPluginInstance?: Starred_PluginInstance starredPluginInstance?: Starred_PluginInstance
bookmarksPluginInstance?: BookmarksPluginInterface, bookmarksPluginInstance?: BookmarksPluginInterface,
@ -371,6 +372,15 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string):
return [false, undefined, undefined] 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 { export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: ProcessingContext): FolderItemForSorting {
let groupIdx: number let groupIdx: number
let determined: boolean = false let determined: boolean = false
@ -468,16 +478,18 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
if (ctx?._mCache) { if (ctx?._mCache) {
// For folders - scan metadata of 'folder note' in same-name-as-folder mode // 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 notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter let frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
const hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName) let hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
// For folders, if index-based folder note mode, scan the index file, giving it the priority // 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 if (aFolder) {
!!! in plugin settings, not exposed - refactoring is a must. const indexNoteBasename = ctx?.plugin?.indexNoteBasename()
if (aFolder && ctx?.plugin?.s) { if (indexNoteBasename) {
frontMatterCache = ctx._mCache.getCache(`${entry.path}/${indexNoteBasename}.md`)?.frontmatter
hasMetadata = hasMetadata || frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
}
} }
if (hasMetadata || folderIndexNoteHasMetadata) { if (hasMetadata) {
determined = true determined = true
} }
} }
@ -561,17 +573,23 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
if (ctx?._mCache) { if (ctx?._mCache) {
// For folders - scan metadata of 'folder note' // For folders - scan metadata of 'folder note'
// and if index-based folder note mode, scan the index file, giving it the priority // 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 notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
if (isPrimaryOrderByMetadata) metadataValueToSortBy = frontMatterCache?.[group?.byMetadataField || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING] let prioFrontMatterCache: FrontMatterCache | undefined = undefined
if (isSecondaryOrderByMetadata) metadataValueSecondaryToSortBy = frontMatterCache?.[group?.byMetadataFieldSecondary || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING] if (aFolder) {
if (isDerivedPrimaryByMetadata) metadataValueDerivedPrimaryToSortBy = frontMatterCache?.[spec.byMetadataField || DEFAULT_METADATA_FIELD_FOR_SORTING] const indexNoteBasename = ctx?.plugin?.indexNoteBasename()
if (isDerivedSecondaryByMetadata) metadataValueDerivedSecondaryToSortBy = frontMatterCache?.[spec.byMetadataFieldSecondary || DEFAULT_METADATA_FIELD_FOR_SORTING] 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)
} }
} }
} }

View File

@ -50,8 +50,13 @@ import {
getBookmarksPlugin, getBookmarksPlugin,
groupNameForPath groupNameForPath
} from "./utils/BookmarksCorePluginSignature"; } from "./utils/BookmarksCorePluginSignature";
import {getIconFolderPlugin} from "./utils/ObsidianIconFolderPluginSignature"; import {
import {lastPathComponent} from "./utils/utils"; getIconFolderPlugin
} from "./utils/ObsidianIconFolderPluginSignature";
import {
extractBasename,
lastPathComponent
} from "./utils/utils";
import { import {
collectSortingAndGroupingTypes, collectSortingAndGroupingTypes,
hasOnlyByBookmarkOrStandardObsidian, hasOnlyByBookmarkOrStandardObsidian,
@ -64,6 +69,7 @@ import {
DEFAULT_SETTING_FOR_1_2_0_UP, DEFAULT_SETTING_FOR_1_2_0_UP,
DEFAULT_SETTINGS DEFAULT_SETTINGS
} from "./settings"; } from "./settings";
import {CustomSortPluginAPI} from "./custom-sort-plugin";
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'
@ -75,7 +81,10 @@ type MonkeyAroundUninstaller = () => void
type ContextMenuProvider = (item: MenuItem) => void type ContextMenuProvider = (item: MenuItem) => void
export default class CustomSortPlugin extends Plugin { export default class CustomSortPlugin
extends Plugin
implements CustomSortPluginAPI
{
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)
@ -725,5 +734,16 @@ export default class CustomSortPlugin extends Plugin {
async saveSettings() { async saveSettings() {
await this.saveData(this.settings); 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
}
}

View File

@ -1,6 +1,7 @@
import { import {
lastPathComponent, lastPathComponent,
extractParentFolderPath extractParentFolderPath,
extractBasename
} from "../../utils/utils"; } from "../../utils/utils";
describe('lastPathComponent and extractParentFolderPath', () => { 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)
})
})

View File

@ -16,3 +16,11 @@ export function extractParentFolderPath(path: string): string {
const lastPathSeparatorIdx = (path ?? '').lastIndexOf('/') const lastPathSeparatorIdx = (path ?? '').lastIndexOf('/')
return lastPathSeparatorIdx > 0 ? path.substring(0, lastPathSeparatorIdx) : '' 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
}
}