Merge pull request #129 from SebastianMC/127-folder-and-file-with-the-same-basename-sorting

#127 - folder and file with the same (base) name advanced sorting support
This commit is contained in:
SebastianMC 2024-01-25 23:11:44 +01:00 committed by GitHub
commit ac8cbc0efd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 107 additions and 3 deletions

View File

@ -35,7 +35,11 @@ export enum CustomSortOrder {
standardObsidian, // whatever user selected in the UI standardObsidian, // whatever user selected in the UI
byBookmarkOrder, byBookmarkOrder,
byBookmarkOrderReverse, byBookmarkOrderReverse,
default = alphabetical fileFirst,
folderFirst,
alphabeticalWithFilesPreferred, // When the (base)names are equal, the file has precedence over a folder
alphabeticalWithFoldersPreferred, // When the (base)names are equal, the file has precedence over a folder
default = alphabeticalWithFilesPreferred
} }
export interface RecognizedOrderValue { export interface RecognizedOrderValue {

View File

@ -22,6 +22,9 @@ import {
sorterByMetadataField, sorterByMetadataField,
SorterFn SorterFn
} from './custom-sort'; } from './custom-sort';
import {
_unitTests
} from './custom-sort'
import { import {
CustomSortGroupType, CustomSortGroupType,
CustomSortOrder, CustomSortOrder,
@ -2959,3 +2962,38 @@ describe('sorterByFolderCDate', () => {
expect(normalizedResultR2).toBe(orderReverseRevParams) expect(normalizedResultR2).toBe(orderReverseRevParams)
}) })
}) })
describe('fileGoesFirstWhenSameBasenameAsFolder', () => {
const file = 'file'
const folder = 'folder'
it.each([
// main scenario - file goes first unconditionally before folder with the same name
[0, file, folder, -1, 1],
[0, folder, file, 1, -1],
// Not possible - two folders with the same name - the test only documents the behavior for clarity
[0, folder, folder, 0, 0],
// Realistic yet useless - two files with the same basename,
[0, file, file, 0, 0],
// Obvious cases - text compare returned !== 0, simply pass through
[1, file, file, 1, 1],
[1, file, folder, 1, 1],
[1, folder, file, 1, 1],
[1, folder, folder, 1, 1],
[-1, file, file, -1, -1],
[-1, file, folder, -1, -1],
[-1, folder, file, -1, -1],
[-1, folder, folder, -1, -1],
])('text compare %s of %s %s gives %s (files first) and %s (folders first)',
(textCompare: number, aIsFolder: string, bIsFolder: string, filePreferredOder: number, folderPreferredOrder: number) => {
// given
const a: Partial<FolderItemForSorting> = { isFolder: aIsFolder === folder }
const b: Partial<FolderItemForSorting> = { isFolder: bIsFolder === folder }
const resultFilePreferred: number = _unitTests.fileGoesFirstWhenSameBasenameAsFolder(textCompare, a as FolderItemForSorting, b as FolderItemForSorting)
const resultFolderPreferred: number = _unitTests.folderGoesFirstWhenSameBasenameAsFolder(textCompare, a as FolderItemForSorting, b as FolderItemForSorting)
// then
expect(resultFilePreferred).toBe(filePreferredOder)
expect(resultFolderPreferred).toBe(folderPreferredOrder)
})
})

View File

@ -169,8 +169,18 @@ export const sorterByFolderMDate:(reverseOrder?: boolean) => SorterFn = (reverse
} }
} }
type FIFS = FolderItemForSorting
const fileGoesFirstWhenSameBasenameAsFolder = (stringCompareResult: number, a: FIFS, b: FIFS) =>
(!!stringCompareResult) ? stringCompareResult : (a.isFolder === b.isFolder ? EQUAL_OR_UNCOMPARABLE : (a.isFolder ? 1 : -1) );
const folderGoesFirstWhenSameBasenameAsFolder = (stringCompareResult: number, a: FIFS, b: FIFS) =>
(!!stringCompareResult) ? stringCompareResult : (a.isFolder === b.isFolder ? EQUAL_OR_UNCOMPARABLE : (a.isFolder ? -1 : 1) );
const Sorters: { [key in CustomSortOrder]: SorterFn } = { const Sorters: { [key in CustomSortOrder]: SorterFn } = {
[CustomSortOrder.alphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString), [CustomSortOrder.alphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
[CustomSortOrder.alphabeticalWithFilesPreferred]: (a: FIFS, b: FIFS) => fileGoesFirstWhenSameBasenameAsFolder(CollatorCompare(a.sortString, b.sortString),a,b),
[CustomSortOrder.alphabeticalWithFoldersPreferred]: (a: FIFS, b: FIFS) => fileGoesFirstWhenSameBasenameAsFolder(CollatorCompare(a.sortString, b.sortString),a,b),
[CustomSortOrder.alphabeticalWithFileExt]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortStringWithExt, b.sortStringWithExt), [CustomSortOrder.alphabeticalWithFileExt]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortStringWithExt, b.sortStringWithExt),
[CustomSortOrder.trueAlphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortString, b.sortString), [CustomSortOrder.trueAlphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortString, b.sortString),
[CustomSortOrder.trueAlphabeticalWithFileExt]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortStringWithExt, b.sortStringWithExt), [CustomSortOrder.trueAlphabeticalWithFileExt]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortStringWithExt, b.sortStringWithExt),
@ -192,9 +202,11 @@ const Sorters: { [key in CustomSortOrder]: SorterFn } = {
[CustomSortOrder.byMetadataFieldTrueAlphabeticalReverse]: sorterByMetadataField(ReverseOrder, TrueAlphabetical, SortingLevelId.forPrimary), [CustomSortOrder.byMetadataFieldTrueAlphabeticalReverse]: sorterByMetadataField(ReverseOrder, TrueAlphabetical, SortingLevelId.forPrimary),
[CustomSortOrder.byBookmarkOrder]: sorterByBookmarkOrder(StraightOrder), [CustomSortOrder.byBookmarkOrder]: sorterByBookmarkOrder(StraightOrder),
[CustomSortOrder.byBookmarkOrderReverse]: sorterByBookmarkOrder(ReverseOrder), [CustomSortOrder.byBookmarkOrderReverse]: sorterByBookmarkOrder(ReverseOrder),
[CustomSortOrder.fileFirst]: (a: FolderItemForSorting, b: FolderItemForSorting) => (a.isFolder && b.isFolder) ? EQUAL_OR_UNCOMPARABLE : (a.isFolder ? 1 : -1),
[CustomSortOrder.folderFirst]: (a: FolderItemForSorting, b: FolderItemForSorting) => (a.isFolder && b.isFolder) ? EQUAL_OR_UNCOMPARABLE : (a.isFolder ? -1 : 0),
// This is a fallback entry which should not be used - the getSorterFor() function below should protect against it // A fallback entry which should not be used - the getSorterFor() function below should protect against it
[CustomSortOrder.standardObsidian]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString), [CustomSortOrder.standardObsidian]: (a: FIFS, b: FIFS) => CollatorCompare(a.sortString, b.sortString),
}; };
// Some sorters are different when used in primary vs. secondary sorting order // Some sorters are different when used in primary vs. secondary sorting order
@ -729,3 +741,8 @@ export const sortFolderItemsForBookmarking = function (folder: TFolder, items: A
return folderItems return folderItems
} }
}; };
export const _unitTests = {
fileGoesFirstWhenSameBasenameAsFolder: fileGoesFirstWhenSameBasenameAsFolder,
folderGoesFirstWhenSameBasenameAsFolder: folderGoesFirstWhenSameBasenameAsFolder
}

View File

@ -527,6 +527,49 @@ describe('SortingSpecProcessor', () => {
}) })
}) })
const txtInputFilesOrFoldersPreferred: string = `
target-folder: AAA
< a-z, files-first
subitems1...
> folders-first, true a-z.
subitems2...
< created, folders-first
`
const expectedSortSpecForFilesOrFoldersPreferred: { [key: string]: CustomSortSpec } = {
"AAA": {
defaultOrder: CustomSortOrder.alphabetical,
defaultSecondaryOrder: CustomSortOrder.fileFirst,
groups: [{
exactPrefix: 'subitems1',
order: CustomSortOrder.folderFirst,
secondaryOrder: CustomSortOrder.trueAlphabeticalWithFileExt,
type: CustomSortGroupType.ExactPrefix
},{
exactPrefix: 'subitems2',
order: CustomSortOrder.byCreatedTime,
secondaryOrder: CustomSortOrder.folderFirst,
type: CustomSortGroupType.ExactPrefix
}, {
type: CustomSortGroupType.Outsiders
}],
outsidersGroupIdx: 2,
targetFoldersPaths: ['AAA']
}
}
describe('SortingSpecProcessor', () => {
let processor: SortingSpecProcessor;
beforeEach(() => {
processor = new SortingSpecProcessor();
});
it('should recognize the files / folders preferred as a primary and secondary orders', () => {
const inputTxtArr: Array<string> = txtInputFilesOrFoldersPreferred.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForFilesOrFoldersPreferred)
})
})
const txtInputThreeDotsCases: string = ` const txtInputThreeDotsCases: string = `
target-folder: AAA target-folder: AAA
... ...

View File

@ -127,6 +127,8 @@ const OrderLiterals: { [key: string]: CustomSortOrderAscDescPair } = {
'standard': {asc: CustomSortOrder.standardObsidian, desc: CustomSortOrder.standardObsidian}, 'standard': {asc: CustomSortOrder.standardObsidian, desc: CustomSortOrder.standardObsidian},
'ui selected': {asc: CustomSortOrder.standardObsidian, desc: CustomSortOrder.standardObsidian}, 'ui selected': {asc: CustomSortOrder.standardObsidian, desc: CustomSortOrder.standardObsidian},
'by-bookmarks-order': {asc: CustomSortOrder.byBookmarkOrder, desc: CustomSortOrder.byBookmarkOrderReverse}, 'by-bookmarks-order': {asc: CustomSortOrder.byBookmarkOrder, desc: CustomSortOrder.byBookmarkOrderReverse},
'files-first': {asc: CustomSortOrder.fileFirst, desc: CustomSortOrder.fileFirst},
'folders-first': {asc: CustomSortOrder.folderFirst, desc: CustomSortOrder.folderFirst}
} }
const OrderByMetadataLexeme: string = 'by-metadata:' const OrderByMetadataLexeme: string = 'by-metadata:'