#29 - Feature: priorities of sorting rules

This commit is contained in:
SebastianMC 2022-11-15 16:56:25 +01:00
parent 1300caf291
commit c397782a99
5 changed files with 331 additions and 21 deletions

View File

@ -57,6 +57,7 @@ export interface CustomSortGroup {
matchFilenameWithExt?: boolean matchFilenameWithExt?: boolean
foldersOnly?: boolean foldersOnly?: boolean
withMetadataFieldName?: string // for 'with-metadata:' withMetadataFieldName?: string // for 'with-metadata:'
priority?: number
} }
export interface CustomSortSpec { export interface CustomSortSpec {
@ -68,9 +69,10 @@ export interface CustomSortSpec {
outsidersFilesGroupIdx?: number outsidersFilesGroupIdx?: number
outsidersFoldersGroupIdx?: number outsidersFoldersGroupIdx?: number
itemsToHide?: Set<string> itemsToHide?: Set<string>
plugin?: Plugin // to hand over the access to App instance to the sorting engine priorityOrder?: Array<number> // Indexes of groups in evaluation order
// For internal transient use // For internal transient use
plugin?: Plugin // to hand over the access to App instance to the sorting engine
_mCache?: MetadataCache _mCache?: MetadataCache
} }

View File

@ -788,6 +788,52 @@ describe('determineSortingGroup', () => {
} as FolderItemForSorting); } as FolderItemForSorting);
}) })
}) })
it('should correctly apply priority group', () => {
// given
const file: TFile = mockTFile('Abcdef!', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
const sortSpec: CustomSortSpec = {
groups: [{
filesOnly: true,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.MatchAll
}, {
foldersOnly: true,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.MatchAll
}, {
exactSuffix: "def!",
priority: 2,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.ExactSuffix
}, {
exactText: "Abcdef!",
order: CustomSortOrder.alphabetical,
priority: 3,
type: CustomSortGroupType.ExactName
}, {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}],
outsidersGroupIdx: 4,
targetFoldersPaths: ['/'],
priorityOrder: [3,2,0,1]
}
// when
const result = determineSortingGroup(file, sortSpec)
// then
expect(result).toEqual({
groupIdx: 3,
isFolder: false,
sortString: "Abcdef!.md",
ctimeNewest: MOCK_TIMESTAMP + 222,
ctimeOldest: MOCK_TIMESTAMP + 222,
mtime: MOCK_TIMESTAMP + 333,
path: 'Some parent folder/Abcdef!.md'
});
})
}) })
describe('determineFolderDatesIfNeeded', () => { describe('determineFolderDatesIfNeeded', () => {

View File

@ -129,8 +129,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
const entryAsTFile: TFile = entry as TFile const entryAsTFile: TFile = entry as TFile
const basename: string = aFolder ? entry.name : entryAsTFile.basename const basename: string = aFolder ? entry.name : entryAsTFile.basename
for (groupIdx = 0; groupIdx < spec.groups.length; groupIdx++) { const numOfGroupsToCheck: number = spec.priorityOrder ? spec.priorityOrder.length : spec.groups.length
for (let idx = 0; idx < numOfGroupsToCheck; idx++) {
matchedGroup = null matchedGroup = null
groupIdx = spec.priorityOrder ? spec.priorityOrder[idx] : idx
const group: CustomSortGroup = spec.groups[groupIdx]; const group: CustomSortGroup = spec.groups[groupIdx];
if (group.foldersOnly && aFile) continue; if (group.foldersOnly && aFile) continue;
if (group.filesOnly && aFolder) continue; if (group.filesOnly && aFolder) continue;
@ -218,12 +220,12 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
break break
} }
if (determined) { if (determined) {
break; break; // No need to check other sorting groups
} }
} }
// the final groupIdx for undetermined folder entry is either the last+1 groupIdx or idx of explicitly defined outsiders group const idxAfterLastGroupIdx: number = spec.groups.length
let determinedGroupIdx: number | undefined = groupIdx; let determinedGroupIdx: number | undefined = determined ? groupIdx! : idxAfterLastGroupIdx
if (!determined) { if (!determined) {
// Automatically assign the index to outsiders group, if relevant was configured // Automatically assign the index to outsiders group, if relevant was configured

View File

@ -7,7 +7,7 @@ import {
escapeRegexUnsafeCharacters, escapeRegexUnsafeCharacters,
extractNumericSortingSymbol, extractNumericSortingSymbol,
hasMoreThanOneNumericSortingSymbol, hasMoreThanOneNumericSortingSymbol,
NumberNormalizerFn, NumberNormalizerFn, ProblemCode,
RegexpUsedAs, RegexpUsedAs,
RomanNumberNormalizerFn, RomanNumberNormalizerFn,
SortingSpecProcessor SortingSpecProcessor
@ -535,7 +535,7 @@ describe('SortingSpecProcessor', () => {
const txtInputSimplistic1: string = ` const txtInputSimplistic1: string = `
target-folder: /* target-folder: /*
/:files /:files
//folders /folders
` `
const expectedSortSpecForSimplistic1: { [key: string]: CustomSortSpec } = { const expectedSortSpecForSimplistic1: { [key: string]: CustomSortSpec } = {
@ -545,11 +545,12 @@ const expectedSortSpecForSimplistic1: { [key: string]: CustomSortSpec } = {
order: CustomSortOrder.alphabetical, order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders type: CustomSortGroupType.Outsiders
}, { }, {
foldersOnly: true,
order: CustomSortOrder.alphabetical, order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders type: CustomSortGroupType.Outsiders
}], }],
outsidersFilesGroupIdx: 0, outsidersFilesGroupIdx: 0,
outsidersGroupIdx: 1, outsidersFoldersGroupIdx: 1,
targetFoldersPaths: ['/*'] targetFoldersPaths: ['/*']
} }
} }
@ -561,11 +562,12 @@ const expectedWildcardMatchingTreeForSimplistic1 = {
order: CustomSortOrder.alphabetical, order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders type: CustomSortGroupType.Outsiders
}, { }, {
foldersOnly: true,
order: CustomSortOrder.alphabetical, order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders type: CustomSortGroupType.Outsiders
}], }],
outsidersFilesGroupIdx: 0, outsidersFilesGroupIdx: 0,
outsidersGroupIdx: 1, outsidersFoldersGroupIdx: 1,
targetFoldersPaths: ['/*'] targetFoldersPaths: ['/*']
}, },
"subtree": {} "subtree": {}
@ -574,7 +576,7 @@ const expectedWildcardMatchingTreeForSimplistic1 = {
const txtInputSimplistic2: string = ` const txtInputSimplistic2: string = `
target-folder: / target-folder: /
/:files /:files
//folders /folders
` `
const expectedSortSpecForSimplistic2: { [key: string]: CustomSortSpec } = { const expectedSortSpecForSimplistic2: { [key: string]: CustomSortSpec } = {
@ -584,11 +586,12 @@ const expectedSortSpecForSimplistic2: { [key: string]: CustomSortSpec } = {
order: CustomSortOrder.alphabetical, order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders type: CustomSortGroupType.Outsiders
}, { }, {
foldersOnly: true,
order: CustomSortOrder.alphabetical, order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders type: CustomSortGroupType.Outsiders
}], }],
outsidersFilesGroupIdx: 0, outsidersFilesGroupIdx: 0,
outsidersGroupIdx: 1, outsidersFoldersGroupIdx: 1,
targetFoldersPaths: ['/'] targetFoldersPaths: ['/']
} }
} }
@ -760,6 +763,123 @@ describe('SortingSpecProcessor edge case', () => {
}) })
}) })
const txtInputPriorityGroups1: string = `
target-folder: /
/:files
/folders
/! /:files Fi...
/!! /folders Fo...
/!!! ...def!
Plain text
/! % Anything
`
const txtInputPriorityGroups2: string = `
target-folder: /
/! /:files Fi...
/!! /folders Fo...
/!!! ...def!
/! Anything
`
const expectedSortSpecForPriorityGroups1: { [key: string]: CustomSortSpec } = {
"/": {
groups: [{
filesOnly: true,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}, {
foldersOnly: true,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}, {
exactPrefix: "Fi",
filesOnly: true,
order: CustomSortOrder.alphabetical,
priority: 1,
type: CustomSortGroupType.ExactPrefix
}, {
exactPrefix: "Fo",
foldersOnly: true,
order: CustomSortOrder.alphabetical,
priority: 2,
type: CustomSortGroupType.ExactPrefix
}, {
exactSuffix: "def!",
priority: 3,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.ExactSuffix
}, {
exactText: "Plain text",
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.ExactName
},{
exactText: "Anything",
order: CustomSortOrder.alphabetical,
priority: 1,
type: CustomSortGroupType.ExactName
}],
outsidersFilesGroupIdx: 0,
outsidersFoldersGroupIdx: 1,
targetFoldersPaths: ['/'],
priorityOrder: [4,3,2,6,5]
}
}
const expectedSortSpecForPriorityGroups2: { [key: string]: CustomSortSpec } = {
"/": {
groups: [{
exactPrefix: "Fi",
filesOnly: true,
order: CustomSortOrder.alphabetical,
priority: 1,
type: CustomSortGroupType.ExactPrefix
}, {
exactPrefix: "Fo",
foldersOnly: true,
order: CustomSortOrder.alphabetical,
priority: 2,
type: CustomSortGroupType.ExactPrefix
}, {
exactSuffix: "def!",
priority: 3,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.ExactSuffix
}, {
exactText: "Anything",
order: CustomSortOrder.alphabetical,
priority: 1,
type: CustomSortGroupType.ExactName
}, {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}],
outsidersGroupIdx: 4,
targetFoldersPaths: ['/'],
priorityOrder: [2,1,0,3]
}
}
describe('SortingSpecProcessor', () => {
let processor: SortingSpecProcessor;
beforeEach(() => {
processor = new SortingSpecProcessor();
});
it('should recognize the sorting groups with priority example 1', () => {
const inputTxtArr: Array<string> = txtInputPriorityGroups1.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityGroups1)
expect(result?.sortSpecByWildcard).toBeUndefined()
})
it('should recognize the sorting groups with priority example 2', () => {
const inputTxtArr: Array<string> = txtInputPriorityGroups2.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityGroups2)
expect(result?.sortSpecByWildcard).toBeUndefined()
})
})
const txtInputTargetFolderMultiSpecA: string = ` const txtInputTargetFolderMultiSpecA: string = `
target-folder: . target-folder: .
< a-z < a-z
@ -1111,6 +1231,22 @@ target-folder: AAA
sorting: standard sorting: standard
` `
const txtInputErrorPriorityAlone: string = `
/!
`
const txtInputErrorPriorityEmptyFilePattern: string = `
/!! /:
`
const txtInputErrorPriorityEmptyFolderPattern: string = `
/!!! /
`
const txtInputErrorPriorityEmptyPattern: string = `
/! %
`
const txtInputEmptySpec: string = `` const txtInputEmptySpec: string = ``
describe('SortingSpecProcessor error detection and reporting', () => { describe('SortingSpecProcessor error detection and reporting', () => {
@ -1207,6 +1343,42 @@ describe('SortingSpecProcessor error detection and reporting', () => {
`${ERR_PREFIX} 14:StandardObsidianSortAllowedOnlyAtFolderLevel The standard Obsidian sort order is only allowed at a folder level (not nested syntax) ${ERR_SUFFIX_IN_LINE(4)}`) `${ERR_PREFIX} 14:StandardObsidianSortAllowedOnlyAtFolderLevel The standard Obsidian sort order is only allowed at a folder level (not nested syntax) ${ERR_SUFFIX_IN_LINE(4)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(' sorting: standard')) expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(' sorting: standard'))
}) })
it('should recognize error: priority indicator alone', () => {
const inputTxtArr: Array<string> = txtInputErrorPriorityAlone.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(2)
expect(errorsLogger).toHaveBeenNthCalledWith(1,
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/!'))
})
it('should recognize error: priority indicator with empty file pattern', () => {
const inputTxtArr: Array<string> = txtInputErrorPriorityEmptyFilePattern.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(2)
expect(errorsLogger).toHaveBeenNthCalledWith(1,
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/!! /:'))
})
it('should recognize error: priority indicator with empty folder pattern', () => {
const inputTxtArr: Array<string> = txtInputErrorPriorityEmptyFolderPattern.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(2)
expect(errorsLogger).toHaveBeenNthCalledWith(1,
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/!!! /'))
})
it('should recognize error: priority indicator with empty pattern', () => {
const inputTxtArr: Array<string> = txtInputErrorPriorityEmptyPattern.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(2)
expect(errorsLogger).toHaveBeenNthCalledWith(1,
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/! %'))
})
it('should recognize empty spec', () => { it('should recognize empty spec', () => {
const inputTxtArr: Array<string> = txtInputEmptySpec.split('\n') const inputTxtArr: Array<string> = txtInputEmptySpec.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md') const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')

View File

@ -46,6 +46,7 @@ interface ParsedSortingGroup {
arraySpec?: Array<string> arraySpec?: Array<string>
outsidersGroup?: boolean // Mutually exclusive with plainSpec and arraySpec outsidersGroup?: boolean // Mutually exclusive with plainSpec and arraySpec
itemToHide?: boolean itemToHide?: boolean
priority?: number
} }
export enum ProblemCode { export enum ProblemCode {
@ -63,7 +64,8 @@ export enum ProblemCode {
ItemToHideExactNameWithExtRequired, ItemToHideExactNameWithExtRequired,
ItemToHideNoSupportForThreeDots, ItemToHideNoSupportForThreeDots,
DuplicateWildcardSortSpecForSameFolder, DuplicateWildcardSortSpecForSameFolder,
StandardObsidianSortAllowedOnlyAtFolderLevel StandardObsidianSortAllowedOnlyAtFolderLevel,
PriorityNotAllowedOnOutsidersGroup
} }
const ContextFreeProblems = new Set<ProblemCode>([ const ContextFreeProblems = new Set<ProblemCode>([
@ -174,7 +176,8 @@ const FilesWithExtGroupVerboseLexeme: string = '/:files.'
const FilesWithExtGroupShortLexeme: string = '/:.' const FilesWithExtGroupShortLexeme: string = '/:.'
const FoldersGroupVerboseLexeme: string = '/folders' const FoldersGroupVerboseLexeme: string = '/folders'
const FoldersGroupShortLexeme: string = '/' const FoldersGroupShortLexeme: string = '/'
const AnyTypeGroupLexeme: string = '%' // See % as a combination of / and : const AnyTypeGroupLexemeShort: string = '%' // See % as a combination of / and :
const AnyTypeGroupLexeme: string = '/%' // See % as a combination of / and :
const HideItemShortLexeme: string = '--%' // See % as a combination of / and : const HideItemShortLexeme: string = '--%' // See % as a combination of / and :
const HideItemVerboseLexeme: string = '/--hide:' const HideItemVerboseLexeme: string = '/--hide:'
@ -182,11 +185,26 @@ const MetadataFieldIndicatorLexeme: string = 'with-metadata:'
const CommentPrefix: string = '//' const CommentPrefix: string = '//'
const FileGroupModifierPrio1Lexeme: string = '/!'
const FileGroupModifierPrio2Lexeme: string = '/!!'
const FileGroupModifierPrio3Lexeme: string = '/!!!'
const PRIO_1: number = 1
const PRIO_2: number = 2
const PRIO_3: number = 3
const SortingGroupPriorityPrefixes: { [key: string]: number } = {
[FileGroupModifierPrio1Lexeme]: PRIO_1,
[FileGroupModifierPrio2Lexeme]: PRIO_2,
[FileGroupModifierPrio3Lexeme]: PRIO_3
}
interface SortingGroupType { interface SortingGroupType {
filesOnly?: boolean filesOnly?: boolean
filenameWithExt?: boolean // The text matching criteria should apply to filename + extension filenameWithExt?: boolean // The text matching criteria should apply to filename + extension
foldersOnly?: boolean foldersOnly?: boolean
itemToHide?: boolean itemToHide?: boolean
priority?: number
} }
const SortingGroupPrefixes: { [key: string]: SortingGroupType } = { const SortingGroupPrefixes: { [key: string]: SortingGroupType } = {
@ -196,6 +214,7 @@ const SortingGroupPrefixes: { [key: string]: SortingGroupType } = {
[FilesWithExtGroupVerboseLexeme]: {filesOnly: true, filenameWithExt: true}, [FilesWithExtGroupVerboseLexeme]: {filesOnly: true, filenameWithExt: true},
[FoldersGroupShortLexeme]: {foldersOnly: true}, [FoldersGroupShortLexeme]: {foldersOnly: true},
[FoldersGroupVerboseLexeme]: {foldersOnly: true}, [FoldersGroupVerboseLexeme]: {foldersOnly: true},
[AnyTypeGroupLexemeShort]: {},
[AnyTypeGroupLexeme]: {}, [AnyTypeGroupLexeme]: {},
[HideItemShortLexeme]: {itemToHide: true}, [HideItemShortLexeme]: {itemToHide: true},
[HideItemVerboseLexeme]: {itemToHide: true} [HideItemVerboseLexeme]: {itemToHide: true}
@ -664,19 +683,38 @@ export class SortingSpecProcessor {
} }
private parseSortingGroupSpec = (line: string): ParsedSortingGroup | null => { private parseSortingGroupSpec = (line: string): ParsedSortingGroup | null => {
const s: string = line.trim() let s: string = line.trim()
if (hasMoreThanOneNumericSortingSymbol(s)) { if (hasMoreThanOneNumericSortingSymbol(s)) {
this.problem(ProblemCode.TooManyNumericSortingSymbols, 'Maximum one numeric sorting indicator allowed per line') this.problem(ProblemCode.TooManyNumericSortingSymbols, 'Maximum one numeric sorting indicator allowed per line')
return null return null
} }
const priorityPrefixAlone: number = SortingGroupPriorityPrefixes[s]
if (priorityPrefixAlone) {
this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
return null
}
let groupPriority: number | undefined = undefined
for (const priorityPrefix of Object.keys(SortingGroupPriorityPrefixes)) {
if (s.startsWith(priorityPrefix + ' ')) {
groupPriority = SortingGroupPriorityPrefixes[priorityPrefix]
s = s.substring(priorityPrefix.length).trim()
break
}
}
const prefixAlone: SortingGroupType = SortingGroupPrefixes[s] const prefixAlone: SortingGroupType = SortingGroupPrefixes[s]
if (prefixAlone) { if (prefixAlone) {
if (prefixAlone.itemToHide) { if (prefixAlone.itemToHide) {
this.problem(ProblemCode.ItemToHideExactNameWithExtRequired, 'Exact name with ext of file or folders to hide is required') this.problem(ProblemCode.ItemToHideExactNameWithExtRequired, 'Exact name with ext of file or folders to hide is required')
return null return null
} else { // !prefixAlone.itemToHide } else { // !prefixAlone.itemToHide
if (groupPriority) {
this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
return null
} else {
return { return {
outsidersGroup: true, outsidersGroup: true,
filesOnly: prefixAlone.filesOnly, filesOnly: prefixAlone.filesOnly,
@ -684,6 +722,7 @@ export class SortingSpecProcessor {
} }
} }
} }
}
for (const prefix of Object.keys(SortingGroupPrefixes)) { for (const prefix of Object.keys(SortingGroupPrefixes)) {
if (s.startsWith(prefix + ' ')) { if (s.startsWith(prefix + ' ')) {
@ -700,12 +739,26 @@ export class SortingSpecProcessor {
plainSpec: s.substring(prefix.length + 1), plainSpec: s.substring(prefix.length + 1),
filesOnly: sortingGroupType.filesOnly, filesOnly: sortingGroupType.filesOnly,
foldersOnly: sortingGroupType.foldersOnly, foldersOnly: sortingGroupType.foldersOnly,
matchFilenameWithExt: sortingGroupType.filenameWithExt matchFilenameWithExt: sortingGroupType.filenameWithExt,
priority: groupPriority ?? undefined
} }
} }
} }
} }
if (groupPriority) {
if (s === '') {
// Edge case: line with only priority prefix and no other content
this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
return null
} else {
// Edge case: line with only priority prefix and no other known syntax, yet some content
return {
plainSpec: s,
priority: groupPriority
}
}
}
return null; return null;
} }
@ -731,8 +784,14 @@ export class SortingSpecProcessor {
if (newGroup) { if (newGroup) {
if (this.adjustSortingGroupForNumericSortingSymbol(newGroup)) { if (this.adjustSortingGroupForNumericSortingSymbol(newGroup)) {
if (this.ctx.currentSpec) { if (this.ctx.currentSpec) {
this.ctx.currentSpec.groups.push(newGroup) const groupIdx = this.ctx.currentSpec.groups.push(newGroup) - 1
this.ctx.currentSpecGroup = newGroup this.ctx.currentSpecGroup = newGroup
// Consume group with priority
if (group.priority && group.priority > 0) {
newGroup.priority = group.priority
this.addExpediteGroupInfo(this.ctx.currentSpec, group.priority, groupIdx)
}
return true; return true;
} else { } else {
return false return false
@ -794,7 +853,7 @@ export class SortingSpecProcessor {
}) })
} }
// Populate sorting order for a bit more efficient sorting later on // Populate sorting order down the hierarchy for more clean sorting logic later on
for (let group of spec.groups) { for (let group of spec.groups) {
if (!group.order) { if (!group.order) {
group.order = spec.defaultOrder ?? DEFAULT_SORT_ORDER group.order = spec.defaultOrder ?? DEFAULT_SORT_ORDER
@ -802,6 +861,18 @@ export class SortingSpecProcessor {
} }
} }
// If any priority sorting group was present in the spec, determine the groups evaluation order
if (spec.priorityOrder) {
// priorityOrder array already contains at least one priority group, so append all non-priority groups for the final order
// (Outsiders groups are ignored intentionally)
for (let idx=0; idx < spec.groups.length; idx++) {
const group: CustomSortGroup = spec.groups[idx]
if (group.priority === undefined && group.type !== CustomSortGroupType.Outsiders) {
spec.priorityOrder.push(idx)
}
}
}
const CURRENT_FOLDER_PREFIX: string = `${CURRENT_FOLDER_SYMBOL}/` const CURRENT_FOLDER_PREFIX: string = `${CURRENT_FOLDER_SYMBOL}/`
// Replace the dot-folder names (coming from: 'target-folder: .') with actual folder names // Replace the dot-folder names (coming from: 'target-folder: .') with actual folder names
@ -1108,4 +1179,21 @@ export class SortingSpecProcessor {
} }
return true return true
} }
private addExpediteGroupInfo = (spec: CustomSortSpec, groupPriority: number, groupIdx: number) => {
if (!spec.priorityOrder) {
spec.priorityOrder = []
}
let inserted: boolean = false
for (let idx=0; idx<spec.priorityOrder.length; idx++) {
if (groupPriority > spec.groups[spec.priorityOrder[idx]].priority!) {
spec.priorityOrder.splice(idx, 0, groupIdx)
inserted = true
break
}
}
if (!inserted) {
spec.priorityOrder.push(groupIdx)
}
}
} }