diff --git a/packages/core/src/database/sql-collection.ts b/packages/core/src/database/sql-collection.ts index 0c248885d..ec76b9b09 100644 --- a/packages/core/src/database/sql-collection.ts +++ b/packages/core/src/database/sql-collection.ts @@ -46,6 +46,15 @@ import { VirtualizedGrouping } from "../utils/virtualized-grouping"; import { groupArray } from "../utils/grouping"; import { toChunks } from "../utils/array"; +const formats = { + month: "%Y-%m", + year: "%Y", + week: "%Y-%W", + abc: null, + default: null, + none: null +} satisfies Record; + export class SQLCollection< TCollectionType extends keyof DatabaseSchema, T extends DatabaseSchema[TCollectionType] = DatabaseSchema[TCollectionType] @@ -482,28 +491,52 @@ export class FilteredSelector { } } - private buildSortExpression(options: SortOptions, persistent?: boolean) { + private buildSortExpression( + options: GroupOptions | SortOptions, + persistent?: boolean + ) { + const sortBy: Set = new Set(); + if (isGroupOptions(options)) { + if (options.groupBy === "abc") sortBy.add("title"); + else if (options.sortBy === "title") sortBy.add("dateCreated"); + } + sortBy.add(options.sortBy); + return ( qb: SelectQueryBuilder ) => { - return qb - .$if(this.type === "notes", (eb) => eb.orderBy("conflicted desc")) - .$if(this.type === "notes" || this.type === "notebooks", (eb) => - eb.orderBy("pinned desc") - ) - .$if(options.sortBy === "title", (eb) => - eb.orderBy( - sql`ltrim(${sql.raw( - options.sortBy - )}, ' \u00a0\r\n\t\v') COLLATE NOCASE ${sql.raw( - options.sortDirection - )}` - ) - ) - .$if(options.sortBy !== "title", (eb) => - eb.orderBy(options.sortBy, options.sortDirection) - ) - .$if(!!persistent, (eb) => eb.orderBy("id asc")); + if (this.type === "notes") qb = qb.orderBy("conflicted desc"); + if (this.type === "notes" || this.type === "notebooks") + qb = qb.orderBy("pinned desc"); + + for (const item of sortBy) { + if (item === "title") { + qb = qb.orderBy( + options.sortBy !== "title" + ? sql`substring(ltrim(title, ' \u00a0\r\n\t\v'), 1, 1) COLLATE NOCASE` + : sql`ltrim(title, ' \u00a0\r\n\t\v') COLLATE NOCASE`, + options.sortDirection + ); + } else { + const timeFormat = isGroupOptions(options) + ? formats[options.groupBy] + : null; + if (!timeFormat) { + qb = qb.orderBy(item, options.sortDirection); + continue; + } + + qb = qb.orderBy( + sql`strftime('${sql.raw(timeFormat)}', ${sql.raw( + item + )} / 1000, 'unixepoch')`, + options.sortDirection + ); + } + } + + if (persistent) qb = qb.orderBy("id asc"); + return qb; }; } @@ -520,3 +553,9 @@ export class FilteredSelector { return fields; } } + +function isGroupOptions( + options: SortOptions | GroupOptions +): options is GroupOptions { + return "groupBy" in options; +} diff --git a/packages/core/src/utils/grouping.ts b/packages/core/src/utils/grouping.ts index 4c4356772..f7761b0bb 100644 --- a/packages/core/src/utils/grouping.ts +++ b/packages/core/src/utils/grouping.ts @@ -82,7 +82,7 @@ function getKeySelector( const date = new Date(); if (item.type === "reminder") return isReminderActive(item as Reminder) ? "Active" : "Inactive"; - else if (options.sortBy === "title" && options.groupBy === "abc") + else if (options.groupBy === "abc") return getFirstCharacter(getTitle(item)); else { const value = getSortValue(options, item) || 0;