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 1261261..d320f56 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
@@ -466,10 +476,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-parent-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
+ 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) {
determined = true
@@ -554,12 +572,24 @@ 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
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 61b4de7..62564c1 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -50,44 +50,26 @@ 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,
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 (*) 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_'
- + ' 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 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.'
- + ' More information on this functionality in the '
- + ''
- + 'manual of this custom-sort plugin.'
- + ' 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
'
- + ' The `.md` filename suffix is optional.'
- + '
'
- + 'The Inside Folder, with Same Name Recommended mode of Folder Notes is handled automatically, no additional configuration needed.'
- + 'sorting-spec:
configurations and they can nicely cooperate.'
- + '
'
- + '
'
- + 'If left empty, all the bookmarked items will be used to impose the order in File Explorer.
'
+ + ' The `.md` filename suffix is optional.'
+ + '
'
+ + '
'
+ + ' 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.') + .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/test/unit/utils.spec.ts b/src/test/unit/utils.spec.ts index d03ba24..2a60bcb 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('extractBasename', () => { + const params: Array<(string|undefined)[]> = [ + // Obvious + ['index', 'index'], + ['index.md', 'index'], + // Edge cases + ['',''], + [undefined,undefined], + ['.','.'], + ['.md',''], + ['.md.md','.md'] + ]; + it.each(params)('>%s< should result in %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 + } +}