Skip to content

Commit d2bf6e3

Browse files
authored
Merge pull request #158 from SebastianMC/156-indexmd-based-folder-note-metadata
Index-based folder note metadata support
2 parents 98dbcc6 + efe8e13 commit d2bf6e3

File tree

6 files changed

+323
-203
lines changed

6 files changed

+323
-203
lines changed

src/custom-sort-plugin.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {
2+
Plugin
3+
} from 'obsidian'
4+
5+
export interface CustomSortPluginAPI extends Plugin {
6+
indexNoteBasename(): string|undefined
7+
}

src/custom-sort/custom-sort.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ import {
3434
import {
3535
BookmarksPluginInterface
3636
} from "../utils/BookmarksCorePluginSignature";
37+
import {CustomSortPluginAPI} from "../custom-sort-plugin";
3738

3839
export interface ProcessingContext {
3940
// For internal transient use
40-
plugin?: Plugin // to hand over the access to App instance to the sorting engine
41+
plugin?: CustomSortPluginAPI // to hand over the access to App instance to the sorting engine
4142
_mCache?: MetadataCache
4243
starredPluginInstance?: Starred_PluginInstance
4344
bookmarksPluginInstance?: BookmarksPluginInterface,
@@ -371,6 +372,15 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string):
371372
return [false, undefined, undefined]
372373
}
373374

375+
const mdataValueFromFMCaches = (mdataFieldName: string, fc?: FrontMatterCache, fcPrio?: FrontMatterCache): any => {
376+
let prioValue = undefined
377+
if (fcPrio) {
378+
prioValue = fcPrio?.[mdataFieldName]
379+
}
380+
381+
return prioValue ?? fc?.[mdataFieldName]
382+
}
383+
374384
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: ProcessingContext): FolderItemForSorting {
375385
let groupIdx: number
376386
let determined: boolean = false
@@ -466,10 +476,18 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
466476
case CustomSortGroupType.HasMetadataField:
467477
if (group.withMetadataFieldName) {
468478
if (ctx?._mCache) {
469-
// For folders - scan metadata of 'folder note'
479+
// For folders - scan metadata of 'folder note' in same-name-as-parent-folder mode
470480
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
471-
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
472-
const hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
481+
let frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
482+
let hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
483+
// For folders, if index-based folder note mode, scan the index file, giving it the priority
484+
if (aFolder) {
485+
const indexNoteBasename = ctx?.plugin?.indexNoteBasename()
486+
if (indexNoteBasename) {
487+
frontMatterCache = ctx._mCache.getCache(`${entry.path}/${indexNoteBasename}.md`)?.frontmatter
488+
hasMetadata = hasMetadata || frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
489+
}
490+
}
473491

474492
if (hasMetadata) {
475493
determined = true
@@ -554,12 +572,24 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
554572
if (isPrimaryOrderByMetadata || isSecondaryOrderByMetadata || isDerivedPrimaryByMetadata || isDerivedSecondaryByMetadata) {
555573
if (ctx?._mCache) {
556574
// For folders - scan metadata of 'folder note'
575+
// and if index-based folder note mode, scan the index file, giving it the priority
557576
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
558577
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
559-
if (isPrimaryOrderByMetadata) metadataValueToSortBy = frontMatterCache?.[group?.byMetadataField || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING]
560-
if (isSecondaryOrderByMetadata) metadataValueSecondaryToSortBy = frontMatterCache?.[group?.byMetadataFieldSecondary || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING]
561-
if (isDerivedPrimaryByMetadata) metadataValueDerivedPrimaryToSortBy = frontMatterCache?.[spec.byMetadataField || DEFAULT_METADATA_FIELD_FOR_SORTING]
562-
if (isDerivedSecondaryByMetadata) metadataValueDerivedSecondaryToSortBy = frontMatterCache?.[spec.byMetadataFieldSecondary || DEFAULT_METADATA_FIELD_FOR_SORTING]
578+
let prioFrontMatterCache: FrontMatterCache | undefined = undefined
579+
if (aFolder) {
580+
const indexNoteBasename = ctx?.plugin?.indexNoteBasename()
581+
if (indexNoteBasename) {
582+
prioFrontMatterCache = ctx._mCache.getCache(`${entry.path}/${indexNoteBasename}.md`)?.frontmatter
583+
}
584+
}
585+
if (isPrimaryOrderByMetadata) metadataValueToSortBy =
586+
mdataValueFromFMCaches (group?.byMetadataField || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache)
587+
if (isSecondaryOrderByMetadata) metadataValueSecondaryToSortBy =
588+
mdataValueFromFMCaches (group?.byMetadataFieldSecondary || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache)
589+
if (isDerivedPrimaryByMetadata) metadataValueDerivedPrimaryToSortBy =
590+
mdataValueFromFMCaches (spec.byMetadataField || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache)
591+
if (isDerivedSecondaryByMetadata) metadataValueDerivedSecondaryToSortBy =
592+
mdataValueFromFMCaches (spec.byMetadataFieldSecondary || DEFAULT_METADATA_FIELD_FOR_SORTING, frontMatterCache, prioFrontMatterCache)
563593
}
564594
}
565595
}

src/main.ts

Lines changed: 33 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -50,44 +50,26 @@ import {
5050
getBookmarksPlugin,
5151
groupNameForPath
5252
} from "./utils/BookmarksCorePluginSignature";
53-
import {getIconFolderPlugin} from "./utils/ObsidianIconFolderPluginSignature";
54-
import {lastPathComponent} from "./utils/utils";
53+
import {
54+
getIconFolderPlugin
55+
} from "./utils/ObsidianIconFolderPluginSignature";
56+
import {
57+
extractBasename,
58+
lastPathComponent
59+
} from "./utils/utils";
5560
import {
5661
collectSortingAndGroupingTypes,
5762
hasOnlyByBookmarkOrStandardObsidian,
5863
HasSortingOrGrouping,
5964
ImplicitSortspecForBookmarksIntegration
6065
} from "./custom-sort/custom-sort-utils";
61-
62-
interface CustomSortPluginSettings {
63-
additionalSortspecFile: string
64-
suspended: boolean
65-
statusBarEntryEnabled: boolean
66-
notificationsEnabled: boolean
67-
mobileNotificationsEnabled: boolean
68-
automaticBookmarksIntegration: boolean
69-
customSortContextSubmenu: boolean
70-
bookmarksContextMenus: boolean
71-
bookmarksGroupToConsumeAsOrderingReference: string
72-
}
73-
74-
const DEFAULT_SETTINGS: CustomSortPluginSettings = {
75-
additionalSortspecFile: '',
76-
suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install
77-
statusBarEntryEnabled: true,
78-
notificationsEnabled: true,
79-
mobileNotificationsEnabled: false,
80-
customSortContextSubmenu: true,
81-
automaticBookmarksIntegration: false,
82-
bookmarksContextMenus: false,
83-
bookmarksGroupToConsumeAsOrderingReference: 'sortspec'
84-
}
85-
86-
// On API 1.2.x+ enable the bookmarks integration by default
87-
const DEFAULT_SETTING_FOR_1_2_0_UP: Partial<CustomSortPluginSettings> = {
88-
automaticBookmarksIntegration: true,
89-
bookmarksContextMenus: true
90-
}
66+
import {
67+
CustomSortPluginSettings,
68+
CustomSortSettingTab,
69+
DEFAULT_SETTING_FOR_1_2_0_UP,
70+
DEFAULT_SETTINGS
71+
} from "./settings";
72+
import {CustomSortPluginAPI} from "./custom-sort-plugin";
9173

9274
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
9375
const SORTINGSPEC_YAML_KEY: string = 'sorting-spec'
@@ -99,7 +81,10 @@ type MonkeyAroundUninstaller = () => void
9981

10082
type ContextMenuProvider = (item: MenuItem) => void
10183

102-
export default class CustomSortPlugin extends Plugin {
84+
export default class CustomSortPlugin
85+
extends Plugin
86+
implements CustomSortPluginAPI
87+
{
10388
settings: CustomSortPluginSettings
10489
statusBarItemEl: HTMLElement
10590
ribbonIconEl: HTMLElement // On small-screen mobile devices this is useless (ribbon is re-created on-the-fly)
@@ -149,10 +134,14 @@ export default class CustomSortPlugin extends Plugin {
149134
if (aFile.name === SORTSPEC_FILE_NAME || // file name == sortspec.md ?
150135
aFile.name === `${SORTSPEC_FILE_NAME}.md` || // file name == sortspec.md.md ?
151136
aFile.basename === parent.name || // Folder Note mode: inside folder, same name
137+
152138
aFile.basename === this.settings.additionalSortspecFile || // when user configured _about_
153139
aFile.name === this.settings.additionalSortspecFile || // when user configured _about_.md
154140
aFile.path === this.settings.additionalSortspecFile || // when user configured Inbox/sort.md
155-
aFile.path === `${this.settings.additionalSortspecFile}.md` // when user configured Inbox/sort
141+
aFile.path === `${this.settings.additionalSortspecFile}.md` || // when user configured Inbox/sort
142+
143+
aFile.basename === this.settings.indexNoteNameForFolderNotes || // when user configured as index
144+
aFile.name === this.settings.indexNoteNameForFolderNotes // when user configured as index.md
156145
) {
157146
const sortingSpecTxt: string = mCache.getCache(aFile.path)?.frontmatter?.[SORTINGSPEC_YAML_KEY]
158147
// Warning: newer Obsidian versions can return objects as well, hence the explicit check for string value
@@ -737,174 +726,24 @@ export default class CustomSortPlugin extends Plugin {
737726
const data: any = await this.loadData() || {}
738727
const isFreshInstall: boolean = Object.keys(data).length === 0
739728
this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
740-
if (requireApiVersion('1.2.0')) {
729+
if (requireApiVersion('1.2.0') && isFreshInstall) {
741730
this.settings = Object.assign(this.settings, DEFAULT_SETTING_FOR_1_2_0_UP)
742731
}
743732
}
744733

745734
async saveSettings() {
746735
await this.saveData(this.settings);
747736
}
748-
}
749-
750-
const pathToFlatString = (path: string): string => {
751-
return path.replace(/\//g,'_').replace(/\\/g, '_')
752-
}
753737

754-
class CustomSortSettingTab extends PluginSettingTab {
755-
plugin: CustomSortPlugin;
738+
// API
739+
derivedIndexNoteNameForFolderNotes: string | undefined
740+
indexNoteNameForFolderNotesDerivedFrom: any
756741

757-
constructor(app: App, plugin: CustomSortPlugin) {
758-
super(app, plugin);
759-
this.plugin = plugin;
760-
}
761-
762-
display(): void {
763-
const {containerEl} = this;
764-
765-
containerEl.empty();
766-
767-
// containerEl.createEl('h2', {text: 'Settings for Custom File Explorer Sorting Plugin'});
768-
769-
const additionalSortspecFileDescr: DocumentFragment = sanitizeHTMLToDom(
770-
'A note name or note path to scan (YAML frontmatter) for sorting specification in addition to the `sortspec` notes and Folder Notes<sup><b>*</b></sup>.'
771-
+ '<br>'
772-
+ ' The `.md` filename suffix is optional.'
773-
+ '<p><b>(*)</b>&nbsp;if you employ the <i>Index-File based</i> approach to folder notes (as documented in '
774-
+ '<a href="https://github.com/aidenlx/alx-folder-note/wiki/folder-note-pref"'
775-
+ '>Aidenlx Folder Note preferences</a>'
776-
+ ') you can enter here the index note name, e.g. <b>_about_</b>'
777-
+ '<br>'
778-
+ 'The <i>Inside Folder, with Same Name Recommended</i> mode of Folder Notes is handled automatically, no additional configuration needed.'
779-
+ '</p>'
780-
+ '<p>NOTE: After updating this setting remember to refresh the custom sorting via clicking on the ribbon icon or via the <b>sort-on</b> command'
781-
+ ' or by restarting Obsidian or reloading the vault</p>'
782-
)
783-
784-
new Setting(containerEl)
785-
.setName('Path or name of additional note(s) containing sorting specification')
786-
.setDesc(additionalSortspecFileDescr)
787-
.addText(text => text
788-
.setPlaceholder('e.g. _about_')
789-
.setValue(this.plugin.settings.additionalSortspecFile)
790-
.onChange(async (value) => {
791-
this.plugin.settings.additionalSortspecFile = value.trim() ? normalizePath(value) : '';
792-
await this.plugin.saveSettings();
793-
}));
794-
795-
new Setting(containerEl)
796-
.setName('Enable the status bar entry')
797-
.setDesc('The status bar entry shows the label `Custom sort:ON` or `Custom sort:OFF`, representing the current state of the plugin.')
798-
.addToggle(toggle => toggle
799-
.setValue(this.plugin.settings.statusBarEntryEnabled)
800-
.onChange(async (value) => {
801-
this.plugin.settings.statusBarEntryEnabled = value;
802-
if (value) {
803-
// Enabling
804-
if (this.plugin.statusBarItemEl) {
805-
// for sanity
806-
this.plugin.statusBarItemEl.detach()
807-
}
808-
this.plugin.statusBarItemEl = this.plugin.addStatusBarItem();
809-
this.plugin.updateStatusBar()
810-
811-
} else { // disabling
812-
if (this.plugin.statusBarItemEl) {
813-
this.plugin.statusBarItemEl.detach()
814-
}
815-
}
816-
await this.plugin.saveSettings();
817-
}));
818-
819-
new Setting(containerEl)
820-
.setName('Enable notifications of plugin state changes')
821-
.setDesc('The plugin can show notifications about its state changes: e.g. when successfully parsed and applied'
822-
+ ' the custom sorting specification, or, when the parsing failed. If the notifications are disabled,'
823-
+ ' the only indicator of plugin state is the ribbon button icon. The developer console presents the parsing'
824-
+ ' error messages regardless if the notifications are enabled or not.')
825-
.addToggle(toggle => toggle
826-
.setValue(this.plugin.settings.notificationsEnabled)
827-
.onChange(async (value) => {
828-
this.plugin.settings.notificationsEnabled = value;
829-
await this.plugin.saveSettings();
830-
}));
831-
832-
new Setting(containerEl)
833-
.setName('Enable notifications of plugin state changes for mobile devices only')
834-
.setDesc('See above.')
835-
.addToggle(toggle => toggle
836-
.setValue(this.plugin.settings.mobileNotificationsEnabled)
837-
.onChange(async (value) => {
838-
this.plugin.settings.mobileNotificationsEnabled = value;
839-
await this.plugin.saveSettings();
840-
}));
841-
842-
new Setting(containerEl)
843-
.setName('Enable File Explorer context submenu`Custom sort:`')
844-
.setDesc('Gives access to operations relevant for custom sorting, e.g. applying custom sorting.')
845-
.addToggle(toggle => toggle
846-
.setValue(this.plugin.settings.customSortContextSubmenu)
847-
.onChange(async (value) => {
848-
this.plugin.settings.customSortContextSubmenu = value;
849-
await this.plugin.saveSettings();
850-
}));
851-
852-
containerEl.createEl('h2', {text: 'Bookmarks integration'});
853-
const bookmarksIntegrationDescription: DocumentFragment = sanitizeHTMLToDom(
854-
'If enabled, order of files and folders in File Explorer will reflect the order '
855-
+ 'of bookmarked items in the bookmarks (core plugin) view. Automatically, without any '
856-
+ 'need for sorting configuration. At the same time, it integrates seamlessly with'
857-
+ ' <pre style="display: inline;">sorting-spec:</pre> configurations and they can nicely cooperate.'
858-
+ '<br>'
859-
+ '<p>To separate regular bookmarks from the bookmarks created for sorting, you can put '
860-
+ 'the latter in a separate dedicated bookmarks group. The default name of the group is '
861-
+ "'<i>" + DEFAULT_SETTINGS.bookmarksGroupToConsumeAsOrderingReference + "</i>' "
862-
+ 'and you can change the group name in the configuration field below.'
863-
+ '<br>'
864-
+ 'If left empty, all the bookmarked items will be used to impose the order in File Explorer.</p>'
865-
+ '<p>More information on this functionality in the '
866-
+ '<a href="https://github.com/SebastianMC/obsidian-custom-sort/blob/master/docs/manual.md#bookmarks-plugin-integration">'
867-
+ 'manual</a> of this custom-sort plugin.'
868-
+ '</p>'
869-
)
870-
871-
new Setting(containerEl)
872-
.setName('Automatic integration with core Bookmarks plugin (for indirect drag & drop ordering)')
873-
.setDesc(bookmarksIntegrationDescription)
874-
.addToggle(toggle => toggle
875-
.setValue(this.plugin.settings.automaticBookmarksIntegration)
876-
.onChange(async (value) => {
877-
this.plugin.settings.automaticBookmarksIntegration = value;
878-
await this.plugin.saveSettings();
879-
}));
880-
881-
new Setting(containerEl)
882-
.setName('Name of the group in Bookmarks from which to read the order of items')
883-
.setDesc('See above.')
884-
.addText(text => text
885-
.setPlaceholder('e.g. Group for sorting')
886-
.setValue(this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference)
887-
.onChange(async (value) => {
888-
value = groupNameForPath(value.trim()).trim()
889-
this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference = value ? pathToFlatString(normalizePath(value)) : '';
890-
await this.plugin.saveSettings();
891-
}));
892-
893-
const bookmarksIntegrationContextMenusDescription: DocumentFragment = sanitizeHTMLToDom(
894-
'Enable <i>Custom-sort: bookmark for sorting</i> and <i>Custom-sort: bookmark+siblings for sorting</i> (and related) entries '
895-
+ 'in context menu in File Explorer'
896-
)
897-
new Setting(containerEl)
898-
.setName('Context menus for Bookmarks integration')
899-
.setDesc(bookmarksIntegrationContextMenusDescription)
900-
.addToggle(toggle => toggle
901-
.setValue(this.plugin.settings.bookmarksContextMenus)
902-
.onChange(async (value) => {
903-
this.plugin.settings.bookmarksContextMenus = value;
904-
if (value) {
905-
this.plugin.settings.customSortContextSubmenu = true; // automatically enable custom sort context submenu
906-
}
907-
await this.plugin.saveSettings();
908-
}))
742+
indexNoteBasename(): string | undefined {
743+
if (!(this.indexNoteNameForFolderNotesDerivedFrom === this.settings.indexNoteNameForFolderNotes)) {
744+
this.derivedIndexNoteNameForFolderNotes = extractBasename(this.settings.indexNoteNameForFolderNotes)
745+
this.indexNoteNameForFolderNotesDerivedFrom = this.settings.indexNoteNameForFolderNotes
746+
}
747+
return this.derivedIndexNoteNameForFolderNotes
909748
}
910749
}

0 commit comments

Comments
 (0)