From e66d7be9d3dc517a2ca1b64a3c79aa4620013a03 Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Sun, 1 Sep 2024 15:23:38 +0200 Subject: [PATCH 1/4] Extracted setting to a separate settings.ts --- src/main.ts | 197 ++---------------------------------------------- src/settings.ts | 195 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 191 deletions(-) create mode 100644 src/settings.ts diff --git a/src/main.ts b/src/main.ts index 61b4de7..d9d61fe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -58,36 +58,12 @@ import { HasSortingOrGrouping, ImplicitSortspecForBookmarksIntegration } from "./custom-sort/custom-sort-utils"; - -interface CustomSortPluginSettings { - additionalSortspecFile: string - suspended: boolean - statusBarEntryEnabled: boolean - notificationsEnabled: boolean - mobileNotificationsEnabled: boolean - automaticBookmarksIntegration: boolean - customSortContextSubmenu: boolean - bookmarksContextMenus: boolean - bookmarksGroupToConsumeAsOrderingReference: string -} - -const DEFAULT_SETTINGS: CustomSortPluginSettings = { - additionalSortspecFile: '', - suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install - statusBarEntryEnabled: true, - notificationsEnabled: true, - mobileNotificationsEnabled: false, - customSortContextSubmenu: true, - automaticBookmarksIntegration: false, - bookmarksContextMenus: false, - bookmarksGroupToConsumeAsOrderingReference: 'sortspec' -} - -// On API 1.2.x+ enable the bookmarks integration by default -const DEFAULT_SETTING_FOR_1_2_0_UP: Partial = { - automaticBookmarksIntegration: true, - bookmarksContextMenus: true -} +import { + CustomSortPluginSettings, + CustomSortSettingTab, + DEFAULT_SETTING_FOR_1_2_0_UP, + DEFAULT_SETTINGS +} from "./settings"; const SORTSPEC_FILE_NAME: string = 'sortspec.md' const SORTINGSPEC_YAML_KEY: string = 'sorting-spec' @@ -747,164 +723,3 @@ export default class CustomSortPlugin extends Plugin { } } -const pathToFlatString = (path: string): string => { - return path.replace(/\//g,'_').replace(/\\/g, '_') -} - -class CustomSortSettingTab extends PluginSettingTab { - plugin: CustomSortPlugin; - - constructor(app: App, plugin: CustomSortPlugin) { - super(app, plugin); - this.plugin = plugin; - } - - display(): void { - const {containerEl} = this; - - containerEl.empty(); - - // containerEl.createEl('h2', {text: 'Settings for Custom File Explorer Sorting Plugin'}); - - 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*.' - + '
' - + ' The `.md` filename suffix is optional.' - + '

(*) if you employ the Index-File based approach to folder notes (as documented in ' - + 'Aidenlx Folder Note preferences' - + ') you can enter here the index note name, e.g. _about_' - + '
' - + 'The Inside Folder, with Same Name Recommended mode of Folder Notes is handled automatically, no additional configuration needed.' - + '

' - + '

NOTE: After updating this setting remember to refresh the custom sorting via clicking on the ribbon icon or via the sort-on command' - + ' or by restarting Obsidian or reloading the vault

' - ) - - new Setting(containerEl) - .setName('Path or name of additional note(s) containing sorting specification') - .setDesc(additionalSortspecFileDescr) - .addText(text => text - .setPlaceholder('e.g. _about_') - .setValue(this.plugin.settings.additionalSortspecFile) - .onChange(async (value) => { - this.plugin.settings.additionalSortspecFile = value.trim() ? normalizePath(value) : ''; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Enable the status bar entry') - .setDesc('The status bar entry shows the label `Custom sort:ON` or `Custom sort:OFF`, representing the current state of the plugin.') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.statusBarEntryEnabled) - .onChange(async (value) => { - this.plugin.settings.statusBarEntryEnabled = value; - if (value) { - // Enabling - if (this.plugin.statusBarItemEl) { - // for sanity - this.plugin.statusBarItemEl.detach() - } - this.plugin.statusBarItemEl = this.plugin.addStatusBarItem(); - this.plugin.updateStatusBar() - - } else { // disabling - if (this.plugin.statusBarItemEl) { - this.plugin.statusBarItemEl.detach() - } - } - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Enable notifications of plugin state changes') - .setDesc('The plugin can show notifications about its state changes: e.g. when successfully parsed and applied' - + ' the custom sorting specification, or, when the parsing failed. If the notifications are disabled,' - + ' the only indicator of plugin state is the ribbon button icon. The developer console presents the parsing' - + ' error messages regardless if the notifications are enabled or not.') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.notificationsEnabled) - .onChange(async (value) => { - this.plugin.settings.notificationsEnabled = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Enable notifications of plugin state changes for mobile devices only') - .setDesc('See above.') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.mobileNotificationsEnabled) - .onChange(async (value) => { - this.plugin.settings.mobileNotificationsEnabled = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Enable File Explorer context submenu`Custom sort:`') - .setDesc('Gives access to operations relevant for custom sorting, e.g. applying custom sorting.') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.customSortContextSubmenu) - .onChange(async (value) => { - this.plugin.settings.customSortContextSubmenu = value; - await this.plugin.saveSettings(); - })); - - containerEl.createEl('h2', {text: 'Bookmarks integration'}); - const bookmarksIntegrationDescription: DocumentFragment = sanitizeHTMLToDom( - 'If enabled, order of files and folders in File Explorer will reflect the order ' - + 'of bookmarked items in the bookmarks (core plugin) view. Automatically, without any ' - + 'need for sorting configuration. At the same time, it integrates seamlessly with' - + '
sorting-spec:
configurations and they can nicely cooperate.' - + '
' - + '

To separate regular bookmarks from the bookmarks created for sorting, you can put ' - + 'the latter in a separate dedicated bookmarks group. The default name of the group is ' - + "'" + DEFAULT_SETTINGS.bookmarksGroupToConsumeAsOrderingReference + "' " - + 'and you can change the group name in the configuration field below.' - + '
' - + 'If left empty, all the bookmarked items will be used to impose the order in File Explorer.

' - + '

More information on this functionality in the ' - + '' - + 'manual of this custom-sort plugin.' - + '

' - ) - - new Setting(containerEl) - .setName('Automatic integration with core Bookmarks plugin (for indirect drag & drop ordering)') - .setDesc(bookmarksIntegrationDescription) - .addToggle(toggle => toggle - .setValue(this.plugin.settings.automaticBookmarksIntegration) - .onChange(async (value) => { - this.plugin.settings.automaticBookmarksIntegration = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Name of the group in Bookmarks from which to read the order of items') - .setDesc('See above.') - .addText(text => text - .setPlaceholder('e.g. Group for sorting') - .setValue(this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference) - .onChange(async (value) => { - value = groupNameForPath(value.trim()).trim() - this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference = value ? pathToFlatString(normalizePath(value)) : ''; - await this.plugin.saveSettings(); - })); - - const bookmarksIntegrationContextMenusDescription: DocumentFragment = sanitizeHTMLToDom( - 'Enable Custom-sort: bookmark for sorting and Custom-sort: bookmark+siblings for sorting (and related) entries ' - + 'in context menu in File Explorer' - ) - new Setting(containerEl) - .setName('Context menus for Bookmarks integration') - .setDesc(bookmarksIntegrationContextMenusDescription) - .addToggle(toggle => toggle - .setValue(this.plugin.settings.bookmarksContextMenus) - .onChange(async (value) => { - this.plugin.settings.bookmarksContextMenus = value; - if (value) { - this.plugin.settings.customSortContextSubmenu = true; // automatically enable custom sort context submenu - } - await this.plugin.saveSettings(); - })) - } -} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..de165b0 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,195 @@ +import {App, normalizePath, PluginSettingTab, sanitizeHTMLToDom, Setting} from "obsidian"; +import {groupNameForPath} from "./utils/BookmarksCorePluginSignature"; +import CustomSortPlugin from "./main"; + +export interface CustomSortPluginSettings { + additionalSortspecFile: string + suspended: boolean + statusBarEntryEnabled: boolean + notificationsEnabled: boolean + mobileNotificationsEnabled: boolean + automaticBookmarksIntegration: boolean + customSortContextSubmenu: boolean + bookmarksContextMenus: boolean + bookmarksGroupToConsumeAsOrderingReference: string +} + +export const DEFAULT_SETTINGS: CustomSortPluginSettings = { + additionalSortspecFile: '', + suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install + statusBarEntryEnabled: true, + notificationsEnabled: true, + mobileNotificationsEnabled: false, + customSortContextSubmenu: true, + automaticBookmarksIntegration: false, + bookmarksContextMenus: false, + bookmarksGroupToConsumeAsOrderingReference: 'sortspec' +} + +// On API 1.2.x+ enable the bookmarks integration by default +export const DEFAULT_SETTING_FOR_1_2_0_UP: Partial = { + automaticBookmarksIntegration: true, + bookmarksContextMenus: true +} + +const pathToFlatString = (path: string): string => { + return path.replace(/\//g,'_').replace(/\\/g, '_') +} + +export class CustomSortSettingTab extends PluginSettingTab { + plugin: CustomSortPlugin; + + constructor(app: App, plugin: CustomSortPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const {containerEl} = this; + + containerEl.empty(); + + // containerEl.createEl('h2', {text: 'Settings for Custom File Explorer Sorting Plugin'}); + + 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*.' + + '
' + + ' The `.md` filename suffix is optional.' + + '

(*) if you employ the Index-File based approach to folder notes (as documented in ' + + 'Aidenlx Folder Note preferences' + + ') you can enter here the index note name, e.g. _about_' + + '
' + + 'The Inside Folder, with Same Name Recommended mode of Folder Notes is handled automatically, no additional configuration needed.' + + '

' + + '

NOTE: After updating this setting remember to refresh the custom sorting via clicking on the ribbon icon or via the sort-on command' + + ' or by restarting Obsidian or reloading the vault

' + ) + + new Setting(containerEl) + .setName('Path or name of additional note(s) containing sorting specification') + .setDesc(additionalSortspecFileDescr) + .addText(text => text + .setPlaceholder('e.g. _about_') + .setValue(this.plugin.settings.additionalSortspecFile) + .onChange(async (value) => { + this.plugin.settings.additionalSortspecFile = value.trim() ? normalizePath(value) : ''; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Enable the status bar entry') + .setDesc('The status bar entry shows the label `Custom sort:ON` or `Custom sort:OFF`, representing the current state of the plugin.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.statusBarEntryEnabled) + .onChange(async (value) => { + this.plugin.settings.statusBarEntryEnabled = value; + if (value) { + // Enabling + if (this.plugin.statusBarItemEl) { + // for sanity + this.plugin.statusBarItemEl.detach() + } + this.plugin.statusBarItemEl = this.plugin.addStatusBarItem(); + this.plugin.updateStatusBar() + + } else { // disabling + if (this.plugin.statusBarItemEl) { + this.plugin.statusBarItemEl.detach() + } + } + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Enable notifications of plugin state changes') + .setDesc('The plugin can show notifications about its state changes: e.g. when successfully parsed and applied' + + ' the custom sorting specification, or, when the parsing failed. If the notifications are disabled,' + + ' the only indicator of plugin state is the ribbon button icon. The developer console presents the parsing' + + ' error messages regardless if the notifications are enabled or not.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.notificationsEnabled) + .onChange(async (value) => { + this.plugin.settings.notificationsEnabled = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Enable notifications of plugin state changes for mobile devices only') + .setDesc('See above.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.mobileNotificationsEnabled) + .onChange(async (value) => { + this.plugin.settings.mobileNotificationsEnabled = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Enable File Explorer context submenu`Custom sort:`') + .setDesc('Gives access to operations relevant for custom sorting, e.g. applying custom sorting.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.customSortContextSubmenu) + .onChange(async (value) => { + this.plugin.settings.customSortContextSubmenu = value; + await this.plugin.saveSettings(); + })); + + containerEl.createEl('h2', {text: 'Bookmarks integration'}); + const bookmarksIntegrationDescription: DocumentFragment = sanitizeHTMLToDom( + 'If enabled, order of files and folders in File Explorer will reflect the order ' + + 'of bookmarked items in the bookmarks (core plugin) view. Automatically, without any ' + + 'need for sorting configuration. At the same time, it integrates seamlessly with' + + '
sorting-spec:
configurations and they can nicely cooperate.' + + '
' + + '

To separate regular bookmarks from the bookmarks created for sorting, you can put ' + + 'the latter in a separate dedicated bookmarks group. The default name of the group is ' + + "'" + DEFAULT_SETTINGS.bookmarksGroupToConsumeAsOrderingReference + "' " + + 'and you can change the group name in the configuration field below.' + + '
' + + 'If left empty, all the bookmarked items will be used to impose the order in File Explorer.

' + + '

More information on this functionality in the ' + + '' + + 'manual of this custom-sort plugin.' + + '

' + ) + + new Setting(containerEl) + .setName('Automatic integration with core Bookmarks plugin (for indirect drag & drop ordering)') + .setDesc(bookmarksIntegrationDescription) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.automaticBookmarksIntegration) + .onChange(async (value) => { + this.plugin.settings.automaticBookmarksIntegration = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Name of the group in Bookmarks from which to read the order of items') + .setDesc('See above.') + .addText(text => text + .setPlaceholder('e.g. Group for sorting') + .setValue(this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference) + .onChange(async (value) => { + value = groupNameForPath(value.trim()).trim() + this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference = value ? pathToFlatString(normalizePath(value)) : ''; + await this.plugin.saveSettings(); + })); + + const bookmarksIntegrationContextMenusDescription: DocumentFragment = sanitizeHTMLToDom( + 'Enable Custom-sort: bookmark for sorting and Custom-sort: bookmark+siblings for sorting (and related) entries ' + + 'in context menu in File Explorer' + ) + new Setting(containerEl) + .setName('Context menus for Bookmarks integration') + .setDesc(bookmarksIntegrationContextMenusDescription) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.bookmarksContextMenus) + .onChange(async (value) => { + this.plugin.settings.bookmarksContextMenus = value; + if (value) { + this.plugin.settings.customSortContextSubmenu = true; // automatically enable custom sort context submenu + } + await this.plugin.saveSettings(); + })) + } +} From 605d5026a61b0d8a4e6f296c0dbd54ef2b78ba88 Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:07:56 +0200 Subject: [PATCH 2/4] #156 - improved support for index-note-based folder notes: PUT ON HOLD - turned out to be not so trivial with one challenging place - PUT ON HOLD - added inline annotations with !!! (intentionally breaking the TS syntax) --- src/custom-sort/custom-sort.ts | 16 ++++++++++++-- src/main.ts | 8 +++++-- src/settings.ts | 39 +++++++++++++++++++++++++++------- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/custom-sort/custom-sort.ts b/src/custom-sort/custom-sort.ts index 1261261..686a252 100644 --- a/src/custom-sort/custom-sort.ts +++ b/src/custom-sort/custom-sort.ts @@ -466,12 +466,18 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus case CustomSortGroupType.HasMetadataField: if (group.withMetadataFieldName) { if (ctx?._mCache) { - // For folders - scan metadata of 'folder note' + // 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) + // 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 (hasMetadata) { + } + + if (hasMetadata || folderIndexNoteHasMetadata) { determined = true } } @@ -554,6 +560,12 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus if (isPrimaryOrderByMetadata || isSecondaryOrderByMetadata || isDerivedPrimaryByMetadata || isDerivedSecondaryByMetadata) { 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] diff --git a/src/main.ts b/src/main.ts index d9d61fe..60c4fcb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -125,10 +125,14 @@ export default class CustomSortPlugin extends Plugin { if (aFile.name === SORTSPEC_FILE_NAME || // file name == sortspec.md ? aFile.name === `${SORTSPEC_FILE_NAME}.md` || // file name == sortspec.md.md ? aFile.basename === parent.name || // Folder Note mode: inside folder, same name + aFile.basename === this.settings.additionalSortspecFile || // when user configured _about_ aFile.name === this.settings.additionalSortspecFile || // when user configured _about_.md aFile.path === this.settings.additionalSortspecFile || // when user configured Inbox/sort.md - aFile.path === `${this.settings.additionalSortspecFile}.md` // when user configured Inbox/sort + aFile.path === `${this.settings.additionalSortspecFile}.md` || // when user configured Inbox/sort + + aFile.basename === this.settings.indexNoteNameForFolderNotes || // when user configured as index + aFile.name === this.settings.indexNoteNameForFolderNotes // when user configured as index.md ) { const sortingSpecTxt: string = mCache.getCache(aFile.path)?.frontmatter?.[SORTINGSPEC_YAML_KEY] // Warning: newer Obsidian versions can return objects as well, hence the explicit check for string value @@ -713,7 +717,7 @@ export default class CustomSortPlugin extends Plugin { const data: any = await this.loadData() || {} const isFreshInstall: boolean = Object.keys(data).length === 0 this.settings = Object.assign({}, DEFAULT_SETTINGS, data); - if (requireApiVersion('1.2.0')) { + if (requireApiVersion('1.2.0') && isFreshInstall) { this.settings = Object.assign(this.settings, DEFAULT_SETTING_FOR_1_2_0_UP) } } diff --git a/src/settings.ts b/src/settings.ts index de165b0..3a0d251 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -4,6 +4,7 @@ import CustomSortPlugin from "./main"; export interface CustomSortPluginSettings { additionalSortspecFile: string + indexNoteNameForFolderNotes: string suspended: boolean statusBarEntryEnabled: boolean notificationsEnabled: boolean @@ -16,6 +17,7 @@ export interface CustomSortPluginSettings { export const DEFAULT_SETTINGS: CustomSortPluginSettings = { additionalSortspecFile: '', + indexNoteNameForFolderNotes: '', suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install statusBarEntryEnabled: true, notificationsEnabled: true, @@ -52,16 +54,10 @@ export class CustomSortSettingTab extends PluginSettingTab { // containerEl.createEl('h2', {text: 'Settings for Custom File Explorer Sorting Plugin'}); 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.' + '
' + ' The `.md` filename suffix is optional.' - + '

(*) if you employ the Index-File based approach to folder notes (as documented in ' - + 'Aidenlx Folder Note preferences' - + ') you can enter here the index note name, e.g. _about_' + '
' - + 'The Inside Folder, with Same Name Recommended mode of Folder Notes is handled automatically, no additional configuration needed.' - + '

' + '

NOTE: After updating this setting remember to refresh the custom sorting via clicking on the ribbon icon or via the sort-on command' + ' or by restarting Obsidian or reloading the vault

' ) @@ -70,13 +66,40 @@ export class CustomSortSettingTab extends PluginSettingTab { .setName('Path or name of additional note(s) containing sorting specification') .setDesc(additionalSortspecFileDescr) .addText(text => text - .setPlaceholder('e.g. _about_') + .setPlaceholder('e.g. sorting-configuration') .setValue(this.plugin.settings.additionalSortspecFile) .onChange(async (value) => { this.plugin.settings.additionalSortspecFile = value.trim() ? normalizePath(value) : ''; await this.plugin.saveSettings(); })); + const indexNoteNameDescr: DocumentFragment = sanitizeHTMLToDom( + 'If you employ the Index-File based approach to folder notes (as documented in ' + + 'Aidenlx Folder Note preferences' + + ') enter here the index note name, e.g. _about_ or index' + + '
' + + ' The `.md` filename suffix is optional.' + + '
' + + 'This will tell the plugin to read sorting specs and also folders metadata from these files.' + + '

' + + 'The Inside Folder, with Same Name Recommended mode of Folder Notes is handled automatically, no additional configuration needed.' + + '

' + + '

NOTE: After updating this setting remember to refresh the custom sorting via clicking on the ribbon icon or via the sort-on command' + + ' or by restarting Obsidian or reloading the vault

' + ) + + new Setting(containerEl) + .setName('Name of index note (Folder Notes support)') + .setDesc(indexNoteNameDescr) + .addText(text => text + .setPlaceholder('e.g. _about_ or index') + .setValue(this.plugin.settings.indexNoteNameForFolderNotes) + .onChange(async (value) => { + this.plugin.settings.indexNoteNameForFolderNotes = value.trim() ? normalizePath(value) : ''; + await this.plugin.saveSettings(); + })); + new Setting(containerEl) .setName('Enable the status bar entry') .setDesc('The status bar entry shows the label `Custom sort:ON` or `Custom sort:OFF`, representing the current state of the plugin.') 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 3/4] #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 + } +} From efe8e13267d7c3167e78b9a02ac94f6f801fc19e Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Thu, 5 Sep 2024 20:01:11 +0200 Subject: [PATCH 4/4] #156 - improved support for index-note-based folder notes - fresh head review - typos correction - comment updates for clarity --- src/custom-sort/custom-sort.ts | 2 +- src/test/unit/utils.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/custom-sort/custom-sort.ts b/src/custom-sort/custom-sort.ts index dc4bb9f..d320f56 100644 --- a/src/custom-sort/custom-sort.ts +++ b/src/custom-sort/custom-sort.ts @@ -476,7 +476,7 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus case CustomSortGroupType.HasMetadataField: if (group.withMetadataFieldName) { 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-parent-folder mode const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md` let frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter let hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName) diff --git a/src/test/unit/utils.spec.ts b/src/test/unit/utils.spec.ts index 1f65045..2a60bcb 100644 --- a/src/test/unit/utils.spec.ts +++ b/src/test/unit/utils.spec.ts @@ -27,7 +27,7 @@ describe('lastPathComponent and extractParentFolderPath', () => { ) }) -describe('extractBasenane', () => { +describe('extractBasename', () => { const params: Array<(string|undefined)[]> = [ // Obvious ['index', 'index'], @@ -39,7 +39,7 @@ describe('extractBasenane', () => { ['.md',''], ['.md.md','.md'] ]; - it.each(params)('>%s< should become %s', (s: string|undefined, out: string|undefined) => { + it.each(params)('>%s< should result in %s', (s: string|undefined, out: string|undefined) => { expect(extractBasename(s)).toBe(out) }) })