diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index 21313b9..feed856 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### ✨ Features +- You can now sort tasks alphabetically by name using `alphabetical` or `alphabeticalDescending` in the sorting field - You can now provide 'time' to the show field on a query. This will only render the time of the task (unless the end of the task is on a different day than the start). ### 🐛 Bug Fixes diff --git a/docs/docs/query-blocks.md b/docs/docs/query-blocks.md index c23894c..7d34be5 100644 --- a/docs/docs/query-blocks.md +++ b/docs/docs/query-blocks.md @@ -61,6 +61,8 @@ autorefresh: 120 The `sorting` property allows you to specify the ordering for how your tasks are rendered. This is specified as a list, where we sort in the order of the properties in the list. The possible values are: +- `alphabetical` or `alphabeticalAscending`: sorts tasks alphabetically by name (A→Z, case-insensitive) +- `alphabeticalDescending`: sorts tasks alphabetically by name (Z→A, case-insensitive) - `date` or `dateAscending`: sorts tasks in ascending order based on due date - `dateDescending`: sorts tasks in descending order based on due date - `priority` or `priorityAscending`: sorting tasks in ascending order based on priority @@ -82,6 +84,17 @@ sorting: ``` ```` +You can also sort tasks alphabetically: + +```` +```todoist +filter: "#work" +sorting: + - priority + - alphabetical +``` +```` + ### `groupBy` The `groupBy` property controls how tasks are grouped when they are rendered. If omitted, there will be no grouping. The possible values are: diff --git a/plugin/src/data/transformations/sorting.test.ts b/plugin/src/data/transformations/sorting.test.ts index 05e8d95..5b39927 100644 --- a/plugin/src/data/transformations/sorting.test.ts +++ b/plugin/src/data/transformations/sorting.test.ts @@ -9,7 +9,7 @@ function makeTask(id: string, opts?: Partial): Task { id, createdAt: opts?.createdAt ?? "1970-01-01", parentId: opts?.parentId, - content: "", + content: opts?.content ?? "", description: "", labels: [], priority: opts?.priority ?? 1, @@ -340,6 +340,96 @@ describe("sortTasks", () => { }), ], }, + { + description: "can sort alphabetically (ascending)", + input: [ + makeTask("a", { content: "zebra" }), + makeTask("b", { content: "apple" }), + makeTask("c", { content: "Banana" }), + makeTask("d", { content: "cherry" }), + ], + sortingOpts: [SortingVariant.Alphabetical], + expectedOutput: [ + makeTask("b", { content: "apple" }), + makeTask("c", { content: "Banana" }), + makeTask("d", { content: "cherry" }), + makeTask("a", { content: "zebra" }), + ], + }, + { + description: "can sort alphabetically descending", + input: [ + makeTask("a", { content: "apple" }), + makeTask("b", { content: "Banana" }), + makeTask("c", { content: "zebra" }), + ], + sortingOpts: [SortingVariant.AlphabeticalDescending], + expectedOutput: [ + makeTask("c", { content: "zebra" }), + makeTask("b", { content: "Banana" }), + makeTask("a", { content: "apple" }), + ], + }, + { + description: "alphabetical sort is case-insensitive", + input: [ + makeTask("a", { content: "APPLE" }), + makeTask("b", { content: "banana" }), + makeTask("c", { content: "Apple" }), + makeTask("d", { content: "BANANA" }), + ], + sortingOpts: [SortingVariant.Alphabetical], + expectedOutput: [ + makeTask("a", { content: "APPLE" }), + makeTask("c", { content: "Apple" }), + makeTask("b", { content: "banana" }), + makeTask("d", { content: "BANANA" }), + ], + }, + { + description: "alphabetical sort handles special characters", + input: [ + makeTask("a", { content: "2. Second task" }), + makeTask("b", { content: "1. First task" }), + makeTask("c", { content: "@mention task" }), + makeTask("d", { content: "#hashtag task" }), + ], + sortingOpts: [SortingVariant.Alphabetical], + expectedOutput: [ + makeTask("c", { content: "@mention task" }), + makeTask("d", { content: "#hashtag task" }), + makeTask("b", { content: "1. First task" }), + makeTask("a", { content: "2. Second task" }), + ], + }, + { + description: "alphabetical sort handles empty strings", + input: [ + makeTask("a", { content: "" }), + makeTask("b", { content: "apple" }), + makeTask("c", { content: "" }), + ], + sortingOpts: [SortingVariant.Alphabetical], + expectedOutput: [ + makeTask("a", { content: "" }), + makeTask("c", { content: "" }), + makeTask("b", { content: "apple" }), + ], + }, + { + description: "can combine alphabetical sort with priority sorting", + input: [ + makeTask("a", { content: "Task B", priority: 2 }), + makeTask("b", { content: "Task A", priority: 2 }), + makeTask("c", { content: "Task C", priority: 3 }), + ], + sortingOpts: [SortingVariant.Priority, SortingVariant.Alphabetical], + expectedOutput: [ + makeTask("c", { content: "Task C", priority: 3 }), + makeTask("b", { content: "Task A", priority: 2 }), + makeTask("a", { content: "Task B", priority: 2 }), + ], + }, ]; for (const tc of testcases) { diff --git a/plugin/src/data/transformations/sorting.ts b/plugin/src/data/transformations/sorting.ts index fc358ce..968576f 100644 --- a/plugin/src/data/transformations/sorting.ts +++ b/plugin/src/data/transformations/sorting.ts @@ -39,6 +39,10 @@ function compareTask(self: T, other: T, sorting: SortingVariant) return compareTaskDateAdded(self, other); case SortingVariant.DateAddedDescending: return -compareTaskDateAdded(self, other); + case SortingVariant.Alphabetical: + return compareTaskAlphabetical(self, other); + case SortingVariant.AlphabeticalDescending: + return -compareTaskAlphabetical(self, other); default: throw new Error(`Unexpected sorting type: '${sorting}'`); } @@ -94,6 +98,10 @@ function compareTaskDateAdded(self: T, other: T): number { return selfDate.compare(otherDate) < 0 ? -1 : 1; } +function compareTaskAlphabetical(self: T, other: T): number { + return self.content.localeCompare(other.content, undefined, { sensitivity: "base" }); +} + function isSameDay(a: Date, b: Date): boolean { return ( a.getFullYear() === b.getFullYear() && diff --git a/plugin/src/query/__snapshots__/parser.test.ts.snap b/plugin/src/query/__snapshots__/parser.test.ts.snap index 0a9f3a8..38162fc 100644 --- a/plugin/src/query/__snapshots__/parser.test.ts.snap +++ b/plugin/src/query/__snapshots__/parser.test.ts.snap @@ -2,7 +2,7 @@ exports[`parseQuery - error message snapshots > array with mixed valid and invalid enum values 1`] = ` [Error: Field 'sorting' elements have the following issues: - Item 'sorting[1]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"] + Item 'sorting[1]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"|"alphabetical"|"alphabeticalAscending"|"alphabeticalDescending"] `; exports[`parseQuery - error message snapshots > autorefresh must be a number 1`] = ` @@ -87,9 +87,9 @@ Field 'show' elements have the following issues: exports[`parseQuery - error message snapshots > sorting array must contain strings 1`] = ` [Error: Field 'sorting' elements have the following issues: - Item 'sorting[0]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending" - Item 'sorting[1]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending" - Item 'sorting[2]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"] + Item 'sorting[0]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"|"alphabetical"|"alphabeticalAscending"|"alphabeticalDescending" + Item 'sorting[1]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"|"alphabetical"|"alphabeticalAscending"|"alphabeticalDescending" + Item 'sorting[2]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"|"alphabetical"|"alphabeticalAscending"|"alphabeticalDescending"] `; exports[`parseQuery - error message snapshots > sorting must be an array 1`] = ` @@ -99,6 +99,6 @@ exports[`parseQuery - error message snapshots > sorting must be an array 1`] = ` exports[`parseQuery - error message snapshots > sorting must have valid enum values 1`] = ` [Error: Field 'sorting' elements have the following issues: - Item 'sorting[0]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending" - Item 'sorting[1]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"] + Item 'sorting[0]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"|"alphabetical"|"alphabeticalAscending"|"alphabeticalDescending" + Item 'sorting[1]': Invalid option: expected one of "priority"|"priorityAscending"|"priorityDescending"|"date"|"dateAscending"|"dateDescending"|"order"|"dateAdded"|"dateAddedAscending"|"dateAddedDescending"|"alphabetical"|"alphabeticalAscending"|"alphabeticalDescending"] `; diff --git a/plugin/src/query/parser.ts b/plugin/src/query/parser.ts index 78e5554..fa0f9ac 100644 --- a/plugin/src/query/parser.ts +++ b/plugin/src/query/parser.ts @@ -95,6 +95,9 @@ const sortingSchema = lookupToEnum({ dateAdded: SortingVariant.DateAdded, dateAddedAscending: SortingVariant.DateAdded, dateAddedDescending: SortingVariant.DateAddedDescending, + alphabetical: SortingVariant.Alphabetical, + alphabeticalAscending: SortingVariant.Alphabetical, + alphabeticalDescending: SortingVariant.AlphabeticalDescending, }); const showSchema = lookupToEnum({ diff --git a/plugin/src/query/query.ts b/plugin/src/query/query.ts index b425897..e2b919d 100644 --- a/plugin/src/query/query.ts +++ b/plugin/src/query/query.ts @@ -6,6 +6,8 @@ export enum SortingVariant { Order = 4, DateAdded = 5, DateAddedDescending = 6, + Alphabetical = 7, + AlphabeticalDescending = 8, } export enum ShowMetadataVariant {