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:
commit
ac8cbc0efd
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
...
|
...
|
||||||
|
|
|
@ -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:'
|
||||||
|
|
Loading…
Reference in New Issue