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 = { - automaticBookmarksIntegration: true, - bookmarksContextMenus: true -} +import { + CustomSortPluginSettings, + CustomSortSettingTab, + 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' @@ -99,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) @@ -149,10 +134,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 @@ -737,7 +726,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) } } @@ -745,166 +734,16 @@ export default class CustomSortPlugin extends Plugin { async saveSettings() { await this.saveData(this.settings); } -} -const pathToFlatString = (path: string): string => { - return path.replace(/\//g,'_').replace(/\\/g, '_') -} + // API + derivedIndexNoteNameForFolderNotes: string | undefined + indexNoteNameForFolderNotesDerivedFrom: any -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(); - })) + 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/settings.ts b/src/settings.ts new file mode 100644 index 0000000..3a0d251 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,218 @@ +import {App, normalizePath, PluginSettingTab, sanitizeHTMLToDom, Setting} from "obsidian"; +import {groupNameForPath} from "./utils/BookmarksCorePluginSignature"; +import CustomSortPlugin from "./main"; + +export interface CustomSortPluginSettings { + additionalSortspecFile: string + indexNoteNameForFolderNotes: string + suspended: boolean + statusBarEntryEnabled: boolean + notificationsEnabled: boolean + mobileNotificationsEnabled: boolean + automaticBookmarksIntegration: boolean + customSortContextSubmenu: boolean + bookmarksContextMenus: boolean + bookmarksGroupToConsumeAsOrderingReference: string +} + +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, + 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.' + + '
' + + '

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. 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.') + .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 + } +}