mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
core: sort & group reminders in sqlite
This commit is contained in:
@@ -29,6 +29,7 @@ import { Reminder } from "../types";
|
||||
import Database from "../api";
|
||||
import { SQLCollection } from "../database/sql-collection";
|
||||
import { isFalse } from "../database";
|
||||
import { sql } from "kysely";
|
||||
|
||||
dayjs.extend(isTomorrow);
|
||||
dayjs.extend(isSameOrBefore);
|
||||
@@ -66,7 +67,7 @@ export class Reminders implements ICollection {
|
||||
};
|
||||
|
||||
if (!reminder.date || !reminder.title)
|
||||
throw new Error("date and title are required in a reminder.");
|
||||
throw new Error(`date and title are required in a reminder.`);
|
||||
|
||||
await this.collection.upsert({
|
||||
id,
|
||||
@@ -247,15 +248,57 @@ export function getUpcomingReminder(reminders: Reminder[]) {
|
||||
}
|
||||
|
||||
export function isReminderActive(reminder: Reminder) {
|
||||
const time =
|
||||
reminder.mode === "once"
|
||||
? reminder.date
|
||||
: getUpcomingReminderTime(reminder);
|
||||
|
||||
return (
|
||||
!reminder.disabled &&
|
||||
(reminder.mode !== "once" ||
|
||||
time > Date.now() ||
|
||||
(reminder.snoozeUntil && reminder.snoozeUntil > Date.now()))
|
||||
reminder.date > Date.now() ||
|
||||
(!!reminder.snoozeUntil && reminder.snoozeUntil > Date.now()))
|
||||
);
|
||||
}
|
||||
|
||||
export function createUpcomingReminderTimeQuery(now = "now") {
|
||||
return sql`CASE
|
||||
WHEN mode = 'once' THEN date / 1000
|
||||
WHEN recurringMode = 'year' THEN
|
||||
strftime('%s',
|
||||
strftime('%Y-', date(${now})) || strftime('%m-%d%H:%M', date / 1000, 'unixepoch', 'localtime'),
|
||||
IIF(datetime(strftime('%Y-', date(${now})) || strftime('%m-%d%H:%M', date / 1000, 'unixepoch', 'localtime')) <= datetime(${now}), '+1 year', '+0 year'),
|
||||
'utc'
|
||||
)
|
||||
WHEN recurringMode = 'day' THEN
|
||||
strftime('%s',
|
||||
date(${now}) || time(date / 1000, 'unixepoch', 'localtime'),
|
||||
IIF(datetime(date(${now}) || time(date / 1000, 'unixepoch', 'localtime')) <= datetime(${now}), '+1 day', '+0 day'),
|
||||
'utc'
|
||||
)
|
||||
WHEN recurringMode = 'week' AND selectedDays IS NOT NULL AND json_array_length(selectedDays) > 0 THEN
|
||||
CASE
|
||||
WHEN CAST(strftime('%w', date(${now})) AS INTEGER) > (SELECT MAX(value) FROM json_each(selectedDays))
|
||||
OR datetime(date(${now}) || time(date / 1000, 'unixepoch', 'localtime')) <= datetime(${now})
|
||||
THEN
|
||||
strftime('%s', datetime(date(${now}), time(date / 1000, 'unixepoch', 'localtime'), '+1 day', 'weekday ' || json_extract(selectedDays, '$[0]'), 'utc'))
|
||||
ELSE
|
||||
strftime('%s', datetime(date(${now}), time(date / 1000, 'unixepoch', 'localtime'), 'weekday ' || (SELECT value FROM json_each(selectedDays) WHERE CAST(strftime('%w', date(${now})) AS INTEGER) <= value), 'utc'))
|
||||
END
|
||||
WHEN recurringMode = 'month' AND selectedDays IS NOT NULL AND json_array_length(selectedDays) > 0 THEN
|
||||
CASE
|
||||
WHEN CAST(strftime('%d', date(${now})) AS INTEGER) > (SELECT MAX(value) FROM json_each(selectedDays))
|
||||
OR datetime(date(${now}) || time(date / 1000, 'unixepoch', 'localtime')) <= datetime(${now})
|
||||
THEN
|
||||
strftime('%s', strftime('%Y-%m-', date(${now})) || printf('%02d', json_extract(selectedDays, '$[0]')) || time(date / 1000, 'unixepoch', 'localtime'), '+1 month', 'utc')
|
||||
ELSE strftime('%s', strftime('%Y-%m-', date(${now})) || (SELECT printf('%02d', value) FROM json_each(selectedDays) WHERE value <= strftime('%d', date(${now}))) || time(date / 1000, 'unixepoch', 'localtime'), 'utc')
|
||||
END
|
||||
ELSE strftime('%s', date(${now}) || time(date / 1000, 'unixepoch', 'localtime'), 'utc')
|
||||
END * 1000
|
||||
`.$castTo<number>();
|
||||
}
|
||||
|
||||
export function createIsReminderActiveQuery(now = "now") {
|
||||
return sql`IIF(
|
||||
(disabled IS NULL OR disabled = 0)
|
||||
AND (mode != 'once'
|
||||
OR datetime(date / 1000, 'unixepoch', 'localtime') > datetime(${now})
|
||||
OR (snoozeUntil IS NOT NULL
|
||||
AND datetime(snoozeUntil / 1000, 'unixepoch', 'localtime') > datetime(${now}))
|
||||
), 1, 0)`.$castTo<boolean>();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ const DEFAULT_GROUP_OPTIONS = (key: GroupingKey) =>
|
||||
sortBy:
|
||||
key === "trash"
|
||||
? "dateDeleted"
|
||||
: key === "tags" || key === "reminders"
|
||||
: key === "tags"
|
||||
? "dateCreated"
|
||||
: key === "reminders"
|
||||
? "dueDate"
|
||||
|
||||
@@ -40,7 +40,6 @@ import {
|
||||
Attachment,
|
||||
Color,
|
||||
ContentItem,
|
||||
GroupOptions,
|
||||
HistorySession,
|
||||
ItemType,
|
||||
MaybeDeletedItem,
|
||||
@@ -125,7 +124,6 @@ export interface DatabaseCollection<T, IsAsync extends boolean> {
|
||||
get(id: string): AsyncOrSyncResult<IsAsync, T | undefined>;
|
||||
put(items: (T | undefined)[]): Promise<SQLiteItem<T>[]>;
|
||||
update(ids: string[], partial: Partial<T>): Promise<void>;
|
||||
ids(options: GroupOptions): AsyncOrSyncResult<IsAsync, string[]>;
|
||||
records(
|
||||
ids: string[]
|
||||
): AsyncOrSyncResult<
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { GroupOptions, MaybeDeletedItem, isDeleted } from "../types";
|
||||
import { MaybeDeletedItem, isDeleted } from "../types";
|
||||
import EventManager from "../utils/event-manager";
|
||||
import { DatabaseAccessor, DatabaseCollection, DatabaseSchema } from ".";
|
||||
import { SQLCollection } from "./sql-collection";
|
||||
@@ -126,10 +126,6 @@ export class SQLCachedCollection<
|
||||
}
|
||||
}
|
||||
|
||||
ids(_options: GroupOptions): string[] {
|
||||
return Array.from(this.cache.keys());
|
||||
}
|
||||
|
||||
records(ids: string[]): Record<string, MaybeDeletedItem<T> | undefined> {
|
||||
const items: Record<string, MaybeDeletedItem<T> | undefined> = {};
|
||||
for (const id of ids) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
isFalse
|
||||
} from ".";
|
||||
import {
|
||||
AliasedRawBuilder,
|
||||
AnyColumn,
|
||||
AnyColumnWithTable,
|
||||
ExpressionOrFactory,
|
||||
@@ -46,6 +47,10 @@ import { VirtualizedGrouping } from "../utils/virtualized-grouping";
|
||||
import { groupArray } from "../utils/grouping";
|
||||
import { toChunks } from "../utils/array";
|
||||
import { Sanitizer } from "./sanitizer";
|
||||
import {
|
||||
createIsReminderActiveQuery,
|
||||
createUpcomingReminderTimeQuery
|
||||
} from "../collections/reminders";
|
||||
|
||||
const formats = {
|
||||
month: "%Y-%m",
|
||||
@@ -207,19 +212,6 @@ export class SQLCollection<
|
||||
}
|
||||
}
|
||||
|
||||
async ids(sortOptions: GroupOptions): Promise<string[]> {
|
||||
const ids = await this.db()
|
||||
.selectFrom<keyof DatabaseSchema>(this.type)
|
||||
.select("id")
|
||||
.where(isFalse("deleted"))
|
||||
.$if(this.type === "notes" || this.type === "notebooks", (eb) =>
|
||||
eb.where(isFalse("dateDeleted"))
|
||||
)
|
||||
.orderBy(sortOptions.sortBy, sortOptions.sortDirection)
|
||||
.execute();
|
||||
return ids.map((id) => id.id);
|
||||
}
|
||||
|
||||
async records(
|
||||
ids: string[]
|
||||
): Promise<Record<string, MaybeDeletedItem<T> | undefined>> {
|
||||
@@ -432,26 +424,26 @@ export class FilteredSelector<T extends Item> {
|
||||
const fields: Array<
|
||||
| AnyColumnWithTable<DatabaseSchema, keyof DatabaseSchema>
|
||||
| AnyColumn<DatabaseSchema, keyof DatabaseSchema>
|
||||
> = ["id", "type", options.sortBy];
|
||||
| AliasedRawBuilder<number, "dueDate">
|
||||
> = ["id", "type"];
|
||||
if (this.type === "notes") fields.push("notes.pinned", "notes.conflicted");
|
||||
else if (this.type === "notebooks") fields.push("notebooks.pinned");
|
||||
else if (this.type === "attachments" && options.groupBy === "abc")
|
||||
fields.push("attachments.filename");
|
||||
else if (this.type === "reminders") {
|
||||
else if (this.type === "reminders" || options.sortBy === "dueDate") {
|
||||
fields.push(
|
||||
"reminders.mode",
|
||||
"reminders.date",
|
||||
"reminders.recurringMode",
|
||||
"reminders.selectedDays",
|
||||
"reminders.snoozeUntil",
|
||||
"reminders.disabled",
|
||||
"reminders.snoozeUntil"
|
||||
"reminders.date",
|
||||
createUpcomingReminderTimeQuery().as("dueDate")
|
||||
);
|
||||
}
|
||||
} else fields.push(options.sortBy);
|
||||
return Array.from(
|
||||
groupArray(
|
||||
await this.filter
|
||||
.$call(this.buildSortExpression(options))
|
||||
.select(fields)
|
||||
.$call(this.buildSortExpression(options, true))
|
||||
.execute(),
|
||||
options
|
||||
).values()
|
||||
@@ -508,7 +500,7 @@ export class FilteredSelector<T extends Item> {
|
||||
|
||||
private buildSortExpression(
|
||||
options: GroupOptions | SortOptions,
|
||||
persistent?: boolean
|
||||
hasDueDate?: boolean
|
||||
) {
|
||||
const sortBy: Set<SortOptions["sortBy"]> = new Set();
|
||||
if (isGroupOptions(options)) {
|
||||
@@ -524,6 +516,11 @@ export class FilteredSelector<T extends Item> {
|
||||
qb = qb.orderBy(sql`IFNULL(conflicted, 0) desc`);
|
||||
if (this.type === "notes" || this.type === "notebooks")
|
||||
qb = qb.orderBy(sql`IFNULL(pinned, 0) desc`);
|
||||
if (this.type === "reminders")
|
||||
qb = qb.orderBy(
|
||||
(qb) => qb.parens(createIsReminderActiveQuery()),
|
||||
"desc"
|
||||
);
|
||||
|
||||
for (const item of sortBy) {
|
||||
if (item === "title") {
|
||||
@@ -538,36 +535,30 @@ export class FilteredSelector<T extends Item> {
|
||||
? formats[options.groupBy]
|
||||
: null;
|
||||
if (!timeFormat || isSortByDate(options)) {
|
||||
qb = qb.orderBy(item, options.sortDirection);
|
||||
if (item === "dueDate") {
|
||||
if (hasDueDate)
|
||||
qb = qb.orderBy(item as any, options.sortDirection);
|
||||
else
|
||||
qb = qb.orderBy(
|
||||
(qb) => qb.parens(createUpcomingReminderTimeQuery()),
|
||||
options.sortDirection
|
||||
);
|
||||
} else qb = qb.orderBy(item, options.sortDirection);
|
||||
continue;
|
||||
}
|
||||
|
||||
qb = qb.orderBy(
|
||||
sql`strftime('${sql.raw(timeFormat)}', ${sql.raw(
|
||||
item
|
||||
)} / 1000, 'unixepoch')`,
|
||||
)} / 1000, 'unixepoch', 'localtime')`,
|
||||
options.sortDirection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (persistent) qb = qb.orderBy("id asc");
|
||||
return qb;
|
||||
};
|
||||
}
|
||||
|
||||
private sortFields(options: SortOptions, persistent?: boolean) {
|
||||
const fields: Array<
|
||||
| AnyColumnWithTable<DatabaseSchema, keyof DatabaseSchema>
|
||||
| AnyColumn<DatabaseSchema, keyof DatabaseSchema>
|
||||
> = [];
|
||||
if (this.type === "notes") fields.push("conflicted");
|
||||
if (this.type === "notes" || this.type === "notebooks")
|
||||
fields.push("pinned");
|
||||
fields.push(options.sortBy);
|
||||
if (persistent) fields.push("id");
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
function isGroupOptions(
|
||||
@@ -582,6 +573,7 @@ function isSortByDate(options: SortOptions | GroupOptions) {
|
||||
options.sortBy === "dateEdited" ||
|
||||
options.sortBy === "dateDeleted" ||
|
||||
options.sortBy === "dateModified" ||
|
||||
options.sortBy === "dateUploaded"
|
||||
options.sortBy === "dateUploaded" ||
|
||||
options.sortBy === "dueDate"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ export type GroupingKey =
|
||||
| "notes"
|
||||
| "notebooks"
|
||||
| "tags"
|
||||
//| "topics"
|
||||
| "trash"
|
||||
| "favorites"
|
||||
| "reminders";
|
||||
|
||||
Reference in New Issue
Block a user