diff --git a/src/Query/Filter/Field.ts b/src/Query/Filter/Field.ts index 69f7246766..3371216c7c 100644 --- a/src/Query/Filter/Field.ts +++ b/src/Query/Filter/Field.ts @@ -3,6 +3,7 @@ import type { Comparator } from '../Sorter'; import * as RegExpTools from '../../lib/RegExpTools'; import { Grouper } from '../Grouper'; import type { GrouperFunction } from '../Grouper'; +import type { Task } from './../../Task'; import type { FilterOrErrorMessage } from './Filter'; /** @@ -287,7 +288,7 @@ export abstract class Field { * @param reverse - false for normal group order, true for reverse group order. */ public createGrouper(reverse: boolean): Grouper { - return new Grouper(this.fieldNameSingular(), this.grouper(), reverse); + return new Grouper(this.fieldNameSingular(), this.grouper(), reverse, this.defaultComparator); } /** @@ -309,4 +310,17 @@ export abstract class Field { public createReverseGrouper(): Grouper { return this.createGrouper(true); } + + protected defaultComparator: Comparator = (a: Task, b: Task) => { + const groupA = this.grouper()(a); + const groupB = this.grouper()(b); + + for (let i = 0; i < groupA.length; i++) { + // The containers are guaranteed to be identical sizes since we are calling the same grouper + return groupA[i].localeCompare(groupB[i], undefined, { numeric: true }); + } + + // identical if we reach here + return 0; + }; } diff --git a/src/Query/Filter/MultiTextField.ts b/src/Query/Filter/MultiTextField.ts index ef6a0d70df..46a187c575 100644 --- a/src/Query/Filter/MultiTextField.ts +++ b/src/Query/Filter/MultiTextField.ts @@ -64,7 +64,7 @@ export abstract class MultiTextField extends TextField { * This overloads {@link Field.createGrouper} to put a plural field name in the {@link Grouper.property}. */ public createGrouper(reverse: boolean): Grouper { - return new Grouper(this.fieldNamePlural(), this.grouper(), reverse); + return new Grouper(this.fieldNamePlural(), this.grouper(), reverse, this.defaultComparator); } protected grouperRegExp(): RegExp { diff --git a/src/Query/Grouper.ts b/src/Query/Grouper.ts index 5adfdc4957..65e51c4005 100644 --- a/src/Query/Grouper.ts +++ b/src/Query/Grouper.ts @@ -1,4 +1,5 @@ import type { Task } from '../Task'; +import type { Comparator } from './Sorter'; /** * A group-naming function, that takes a Task object and returns zero or more @@ -36,12 +37,20 @@ export class Grouper { /** * Whether the headings for this group should be reversed. + * TODO now reverse used only in TaskGroups.toString(), shall be removed. */ public readonly reverse: boolean; - constructor(property: string, grouper: GrouperFunction, reverse: boolean) { + public readonly comparator: Comparator; + + constructor(property: string, grouper: GrouperFunction, reverse: boolean, comparator: Comparator) { this.property = property; this.grouper = grouper; this.reverse = reverse; + this.comparator = (a: Task, b: Task) => { + const result = comparator(a, b); + + return reverse ? -result : result; + }; } } diff --git a/src/Query/TaskGroups.ts b/src/Query/TaskGroups.ts index dca1c7647b..1af6184018 100644 --- a/src/Query/TaskGroups.ts +++ b/src/Query/TaskGroups.ts @@ -105,20 +105,12 @@ export class TaskGroups { private sortTaskGroups() { const compareFn = (group1: TaskGroup, group2: TaskGroup) => { - // Compare two TaskGroup objects, sorting them by the group names at each grouping level. - const groupNames1 = group1.groups; - const groupNames2 = group2.groups; - // The containers are guaranteed to be identical sizes, - // they have one value for each 'group by' line in the query. - for (let i = 0; i < groupNames1.length; i++) { - // For now, we only have one sort option: sort by the names of the groups. - // In future, we will add control over the sorting of group headings, - // which will likely involve adjusting this code to sort by applying a Comparator - // to the first Task in each group. + // Compare two TaskGroup objects, sorting them by first task in each group. + for (let i = 0; i < this._groupers.length; i++) { const grouper = this._groupers[i]; - const result = groupNames1[i].localeCompare(groupNames2[i], undefined, { numeric: true }); + const result = grouper.comparator(group1.tasks[0], group2.tasks[0]); if (result !== 0) { - return grouper.reverse ? -result : result; + return result; } } // identical if we reach here diff --git a/tests/TaskGroups.test.ts b/tests/TaskGroups.test.ts index 5202b2f824..e1cf6942b4 100644 --- a/tests/TaskGroups.test.ts +++ b/tests/TaskGroups.test.ts @@ -252,7 +252,8 @@ describe('Grouping tasks', () => { const inputs = [a, b]; const groupByTags: GrouperFunction = (task: Task) => task.tags; - const grouper = new Grouper('custom tag grouper', groupByTags, false); + const groupComparator = new TagsField().comparator(); + const grouper = new Grouper('custom tag grouper', groupByTags, false, groupComparator); const groups = new TaskGroups([grouper], inputs); expect(groups.totalTasksCount()).toEqual(2);