From 61eb448f0f612f5964a8bfd8b5c858d771ab9922 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Mon, 10 Apr 2023 21:47:21 -0700 Subject: [PATCH 01/93] System Notifications --- .gitignore | 7 + src/main.ts | 5 + .../components/Icon.svelte | 19 +++ .../components/Markdown.svelte | 22 +++ .../components/Reminder.svelte | 92 +++++++++++ src/reminder-testing-folder/notification.ts | 145 ++++++++++++++++++ 6 files changed, 290 insertions(+) create mode 100644 src/reminder-testing-folder/components/Icon.svelte create mode 100644 src/reminder-testing-folder/components/Markdown.svelte create mode 100644 src/reminder-testing-folder/components/Reminder.svelte create mode 100644 src/reminder-testing-folder/notification.ts diff --git a/.gitignore b/.gitignore index b46578861a..db5f0fb37b 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,10 @@ yarn-error.log # Backup files. *.bak +.vscode/settings.json +.vscode/settings.json +src/.vscode/settings.json +.vscode/settings.json +src/.vscode/settings.json +.vscode/settings.json +src/.vscode/settings.json diff --git a/src/main.ts b/src/main.ts index c55f403e86..9e3e436746 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ import { Plugin } from 'obsidian'; +import { TaskNotification } from './reminder-testing-folder/notification'; import { Cache } from './Cache'; import { Commands } from './Commands'; import { TasksEvents } from './TasksEvents'; @@ -20,6 +21,7 @@ export default class TasksPlugin extends Plugin { private cache: Cache | undefined; public inlineRenderer: InlineRenderer | undefined; public queryRenderer: QueryRenderer | undefined; + private taskNotification: TaskNotification | undefined; get apiV1() { return tasksApiV1(app); @@ -28,6 +30,9 @@ export default class TasksPlugin extends Plugin { async onload() { logging.registerConsoleLogger(); console.log('loading plugin "tasks"'); + console.log('Trigger notification'); + this.taskNotification = new TaskNotification(); + this.taskNotification.show(); await this.loadSettings(); this.addSettingTab(new SettingsTab({ plugin: this })); diff --git a/src/reminder-testing-folder/components/Icon.svelte b/src/reminder-testing-folder/components/Icon.svelte new file mode 100644 index 0000000000..7a77c44798 --- /dev/null +++ b/src/reminder-testing-folder/components/Icon.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/reminder-testing-folder/components/Markdown.svelte b/src/reminder-testing-folder/components/Markdown.svelte new file mode 100644 index 0000000000..84cf1bd223 --- /dev/null +++ b/src/reminder-testing-folder/components/Markdown.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/src/reminder-testing-folder/components/Reminder.svelte b/src/reminder-testing-folder/components/Reminder.svelte new file mode 100644 index 0000000000..a4670ab028 --- /dev/null +++ b/src/reminder-testing-folder/components/Reminder.svelte @@ -0,0 +1,92 @@ + + +
+

+ +

+ + + {reminder.file} + +
+ + + +
+
+ + diff --git a/src/reminder-testing-folder/notification.ts b/src/reminder-testing-folder/notification.ts new file mode 100644 index 0000000000..51816d9010 --- /dev/null +++ b/src/reminder-testing-folder/notification.ts @@ -0,0 +1,145 @@ +// import type { ReadOnlyReference } from "model/ref"; +// import type { DateTime } from "model/time"; +// import { App } from 'obsidian'; +// import { SETTINGS } from "settings"; +// import type { Reminder } from "../model/reminder"; +// import type { Later } from "../model/time"; +// import ReminderView from './components/Reminder.svelte'; +const electron = require('electron'); +const Notification = electron.remote.Notification; +// (electron as any).remote.Notification; + +export class TaskNotification { + constructor() {} //private app: App + + // not sure if I can use Async functions + // public enableNotifications(): boolean { + // if (Notification.permission === 'denied') { + // Notification.requestPermission().then((result: any) => { + // console.log(result); + // return Notification.permission === 'granted' ? true : false; + // }); + // } + // return Notification.permission === 'granted' ? true : false; + // } + + // Notifications will need to be enabled first before showing + // Idealy this will be called in settings menu and will only togle after user has enabled + public async enableNotifications(): Promise { + if (Notification.permission === 'denied') { + const result = await Notification.requestPermission(); + console.log(result); + } + return Notification.permission === 'granted'; + } + + public show() { + if (Notification.isSupported() && Notification.permission === 'granted') { + // Show system notification + const n = new Notification({ + title: 'Obsidian Reminder', + body: "Hello World, You've got mail!", + }); + n.on('click', () => { + console.log('Notification clicked'); + n.close(); + // this.showBuiltinReminder(reminder, onRemindMeLater, onDone, onMute, onOpenFile); + }); + n.on('close', () => { + console.log('Notification closed'); + }); + // action only supported in macOS + { + // const laters = SETTINGS.laters.value; + n.on('action', (_: any, index: any) => { + if (index === 0) { + // mark task as done + return; + } + // const later = laters[index - 1]!; + // onRemindMeLater(later.later()); + }); + const actions = [{ type: 'button', text: 'Mark as Done' }]; + // laters.forEach((later) => { + // actions.push({ type: 'button', text: later.label }); + // }); + n.actions = actions as any; + } + + n.show(); + } + } + + // private showBuiltinReminder( + // reminder: Reminder, + // onRemindMeLater: (time: DateTime) => void, + // onDone: () => void, + // onCancel: () => void, + // onOpenFile: () => void + // ) { + // new NotificationModal(this.app, this.laters.value, reminder, onRemindMeLater, onDone, onCancel, onOpenFile).open(); + // } +} + +// class ObsidianNotificationModal extends Modal { // was NotificationModal + +// canceled: boolean = true; + +// constructor( +// app: App, +// private laters: Array, +// private reminder: Reminder, +// private onRemindMeLater: (time: DateTime) => void, +// private onDone: () => void, +// private onCancel: () => void, +// private onOpenFile: () => void +// ) { +// super(app); +// } + +// override onOpen() { +// // When the modal is opened we mark the reminder as being displayed. This +// // lets us introspect the reminder's display state from elsewhere. +// this.reminder.beingDisplayed = true; + +// let { contentEl } = this; +// new ReminderView({ +// target: contentEl, +// props: { +// reminder: this.reminder, +// laters: this.laters, +// component: this, +// onRemindMeLater: (time: DateTime) => { +// this.onRemindMeLater(time); +// this.canceled = false; +// this.close(); +// }, +// onDone: () => { +// this.canceled = false; +// this.onDone(); +// this.close(); +// }, +// onOpenFile: () => { +// this.canceled = true; +// this.onOpenFile(); +// this.close(); +// }, +// onMute: () => { +// this.canceled = true; +// this.close(); +// }, +// }, +// }); +// } + +// override onClose() { +// // Unset the reminder from being displayed. This lets other parts of the +// // plugin continue. +// this.reminder.beingDisplayed = false; +// let { contentEl } = this; +// contentEl.empty(); +// if (this.canceled) { +// this.onCancel(); +// } +// } +// } From 24047babf9eae4820e67cc830ab121ee153c44a0 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Mon, 10 Apr 2023 21:47:39 -0700 Subject: [PATCH 02/93] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index db5f0fb37b..2664fad315 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ src/.vscode/settings.json src/.vscode/settings.json .vscode/settings.json src/.vscode/settings.json +.vscode/settings.json From 6fbcf7540255472b20da0192f6b4eb094ebd5b0b Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Tue, 11 Apr 2023 21:25:57 -0700 Subject: [PATCH 03/93] obsidian modal --- .gitignore | 1 + main.css | 27 +++ src/main.ts | 2 +- .../components/Icon.svelte | 2 +- .../components/Markdown.svelte | 2 +- .../components/Reminder.svelte | 15 +- src/reminder-testing-folder/notification.ts | 177 ++++++++---------- 7 files changed, 112 insertions(+), 114 deletions(-) create mode 100644 main.css diff --git a/.gitignore b/.gitignore index 2664fad315..6948fce35c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ src/.vscode/settings.json .vscode/settings.json src/.vscode/settings.json .vscode/settings.json +.vscode/settings.json diff --git a/main.css b/main.css new file mode 100644 index 0000000000..2ffd258a2b --- /dev/null +++ b/main.css @@ -0,0 +1,27 @@ +/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/reminder-testing-folder/components/Icon.esbuild-svelte-fake-css */ +.icon.svelte-1gcidq0 { + vertical-align: middle; +} + +/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/reminder-testing-folder/components/Reminder.esbuild-svelte-fake-css */ +main.svelte-yfmg28 { + padding: 1em; + margin: 0 auto; +} +.reminder-actions.svelte-yfmg28 { + margin-top: 1rem; + display: flex; + gap: 0.5rem; +} +.reminder-file.svelte-yfmg28 { + color: var(--text-muted); + cursor: pointer; +} +.reminder-file.svelte-yfmg28:hover { + color: var(--text-normal); + text-decoration: underline; +} +.later-select.svelte-yfmg28 { + font-size: 14px; +} +/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiSWNvbi5zdmVsdGUiLCAiUmVtaW5kZXIuc3ZlbHRlIl0sCiAgInNvdXJjZXNDb250ZW50IjogW251bGwsIG51bGxdLAogICJtYXBwaW5ncyI6ICI7QUFlSTtBQUNJOzs7O0FDNENOO0FBQ0U7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7O0FBR0Y7QUFDRTtBQUNBOztBQUdGO0FBQ0U7OyIsCiAgIm5hbWVzIjogW10KfQo= */ diff --git a/src/main.ts b/src/main.ts index 9e3e436746..3f63d4b0ca 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,7 +31,7 @@ export default class TasksPlugin extends Plugin { logging.registerConsoleLogger(); console.log('loading plugin "tasks"'); console.log('Trigger notification'); - this.taskNotification = new TaskNotification(); + this.taskNotification = new TaskNotification(this.app); this.taskNotification.show(); await this.loadSettings(); diff --git a/src/reminder-testing-folder/components/Icon.svelte b/src/reminder-testing-folder/components/Icon.svelte index 7a77c44798..ce55f660fa 100644 --- a/src/reminder-testing-folder/components/Icon.svelte +++ b/src/reminder-testing-folder/components/Icon.svelte @@ -1,4 +1,4 @@ - diff --git a/src/reminder-testing-folder/notification.ts b/src/reminder-testing-folder/notification.ts index 51816d9010..a15ad51d84 100644 --- a/src/reminder-testing-folder/notification.ts +++ b/src/reminder-testing-folder/notification.ts @@ -1,40 +1,22 @@ -// import type { ReadOnlyReference } from "model/ref"; -// import type { DateTime } from "model/time"; -// import { App } from 'obsidian'; -// import { SETTINGS } from "settings"; -// import type { Reminder } from "../model/reminder"; -// import type { Later } from "../model/time"; -// import ReminderView from './components/Reminder.svelte'; +import { App, Modal } from 'obsidian'; +import ReminderView from './components/Reminder.svelte'; const electron = require('electron'); const Notification = electron.remote.Notification; -// (electron as any).remote.Notification; export class TaskNotification { - constructor() {} //private app: App - - // not sure if I can use Async functions - // public enableNotifications(): boolean { - // if (Notification.permission === 'denied') { - // Notification.requestPermission().then((result: any) => { - // console.log(result); - // return Notification.permission === 'granted' ? true : false; - // }); - // } - // return Notification.permission === 'granted' ? true : false; - // } - - // Notifications will need to be enabled first before showing - // Idealy this will be called in settings menu and will only togle after user has enabled - public async enableNotifications(): Promise { - if (Notification.permission === 'denied') { - const result = await Notification.requestPermission(); - console.log(result); - } - return Notification.permission === 'granted'; - } + constructor(private app: App) {} //private app: App public show() { - if (Notification.isSupported() && Notification.permission === 'granted') { + const reminder = { + title: 'Reminder Title', + file: 'path/to/file.md', + time: new Date(), + rowNumber: 1, + done: false, + }; + + // if election notification is supported, aka desktop app + if (Notification.isSupported()) { // Show system notification const n = new Notification({ title: 'Obsidian Reminder', @@ -43,7 +25,7 @@ export class TaskNotification { n.on('click', () => { console.log('Notification clicked'); n.close(); - // this.showBuiltinReminder(reminder, onRemindMeLater, onDone, onMute, onOpenFile); + this.showBuiltinReminder(reminder); }); n.on('close', () => { console.log('Notification closed'); @@ -67,79 +49,74 @@ export class TaskNotification { } n.show(); + } else { + // Show obsidian modal notification for mobile users + // Must be in app for this to trigger + this.showBuiltinReminder(reminder); } } - // private showBuiltinReminder( - // reminder: Reminder, - // onRemindMeLater: (time: DateTime) => void, - // onDone: () => void, - // onCancel: () => void, - // onOpenFile: () => void - // ) { - // new NotificationModal(this.app, this.laters.value, reminder, onRemindMeLater, onDone, onCancel, onOpenFile).open(); - // } + private showBuiltinReminder( + reminder: any, + // onRemindMeLater: (time: any) => void, + // onDone: () => void, + // onCancel: () => void, + // onOpenFile: () => void, + ) { + new ObsidianNotificationModal( + this.app, + [1, 2, 3, 4, 5], + reminder, + // onRemindMeLater, + // onDone, + // onCancel, + // onOpenFile, + ).open(); + } } -// class ObsidianNotificationModal extends Modal { // was NotificationModal - -// canceled: boolean = true; - -// constructor( -// app: App, -// private laters: Array, -// private reminder: Reminder, -// private onRemindMeLater: (time: DateTime) => void, -// private onDone: () => void, -// private onCancel: () => void, -// private onOpenFile: () => void -// ) { -// super(app); -// } +// Probably want to rewrite this modal to better display task infor +class ObsidianNotificationModal extends Modal { + constructor( + app: App, + private laters: Array, + private reminder: any, // callbacks // private onRemindMeLater: (time: any) => void, // private onDone: () => void, // private onCancel: () => void, // private onOpenFile: () => void, + ) { + super(app); + } -// override onOpen() { -// // When the modal is opened we mark the reminder as being displayed. This -// // lets us introspect the reminder's display state from elsewhere. -// this.reminder.beingDisplayed = true; + override onOpen() { + const { contentEl } = this; + new ReminderView({ + target: contentEl, + props: { + reminder: this.reminder, + laters: this.laters, + component: this, + onRemindMeLater: () => { + // this.onRemindMeLater(time); -// let { contentEl } = this; -// new ReminderView({ -// target: contentEl, -// props: { -// reminder: this.reminder, -// laters: this.laters, -// component: this, -// onRemindMeLater: (time: DateTime) => { -// this.onRemindMeLater(time); -// this.canceled = false; -// this.close(); -// }, -// onDone: () => { -// this.canceled = false; -// this.onDone(); -// this.close(); -// }, -// onOpenFile: () => { -// this.canceled = true; -// this.onOpenFile(); -// this.close(); -// }, -// onMute: () => { -// this.canceled = true; -// this.close(); -// }, -// }, -// }); -// } + this.close(); + }, + onDone: () => { + // this.onDone(); + this.close(); + }, + onOpenFile: () => { + // this.onOpenFile(); + this.close(); + }, + onMute: () => { + this.close(); + }, + }, + }); + } -// override onClose() { -// // Unset the reminder from being displayed. This lets other parts of the -// // plugin continue. -// this.reminder.beingDisplayed = false; -// let { contentEl } = this; -// contentEl.empty(); -// if (this.canceled) { -// this.onCancel(); -// } -// } -// } + override onClose() { + // Unset the reminder from being displayed. This lets other parts of the + // plugin continue. + const { contentEl } = this; + contentEl.empty(); + } +} From c609564a9c10cfb626d422dc52a48649955c0c59 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Thu, 13 Apr 2023 00:27:31 -0700 Subject: [PATCH 04/93] switched from interface to class --- main.css | 28 +------------------ src/Commands/CreateOrEditTaskParser.ts | 2 ++ src/Task.ts | 26 +++++++++++++++++ src/TaskLayout.ts | 3 ++ src/TaskLineRenderer.ts | 1 + src/TaskSerializer/DefaultTaskSerializer.ts | 21 ++++++++++++++ src/TaskSerializer/index.ts | 1 + src/main.ts | 2 +- src/reminders/Reminder.ts | 23 +++++++++++++++ .../components/Icon.svelte | 0 .../components/Markdown.svelte | 0 .../components/Reminder.svelte | 0 .../notification.ts | 0 .../CustomMatchersForTaskSerializer.ts | 1 + tests/Query.test.ts | 2 ++ tests/Task.test.ts | 9 ++++++ .../DefaultTaskSerializer.test.ts | 13 ++++++--- tests/TaskSerializer/TaskSerializer.test.ts | 14 ++++++++-- tests/TestingTools/TaskBuilder.ts | 8 ++++++ 19 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 src/reminders/Reminder.ts rename src/{reminder-testing-folder => reminders}/components/Icon.svelte (100%) rename src/{reminder-testing-folder => reminders}/components/Markdown.svelte (100%) rename src/{reminder-testing-folder => reminders}/components/Reminder.svelte (100%) rename src/{reminder-testing-folder => reminders}/notification.ts (100%) diff --git a/main.css b/main.css index 2ffd258a2b..cc7f2f83e2 100644 --- a/main.css +++ b/main.css @@ -1,27 +1 @@ -/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/reminder-testing-folder/components/Icon.esbuild-svelte-fake-css */ -.icon.svelte-1gcidq0 { - vertical-align: middle; -} - -/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/reminder-testing-folder/components/Reminder.esbuild-svelte-fake-css */ -main.svelte-yfmg28 { - padding: 1em; - margin: 0 auto; -} -.reminder-actions.svelte-yfmg28 { - margin-top: 1rem; - display: flex; - gap: 0.5rem; -} -.reminder-file.svelte-yfmg28 { - color: var(--text-muted); - cursor: pointer; -} -.reminder-file.svelte-yfmg28:hover { - color: var(--text-normal); - text-decoration: underline; -} -.later-select.svelte-yfmg28 { - font-size: 14px; -} -/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiSWNvbi5zdmVsdGUiLCAiUmVtaW5kZXIuc3ZlbHRlIl0sCiAgInNvdXJjZXNDb250ZW50IjogW251bGwsIG51bGxdLAogICJtYXBwaW5ncyI6ICI7QUFlSTtBQUNJOzs7O0FDNENOO0FBQ0U7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7O0FBR0Y7QUFDRTtBQUNBOztBQUdGO0FBQ0U7OyIsCiAgIm5hbWVzIjogW10KfQo= */ +.icon.svelte-1gcidq0{vertical-align:middle}main.svelte-yfmg28{padding:1em;margin:0 auto}.reminder-actions.svelte-yfmg28{margin-top:1rem;display:flex;gap:.5rem}.reminder-file.svelte-yfmg28{color:var(--text-muted);cursor:pointer}.reminder-file.svelte-yfmg28:hover{color:var(--text-normal);text-decoration:underline}.later-select.svelte-yfmg28{font-size:14px} diff --git a/src/Commands/CreateOrEditTaskParser.ts b/src/Commands/CreateOrEditTaskParser.ts index d97ed8a890..187549fb9a 100644 --- a/src/Commands/CreateOrEditTaskParser.ts +++ b/src/Commands/CreateOrEditTaskParser.ts @@ -60,6 +60,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta recurrence: null, blockLink: '', tags: [], + reminders: [], originalMarkdown: '', scheduledDateIsInferred: false, }); @@ -95,6 +96,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta doneDate: null, recurrence: null, tags: [], + reminders: [], originalMarkdown: '', // Not needed since the inferred status is always re-computed after submitting. scheduledDateIsInferred: false, diff --git a/src/Task.ts b/src/Task.ts index 2212934c38..ec38e45b52 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -10,6 +10,7 @@ import { renderTaskLine } from './TaskLineRenderer'; import type { TaskLineRenderDetails } from './TaskLineRenderer'; import { DateFallback } from './DateFallback'; import { compareByDate } from './lib/DateTools'; +import type { Reminder } from './reminders/Reminder'; /** * When sorting, make sure low always comes after none. This way any tasks with low will be below any exiting @@ -101,6 +102,9 @@ export class Task { public readonly taskLocation: TaskLocation; public readonly tags: string[]; + // setup as an array of objects to allow for multiple reminders in the future + // i.e remind on due or start date, currently only used for an explicit Reminder + public readonly reminders: Reminder[]; public readonly priority: Priority; @@ -139,6 +143,7 @@ export class Task { recurrence, blockLink, tags, + reminders, originalMarkdown, scheduledDateIsInferred, }: { @@ -156,6 +161,7 @@ export class Task { recurrence: Recurrence | null; blockLink: string; tags: string[] | []; + reminders: Reminder[] | []; originalMarkdown: string; scheduledDateIsInferred: boolean; }) { @@ -166,6 +172,7 @@ export class Task { this.taskLocation = taskLocation; this.tags = tags; + this.reminders = reminders; this.priority = priority; @@ -248,6 +255,8 @@ export class Task { // Remove the Global Filter if it is there taskInfo.tags = taskInfo.tags.filter((tag) => !GlobalFilter.equals(tag)); + taskInfo.reminders = []; + return new Task({ ...taskInfo, status, @@ -494,6 +503,12 @@ export class Task { return false; } + // compare reminders + if (this.reminders.length !== other.reminders.length) { + return false; + } + // TODO check that date has changed + // Compare Date fields args = ['createdDate', 'startDate', 'scheduledDate', 'dueDate', 'doneDate']; for (const el of args) { @@ -527,4 +542,15 @@ export class Task { public static extractHashtags(description: string): string[] { return description.match(TaskRegularExpressions.hashTags)?.map((tag) => tag.trim()) ?? []; } + + /** + * Returns an array of reminders found in string + * + * @param description A task description that may contain reminders + * + * @returns An array of reminders found in the string + */ + public static extractReminders(description: string): string[] { + return description.match(TaskRegularExpressions.hashTags)?.map((tag) => tag.trim()) ?? []; + } } diff --git a/src/TaskLayout.ts b/src/TaskLayout.ts index 078261c98d..42706a16d9 100644 --- a/src/TaskLayout.ts +++ b/src/TaskLayout.ts @@ -2,6 +2,7 @@ * Various rendering options for a query. */ export class LayoutOptions { + // TODO: hideReminderDate hideTaskCount: boolean = false; hideBacklinks: boolean = false; hidePriority: boolean = false; @@ -25,6 +26,7 @@ export type TaskLayoutComponent = | 'startDate' | 'scheduledDate' | 'dueDate' + | 'reminderDate' | 'doneDate' | 'blockLink'; @@ -42,6 +44,7 @@ export class TaskLayout { 'startDate', 'scheduledDate', 'dueDate', + 'reminderDate', 'doneDate', 'blockLink', ]; diff --git a/src/TaskLineRenderer.ts b/src/TaskLineRenderer.ts index 46907f218a..4eb9c69314 100644 --- a/src/TaskLineRenderer.ts +++ b/src/TaskLineRenderer.ts @@ -25,6 +25,7 @@ export const LayoutClasses: { [c in TaskLayoutComponent]: string } = { createdDate: 'task-created', scheduledDate: 'task-scheduled', doneDate: 'task-done', + reminderDate: 'task-reminder', recurrenceRule: 'task-recurring', blockLink: '', }; diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index f69012a615..22ce5b5df2 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -1,4 +1,5 @@ import type { Moment } from 'moment'; +import { Reminder } from '../reminders/Reminder'; import { TaskLayout } from '../TaskLayout'; import type { TaskLayoutComponent } from '../TaskLayout'; import { Recurrence } from '../Recurrence'; @@ -24,6 +25,7 @@ export interface DefaultTaskSerializerSymbols { readonly dueDateSymbol: string; readonly doneDateSymbol: string; readonly recurrenceSymbol: string; + readonly reminderDateSymbol: string; readonly TaskFormatRegularExpressions: { priorityRegex: RegExp; startDateRegex: RegExp; @@ -32,6 +34,7 @@ export interface DefaultTaskSerializerSymbols { dueDateRegex: RegExp; doneDateRegex: RegExp; recurrenceRegex: RegExp; + reminderDateRegex: RegExp; }; } @@ -52,6 +55,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateSymbol: '📅', doneDateSymbol: '✅', recurrenceSymbol: '🔁', + reminderDateSymbol: '⏲️', TaskFormatRegularExpressions: { // The following regex's end with `$` because they will be matched and // removed from the end until none are left. @@ -62,6 +66,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u, doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, + reminderDateRegex: /⏲️ *(\d{4}-\d{2}-\d{2})$/u, }, } as const; @@ -95,6 +100,7 @@ export class DefaultTaskSerializer implements TaskSerializer { doneDateSymbol, recurrenceSymbol, dueDateSymbol, + reminderDateSymbol, } = this.symbols; switch (component) { @@ -137,6 +143,11 @@ export class DefaultTaskSerializer implements TaskSerializer { return layout.options.shortMode ? ' ' + dueDateSymbol : ` ${dueDateSymbol} ${task.dueDate.format(TaskRegularExpressions.dateFormat)}`; + case 'reminderDate': + if (task.reminders.length <= 0 || task.reminders[0].date === null) return ''; + return layout.options.shortMode + ? ' ' + reminderDateSymbol + : ` ${reminderDateSymbol} ${task.reminders[0].date.format(TaskRegularExpressions.dateFormat)}`; case 'recurrenceRule': if (!task.recurrence) return ''; return layout.options.shortMode @@ -167,6 +178,7 @@ export class DefaultTaskSerializer implements TaskSerializer { let scheduledDate: Moment | null = null; let dueDate: Moment | null = null; let doneDate: Moment | null = null; + const reminderDate: Reminder[] = []; let createdDate: Moment | null = null; let recurrenceRule: string = ''; let recurrence: Recurrence | null = null; @@ -254,6 +266,14 @@ export class DefaultTaskSerializer implements TaskSerializer { trailingTags = trailingTags.length > 0 ? [tagName, trailingTags].join(' ') : tagName; } + const reminderDateRegex = line.match(TaskFormatRegularExpressions.reminderDateRegex); + if (reminderDateRegex !== null) { + reminderDate.push(new Reminder(window.moment(reminderDateRegex[1], TaskRegularExpressions.dateFormat))); + line = line.replace(TaskFormatRegularExpressions.reminderDateRegex, '').trim(); + matched = true; + console.log(reminderDate.length); + } + runs++; } while (matched && runs <= maxRuns); @@ -282,6 +302,7 @@ export class DefaultTaskSerializer implements TaskSerializer { doneDate, recurrence, tags: Task.extractHashtags(line), + reminders: reminderDate, }; } } diff --git a/src/TaskSerializer/index.ts b/src/TaskSerializer/index.ts index 85d6820122..e58d6be6de 100644 --- a/src/TaskSerializer/index.ts +++ b/src/TaskSerializer/index.ts @@ -20,6 +20,7 @@ export type TaskDetails = Writeable< | 'doneDate' | 'recurrence' | 'tags' + | 'reminders' > >; diff --git a/src/main.ts b/src/main.ts index 3f63d4b0ca..809dc1a94f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { Plugin } from 'obsidian'; -import { TaskNotification } from './reminder-testing-folder/notification'; +import { TaskNotification } from './reminders/notification'; import { Cache } from './Cache'; import { Commands } from './Commands'; import { TasksEvents } from './TasksEvents'; diff --git a/src/reminders/Reminder.ts b/src/reminders/Reminder.ts new file mode 100644 index 0000000000..fe1a55a740 --- /dev/null +++ b/src/reminders/Reminder.ts @@ -0,0 +1,23 @@ +import type { Moment } from 'moment'; + +export class Reminder { + public date: Moment | null; + private isAck: boolean; + + constructor(date: Moment) { + this.date = date; + this.isAck = false; + } + + public getDate(): Moment | null { + return this.date; + } + + public getIsCompleted(): boolean { + return this.isAck; + } + + public complete(): void { + this.isAck = true; + } +} diff --git a/src/reminder-testing-folder/components/Icon.svelte b/src/reminders/components/Icon.svelte similarity index 100% rename from src/reminder-testing-folder/components/Icon.svelte rename to src/reminders/components/Icon.svelte diff --git a/src/reminder-testing-folder/components/Markdown.svelte b/src/reminders/components/Markdown.svelte similarity index 100% rename from src/reminder-testing-folder/components/Markdown.svelte rename to src/reminders/components/Markdown.svelte diff --git a/src/reminder-testing-folder/components/Reminder.svelte b/src/reminders/components/Reminder.svelte similarity index 100% rename from src/reminder-testing-folder/components/Reminder.svelte rename to src/reminders/components/Reminder.svelte diff --git a/src/reminder-testing-folder/notification.ts b/src/reminders/notification.ts similarity index 100% rename from src/reminder-testing-folder/notification.ts rename to src/reminders/notification.ts diff --git a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts index 2c9814cdbf..da1853c887 100644 --- a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts +++ b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts @@ -80,6 +80,7 @@ function summarizeTaskDetails(t: TaskDetails | null): SummarizedTaskDetails | nu dueDate: t.dueDate?.format(TaskRegularExpressions.dateFormat) ?? null, doneDate: t.doneDate?.format(TaskRegularExpressions.dateFormat) ?? null, recurrence: t.recurrence?.toText() ?? null, + reminders: 'null?', }; } diff --git a/tests/Query.test.ts b/tests/Query.test.ts index 6f1ad9d9b8..e0d05e0e61 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -318,6 +318,7 @@ describe('Query', () => { recurrence: null, blockLink: '', tags: [], + reminders: [], originalMarkdown: '', scheduledDateIsInferred: false, createdDate: null, @@ -336,6 +337,7 @@ describe('Query', () => { recurrence: null, blockLink: '', tags: [], + reminders: [], originalMarkdown: '', scheduledDateIsInferred: false, createdDate: null, diff --git a/tests/Task.test.ts b/tests/Task.test.ts index b0df5e5d65..a2b88daea9 100644 --- a/tests/Task.test.ts +++ b/tests/Task.test.ts @@ -2,6 +2,7 @@ * @jest-environment jsdom */ import moment from 'moment'; +import { Reminder } from '../src/reminders/Reminder'; import { Status } from '../src/Status'; import { Priority, Task } from '../src/Task'; import { resetSettings, updateSettings } from '../src/Config/Settings'; @@ -1211,6 +1212,14 @@ describe('identicalTo', () => { expect(lhs).toBeIdenticalTo(new TaskBuilder().tags([])); expect(lhs).not.toBeIdenticalTo(new TaskBuilder().tags(['#stuff'])); }); + + it('should check reminders', () => { + const lhs = new TaskBuilder().reminders([]); + expect(lhs).toBeIdenticalTo(new TaskBuilder().reminders([])); + expect(lhs).not.toBeIdenticalTo( + new TaskBuilder().reminders([new Reminder(moment('2023-03-07', 'YYYY-MM-DD'))]), + ); + }); }); describe('checking if task lists are identical', () => { diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index e9c1774b91..2ee282fe29 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -29,7 +29,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ describe('deserialize', () => { it('should parse an empty string', () => { const taskDetails = deserialize(''); - expect(taskDetails).toMatchTaskDetails({}); + expect(taskDetails).toMatchTaskDetails({ reminders: [] }); }); it.each([ @@ -40,7 +40,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ { what: 'doneDate', symbol: doneDateSymbol }, ] as const)('should parse a $what', ({ what, symbol }) => { const taskDetails = deserialize(`${symbol} 2021-06-20`); - expect(taskDetails).toMatchTaskDetails({ [what]: moment('2021-06-20', 'YYYY-MM-DD') }); + expect(taskDetails).toMatchTaskDetails({ [what]: moment('2021-06-20', 'YYYY-MM-DD'), reminders: [] }); }); it('should parse a priority', () => { @@ -51,7 +51,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const taskDetails = deserialize(`${prioritySymbol}`); - expect(taskDetails).toMatchTaskDetails({ priority }); + expect(taskDetails).toMatchTaskDetails({ priority, reminders: [] }); } }); @@ -59,13 +59,18 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const taskDetails = deserialize(`${recurrenceSymbol} every day`); expect(taskDetails).toMatchTaskDetails({ recurrence: new RecurrenceBuilder().rule('every day').build(), + reminders: [], }); }); it('should parse tags', () => { const description = ' #hello #world #task'; const taskDetails = deserialize(description); - expect(taskDetails).toMatchTaskDetails({ tags: ['#hello', '#world', '#task'], description }); + expect(taskDetails).toMatchTaskDetails({ + tags: ['#hello', '#world', '#task'], + description, + reminders: [], + }); }); }); diff --git a/tests/TaskSerializer/TaskSerializer.test.ts b/tests/TaskSerializer/TaskSerializer.test.ts index 3798049d4d..3b3167bd58 100644 --- a/tests/TaskSerializer/TaskSerializer.test.ts +++ b/tests/TaskSerializer/TaskSerializer.test.ts @@ -61,6 +61,7 @@ describe('TaskSerializer Example', () => { return { description, tags: Task.extractHashtags(description), + reminders: [], dueDate, priority, startDate: null, @@ -83,16 +84,17 @@ describe('TaskSerializer Example', () => { describe('deserialize', () => { it('should parse the empty string', () => { - expect(ts.deserialize('')).toMatchTaskDetails({}); + expect(ts.deserialize('')).toMatchTaskDetails({ reminders: [] }); }); it('should parse just a priority', () => { - expect(ts.deserialize('1')).toMatchTaskDetails({ priority: Priority.High }); + expect(ts.deserialize('1')).toMatchTaskDetails({ priority: Priority.High, reminders: [] }); }); it('should parse just a description', () => { expect(ts.deserialize('Hello World, this is a task description')).toMatchTaskDetails({ description: 'Hello World, this is a task description', + reminders: [], }); }); @@ -100,11 +102,16 @@ describe('TaskSerializer Example', () => { expect(ts.deserialize('1 1978-09-21')).toMatchTaskDetails({ priority: Priority.High, dueDate: moment('1978-09-21', 'YYYY-MM-DD'), + reminders: [], }); }); it('should parse a priority and description', () => { - expect(ts.deserialize('1 Wobble')).toMatchTaskDetails({ priority: Priority.High, description: 'Wobble' }); + expect(ts.deserialize('1 Wobble')).toMatchTaskDetails({ + priority: Priority.High, + description: 'Wobble', + reminders: [], + }); }); it('should parse a full task', () => { @@ -112,6 +119,7 @@ describe('TaskSerializer Example', () => { priority: Priority.High, description: 'Wobble', dueDate: moment('1978-09-21', 'YYYY-MM-DD'), + reminders: [], }); }); }); diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index fc1d7c0cc7..4e86892678 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -6,6 +6,7 @@ import type { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; import { StatusConfiguration, StatusType } from '../../src/StatusConfiguration'; import { TaskLocation } from '../../src/TaskLocation'; +import type { Reminder } from '../../src/reminders/Reminder'; /** * A fluent class for creating tasks for tests. @@ -39,6 +40,7 @@ export class TaskBuilder { private _scheduledDate: Moment | null = null; private _dueDate: Moment | null = null; private _doneDate: Moment | null = null; + private _reminders: Reminder[] = []; private _recurrence: Recurrence | null = null; private _blockLink: string = ''; @@ -83,6 +85,7 @@ export class TaskBuilder { recurrence: this._recurrence, blockLink: this._blockLink, tags: this._tags, + reminders: this._reminders, originalMarkdown: '', scheduledDateIsInferred: this._scheduledDateIsInferred, }); @@ -194,6 +197,11 @@ export class TaskBuilder { return this; } + public reminders(reminders: Reminder[]): TaskBuilder { + this._reminders = reminders; + return this; + } + public recurrence(recurrence: Recurrence | null): TaskBuilder { this._recurrence = recurrence; return this; From cd1819b1e8ffa07a68ada2336d6598b8d17a995a Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Sat, 15 Apr 2023 18:28:34 -0700 Subject: [PATCH 05/93] Reminder parsing and queries + model field --- main.css | 28 ++++++++++++- src/Query/.vscode/settings.json | 11 ++++++ src/Query/Filter/ReminderDateField.ts | 24 ++++++++++++ src/Query/FilterParser.ts | 2 + src/Recurrence.ts | 24 ++++++++++++ src/Suggestor/Suggestor.ts | 1 + src/Task.ts | 4 +- src/TaskSerializer/DefaultTaskSerializer.ts | 8 ++-- src/reminders/Reminder.ts | 4 ++ src/ui/EditTask.svelte | 39 +++++++++++++++++-- .../CustomMatchersForTaskSerializer.ts | 3 +- tests/Recurrence.test.ts | 10 +++++ tests/TestingTools/RecurrenceBuilder.ts | 1 + 13 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 src/Query/.vscode/settings.json create mode 100644 src/Query/Filter/ReminderDateField.ts diff --git a/main.css b/main.css index cc7f2f83e2..1a502b9bed 100644 --- a/main.css +++ b/main.css @@ -1 +1,27 @@ -.icon.svelte-1gcidq0{vertical-align:middle}main.svelte-yfmg28{padding:1em;margin:0 auto}.reminder-actions.svelte-yfmg28{margin-top:1rem;display:flex;gap:.5rem}.reminder-file.svelte-yfmg28{color:var(--text-muted);cursor:pointer}.reminder-file.svelte-yfmg28:hover{color:var(--text-normal);text-decoration:underline}.later-select.svelte-yfmg28{font-size:14px} +/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/reminders/components/Icon.esbuild-svelte-fake-css */ +.icon.svelte-1gcidq0 { + vertical-align: middle; +} + +/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/reminders/components/Reminder.esbuild-svelte-fake-css */ +main.svelte-yfmg28 { + padding: 1em; + margin: 0 auto; +} +.reminder-actions.svelte-yfmg28 { + margin-top: 1rem; + display: flex; + gap: 0.5rem; +} +.reminder-file.svelte-yfmg28 { + color: var(--text-muted); + cursor: pointer; +} +.reminder-file.svelte-yfmg28:hover { + color: var(--text-normal); + text-decoration: underline; +} +.later-select.svelte-yfmg28 { + font-size: 14px; +} +/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiSWNvbi5zdmVsdGUiLCAiUmVtaW5kZXIuc3ZlbHRlIl0sCiAgInNvdXJjZXNDb250ZW50IjogW251bGwsIG51bGxdLAogICJtYXBwaW5ncyI6ICI7QUFlSTtBQUNJOzs7O0FDNENOO0FBQ0U7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7O0FBR0Y7QUFDRTtBQUNBOztBQUdGO0FBQ0U7OyIsCiAgIm5hbWVzIjogW10KfQo= */ diff --git a/src/Query/.vscode/settings.json b/src/Query/.vscode/settings.json new file mode 100644 index 0000000000..e6e7934b02 --- /dev/null +++ b/src/Query/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true + }, + "hide-files.files": [] +} \ No newline at end of file diff --git a/src/Query/Filter/ReminderDateField.ts b/src/Query/Filter/ReminderDateField.ts new file mode 100644 index 0000000000..3dd92ef587 --- /dev/null +++ b/src/Query/Filter/ReminderDateField.ts @@ -0,0 +1,24 @@ +import type { Moment } from 'moment'; +import type { Task } from '../../Task'; +import { DateField } from './DateField'; + +/** + * Support the 'due' search instruction. + */ +export class ReminderDateField extends DateField { + public fieldName(): string { + return 'reminder'; + } + + public date(task: Task): Moment | null { + if (task.reminders.length > 0) { + return task.reminders[0].getDate(); + } else { + return null; + } + } + + protected filterResultIfFieldMissing() { + return false; + } +} diff --git a/src/Query/FilterParser.ts b/src/Query/FilterParser.ts index 0c952b5a95..956c207cb3 100644 --- a/src/Query/FilterParser.ts +++ b/src/Query/FilterParser.ts @@ -2,6 +2,7 @@ import { DescriptionField } from './Filter/DescriptionField'; import { CreatedDateField } from './Filter/CreatedDateField'; import { DoneDateField } from './Filter/DoneDateField'; import { DueDateField } from './Filter/DueDateField'; +import { ReminderDateField } from './Filter/ReminderDateField'; import { ExcludeSubItemsField } from './Filter/ExcludeSubItemsField'; import { HeadingField } from './Filter/HeadingField'; import { PathField } from './Filter/PathField'; @@ -35,6 +36,7 @@ const fieldCreators = [ () => new ScheduledDateField(), () => new DueDateField(), () => new DoneDateField(), + () => new ReminderDateField(), () => new PathField(), () => new DescriptionField(), () => new TagsField(), diff --git a/src/Recurrence.ts b/src/Recurrence.ts index af001e65f1..c7902e0ca8 100644 --- a/src/Recurrence.ts +++ b/src/Recurrence.ts @@ -8,6 +8,7 @@ export class Recurrence { private readonly startDate: Moment | null; private readonly scheduledDate: Moment | null; private readonly dueDate: Moment | null; + private readonly reminderDate: Moment | null; /** * The reference date is used to calculate future occurrences. @@ -31,6 +32,7 @@ export class Recurrence { startDate, scheduledDate, dueDate, + reminderDate, }: { rrule: RRule; baseOnToday: boolean; @@ -38,6 +40,7 @@ export class Recurrence { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; + reminderDate: Moment | null; }) { this.rrule = rrule; this.baseOnToday = baseOnToday; @@ -45,6 +48,7 @@ export class Recurrence { this.startDate = startDate; this.scheduledDate = scheduledDate; this.dueDate = dueDate; + this.reminderDate = reminderDate; } public static fromText({ @@ -52,11 +56,13 @@ export class Recurrence { startDate, scheduledDate, dueDate, + reminderDate, }: { recurrenceRuleText: string; startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; + reminderDate: Moment | null; }): Recurrence | null { try { const match = recurrenceRuleText.match(/^([a-zA-Z0-9, !]+?)( when done)?$/i); @@ -79,6 +85,8 @@ export class Recurrence { referenceDate = window.moment(scheduledDate); } else if (startDate) { referenceDate = window.moment(startDate); + } else if (reminderDate) { + referenceDate = window.moment(reminderDate); } if (!baseOnToday && referenceDate !== null) { @@ -95,6 +103,7 @@ export class Recurrence { startDate, scheduledDate, dueDate, + reminderDate, }); } } catch (error) { @@ -120,6 +129,7 @@ export class Recurrence { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; + reminderDate: Moment | null; } | null { let next: Date; if (this.baseOnToday) { @@ -149,6 +159,7 @@ export class Recurrence { let startDate: Moment | null = null; let scheduledDate: Moment | null = null; let dueDate: Moment | null = null; + let reminderDate: Moment | null = null; // Only if a reference date is given. A reference date will exist if at // least one of the other dates is set. @@ -177,12 +188,22 @@ export class Recurrence { // Rounding days to handle cross daylight-savings-time recurrences. dueDate.add(Math.round(originalDifference.asDays()), 'days'); } + //TODO - copliot review this + if (this.reminderDate) { + const originalDifference = window.moment.duration(this.reminderDate.diff(this.referenceDate)); + + // Cloning so that original won't be manipulated: + reminderDate = window.moment(next); + // Rounding days to handle cross daylight-savings-time recurrences. + reminderDate.add(Math.round(originalDifference.asDays()), 'days'); + } } return { startDate, scheduledDate, dueDate, + reminderDate, }; } @@ -204,6 +225,9 @@ export class Recurrence { if (compareByDate(this.dueDate, other.dueDate) !== 0) { return false; } + if (compareByDate(this.reminderDate, other.reminderDate) !== 0) { + return false; + } return this.toText() === other.toText(); // this also checks baseOnToday } diff --git a/src/Suggestor/Suggestor.ts b/src/Suggestor/Suggestor.ts index 09c42f30b5..d10bc2e51e 100644 --- a/src/Suggestor/Suggestor.ts +++ b/src/Suggestor/Suggestor.ts @@ -251,6 +251,7 @@ function addRecurrenceSuggestions(line: string, cursorPos: number, settings: Set startDate: null, scheduledDate: null, dueDate: null, + reminderDate: null, })?.toText(); if (parsedRecurrence) { const appendedText = `${recurrencePrefix} ${parsedRecurrence} `; diff --git a/src/Task.ts b/src/Task.ts index ec38e45b52..ad229adc2d 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -242,6 +242,8 @@ export class Task { const { taskSerializer } = getUserSelectedTaskFormat(); const taskInfo = taskSerializer.deserialize(description); + console.log(taskInfo); + let scheduledDateIsInferred = false; // Infer the scheduled date from the file name if not set explicitly if (DateFallback.canApplyFallback(taskInfo) && fallbackDate !== null) { @@ -255,8 +257,6 @@ export class Task { // Remove the Global Filter if it is there taskInfo.tags = taskInfo.tags.filter((tag) => !GlobalFilter.equals(tag)); - taskInfo.reminders = []; - return new Task({ ...taskInfo, status, diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index 22ce5b5df2..be43bad9e6 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -178,7 +178,7 @@ export class DefaultTaskSerializer implements TaskSerializer { let scheduledDate: Moment | null = null; let dueDate: Moment | null = null; let doneDate: Moment | null = null; - const reminderDate: Reminder[] = []; + let reminderDate: Moment | null = null; let createdDate: Moment | null = null; let recurrenceRule: string = ''; let recurrence: Recurrence | null = null; @@ -268,10 +268,9 @@ export class DefaultTaskSerializer implements TaskSerializer { const reminderDateRegex = line.match(TaskFormatRegularExpressions.reminderDateRegex); if (reminderDateRegex !== null) { - reminderDate.push(new Reminder(window.moment(reminderDateRegex[1], TaskRegularExpressions.dateFormat))); + reminderDate = window.moment(reminderDateRegex[1], TaskRegularExpressions.dateFormat); line = line.replace(TaskFormatRegularExpressions.reminderDateRegex, '').trim(); matched = true; - console.log(reminderDate.length); } runs++; @@ -284,6 +283,7 @@ export class DefaultTaskSerializer implements TaskSerializer { startDate, scheduledDate, dueDate, + reminderDate, }); } // Add back any trailing tags to the description. We removed them so we can parse the rest of the @@ -302,7 +302,7 @@ export class DefaultTaskSerializer implements TaskSerializer { doneDate, recurrence, tags: Task.extractHashtags(line), - reminders: reminderDate, + reminders: reminderDate ? [new Reminder(reminderDate)] : [], }; } } diff --git a/src/reminders/Reminder.ts b/src/reminders/Reminder.ts index fe1a55a740..a8571be490 100644 --- a/src/reminders/Reminder.ts +++ b/src/reminders/Reminder.ts @@ -9,6 +9,10 @@ export class Reminder { this.isAck = false; } + public toString(): string { + return `${this.date?.format('YYYY-MM-DD')}`; + } + public getDate(): Moment | null { return this.date; } diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index ea48fbc471..cac5711c15 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -7,6 +7,7 @@ import { Status } from '../Status'; import { Priority, Task } from '../Task'; import { doAutocomplete } from '../DateAbbreviations'; + import { Reminder } from '../reminders/Reminder'; // These exported variables are passed in as props by TaskModal.onOpen(): export let task: Task; @@ -19,6 +20,7 @@ startDateSymbol, scheduledDateSymbol, dueDateSymbol, + reminderDateSymbol, } = TASK_FORMATS.tasksPluginEmoji.taskSerializer.symbols; let descriptionInput: HTMLTextAreaElement; @@ -32,6 +34,7 @@ scheduledDate: string; dueDate: string; doneDate: string; + reminderDate: string; forwardOnly: boolean; } = { description: '', @@ -43,6 +46,7 @@ scheduledDate: '', dueDate: '', doneDate: '', + reminderDate: '', forwardOnly: true }; @@ -54,6 +58,8 @@ let isScheduledDateValid: boolean = true; let parsedDueDate: string = ''; let isDueDateValid: boolean = true; + let parsedReminderDate: string = ''; + let isReminderDateValid: boolean = true; let parsedRecurrence: string = ''; let isRecurrenceValid: boolean = true; let parsedDone: string = ''; @@ -110,7 +116,7 @@ * @returns the parsed date string. Includes "invalid" if {@code typedDate} was invalid. */ function parseTypedDateForDisplay( - fieldName: 'created' | 'start' | 'scheduled' | 'due' | 'done', + fieldName: 'created' | 'start' | 'scheduled' | 'due' | 'reminder' | 'done', typedDate: string, forwardDate: Date | undefined = undefined, ): string { @@ -132,7 +138,7 @@ * @param typedDate - what the user has entered, such as '2023-01-23' or 'tomorrow' * @returns the parsed date string. Includes "invalid" if {@code typedDate} was invalid. */ - function parseTypedDateForDisplayUsingFutureDate(fieldName: 'start' | 'scheduled' | 'due' | 'done', typedDate: string): string { + function parseTypedDateForDisplayUsingFutureDate(fieldName: 'start' | 'scheduled' | 'due' | 'done' | 'reminder', typedDate: string): string { return parseTypedDateForDisplay( fieldName, typedDate, @@ -158,7 +164,7 @@ } $: accesskey = (key: string) => withAccessKeys ? key : null; - $: formIsValid = isDueDateValid && isRecurrenceValid && isScheduledDateValid && isStartDateValid && isDescriptionValid; + $: formIsValid = isDueDateValid && isRecurrenceValid && isScheduledDateValid && isStartDateValid && isReminderDateValid && isDescriptionValid; $: isDescriptionValid = editableTask.description.trim() !== ''; $: { @@ -179,6 +185,12 @@ isDueDateValid = !parsedDueDate.includes('invalid'); } + $: { + editableTask.reminderDate = doAutocomplete(editableTask.reminderDate); + parsedReminderDate = parseTypedDateForDisplayUsingFutureDate('reminder', editableTask.reminderDate); + isReminderDateValid = !parsedReminderDate.includes('invalid'); + } + $: { isRecurrenceValid = true; if (!editableTask.recurrenceRule) { @@ -190,6 +202,7 @@ startDate: null, scheduledDate: null, dueDate: null, + reminderDate: null, })?.toText(); if (!recurrenceFromText) { parsedRecurrence = 'invalid recurrence rule'; @@ -239,6 +252,7 @@ ? task.scheduledDate.format('YYYY-MM-DD') : '', dueDate: task.dueDate ? task.dueDate.format('YYYY-MM-DD') : '', + reminderDate: task.reminders[0] ? task.reminders[0].date!.format('YYYY-MM-DD') : '', // todo update to remove ! doneDate: task.doneDate ? task.doneDate.format('YYYY-MM-DD') : '', forwardOnly: true, }; @@ -287,6 +301,8 @@ const dueDate = parseTypedDateForSaving(editableTask.dueDate); + const reminderDate = parseTypedDateForSaving(editableTask.reminderDate); + let recurrence: Recurrence | null = null; if (editableTask.recurrenceRule) { recurrence = Recurrence.fromText({ @@ -294,6 +310,7 @@ startDate, scheduledDate, dueDate, + reminderDate, }); } @@ -321,6 +338,7 @@ startDate, scheduledDate, dueDate, + reminders: reminderDate ? [new Reminder(reminderDate)] : [], doneDate: window .moment(editableTask.doneDate, 'YYYY-MM-DD') .isValid() @@ -443,6 +461,21 @@ /> {startDateSymbol} {@html parsedStartDate} + + + + + + + {reminderDateSymbol} {@html parsedReminderDate} + diff --git a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts index da1853c887..9ac2fd2056 100644 --- a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts +++ b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts @@ -80,7 +80,7 @@ function summarizeTaskDetails(t: TaskDetails | null): SummarizedTaskDetails | nu dueDate: t.dueDate?.format(TaskRegularExpressions.dateFormat) ?? null, doneDate: t.doneDate?.format(TaskRegularExpressions.dateFormat) ?? null, recurrence: t.recurrence?.toText() ?? null, - reminders: 'null?', + reminders: t.reminders ? t.reminders[0].toString() : '', //todo see how tags are handled }; } @@ -104,6 +104,7 @@ function tryBuildTaskDetails(t: object): TaskDetails | null { doneDate: null, recurrence: null, tags: [], + reminders: [], ...t, }; if (!isTaskDetails(toReturn)) return null; diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index 9892613774..f8d6280e0a 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -16,6 +16,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: null, + reminderDate: null, }); // Act @@ -26,6 +27,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: null, + reminderDate: null, }); }); @@ -36,6 +38,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2022-01-31').startOf('day'), + reminderDate: null, }); // Act @@ -54,6 +57,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2022-01-31').startOf('day'), + reminderDate: null, }); // Act @@ -72,6 +76,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2023-12-31').startOf('day'), + reminderDate: null, }); // Act @@ -90,6 +95,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2024-02-29').startOf('day'), + reminderDate: null, }); // Act @@ -108,6 +114,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2020-03-31').startOf('day'), + reminderDate: null, }); // Act @@ -126,6 +133,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2020-01-31').startOf('day'), + reminderDate: null, }); // Act @@ -148,6 +156,7 @@ describe('Recurrence - with invalid dates in tasks', () => { startDate: null, scheduledDate: null, dueDate: moment('2022-02-30').startOf('day'), // 30th February: invalid date + reminderDate: null, }); // Assert @@ -168,6 +177,7 @@ describe('Recurrence - with invalid dates in tasks', () => { startDate: null, scheduledDate: moment('2022-02-30').startOf('day'), // 30th February: invalid date dueDate: moment('2022-02-27').startOf('day'), + reminderDate: null, }); // Act diff --git a/tests/TestingTools/RecurrenceBuilder.ts b/tests/TestingTools/RecurrenceBuilder.ts index bced56128c..ca072bc118 100644 --- a/tests/TestingTools/RecurrenceBuilder.ts +++ b/tests/TestingTools/RecurrenceBuilder.ts @@ -38,6 +38,7 @@ export class RecurrenceBuilder { startDate: this._startDate, scheduledDate: this._scheduledDate, dueDate: this._dueDate, + reminderDate: null, }) as Recurrence; } From 836bd1e2676d4defad6c4077f46151d0205d7135 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Sun, 16 Apr 2023 21:50:21 -0700 Subject: [PATCH 06/93] reminders watcher --- src/Query/Filter/ReminderDateField.ts | 2 +- src/Task.ts | 2 -- src/main.ts | 11 ++++-- src/reminders/Reminder.ts | 14 ++++++++ src/reminders/notification.ts | 51 +++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/Query/Filter/ReminderDateField.ts b/src/Query/Filter/ReminderDateField.ts index 3dd92ef587..1d45f86d90 100644 --- a/src/Query/Filter/ReminderDateField.ts +++ b/src/Query/Filter/ReminderDateField.ts @@ -11,7 +11,7 @@ export class ReminderDateField extends DateField { } public date(task: Task): Moment | null { - if (task.reminders.length > 0) { + if (task && task.reminders.length > 0) { return task.reminders[0].getDate(); } else { return null; diff --git a/src/Task.ts b/src/Task.ts index ad229adc2d..7a33f1b79a 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -242,8 +242,6 @@ export class Task { const { taskSerializer } = getUserSelectedTaskFormat(); const taskInfo = taskSerializer.deserialize(description); - console.log(taskInfo); - let scheduledDateIsInferred = false; // Infer the scheduled date from the file name if not set explicitly if (DateFallback.canApplyFallback(taskInfo) && fallbackDate !== null) { diff --git a/src/main.ts b/src/main.ts index 809dc1a94f..0337b1960a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,9 +30,8 @@ export default class TasksPlugin extends Plugin { async onload() { logging.registerConsoleLogger(); console.log('loading plugin "tasks"'); - console.log('Trigger notification'); this.taskNotification = new TaskNotification(this.app); - this.taskNotification.show(); + // this.taskNotification.show(); await this.loadSettings(); this.addSettingTab(new SettingsTab({ plugin: this })); @@ -58,6 +57,14 @@ export default class TasksPlugin extends Plugin { this.registerEditorExtension(newLivePreviewExtension()); this.registerEditorSuggest(new EditorSuggestor(this.app, getSettings())); new Commands({ plugin: this }); + + // Register the watcher for reminders. + this.app.workspace.onLayoutReady(async () => { + const tasks = this.getTasks(); + if (this.taskNotification && tasks) { + this.registerInterval(this.taskNotification.watcher(tasks)!); + } + }); } async loadTaskStatuses() { diff --git a/src/reminders/Reminder.ts b/src/reminders/Reminder.ts index a8571be490..bce1d29f90 100644 --- a/src/reminders/Reminder.ts +++ b/src/reminders/Reminder.ts @@ -24,4 +24,18 @@ export class Reminder { public complete(): void { this.isAck = true; } + + // public getExpiredReminders(defaultTime: Time): Array { + // const now = new Date().getTime(); + // const result: Array = []; + // for (let i = 0; i < this.reminders.length; i++) { + // const reminder = this.reminders[i]!; + // if (reminder.time.getTimeInMillis(defaultTime) <= now) { + // result.push(reminder); + // } else { + // break; + // } + // } + // return result; + // } } diff --git a/src/reminders/notification.ts b/src/reminders/notification.ts index a15ad51d84..c4542c38e7 100644 --- a/src/reminders/notification.ts +++ b/src/reminders/notification.ts @@ -1,7 +1,11 @@ import { App, Modal } from 'obsidian'; +import type { Task } from '../Task'; +import { Query } from '../Query/Query'; import ReminderView from './components/Reminder.svelte'; const electron = require('electron'); const Notification = electron.remote.Notification; +// Platform.isDesktopApp, // Platform.isMobileApp, +// import { Platform } from 'obsidian'; export class TaskNotification { constructor(private app: App) {} //private app: App @@ -56,6 +60,53 @@ export class TaskNotification { } } + public watcher(tasks: Task[]): number | undefined { + let intervalTaskRunning = true; + // Force the view to refresh as soon as possible. + this.reminderEvent(tasks).finally(() => { + intervalTaskRunning = false; + }); + + // Set up the recurring check for reminders. + return window.setInterval(() => { + if (intervalTaskRunning) { + console.log('Skip reminder interval task because task is already running.'); + return; + } + intervalTaskRunning = true; + this.reminderEvent(tasks).finally(() => { + intervalTaskRunning = false; + }); + }, 1000); + } + + private async reminderEvent(tasks: Task[]): Promise { + if (!tasks?.length) { + return; // No tasks, nothing to do. + } + const input = 'has reminder date'; + const query = new Query({ source: input }); + + // Get list of tasks with reminders + let reminderTasks = [...tasks]; + query.filters.forEach((filter) => { + reminderTasks = reminderTasks.filter(filter.filterFunction); + }); + + for (const rTask of reminderTasks) { + const now = window.moment(); + const daily = window.moment().startOf('day'); // match on moments that have no time set + // Might want to adjust this offset to be based on the setInterval + ~1 second + if (rTask.reminders[0].date?.isBetween(now.subtract(30, 'seconds'), now.add(30, 'seconds'))) { + console.log('Show Notification'); + } else if (rTask.reminders[0].date?.isSame(daily)) { + // Add check for if {{9am}} + console.log('Daily Notification'); + //this.show(); + } + } + } + private showBuiltinReminder( reminder: any, // onRemindMeLater: (time: any) => void, From a29ad68b13edb11b43430b86a0af9ec3b3c205e0 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Tue, 18 Apr 2023 23:12:13 -0700 Subject: [PATCH 07/93] hooked up tasks to notification modals --- .vscode/settings.json | 64 +++++++++++++++++++++++- main.css | 12 ++--- src/main.ts | 3 +- src/reminders/Reminder.ts | 20 ++------ src/reminders/components/Reminder.svelte | 19 +++---- src/reminders/notification.ts | 50 +++++++++--------- 6 files changed, 109 insertions(+), 59 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5623ddbe2e..aa5b37f3fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,65 @@ { - "npm.packageManager": "yarn" + "npm.packageManager": "yarn", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "docs": true, + ".github": true, + "contributing": true, + "docs-snippets": true, + "node_modules": true, + "resources": true, + ".eslintrc.js": true, + ".editorconfig": true, + ".eslintignore": true, + ".gitattributes": true, + ".gitignore": true, + ".markdownlint.jsonc": true, + ".prettierrc.js": true, + "CODE_OF_CONDUCT.md": true, + "CONTRIBUTING.md": true, + "esbuild.config.mjs": true, + "jest.config.js": true, + "lefthook.yml": true, + "LICENSE": true, + "yarn.lock": true, + "versions.json": true, + "styles.css": true, + "release.sh": true, + "manifest.json": true, + "mdsnippets.json": true, + "package.json": true + }, + "hide-files.files": [ + "docs", + ".github", + "contributing", + "docs-snippets", + "node_modules", + "resources", + ".eslintrc.js", + ".editorconfig", + ".eslintignore", + ".gitattributes", + ".gitignore", + ".markdownlint.jsonc", + ".prettierrc.js", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "esbuild.config.mjs", + "jest.config.js", + "lefthook.yml", + "LICENSE", + "yarn.lock", + "versions.json", + "styles.css", + "release.sh", + "manifest.json", + "mdsnippets.json", + "package.json", + ] } diff --git a/main.css b/main.css index 1a502b9bed..3580388e6c 100644 --- a/main.css +++ b/main.css @@ -4,24 +4,24 @@ } /* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/reminders/components/Reminder.esbuild-svelte-fake-css */ -main.svelte-yfmg28 { +main.svelte-1fdazlx { padding: 1em; margin: 0 auto; } -.reminder-actions.svelte-yfmg28 { +.task-actions.svelte-1fdazlx { margin-top: 1rem; display: flex; gap: 0.5rem; } -.reminder-file.svelte-yfmg28 { +.task-file.svelte-1fdazlx { color: var(--text-muted); cursor: pointer; } -.reminder-file.svelte-yfmg28:hover { +.task-file.svelte-1fdazlx:hover { color: var(--text-normal); text-decoration: underline; } -.later-select.svelte-yfmg28 { +.later-select.svelte-1fdazlx { font-size: 14px; } -/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiSWNvbi5zdmVsdGUiLCAiUmVtaW5kZXIuc3ZlbHRlIl0sCiAgInNvdXJjZXNDb250ZW50IjogW251bGwsIG51bGxdLAogICJtYXBwaW5ncyI6ICI7QUFlSTtBQUNJOzs7O0FDNENOO0FBQ0U7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7O0FBR0Y7QUFDRTtBQUNBOztBQUdGO0FBQ0U7OyIsCiAgIm5hbWVzIjogW10KfQo= */ +/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiSWNvbi5zdmVsdGUiLCAiUmVtaW5kZXIuc3ZlbHRlIl0sCiAgInNvdXJjZXNDb250ZW50IjogW251bGwsIG51bGxdLAogICJtYXBwaW5ncyI6ICI7QUFlSTtBQUNJOzs7O0FDNkNOO0FBQ0U7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7O0FBR0Y7QUFDRTtBQUNBOztBQUdGO0FBQ0U7OyIsCiAgIm5hbWVzIjogW10KfQo= */ diff --git a/src/main.ts b/src/main.ts index 0337b1960a..2644f1d531 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { Plugin } from 'obsidian'; -import { TaskNotification } from './reminders/notification'; +import { TaskNotification } from './reminders/Notification'; import { Cache } from './Cache'; import { Commands } from './Commands'; import { TasksEvents } from './TasksEvents'; @@ -51,6 +51,7 @@ export default class TasksPlugin extends Plugin { vault: this.app.vault, events, }); + console.log('cache', this.cache); this.inlineRenderer = new InlineRenderer({ plugin: this }); this.queryRenderer = new QueryRenderer({ plugin: this, events }); diff --git a/src/reminders/Reminder.ts b/src/reminders/Reminder.ts index bce1d29f90..7830809975 100644 --- a/src/reminders/Reminder.ts +++ b/src/reminders/Reminder.ts @@ -1,7 +1,7 @@ import type { Moment } from 'moment'; export class Reminder { - public date: Moment | null; + public date: Moment; private isAck: boolean; constructor(date: Moment) { @@ -10,10 +10,10 @@ export class Reminder { } public toString(): string { - return `${this.date?.format('YYYY-MM-DD')}`; + return `${this.date.format('YYYY-MM-DD')}`; } - public getDate(): Moment | null { + public getDate(): Moment { return this.date; } @@ -24,18 +24,4 @@ export class Reminder { public complete(): void { this.isAck = true; } - - // public getExpiredReminders(defaultTime: Time): Array { - // const now = new Date().getTime(); - // const result: Array = []; - // for (let i = 0; i < this.reminders.length; i++) { - // const reminder = this.reminders[i]!; - // if (reminder.time.getTimeInMillis(defaultTime) <= now) { - // result.push(reminder); - // } else { - // break; - // } - // } - // return result; - // } } diff --git a/src/reminders/components/Reminder.svelte b/src/reminders/components/Reminder.svelte index 6bb088ec13..cd122ced08 100644 --- a/src/reminders/components/Reminder.svelte +++ b/src/reminders/components/Reminder.svelte @@ -2,8 +2,9 @@ import type { Component } from "obsidian"; import Icon from "./Icon.svelte"; import Markdown from "./Markdown.svelte"; + import type { Task } from "../../Task"; - export let reminder: any; + export let task: Task; export let component: Component; export let onRemindMeLater: (time: any) => void; export let onDone: () => void; @@ -26,16 +27,16 @@

- + - {reminder.file} + {"task.file"} -
+
@@ -63,18 +64,18 @@ margin: 0 auto; } - .reminder-actions { + .task-actions { margin-top: 1rem; display: flex; gap: 0.5rem; } - .reminder-file { + .task-file { color: var(--text-muted); cursor: pointer; } - .reminder-file:hover { + .task-file:hover { color: var(--text-normal); text-decoration: underline; } diff --git a/src/reminders/notification.ts b/src/reminders/notification.ts index c4542c38e7..10c0a6a0d9 100644 --- a/src/reminders/notification.ts +++ b/src/reminders/notification.ts @@ -7,29 +7,23 @@ const Notification = electron.remote.Notification; // Platform.isDesktopApp, // Platform.isMobileApp, // import { Platform } from 'obsidian'; +const notificationTitle = 'Task Reminder'; //TODO constant for language localization, is there a file for these already? + export class TaskNotification { constructor(private app: App) {} //private app: App - public show() { - const reminder = { - title: 'Reminder Title', - file: 'path/to/file.md', - time: new Date(), - rowNumber: 1, - done: false, - }; - + public show(task: Task) { // if election notification is supported, aka desktop app if (Notification.isSupported()) { // Show system notification const n = new Notification({ - title: 'Obsidian Reminder', - body: "Hello World, You've got mail!", + title: notificationTitle, // todo set to constant for language localization + body: task.description, }); n.on('click', () => { console.log('Notification clicked'); n.close(); - this.showBuiltinReminder(reminder); + this.showBuiltinReminder(task); }); n.on('close', () => { console.log('Notification closed'); @@ -56,7 +50,7 @@ export class TaskNotification { } else { // Show obsidian modal notification for mobile users // Must be in app for this to trigger - this.showBuiltinReminder(reminder); + this.showBuiltinReminder(task); } } @@ -77,7 +71,7 @@ export class TaskNotification { this.reminderEvent(tasks).finally(() => { intervalTaskRunning = false; }); - }, 1000); + }, 1000000); } private async reminderEvent(tasks: Task[]): Promise { @@ -93,16 +87,22 @@ export class TaskNotification { reminderTasks = reminderTasks.filter(filter.filterFunction); }); - for (const rTask of reminderTasks) { - const now = window.moment(); - const daily = window.moment().startOf('day'); // match on moments that have no time set - // Might want to adjust this offset to be based on the setInterval + ~1 second - if (rTask.reminders[0].date?.isBetween(now.subtract(30, 'seconds'), now.add(30, 'seconds'))) { + for (const task of reminderTasks) { + const now = window.moment(); // current date + time + const alertTime = window.moment('09:00', 'hh:mm'); // time to show daily reminders + const daily = window.moment().startOf('day'); // today with a blank time + + // Check if reminder is within 30 seconds of now + // need .clone() to avoid mutating original date todo: is there a better way to do this? + if (task.reminders[0].date.isBetween(now, now.clone().add(30, 'seconds'))) { console.log('Show Notification'); - } else if (rTask.reminders[0].date?.isSame(daily)) { - // Add check for if {{9am}} - console.log('Daily Notification'); - //this.show(); + this.show(task); + // else check for daily reminder + } else if ( + task.reminders[0].date.isSame(daily) && + now.isBetween(alertTime, alertTime.clone().add(30, 'minutes')) + ) { + this.show(task); } } } @@ -131,7 +131,7 @@ class ObsidianNotificationModal extends Modal { constructor( app: App, private laters: Array, - private reminder: any, // callbacks // private onRemindMeLater: (time: any) => void, // private onDone: () => void, // private onCancel: () => void, // private onOpenFile: () => void, + private task: Task, // callbacks // private onRemindMeLater: (time: any) => void, // private onDone: () => void, // private onCancel: () => void, // private onOpenFile: () => void, ) { super(app); } @@ -141,7 +141,7 @@ class ObsidianNotificationModal extends Modal { new ReminderView({ target: contentEl, props: { - reminder: this.reminder, + task: this.task, laters: this.laters, component: this, onRemindMeLater: () => { From b51b2044165be1d98f28da1c8b79718c79291930 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Wed, 19 Apr 2023 21:57:57 -0700 Subject: [PATCH 08/93] Time support --- src/Task.ts | 1 + src/TaskSerializer/DefaultTaskSerializer.ts | 15 +++++++--- src/lib/DateTools.ts | 14 +++++++++ src/reminders/Reminder.ts | 2 +- src/reminders/notification.ts | 33 +++++++++++---------- src/ui/EditTask.svelte | 29 ++++++++++++++++-- 6 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/Task.ts b/src/Task.ts index 7a33f1b79a..f5ae5b111a 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -28,6 +28,7 @@ export enum Priority { export class TaskRegularExpressions { public static readonly dateFormat = 'YYYY-MM-DD'; + public static readonly dateTimeFormat = 'YYYY-MM-DD h:mm a'; // Matches indentation before a list marker (including > for potentially nested blockquotes or Obsidian callouts) public static readonly indentationRegex = /^([\s\t>]*)/; diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index be43bad9e6..f5f2986aa8 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -35,6 +35,7 @@ export interface DefaultTaskSerializerSymbols { doneDateRegex: RegExp; recurrenceRegex: RegExp; reminderDateRegex: RegExp; + reminderDateTimeRegex: RegExp; }; } @@ -67,8 +68,11 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, reminderDateRegex: /⏲️ *(\d{4}-\d{2}-\d{2})$/u, + reminderDateTimeRegex: /⏲️ *(\d{4}-\d{2}-\d{2}\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)$/u, //*(\d{4}-\d{2}-\d{2} \d{2}:\d{2} \s{2})$/u, }, } as const; +// ⏲️ *(\d{4}-\d{2}-\d{2}\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?) works +// ^.*⏲️ *(\d{4}-\d{2}-\d{2}\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?).*$ export class DefaultTaskSerializer implements TaskSerializer { constructor(public readonly symbols: DefaultTaskSerializerSymbols) {} @@ -147,7 +151,7 @@ export class DefaultTaskSerializer implements TaskSerializer { if (task.reminders.length <= 0 || task.reminders[0].date === null) return ''; return layout.options.shortMode ? ' ' + reminderDateSymbol - : ` ${reminderDateSymbol} ${task.reminders[0].date.format(TaskRegularExpressions.dateFormat)}`; + : ` ${reminderDateSymbol} ${task.reminders[0].date.format(TaskRegularExpressions.dateTimeFormat)}`; case 'recurrenceRule': if (!task.recurrence) return ''; return layout.options.shortMode @@ -266,9 +270,12 @@ export class DefaultTaskSerializer implements TaskSerializer { trailingTags = trailingTags.length > 0 ? [tagName, trailingTags].join(' ') : tagName; } - const reminderDateRegex = line.match(TaskFormatRegularExpressions.reminderDateRegex); - if (reminderDateRegex !== null) { - reminderDate = window.moment(reminderDateRegex[1], TaskRegularExpressions.dateFormat); + // TODO probably could do this with a single regex, but i'm trash at regex + const reminderRegex = + line.match(TaskFormatRegularExpressions.reminderDateTimeRegex) || + line.match(TaskFormatRegularExpressions.reminderDateRegex); + if (reminderRegex !== null) { + reminderDate = window.moment(reminderRegex[1], TaskRegularExpressions.dateTimeFormat); line = line.replace(TaskFormatRegularExpressions.reminderDateRegex, '').trim(); matched = true; } diff --git a/src/lib/DateTools.ts b/src/lib/DateTools.ts index f6581527f9..74dd4d9761 100644 --- a/src/lib/DateTools.ts +++ b/src/lib/DateTools.ts @@ -1,3 +1,5 @@ +import type { DurationInputArg1, DurationInputArg2 } from 'moment'; + export function compareByDate(a: moment.Moment | null, b: moment.Moment | null): -1 | 0 | 1 { if (a !== null && b === null) { return -1; @@ -21,3 +23,15 @@ export function compareByDate(a: moment.Moment | null, b: moment.Moment | null): return 0; } } + +export function isDateBetween( + a: moment.Moment | null, + b: moment.Moment, + offset: DurationInputArg1, + unit: DurationInputArg2, +) { + if (a === null || b === null || !a.isValid() || !b.isValid()) { + return false; + } + return a.isBetween(b, b.clone().add(offset, unit)); +} diff --git a/src/reminders/Reminder.ts b/src/reminders/Reminder.ts index 7830809975..d98e19181c 100644 --- a/src/reminders/Reminder.ts +++ b/src/reminders/Reminder.ts @@ -10,7 +10,7 @@ export class Reminder { } public toString(): string { - return `${this.date.format('YYYY-MM-DD')}`; + return `${this.date.format('YYYY-MM-DD h:mm a')}`; } public getDate(): Moment { diff --git a/src/reminders/notification.ts b/src/reminders/notification.ts index 10c0a6a0d9..9afa996e58 100644 --- a/src/reminders/notification.ts +++ b/src/reminders/notification.ts @@ -1,6 +1,7 @@ import { App, Modal } from 'obsidian'; import type { Task } from '../Task'; import { Query } from '../Query/Query'; +import { isDateBetween } from '../lib/DateTools'; import ReminderView from './components/Reminder.svelte'; const electron = require('electron'); const Notification = electron.remote.Notification; @@ -34,6 +35,7 @@ export class TaskNotification { n.on('action', (_: any, index: any) => { if (index === 0) { // mark task as done + // task.toggle(); return; } // const later = laters[index - 1]!; @@ -88,20 +90,16 @@ export class TaskNotification { }); for (const task of reminderTasks) { + const reminderDate = task.reminders[0].date; const now = window.moment(); // current date + time - const alertTime = window.moment('09:00', 'hh:mm'); // time to show daily reminders - const daily = window.moment().startOf('day'); // today with a blank time - // Check if reminder is within 30 seconds of now - // need .clone() to avoid mutating original date todo: is there a better way to do this? - if (task.reminders[0].date.isBetween(now, now.clone().add(30, 'seconds'))) { + if (isDateBetween(reminderDate, now, 30, 'seconds')) { console.log('Show Notification'); this.show(task); // else check for daily reminder - } else if ( - task.reminders[0].date.isSame(daily) && - now.isBetween(alertTime, alertTime.clone().add(30, 'minutes')) - ) { + } else if (isDailyReminder(reminderDate)) { + // TODO dont think this is being used since modal autos to current + // time even when left blank. Will only hit this if task created manually this.show(task); } } @@ -110,18 +108,12 @@ export class TaskNotification { private showBuiltinReminder( reminder: any, // onRemindMeLater: (time: any) => void, - // onDone: () => void, - // onCancel: () => void, - // onOpenFile: () => void, ) { new ObsidianNotificationModal( this.app, [1, 2, 3, 4, 5], reminder, // onRemindMeLater, - // onDone, - // onCancel, - // onOpenFile, ).open(); } } @@ -151,6 +143,7 @@ class ObsidianNotificationModal extends Modal { }, onDone: () => { // this.onDone(); + // call task.toggle() ? this.close(); }, onOpenFile: () => { @@ -158,6 +151,7 @@ class ObsidianNotificationModal extends Modal { this.close(); }, onMute: () => { + // remove reminder from task this.close(); }, }, @@ -171,3 +165,12 @@ class ObsidianNotificationModal extends Modal { contentEl.empty(); } } + +function isDailyReminder(date: moment.Moment) { + const daily = window.moment().startOf('day'); // todays date with a blank time + const alertTime = window.moment('09:00', 'hh:mm'); // time to show daily reminders + const now = window.moment(); // current date + time + + // time has not been set and is between alertTime and offset + return date.isSame(daily) && isDateBetween(alertTime, now, 30, 'seconds'); +} diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index cac5711c15..9c119d9172 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -132,6 +132,23 @@ return `invalid ${fieldName} date`; } + function parseTypedDateTimeForDisplay( + fieldName: 'created' | 'start' | 'scheduled' | 'due' | 'reminder' | 'done', + typedDate: string, + forwardDate: Date | undefined = undefined, + ): string { + if (!typedDate) { + return `no ${fieldName} date`; + } + const parsed = chrono.parseDate(typedDate, forwardDate, { + forwardDate: forwardDate != undefined, + }); + if (parsed !== null) { + return window.moment(parsed).format('YYYY-MM-DD h:mm a'); + } + return `invalid ${fieldName} date`; + } + /** * Like {@link parseTypedDateForDisplay} but also accounts for the 'Only future dates' setting. * @param fieldName @@ -146,6 +163,14 @@ ); } + function parseTypedDateTimeForDisplayUsingFutureDate(fieldName: 'start' | 'scheduled' | 'due' | 'done' | 'reminder', typedDate: string): string { + return parseTypedDateTimeForDisplay( + fieldName, + typedDate, + editableTask.forwardOnly ? new Date() : undefined, + ); + } + /** * Read the entered value for a date field, and return the value to be saved in the edited task. * @param typedDate - what the user has entered, such as '2023-01-23' or 'tomorrow' @@ -187,7 +212,7 @@ $: { editableTask.reminderDate = doAutocomplete(editableTask.reminderDate); - parsedReminderDate = parseTypedDateForDisplayUsingFutureDate('reminder', editableTask.reminderDate); + parsedReminderDate = parseTypedDateTimeForDisplayUsingFutureDate('reminder', editableTask.reminderDate); isReminderDateValid = !parsedReminderDate.includes('invalid'); } @@ -252,7 +277,7 @@ ? task.scheduledDate.format('YYYY-MM-DD') : '', dueDate: task.dueDate ? task.dueDate.format('YYYY-MM-DD') : '', - reminderDate: task.reminders[0] ? task.reminders[0].date!.format('YYYY-MM-DD') : '', // todo update to remove ! + reminderDate: task.reminders[0] ? task.reminders[0].date!.format('YYYY-MM-DD h:mm a') : '', // todo update to remove ! doneDate: task.doneDate ? task.doneDate.format('YYYY-MM-DD') : '', forwardOnly: true, }; From d759ba5be78cff6448994a6ecc859da9ede8ffe7 Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Wed, 19 Apr 2023 23:33:33 -0700 Subject: [PATCH 09/93] Delete .vscode directory --- .vscode/extensions.json | 5 ---- .vscode/settings.json | 65 ----------------------------------------- 2 files changed, 70 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 212e327868..0000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "DavidAnson.vscode-markdownlint" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index aa5b37f3fd..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "npm.packageManager": "yarn", - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/Thumbs.db": true, - "docs": true, - ".github": true, - "contributing": true, - "docs-snippets": true, - "node_modules": true, - "resources": true, - ".eslintrc.js": true, - ".editorconfig": true, - ".eslintignore": true, - ".gitattributes": true, - ".gitignore": true, - ".markdownlint.jsonc": true, - ".prettierrc.js": true, - "CODE_OF_CONDUCT.md": true, - "CONTRIBUTING.md": true, - "esbuild.config.mjs": true, - "jest.config.js": true, - "lefthook.yml": true, - "LICENSE": true, - "yarn.lock": true, - "versions.json": true, - "styles.css": true, - "release.sh": true, - "manifest.json": true, - "mdsnippets.json": true, - "package.json": true - }, - "hide-files.files": [ - "docs", - ".github", - "contributing", - "docs-snippets", - "node_modules", - "resources", - ".eslintrc.js", - ".editorconfig", - ".eslintignore", - ".gitattributes", - ".gitignore", - ".markdownlint.jsonc", - ".prettierrc.js", - "CODE_OF_CONDUCT.md", - "CONTRIBUTING.md", - "esbuild.config.mjs", - "jest.config.js", - "lefthook.yml", - "LICENSE", - "yarn.lock", - "versions.json", - "styles.css", - "release.sh", - "manifest.json", - "mdsnippets.json", - "package.json", - ] -} From 528fc5b64943943d633e8bea83d9758bb40c0a81 Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Wed, 19 Apr 2023 23:37:22 -0700 Subject: [PATCH 10/93] Update .gitignore --- .gitignore | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 6948fce35c..004dbb62af 100644 --- a/.gitignore +++ b/.gitignore @@ -34,12 +34,11 @@ yarn-error.log # Backup files. *.bak -.vscode/settings.json -.vscode/settings.json -src/.vscode/settings.json -.vscode/settings.json -src/.vscode/settings.json -.vscode/settings.json -src/.vscode/settings.json -.vscode/settings.json -.vscode/settings.json + +# vscode files +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets From edd333cf52c71f471e16b3079fc4adf14f871f13 Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Wed, 19 Apr 2023 23:38:25 -0700 Subject: [PATCH 11/93] Delete src/Query/.vscode directory --- src/Query/.vscode/settings.json | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/Query/.vscode/settings.json diff --git a/src/Query/.vscode/settings.json b/src/Query/.vscode/settings.json deleted file mode 100644 index e6e7934b02..0000000000 --- a/src/Query/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/Thumbs.db": true - }, - "hide-files.files": [] -} \ No newline at end of file From 71736c932d5a0222c5f23182cb2353f0c6f6e5ea Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Fri, 21 Apr 2023 10:26:05 -0700 Subject: [PATCH 12/93] todos --- src/Recurrence.ts | 2 +- src/Task.ts | 5 ++--- src/TaskLayout.ts | 3 ++- src/TaskSerializer/DefaultTaskSerializer.ts | 17 ++++++----------- src/reminders/Reminder.ts | 12 ++++++++++-- src/reminders/components/Markdown.svelte | 4 +--- src/reminders/components/Reminder.svelte | 4 ++-- src/reminders/notification.ts | 13 ++++++------- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Recurrence.ts b/src/Recurrence.ts index c7902e0ca8..e247cfc2d3 100644 --- a/src/Recurrence.ts +++ b/src/Recurrence.ts @@ -188,7 +188,7 @@ export class Recurrence { // Rounding days to handle cross daylight-savings-time recurrences. dueDate.add(Math.round(originalDifference.asDays()), 'days'); } - //TODO - copliot review this + //TODO erik-handeland - copliot review this if (this.reminderDate) { const originalDifference = window.moment.duration(this.reminderDate.diff(this.referenceDate)); diff --git a/src/Task.ts b/src/Task.ts index f5ae5b111a..44ab9e634e 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -10,7 +10,7 @@ import { renderTaskLine } from './TaskLineRenderer'; import type { TaskLineRenderDetails } from './TaskLineRenderer'; import { DateFallback } from './DateFallback'; import { compareByDate } from './lib/DateTools'; -import type { Reminder } from './reminders/Reminder'; +import { type Reminder, reminderSettings } from './reminders/Reminder'; /** * When sorting, make sure low always comes after none. This way any tasks with low will be below any exiting @@ -28,7 +28,7 @@ export enum Priority { export class TaskRegularExpressions { public static readonly dateFormat = 'YYYY-MM-DD'; - public static readonly dateTimeFormat = 'YYYY-MM-DD h:mm a'; + public static readonly dateTimeFormat = reminderSettings.dateTimeRegex; // Matches indentation before a list marker (including > for potentially nested blockquotes or Obsidian callouts) public static readonly indentationRegex = /^([\s\t>]*)/; @@ -506,7 +506,6 @@ export class Task { if (this.reminders.length !== other.reminders.length) { return false; } - // TODO check that date has changed // Compare Date fields args = ['createdDate', 'startDate', 'scheduledDate', 'dueDate', 'doneDate']; diff --git a/src/TaskLayout.ts b/src/TaskLayout.ts index 42706a16d9..85203e0acd 100644 --- a/src/TaskLayout.ts +++ b/src/TaskLayout.ts @@ -2,7 +2,6 @@ * Various rendering options for a query. */ export class LayoutOptions { - // TODO: hideReminderDate hideTaskCount: boolean = false; hideBacklinks: boolean = false; hidePriority: boolean = false; @@ -11,6 +10,7 @@ export class LayoutOptions { hideScheduledDate: boolean = false; hideDoneDate: boolean = false; hideDueDate: boolean = false; + hideReminderDate: boolean = false; hideRecurrenceRule: boolean = false; hideEditButton: boolean = false; hideUrgency: boolean = true; @@ -97,6 +97,7 @@ export class TaskLayout { newComponents = removeIf(newComponents, layoutOptions.hideScheduledDate, 'scheduledDate'); newComponents = removeIf(newComponents, layoutOptions.hideDueDate, 'dueDate'); newComponents = removeIf(newComponents, layoutOptions.hideDoneDate, 'doneDate'); + newComponents = removeIf(newComponents, layoutOptions.hideReminderDate, 'reminderDate'); if (layoutOptions.shortMode) this.specificClasses.push('tasks-layout-short-mode'); return newComponents; } diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index f5f2986aa8..92070c9bf6 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -34,8 +34,7 @@ export interface DefaultTaskSerializerSymbols { dueDateRegex: RegExp; doneDateRegex: RegExp; recurrenceRegex: RegExp; - reminderDateRegex: RegExp; - reminderDateTimeRegex: RegExp; + reminderRegex: RegExp; }; } @@ -67,8 +66,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u, doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, - reminderDateRegex: /⏲️ *(\d{4}-\d{2}-\d{2})$/u, - reminderDateTimeRegex: /⏲️ *(\d{4}-\d{2}-\d{2}\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)$/u, //*(\d{4}-\d{2}-\d{2} \d{2}:\d{2} \s{2})$/u, + reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?)$/u, }, } as const; // ⏲️ *(\d{4}-\d{2}-\d{2}\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?) works @@ -270,13 +268,10 @@ export class DefaultTaskSerializer implements TaskSerializer { trailingTags = trailingTags.length > 0 ? [tagName, trailingTags].join(' ') : tagName; } - // TODO probably could do this with a single regex, but i'm trash at regex - const reminderRegex = - line.match(TaskFormatRegularExpressions.reminderDateTimeRegex) || - line.match(TaskFormatRegularExpressions.reminderDateRegex); - if (reminderRegex !== null) { - reminderDate = window.moment(reminderRegex[1], TaskRegularExpressions.dateTimeFormat); - line = line.replace(TaskFormatRegularExpressions.reminderDateRegex, '').trim(); + const reminderMatch = line.match(TaskFormatRegularExpressions.reminderRegex); + if (reminderMatch !== null) { + reminderDate = window.moment(reminderMatch[1], TaskRegularExpressions.dateTimeFormat); + line = line.replace(TaskFormatRegularExpressions.reminderRegex, '').trim(); matched = true; } diff --git a/src/reminders/Reminder.ts b/src/reminders/Reminder.ts index d98e19181c..becb39a74e 100644 --- a/src/reminders/Reminder.ts +++ b/src/reminders/Reminder.ts @@ -1,8 +1,16 @@ import type { Moment } from 'moment'; +// not used now, might want to give user ability to switch from 12 to 24 hour time +export const reminderSettings = { + enabled: true, + dateTimeRegex: 'YYYY-MM-DD h:mm a', + dailyReminderTime: '09:00 am', + refreshInterval: 30 * 1000, // Miliseconds +}; + export class Reminder { public date: Moment; - private isAck: boolean; + private isAck: boolean; // is acknowledged, not sure if needed could just remove from array constructor(date: Moment) { this.date = date; @@ -10,7 +18,7 @@ export class Reminder { } public toString(): string { - return `${this.date.format('YYYY-MM-DD h:mm a')}`; + return `${this.date.format(reminderSettings.dateTimeRegex)}`; } public getDate(): Moment { diff --git a/src/reminders/components/Markdown.svelte b/src/reminders/components/Markdown.svelte index 8859fb51b5..0d3a8b1712 100644 --- a/src/reminders/components/Markdown.svelte +++ b/src/reminders/components/Markdown.svelte @@ -17,6 +17,4 @@ }); - - - + diff --git a/src/reminders/components/Reminder.svelte b/src/reminders/components/Reminder.svelte index cd122ced08..885cdf6883 100644 --- a/src/reminders/components/Reminder.svelte +++ b/src/reminders/components/Reminder.svelte @@ -25,10 +25,10 @@
-

+

diff --git a/src/reminders/notification.ts b/src/reminders/notification.ts index 9afa996e58..58a759c95e 100644 --- a/src/reminders/notification.ts +++ b/src/reminders/notification.ts @@ -3,12 +3,13 @@ import type { Task } from '../Task'; import { Query } from '../Query/Query'; import { isDateBetween } from '../lib/DateTools'; import ReminderView from './components/Reminder.svelte'; +import { reminderSettings } from './Reminder'; const electron = require('electron'); const Notification = electron.remote.Notification; // Platform.isDesktopApp, // Platform.isMobileApp, // import { Platform } from 'obsidian'; -const notificationTitle = 'Task Reminder'; //TODO constant for language localization, is there a file for these already? +const notificationTitle = 'Task Reminder'; // todo is there a file for language localization? export class TaskNotification { constructor(private app: App) {} //private app: App @@ -73,7 +74,7 @@ export class TaskNotification { this.reminderEvent(tasks).finally(() => { intervalTaskRunning = false; }); - }, 1000000); + }, reminderSettings.refreshInterval); } private async reminderEvent(tasks: Task[]): Promise { @@ -92,14 +93,12 @@ export class TaskNotification { for (const task of reminderTasks) { const reminderDate = task.reminders[0].date; const now = window.moment(); // current date + time - // Check if reminder is within 30 seconds of now - if (isDateBetween(reminderDate, now, 30, 'seconds')) { + // Check if reminder will occur inbetween now and next refresh interval, + 1 to account for rounding + if (isDateBetween(reminderDate, now, reminderSettings.refreshInterval + 1, 'milliseconds')) { console.log('Show Notification'); this.show(task); // else check for daily reminder } else if (isDailyReminder(reminderDate)) { - // TODO dont think this is being used since modal autos to current - // time even when left blank. Will only hit this if task created manually this.show(task); } } @@ -168,7 +167,7 @@ class ObsidianNotificationModal extends Modal { function isDailyReminder(date: moment.Moment) { const daily = window.moment().startOf('day'); // todays date with a blank time - const alertTime = window.moment('09:00', 'hh:mm'); // time to show daily reminders + const alertTime = window.moment(reminderSettings.dailyReminderTime, reminderSettings.dateTimeRegex); const now = window.moment(); // current date + time // time has not been set and is between alertTime and offset From a4c194564824f2541fd53b2e561a02076c63d194 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Fri, 21 Apr 2023 11:09:42 -0700 Subject: [PATCH 13/93] clean up --- src/Query/Filter/ReminderDateField.ts | 3 --- src/Recurrence.ts | 2 +- src/Task.ts | 21 +++++++------------ src/TaskSerializer/DefaultTaskSerializer.ts | 8 +++---- src/main.ts | 2 -- src/reminders/Reminder.ts | 7 ++++--- src/reminders/components/Reminder.svelte | 6 +++--- src/reminders/notification.ts | 18 +++++++--------- src/ui/EditTask.svelte | 2 +- .../CustomMatchersForTaskSerializer.ts | 2 +- 10 files changed, 29 insertions(+), 42 deletions(-) diff --git a/src/Query/Filter/ReminderDateField.ts b/src/Query/Filter/ReminderDateField.ts index 1d45f86d90..cc0129517f 100644 --- a/src/Query/Filter/ReminderDateField.ts +++ b/src/Query/Filter/ReminderDateField.ts @@ -2,9 +2,6 @@ import type { Moment } from 'moment'; import type { Task } from '../../Task'; import { DateField } from './DateField'; -/** - * Support the 'due' search instruction. - */ export class ReminderDateField extends DateField { public fieldName(): string { return 'reminder'; diff --git a/src/Recurrence.ts b/src/Recurrence.ts index e247cfc2d3..7e40a2be44 100644 --- a/src/Recurrence.ts +++ b/src/Recurrence.ts @@ -188,7 +188,7 @@ export class Recurrence { // Rounding days to handle cross daylight-savings-time recurrences. dueDate.add(Math.round(originalDifference.asDays()), 'days'); } - //TODO erik-handeland - copliot review this + //TODO erik-handeland: - copliot review this if (this.reminderDate) { const originalDifference = window.moment.duration(this.reminderDate.diff(this.referenceDate)); diff --git a/src/Task.ts b/src/Task.ts index 44ab9e634e..af3a1473c3 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -103,8 +103,6 @@ export class Task { public readonly taskLocation: TaskLocation; public readonly tags: string[]; - // setup as an array of objects to allow for multiple reminders in the future - // i.e remind on due or start date, currently only used for an explicit Reminder public readonly reminders: Reminder[]; public readonly priority: Priority; @@ -506,6 +504,14 @@ export class Task { if (this.reminders.length !== other.reminders.length) { return false; } + // reminders are the same only if the values are in the same order + if ( + !this.reminders.every(function (element, index) { + return element === other.reminders[index]; + }) + ) { + return false; + } // Compare Date fields args = ['createdDate', 'startDate', 'scheduledDate', 'dueDate', 'doneDate']; @@ -540,15 +546,4 @@ export class Task { public static extractHashtags(description: string): string[] { return description.match(TaskRegularExpressions.hashTags)?.map((tag) => tag.trim()) ?? []; } - - /** - * Returns an array of reminders found in string - * - * @param description A task description that may contain reminders - * - * @returns An array of reminders found in the string - */ - public static extractReminders(description: string): string[] { - return description.match(TaskRegularExpressions.hashTags)?.map((tag) => tag.trim()) ?? []; - } } diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index 92070c9bf6..bbcb4c0f09 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -69,8 +69,6 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?)$/u, }, } as const; -// ⏲️ *(\d{4}-\d{2}-\d{2}\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?) works -// ^.*⏲️ *(\d{4}-\d{2}-\d{2}\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?).*$ export class DefaultTaskSerializer implements TaskSerializer { constructor(public readonly symbols: DefaultTaskSerializerSymbols) {} @@ -146,10 +144,12 @@ export class DefaultTaskSerializer implements TaskSerializer { ? ' ' + dueDateSymbol : ` ${dueDateSymbol} ${task.dueDate.format(TaskRegularExpressions.dateFormat)}`; case 'reminderDate': - if (task.reminders.length <= 0 || task.reminders[0].date === null) return ''; + if (task.reminders.length <= 0 || task.reminders[0].getDate() === null) return ''; return layout.options.shortMode ? ' ' + reminderDateSymbol - : ` ${reminderDateSymbol} ${task.reminders[0].date.format(TaskRegularExpressions.dateTimeFormat)}`; + : ` ${reminderDateSymbol} ${task.reminders[0] + .getDate() + .format(TaskRegularExpressions.dateTimeFormat)}`; case 'recurrenceRule': if (!task.recurrence) return ''; return layout.options.shortMode diff --git a/src/main.ts b/src/main.ts index 2644f1d531..8e984b3af7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,7 +31,6 @@ export default class TasksPlugin extends Plugin { logging.registerConsoleLogger(); console.log('loading plugin "tasks"'); this.taskNotification = new TaskNotification(this.app); - // this.taskNotification.show(); await this.loadSettings(); this.addSettingTab(new SettingsTab({ plugin: this })); @@ -51,7 +50,6 @@ export default class TasksPlugin extends Plugin { vault: this.app.vault, events, }); - console.log('cache', this.cache); this.inlineRenderer = new InlineRenderer({ plugin: this }); this.queryRenderer = new QueryRenderer({ plugin: this, events }); diff --git a/src/reminders/Reminder.ts b/src/reminders/Reminder.ts index becb39a74e..9c27c07466 100644 --- a/src/reminders/Reminder.ts +++ b/src/reminders/Reminder.ts @@ -1,6 +1,6 @@ import type { Moment } from 'moment'; -// not used now, might want to give user ability to switch from 12 to 24 hour time +//TODO erik-handeland: not used now, might want to give user ability to switch from 12 to 24 hour time export const reminderSettings = { enabled: true, dateTimeRegex: 'YYYY-MM-DD h:mm a', @@ -9,7 +9,7 @@ export const reminderSettings = { }; export class Reminder { - public date: Moment; + private date: Moment; private isAck: boolean; // is acknowledged, not sure if needed could just remove from array constructor(date: Moment) { @@ -25,7 +25,8 @@ export class Reminder { return this.date; } - public getIsCompleted(): boolean { + // could probably remove this and just check if reminder is in array + public isComplete(): boolean { return this.isAck; } diff --git a/src/reminders/components/Reminder.svelte b/src/reminders/components/Reminder.svelte index 885cdf6883..9b6690594d 100644 --- a/src/reminders/components/Reminder.svelte +++ b/src/reminders/components/Reminder.svelte @@ -9,7 +9,7 @@ export let onRemindMeLater: (time: any) => void; export let onDone: () => void; export let onOpenFile: () => void; - export let onMute: () => void; + export let onIgnore: () => void; // Do not set initial value so that svelte can render the placeholder `Remind Me Later`. let selectedIndex: number; @@ -40,8 +40,8 @@ - { intervalTaskRunning = false; }); - }, reminderSettings.refreshInterval); + }, reminderSettings.refreshIntervalMilliseconds); } private async reminderEvent(tasks: Task[]): Promise { diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index d1bf667106..1d308564ef 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -7,7 +7,7 @@ import { Status } from '../Status'; import { Priority, Task } from '../Task'; import { doAutocomplete } from '../DateAbbreviations'; - import { ReminderList } from '../reminders/Reminder'; + import { ReminderList } from '../Reminders/Reminder'; // These exported variables are passed in as props by TaskModal.onOpen(): export let task: Task; diff --git a/tests/Reminder/Notifications.test.ts b/tests/Reminder/Notifications.test.ts index ebfe3e0296..1a8f116cf8 100644 --- a/tests/Reminder/Notifications.test.ts +++ b/tests/Reminder/Notifications.test.ts @@ -3,8 +3,8 @@ */ jest.mock('obsidian'); import moment from 'moment'; -import { Reminder, ReminderType, parseMoment } from '../../src/reminders/Reminder'; -//import { TaskNotification } from '../../src/reminders/Notification'; +import { Reminder, ReminderType, parseMoment } from '../../src/Reminders/Reminder'; +//import { TaskNotification } from '../../src/Reminders/Notification'; window.moment = moment; describe('Notifications ', () => { @@ -29,7 +29,7 @@ describe('Notifications ', () => { // /* Fails: TypeError: Class extends value undefined is not a constructor or null // > 145 | class ObsidianNotificationModal extends Modal { // | ^ - // at Object. (src/reminders/Notification.ts:145:41) + // at Object. (src/Reminders/Notification.ts:145:41) // at Object. (tests/Reminder/Notifications.test.ts:8:1) // */ diff --git a/tests/Reminder/Reminder.test.ts b/tests/Reminder/Reminder.test.ts index df952cb86c..853416dc3b 100644 --- a/tests/Reminder/Reminder.test.ts +++ b/tests/Reminder/Reminder.test.ts @@ -3,7 +3,7 @@ */ jest.mock('obsidian'); import moment from 'moment'; -import { Reminder, ReminderType, parseMoment } from '../../src/reminders/Reminder'; +import { Reminder, ReminderType, parseMoment } from '../../src/Reminders/Reminder'; window.moment = moment; diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index b1389232e0..1a5f031e95 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -8,7 +8,7 @@ import { DefaultTaskSerializer } from '../../src/TaskSerializer'; import { RecurrenceBuilder } from '../TestingTools/RecurrenceBuilder'; import { DEFAULT_SYMBOLS, type DefaultTaskSerializerSymbols } from '../../src/TaskSerializer/DefaultTaskSerializer'; import { TaskBuilder } from '../TestingTools/TaskBuilder'; -import { ReminderList } from '../../src/reminders/Reminder'; +import { ReminderList } from '../../src/Reminders/Reminder'; jest.mock('obsidian'); window.moment = moment; diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index 1166e8f8ef..bd05d845c8 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -6,7 +6,7 @@ import type { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; import { StatusConfiguration, StatusType } from '../../src/StatusConfiguration'; import { TaskLocation } from '../../src/TaskLocation'; -import { Reminder, ReminderList, parseDateTime } from '../../src/reminders/Reminder'; +import { Reminder, ReminderList, parseDateTime } from '../../src/Reminders/Reminder'; /** * A fluent class for creating tasks for tests. From a21c7b15eed3dab2cf5ce9227cf265bb26269b7d Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Tue, 2 May 2023 21:30:59 -0700 Subject: [PATCH 36/93] Rename notification.ts to Notification.ts --- src/reminders/{notification.ts => Notification.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/reminders/{notification.ts => Notification.ts} (100%) diff --git a/src/reminders/notification.ts b/src/reminders/Notification.ts similarity index 100% rename from src/reminders/notification.ts rename to src/reminders/Notification.ts From 2e5ea712c27e2da97edf627d34d1694f06b02ca3 Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Tue, 2 May 2023 21:33:13 -0700 Subject: [PATCH 37/93] Rename Notification.ts to Notification.ts --- src/{reminders => Reminders}/Notification.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{reminders => Reminders}/Notification.ts (100%) diff --git a/src/reminders/Notification.ts b/src/Reminders/Notification.ts similarity index 100% rename from src/reminders/Notification.ts rename to src/Reminders/Notification.ts From 187d6e7bd1f7e5bffd7bf52c714de844d46bb3ca Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Tue, 2 May 2023 21:33:49 -0700 Subject: [PATCH 38/93] Rename Reminder.ts to Reminder.ts --- src/{reminders => Reminders}/Reminder.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{reminders => Reminders}/Reminder.ts (100%) diff --git a/src/reminders/Reminder.ts b/src/Reminders/Reminder.ts similarity index 100% rename from src/reminders/Reminder.ts rename to src/Reminders/Reminder.ts From e770b951ae42aad6f23387346f5b305fdaac2940 Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Tue, 2 May 2023 21:35:44 -0700 Subject: [PATCH 39/93] Rename Icon.svelte to Icon.svelte --- src/{reminders/components => Reminders/Components}/Icon.svelte | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{reminders/components => Reminders/Components}/Icon.svelte (100%) diff --git a/src/reminders/components/Icon.svelte b/src/Reminders/Components/Icon.svelte similarity index 100% rename from src/reminders/components/Icon.svelte rename to src/Reminders/Components/Icon.svelte From 378638fbf67cdcb884ace3606343d7d346429994 Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Tue, 2 May 2023 21:36:16 -0700 Subject: [PATCH 40/93] Rename Markdown.svelte to Markdown.svelte --- .../components => Reminders/Components}/Markdown.svelte | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{reminders/components => Reminders/Components}/Markdown.svelte (100%) diff --git a/src/reminders/components/Markdown.svelte b/src/Reminders/Components/Markdown.svelte similarity index 100% rename from src/reminders/components/Markdown.svelte rename to src/Reminders/Components/Markdown.svelte From 3731393b88dcd173ebac31b9d084b344de7f181d Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Tue, 2 May 2023 21:36:39 -0700 Subject: [PATCH 41/93] Rename Reminder.svelte to Reminder.svelte --- .../components => Reminders/Components}/Reminder.svelte | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{reminders/components => Reminders/Components}/Reminder.svelte (100%) diff --git a/src/reminders/components/Reminder.svelte b/src/Reminders/Components/Reminder.svelte similarity index 100% rename from src/reminders/components/Reminder.svelte rename to src/Reminders/Components/Reminder.svelte From 10fa48a6ede470420ac6b82afeb08e036aca58e7 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Tue, 2 May 2023 21:38:51 -0700 Subject: [PATCH 42/93] Rename folders and remove main.css --- .gitignore | 1 + main.css | 27 --------------------------- src/Reminders/Notification.ts | 2 +- 3 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 main.css diff --git a/.gitignore b/.gitignore index 004dbb62af..7533d8a623 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ yarn-error.log !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets +main.css diff --git a/main.css b/main.css deleted file mode 100644 index 100489faf6..0000000000 --- a/main.css +++ /dev/null @@ -1,27 +0,0 @@ -/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/Reminders/components/Icon.esbuild-svelte-fake-css */ -.icon.svelte-1gcidq0 { - vertical-align: middle; -} - -/* fakecss:/Users/erikhandeland/Documents/Developer/obsidian-tasks/src/Reminders/components/Reminder.esbuild-svelte-fake-css */ -main.svelte-1fdazlx { - padding: 1em; - margin: 0 auto; -} -.task-actions.svelte-1fdazlx { - margin-top: 1rem; - display: flex; - gap: 0.5rem; -} -.task-file.svelte-1fdazlx { - color: var(--text-muted); - cursor: pointer; -} -.task-file.svelte-1fdazlx:hover { - color: var(--text-normal); - text-decoration: underline; -} -.later-select.svelte-1fdazlx { - font-size: 14px; -} -/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiSWNvbi5zdmVsdGUiLCAiUmVtaW5kZXIuc3ZlbHRlIl0sCiAgInNvdXJjZXNDb250ZW50IjogW251bGwsIG51bGxdLAogICJtYXBwaW5ncyI6ICI7QUFjSTtBQUNJOzs7O0FDMENOO0FBQ0U7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7QUFDQTs7QUFHRjtBQUNFO0FBQ0E7O0FBR0Y7QUFDRTtBQUNBOztBQUdGO0FBQ0U7OyIsCiAgIm5hbWVzIjogW10KfQo= */ diff --git a/src/Reminders/Notification.ts b/src/Reminders/Notification.ts index f26c43aaaf..3de4801a26 100644 --- a/src/Reminders/Notification.ts +++ b/src/Reminders/Notification.ts @@ -6,7 +6,7 @@ import { Query } from '../Query/Query'; import { sameDateTime } from '../lib/DateTools'; import type { Cache } from '../Cache'; import { getTaskLineAndFile } from '../File'; -import ReminderView from './components/Reminder.svelte'; +import ReminderView from './Components/Reminder.svelte'; import { Reminder, ReminderType } from './Reminder'; export class TaskNotification { From 807a58472ab3e74dcf11c71967ff7b7be194c9c2 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Sat, 6 May 2023 11:16:34 -0700 Subject: [PATCH 43/93] Fixed testing issue Fixes issue where isRemindersSame wasn't updated for new reminder data structure --- src/Reminders/Reminder.ts | 2 +- src/lib/DateTools.ts | 4 ++-- tests/Task.test.ts | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index c99fa6983f..123d2c0eef 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -24,7 +24,7 @@ export class ReminderList { return this.reminders.map((reminder) => `${reminder.toString()}`).join(', '); } - // TODO only used in ReminderDateField need way to deal with modal multiple reminders + // TODO only used in ReminderDateField & Edit modal need way to deal with modal multiple reminders public peek(): Moment | null { if (this.reminders.length === 0) { return null; diff --git a/src/lib/DateTools.ts b/src/lib/DateTools.ts index a25d34b3a1..ac3c6f917e 100644 --- a/src/lib/DateTools.ts +++ b/src/lib/DateTools.ts @@ -38,8 +38,8 @@ export function isRemindersSame(a: ReminderList | null, b: ReminderList | null) return false; } - const sortedA = a.reminders.map((reminder) => reminder.valueOf()).sort(); - const sortedB = b.reminders.map((reminder) => reminder.valueOf()).sort(); + const sortedA = a.reminders.map((reminder) => reminder.time.valueOf()).sort(); + const sortedB = b.reminders.map((reminder) => reminder.time.valueOf()).sort(); for (let i = 0; i < sortedA.length; i++) { if (sortedA[i] !== sortedB[i]) { diff --git a/tests/Task.test.ts b/tests/Task.test.ts index 1ae1f08d15..d6c6ccbde7 100644 --- a/tests/Task.test.ts +++ b/tests/Task.test.ts @@ -1213,9 +1213,11 @@ describe('identicalTo', () => { }); it('should check reminders', () => { - const lhs = new TaskBuilder().reminders([]); - expect(lhs).toBeIdenticalTo(new TaskBuilder().reminders([])); + const lhs = new TaskBuilder().reminders(['2023-03-07 09:25 am']); + expect(lhs).toBeIdenticalTo(new TaskBuilder().reminders(['2023-03-07 09:25 am'])); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders([])); expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders(['2023-03-07'])); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders(['2023-03-07 09:27 am'])); }); }); From d08fdefcc20fce027c5b1000a16149a7dfb08df9 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Sat, 6 May 2023 12:06:44 -0700 Subject: [PATCH 44/93] fixed regex and added tests from claremacrae --- src/TaskSerializer/DefaultTaskSerializer.ts | 3 +- tests/Reminder/Reminder.test.ts | 74 +++++++++++++++++++-- tests/TestingTools/TaskBuilder.ts | 16 ++--- 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index ceaadabd25..d5492e976d 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -67,8 +67,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u, doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, - reminderRegex: /⏲️ *((\d{4}-\d{2}-\d{2}(?: \d{1,2}:\d{2} (?:am|pm|PM|AM))?\s*(?:,\s*)?)+)$/u, // multiple dates - // reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?)/u, // just one date + reminderRegex: /⏲️ *((\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?\s*(?:,\s*)?)+)$/u, }, } as const; diff --git a/tests/Reminder/Reminder.test.ts b/tests/Reminder/Reminder.test.ts index 853416dc3b..3f5bfd3d89 100644 --- a/tests/Reminder/Reminder.test.ts +++ b/tests/Reminder/Reminder.test.ts @@ -3,20 +3,80 @@ */ jest.mock('obsidian'); import moment from 'moment'; -import { Reminder, ReminderType, parseMoment } from '../../src/Reminders/Reminder'; +import { TIME_FORMATS, getSettings, resetSettings, updateSettings } from '../../src/Config/Settings'; +import { Reminder, ReminderType, parseDateTime, parseMoment } from '../../src/Reminders/Reminder'; +import { fromLine } from '../TestHelpers'; window.moment = moment; -describe('Reminder ', () => { - it('testing parsing Moment() datetime', () => { - const reminder = moment('2023-04-30 11:44 am'); +function setDateTimeFormat(dateTimeFormat: string) { + const settings = getSettings().reminderSettings; + settings.dateTimeFormat = dateTimeFormat; + updateSettings({ reminderSettings: settings }); +} - expect(parseMoment(reminder)).toStrictEqual(new Reminder(moment('2023-04-30 11:44 am'), ReminderType.DateTime)); +function checkParsedDateTime(input: string, output: string) { + const dateTime = parseDateTime(input); + expect(dateTime).not.toBeNull(); + expect(dateTime!.toString()).toEqual(output); +} + +describe('should parse Moment() dates & times as reminder: ', () => { + // todo mimic test below + it('test Moment() datetime', () => { + const reminder = moment('2023-04-30 11:44 am', 'YYYY-MM-DD h:mm a'); + expect(parseMoment(reminder)).toStrictEqual( + new Reminder(moment('2023-04-30 11:44 am', 'YYYY-MM-DD h:mm a'), ReminderType.DateTime), + ); }); - it('testing parsing Moment() date', () => { + it('test Moment() date', () => { const reminder = moment('2023-04-30'); - expect(parseMoment(reminder)).toStrictEqual(new Reminder(moment('2023-04-30'), ReminderType.Date)); }); }); + +describe('should parse string dates & times as reminder: ', () => { + afterEach(function () { + resetSettings(); + }); + + it('test 12-hour format', () => { + setDateTimeFormat(TIME_FORMATS.twelveHour); + + checkParsedDateTime('2023-01-15', '2023-01-15'); + checkParsedDateTime('2024-01-15 13:45', '2024-01-15 1:45 pm'); // 12-hour format reads 24-hour OK + checkParsedDateTime('2023-01-15 1:45 am', '2023-01-15 1:45 am'); + checkParsedDateTime('12/13/2019', 'Invalid date'); + }); + + it('test 24-hour format', () => { + setDateTimeFormat(TIME_FORMATS.twentyFourHour); + + checkParsedDateTime('2023-01-15', '2023-01-15'); + checkParsedDateTime('2023-01-15 13:45', '2023-01-15 13:45'); + checkParsedDateTime('2023-01-15 1:45 pm', '2023-01-15 01:45'); // the pm & leading 0 are ignored + }); +}); + +describe('should parse task strings: ', () => { + afterEach(function () { + resetSettings(); + }); + + it('valid task - in 12-hour format', () => { + setDateTimeFormat(TIME_FORMATS.twelveHour); + + const line = '- [ ] #task Reminder at 13:57 ⏲️ 2023-05-03 1:57 pm'; + const task = fromLine({ line: line }); + expect(task.reminders).not.toBeNull(); + }); + + it('valid task - in 24-hour format', () => { + setDateTimeFormat(TIME_FORMATS.twentyFourHour); + + const line = '- [ ] #task Reminder at 13:57 ⏲️ 2023-05-03 13:57'; + const task = fromLine({ line: line }); + expect(task.reminders).not.toBeNull(); + }); +}); diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index bd05d845c8..a4ed15471d 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -198,16 +198,16 @@ export class TaskBuilder { } public reminders(reminders: string[]): TaskBuilder { - if (reminders.length > 0) { - const parsedReminders = new ReminderList(null); - for (const reminder of reminders) { - const reminderDate = TaskBuilder.parseReminder(reminder); - if (reminderDate) { - parsedReminders.reminders.push(reminderDate); - } + const parsedReminders = new ReminderList(null); + for (const reminder of reminders) { + const reminderDate = TaskBuilder.parseReminder(reminder); + if (reminderDate) { + parsedReminders.reminders.push(reminderDate); + } else { + throw new Error(`TaskBuilder.parseReminder() was unable to parse: ${reminder}`); } - this._reminders = parsedReminders; } + this._reminders = parsedReminders; return this; } From 24af196c48742ed3b3224c39f3f24fb79a25353f Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Sat, 6 May 2023 16:19:47 -0700 Subject: [PATCH 45/93] more serialize tests --- tests/Reminder/Reminder.test.ts | 31 +++-- .../DefaultTaskSerializer.test.ts | 120 +++++++++++++++--- tests/TestHelpers.ts | 7 + 3 files changed, 125 insertions(+), 33 deletions(-) diff --git a/tests/Reminder/Reminder.test.ts b/tests/Reminder/Reminder.test.ts index 3f5bfd3d89..0f485cf207 100644 --- a/tests/Reminder/Reminder.test.ts +++ b/tests/Reminder/Reminder.test.ts @@ -3,18 +3,12 @@ */ jest.mock('obsidian'); import moment from 'moment'; -import { TIME_FORMATS, getSettings, resetSettings, updateSettings } from '../../src/Config/Settings'; +import { TIME_FORMATS, resetSettings } from '../../src/Config/Settings'; import { Reminder, ReminderType, parseDateTime, parseMoment } from '../../src/Reminders/Reminder'; -import { fromLine } from '../TestHelpers'; +import { fromLine, setDateTimeFormat } from '../TestHelpers'; window.moment = moment; -function setDateTimeFormat(dateTimeFormat: string) { - const settings = getSettings().reminderSettings; - settings.dateTimeFormat = dateTimeFormat; - updateSettings({ reminderSettings: settings }); -} - function checkParsedDateTime(input: string, output: string) { const dateTime = parseDateTime(input); expect(dateTime).not.toBeNull(); @@ -22,11 +16,17 @@ function checkParsedDateTime(input: string, output: string) { } describe('should parse Moment() dates & times as reminder: ', () => { - // todo mimic test below - it('test Moment() datetime', () => { - const reminder = moment('2023-04-30 11:44 am', 'YYYY-MM-DD h:mm a'); + it('test Moment() datetime 12 hr', () => { + const reminder = moment('2023-04-30 11:44 am', TIME_FORMATS.twelveHour); + expect(parseMoment(reminder)).toStrictEqual( + new Reminder(moment('2023-04-30 11:44 am', TIME_FORMATS.twelveHour), ReminderType.DateTime), + ); + }); + + it('test Moment() datetime 24hr', () => { + const reminder = moment('2023-04-30 13:45', TIME_FORMATS.twentyFourHour); expect(parseMoment(reminder)).toStrictEqual( - new Reminder(moment('2023-04-30 11:44 am', 'YYYY-MM-DD h:mm a'), ReminderType.DateTime), + new Reminder(moment('2023-04-30 13:45', TIME_FORMATS.twentyFourHour), ReminderType.DateTime), ); }); @@ -36,7 +36,7 @@ describe('should parse Moment() dates & times as reminder: ', () => { }); }); -describe('should parse string dates & times as reminder: ', () => { +describe('should parse dates & times string as reminder: ', () => { afterEach(function () { resetSettings(); }); @@ -45,9 +45,12 @@ describe('should parse string dates & times as reminder: ', () => { setDateTimeFormat(TIME_FORMATS.twelveHour); checkParsedDateTime('2023-01-15', '2023-01-15'); - checkParsedDateTime('2024-01-15 13:45', '2024-01-15 1:45 pm'); // 12-hour format reads 24-hour OK checkParsedDateTime('2023-01-15 1:45 am', '2023-01-15 1:45 am'); checkParsedDateTime('12/13/2019', 'Invalid date'); + checkParsedDateTime('2024-01-15 13:45', '2024-01-15 1:45 pm'); // 12-hour format reads 24-hour OK + checkParsedDateTime('2023-01-15 1:45', '2023-01-15 1:45 am'); // forgeting am/pm defaults to am + checkParsedDateTime('2023-01-15 1:45 p', '2023-01-15 1:45 pm'); // can handle partial am/pm + checkParsedDateTime('2023-01-15 01:45 pm', '2023-01-15 1:45 pm'); // can handle leading zero }); it('test 24-hour format', () => { diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index 1a5f031e95..9eed6088a2 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -3,12 +3,13 @@ */ import moment from 'moment'; import { Priority } from '../../src/Task'; -import { type Settings, getSettings } from '../../src/Config/Settings'; +import { type Settings, TIME_FORMATS, resetSettings } from '../../src/Config/Settings'; import { DefaultTaskSerializer } from '../../src/TaskSerializer'; import { RecurrenceBuilder } from '../TestingTools/RecurrenceBuilder'; import { DEFAULT_SYMBOLS, type DefaultTaskSerializerSymbols } from '../../src/TaskSerializer/DefaultTaskSerializer'; import { TaskBuilder } from '../TestingTools/TaskBuilder'; import { ReminderList } from '../../src/Reminders/Reminder'; +import { setDateTimeFormat } from '../TestHelpers'; jest.mock('obsidian'); window.moment = moment; @@ -24,7 +25,6 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const taskSerializer = new DefaultTaskSerializer(symbols); const serialize = taskSerializer.serialize.bind(taskSerializer); const deserialize = taskSerializer.deserialize.bind(taskSerializer); - const { reminderSettings } = getSettings(); const { startDateSymbol, createdDateSymbol, @@ -79,27 +79,63 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ description, }); }); + }); - it('should parse a single reminder date', () => { - const taskDetails = deserialize(`${reminderDateSymbol} 2021-06-20`); - expect(taskDetails).toMatchTaskDetails({ - reminders: new ReminderList([moment('2021-06-20', reminderSettings.dateTimeFormat)]), + describe('deserialize reminders', () => { + afterEach(function () { + resetSettings(); + }); + + it('should parse a single 12h reminder', () => { + setDateTimeFormat(TIME_FORMATS.twelveHour); + const times = [ + '2021-06-20 1:45 pm', + '2021-06-20 1:45 am', + '2021-06-20 01:45 PM', + '2021-06-20 01:45 AM', + '2021-06-20', + ]; + times.forEach((time) => { + const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); + expect(taskDetails).toMatchTaskDetails({ + reminders: new ReminderList([moment(time, TIME_FORMATS.twelveHour)]), + }); }); }); - it('should parse a single reminder date time', () => { - const taskDetails = deserialize(`${reminderDateSymbol} 2021-06-20 10:00 am`); + it('should parse a single 24h reminder', () => { + setDateTimeFormat(TIME_FORMATS.twentyFourHour); + const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; + times.forEach((time) => { + const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); + expect(taskDetails).toMatchTaskDetails({ + reminders: new ReminderList([moment(time, TIME_FORMATS.twentyFourHour)]), + }); + }); + }); + + it('should parse multiple 12h reminders', () => { + setDateTimeFormat(TIME_FORMATS.twelveHour); + const taskDetails = deserialize( + `${reminderDateSymbol} 2021-06-20 10:00 am, 2021-06-21, 2021-07-19 3:00 pm`, + ); expect(taskDetails).toMatchTaskDetails({ - reminders: new ReminderList([moment('2021-06-20 10:00 am', reminderSettings.dateTimeFormat)]), + reminders: new ReminderList([ + moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour), + moment('2021-06-21', TIME_FORMATS.twelveHour), + moment('2021-07-19 3:00 pm', TIME_FORMATS.twelveHour), + ]), }); }); - it('should parse a multiple reminder string', () => { - const taskDetails = deserialize(`${reminderDateSymbol} 2021-06-20 10:00 am, 2021-06-21`); + it('should parse multiple 24h reminders', () => { + setDateTimeFormat(TIME_FORMATS.twentyFourHour); + const taskDetails = deserialize(`${reminderDateSymbol} 2021-06-20 10:00, 2021-06-21, 2021-07-19 16:00`); expect(taskDetails).toMatchTaskDetails({ reminders: new ReminderList([ - moment('2021-06-20 10:00 am', reminderSettings.dateTimeFormat), - moment('2021-06-21', reminderSettings.dateTimeFormat), + moment('2021-06-20 10:00', TIME_FORMATS.twentyFourHour), + moment('2021-06-21', TIME_FORMATS.twentyFourHour), + moment('2021-07-19 16:00', TIME_FORMATS.twentyFourHour), ]), }); }); @@ -156,17 +192,63 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const serialized = serialize(new TaskBuilder().reminders(['2021-06-20']).description('').build()); expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20`); }); + }); + + describe('serialize reminders', () => { + afterEach(function () { + resetSettings(); + }); + + it('should serialize a single 12h reminder', () => { + setDateTimeFormat(TIME_FORMATS.twelveHour); + const times = ['2021-06-20 1:45 pm', '2021-06-20 1:45 am', '2021-06-20']; - it('should serialize a single reminder date time', () => { - const serialized = serialize(new TaskBuilder().reminders(['2021-06-20 5:00 pm']).description('').build()); - expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 5:00 pm`); + times.forEach((time) => { + const serialized = serialize(new TaskBuilder().reminders([time]).description('').build()); + expect(serialized).toEqual(` ${reminderDateSymbol} ${time}`); + }); + }); + + it('should serialize malformed 12h reminders', () => { + setDateTimeFormat(TIME_FORMATS.twelveHour); + const times = ['2021-06-20 01:45 pm', '2021-06-20 1:45 PM', '2021-06-20 01:45 PM', '2021-06-20 1:45 p']; + + times.forEach((time) => { + const serialized = serialize(new TaskBuilder().reminders([time]).description('').build()); + expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 1:45 pm`); + }); + }); + + it('should serialize a single 24h reminder', () => { + setDateTimeFormat(TIME_FORMATS.twentyFourHour); + const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; + + times.forEach((time) => { + const serialized = serialize(new TaskBuilder().reminders([time]).description('').build()); + expect(serialized).toEqual(` ${reminderDateSymbol} ${time}`); + }); + }); + + it('should serialize a multiple 12h reminders', () => { + setDateTimeFormat(TIME_FORMATS.twelveHour); + const serialized = serialize( + new TaskBuilder() + .reminders(['2021-06-20 5:00 pm', '2021-06-21', '2021-06-20 10:00 am']) + .description('') + .build(), + ); + expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 5:00 pm, 2021-06-21, 2021-06-20 10:00 am`); }); - it('should serialize a multiple reminder dates', () => { + it('should serialize a multiple 24h reminders', () => { + setDateTimeFormat(TIME_FORMATS.twentyFourHour); const serialized = serialize( - new TaskBuilder().reminders(['2021-06-20 5:00 pm', '2021-06-21']).description('').build(), + new TaskBuilder() + .reminders(['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']) + .description('') + .build(), ); - expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 5:00 pm, 2021-06-21`); + expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 13:45, 2021-06-20 01:45, 2021-06-20`); }); }); }); diff --git a/tests/TestHelpers.ts b/tests/TestHelpers.ts index bbe4ebf720..5f2a4216c6 100644 --- a/tests/TestHelpers.ts +++ b/tests/TestHelpers.ts @@ -1,3 +1,4 @@ +import { getSettings, updateSettings } from '../src/Config/Settings'; import { Task } from '../src/Task'; import { TaskLocation } from '../src/TaskLocation'; @@ -32,3 +33,9 @@ export function createTasksFromMarkdown(tasksAsMarkdown: string, path: string, p } return tasks; } + +export function setDateTimeFormat(dateTimeFormat: string) { + const settings = getSettings().reminderSettings; + settings.dateTimeFormat = dateTimeFormat; + updateSettings({ reminderSettings: settings }); +} From a227f0b9baf5e19a7947534eb6ff494d5088798f Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Sat, 6 May 2023 18:17:13 -0700 Subject: [PATCH 46/93] query reminder tests --- tests/Query.test.ts | 76 +++++++++++++++++++- tests/Query/Filter/ReminderDateField.test.ts | 59 +++++++++++++++ 2 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 tests/Query/Filter/ReminderDateField.test.ts diff --git a/tests/Query.test.ts b/tests/Query.test.ts index 05d4ddf5f5..e4bbd22c30 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -39,9 +39,6 @@ describe('Query parsing', () => { 'due in 2021-12-27 2021-12-29', 'due on 2021-12-27', 'due this week', - 'reminder after yesterday', - 'reminder before 2021-12-27', - 'reminder date is invalid', 'exclude sub-items', 'filename includes wibble', 'happens after 2021-12-27', @@ -96,6 +93,13 @@ describe('Query parsing', () => { 'starts in 2021-12-27 2021-12-29', 'starts on 2021-12-27', 'starts this week', + 'reminder date is invalid', + 'reminder after 2021-12-27', + 'reminder before 2021-12-27', + 'reminder in 2021-12-27 2021-12-29', + 'reminder on 2021-12-27', + 'reminder this week', + 'reminder after yesterday', 'status.name includes cancelled', 'status.type is IN_PROGRESS', 'tag does not include #sometag', @@ -175,6 +179,8 @@ describe('Query parsing', () => { 'sort by priority', 'sort by scheduled reverse', 'sort by scheduled', + 'sort by reminder reverse', + 'sort by reminder', 'sort by start reverse', 'sort by start', 'sort by status reverse', @@ -218,6 +224,7 @@ describe('Query parsing', () => { 'group by recurring', 'group by root', 'group by scheduled', + 'group by reminder', 'group by start', 'group by status', 'group by status.name', @@ -248,6 +255,8 @@ describe('Query parsing', () => { 'hide priority', 'hide recurrence rule', 'hide scheduled date', + 'hide reminder date', + 'hide reminders', 'hide start date', 'hide task count', 'hide urgency', @@ -262,6 +271,7 @@ describe('Query parsing', () => { 'show priority', 'show recurrence rule', 'show scheduled date', + 'show reminder date', 'show created date', 'show start date', 'show task count', @@ -408,6 +418,21 @@ describe('Query', () => { ], }, ], + [ + 'by reminder date presence', + { + filters: ['has reminder date'], + tasks: [ + '- [ ] task 1', + '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏲️ 2022-04-20', + '- [ ] task 3 ⏲️ 2022-04-20', + ], + expectedResult: [ + '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏲️ 2022-04-20', + '- [ ] task 3 ⏲️ 2022-04-20', + ], + }, + ], [ 'by due date absence', { @@ -444,6 +469,18 @@ describe('Query', () => { expectedResult: ['- [ ] task 1'], }, ], + [ + 'by reminder date absence', + { + filters: ['no reminder date'], + tasks: [ + '- [ ] task 1', + '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏲️ 2022-04-20', + '- [ ] task 3 ⏲️ 2022-04-20', + ], + expectedResult: ['- [ ] task 1'], + }, + ], [ 'by start date (before)', { @@ -473,6 +510,19 @@ describe('Query', () => { expectedResult: ['- [ ] task 2 ⏳ 2022-04-15'], }, ], + [ + 'by reminder date (before)', // TODO Erik + { + filters: ['reminder before 2022-04-20'], + tasks: [ + '- [ ] task 1', + '- [ ] task 2 ⏲️ 2022-04-15', + '- [ ] task 3 ⏲️ 2022-04-20', + '- [ ] task 4 ⏲️ 2022-04-25', + ], + expectedResult: ['- [ ] task 2 ⏲️ 2022-04-15'], + }, + ], [ 'by done date (before)', { @@ -489,6 +539,26 @@ describe('Query', () => { }); }); + describe('filtering reminders', () => { + test.concurrent.each<[string, FilteringCase]>([ + [ + 'Reminder after AND not done', + { + filters: ['"reminder after 2022-04-20" AND "not done"'], + tasks: [ + '- [ ] task 1', + '- [ ] task 2 ⏲️ 2022-04-20', + '- [x] task 3 ⏲️ 2022-04-21', + '- [ ] task 4 ⏲️ 2022-04-21', + ], + expectedResult: ['- [ ] task 4 ⏲️ 2022-04-21'], + }, + ], + ])('should support reminder filter %s', (_, { tasks: allTaskLines, filters, expectedResult }) => { + shouldSupportFiltering(filters, allTaskLines, expectedResult); + }); + }); + describe('filtering with "happens"', () => { type HappensCase = { description: string; diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts new file mode 100644 index 0000000000..8c57711320 --- /dev/null +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -0,0 +1,59 @@ +/** + * @jest-environment jsdom + */ +import moment from 'moment'; +import { ReminderDateField } from '../../../src/Query/Filter/ReminderDateField'; +import { TaskBuilder } from '../../TestingTools/TaskBuilder'; +import { expectTaskComparesAfter, expectTaskComparesBefore } from '../../CustomMatchers/CustomMatchersForSorting'; + +window.moment = moment; + +describe('explain reminder date queries', () => { + it('should explain explicit date', () => { + const filterOrMessage = new ReminderDateField().createFilterOrErrorMessage('reminder before 2023-01-02'); + expect(filterOrMessage).toHaveExplanation('reminder date is before 2023-01-02 (Monday 2nd January 2023)'); + }); + + it('implicit "on" gets added to explanation', () => { + const filterOrMessage = new ReminderDateField().createFilterOrErrorMessage('reminder 2023-01-02'); + expect(filterOrMessage).toHaveExplanation('reminder date is on 2023-01-02 (Monday 2nd January 2023)'); + }); +}); + +describe('sorting by reminder', () => { + it('supports Field sorting methods correctly', () => { + const field = new ReminderDateField(); + expect(field.supportsSorting()).toEqual(true); + }); + + // These are minimal tests just to confirm basic behaviour is set up for this field. + // Thorough testing is done in DueDateField.test.ts. + + const date1 = new TaskBuilder().reminders(['2021-01-12']).build(); + const date2 = new TaskBuilder().reminders(['2022-12-23']).build(); + + it('sort by reminder', () => { + expectTaskComparesBefore(new ReminderDateField().createNormalSorter(), date1, date2); + }); + + it('sort by reminder reverse', () => { + expectTaskComparesAfter(new ReminderDateField().createReverseSorter(), date1, date2); + }); +}); + +describe('grouping by reminder date', () => { + it('supports Field grouping methods correctly', () => { + expect(new ReminderDateField()).toSupportGroupingWithProperty('reminder'); + }); + + it('group by reminder date', () => { + // Arrange + const grouper = new ReminderDateField().createGrouper(); + const taskWithDate = new TaskBuilder().reminders(['1970-01-01']).build(); + const taskWithoutDate = new TaskBuilder().build(); + + // Assert + expect(grouper.grouper(taskWithDate)).toEqual(['1970-01-01 Thursday']); + expect(grouper.grouper(taskWithoutDate)).toEqual(['No reminder date']); + }); +}); From 06482ec212ed4daa02beaeaec0bb98b3c813bc71 Mon Sep 17 00:00:00 2001 From: Erik-Handeland Date: Sat, 6 May 2023 20:16:43 -0700 Subject: [PATCH 47/93] added Recurrence reminder tests --- src/Recurrence.ts | 7 +- src/Reminders/Reminder.ts | 40 +++++++++ src/Task.ts | 4 +- src/lib/DateTools.ts | 25 ------ tests/Recurrence.test.ts | 113 ++++++++++++++++++++++++ tests/TestingTools/RecurrenceBuilder.ts | 8 +- tests/TestingTools/TaskBuilder.ts | 20 +---- 7 files changed, 168 insertions(+), 49 deletions(-) diff --git a/src/Recurrence.ts b/src/Recurrence.ts index ab079f68b5..4b02a236a8 100644 --- a/src/Recurrence.ts +++ b/src/Recurrence.ts @@ -1,7 +1,7 @@ import type { Moment } from 'moment'; import { RRule } from 'rrule'; -import { compareByDate, isRemindersSame } from './lib/DateTools'; -import { Reminder, ReminderList } from './Reminders/Reminder'; +import { compareByDate } from './lib/DateTools'; +import { Reminder, ReminderList, isRemindersSame } from './Reminders/Reminder'; export class Recurrence { private readonly rrule: RRule; @@ -86,6 +86,8 @@ export class Recurrence { referenceDate = window.moment(scheduledDate); } else if (startDate) { referenceDate = window.moment(startDate); + } else if (reminders?.peek()) { + referenceDate = window.moment(reminders.peek()?.format('YYYY-MM-DD')); } if (!baseOnToday && referenceDate !== null) { @@ -167,7 +169,6 @@ export class Recurrence { // Rounding days to handle cross daylight-savings-time recurrences. dueDate.add(Math.round(originalDifference.asDays()), 'days'); } - if (this.reminders) { reminders = new ReminderList(null); this.reminders.reminders.forEach((reminder) => { diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index 123d2c0eef..869542317d 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -35,6 +35,10 @@ export class ReminderList { public push(reminder: Moment) { this.reminders.push(parseMoment(reminder)); } + + isSame(other: ReminderList | null) { + return isRemindersSame(this, other); + } } export enum ReminderType { @@ -67,6 +71,19 @@ export function parseDateTime(dateTime: string): Reminder { return parseMoment(reminder); } +export function parseDateTimes(dateTimes: string[]): ReminderList { + const parsedReminders = new ReminderList(null); + for (const reminder of dateTimes) { + const reminderDate = parseDateTime(reminder); + if (reminderDate) { + parsedReminders.reminders.push(reminderDate); + } else { + throw new Error(`TaskBuilder.parseReminder() was unable to parse: ${reminder}`); + } + } + return parsedReminders; +} + export function parseMoment(reminder: Moment): Reminder { if (reminder.format('h:mm a') === '12:00 am') { //aka .startOf(day) which is the default time for reminders @@ -75,3 +92,26 @@ export function parseMoment(reminder: Moment): Reminder { return new Reminder(reminder, ReminderType.DateTime); } } + +export function isRemindersSame(a: ReminderList | null, b: ReminderList | null) { + if (a === null && b !== null) { + return false; + } else if (a !== null && b === null) { + return false; + } else if (a !== null && b !== null) { + if (a.reminders.length !== b.reminders.length) { + return false; + } + + const sortedA = a.reminders.map((reminder) => reminder.time.valueOf()).sort(); + const sortedB = b.reminders.map((reminder) => reminder.time.valueOf()).sort(); + + for (let i = 0; i < sortedA.length; i++) { + if (sortedA[i] !== sortedB[i]) { + return false; + } + } + } + + return true; +} diff --git a/src/Task.ts b/src/Task.ts index 941c6e840b..d3a0448e00 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -9,8 +9,8 @@ import { Urgency } from './Urgency'; import { renderTaskLine } from './TaskLineRenderer'; import type { TaskLineRenderDetails } from './TaskLineRenderer'; import { DateFallback } from './DateFallback'; -import { compareByDate, isRemindersSame } from './lib/DateTools'; -import type { ReminderList } from './Reminders/Reminder'; +import { compareByDate } from './lib/DateTools'; +import { type ReminderList, isRemindersSame } from './Reminders/Reminder'; import { replaceTaskWithTasks } from './File'; /** diff --git a/src/lib/DateTools.ts b/src/lib/DateTools.ts index ac3c6f917e..ffbed6241a 100644 --- a/src/lib/DateTools.ts +++ b/src/lib/DateTools.ts @@ -1,5 +1,3 @@ -import type { ReminderList } from '../Reminders/Reminder'; - export function compareByDate(a: moment.Moment | null, b: moment.Moment | null): -1 | 0 | 1 { if (a !== null && b === null) { return -1; @@ -27,26 +25,3 @@ export function compareByDate(a: moment.Moment | null, b: moment.Moment | null): export function sameDateTime(a: moment.Moment, b: moment.Moment) { return a.format('YYYY-MM-DD HH:mm') === b.format('YYYY-MM-DD HH:mm'); } - -export function isRemindersSame(a: ReminderList | null, b: ReminderList | null) { - if (a === null && b !== null) { - return false; - } else if (a !== null && b === null) { - return false; - } else if (a !== null && b !== null) { - if (a.reminders.length !== b.reminders.length) { - return false; - } - - const sortedA = a.reminders.map((reminder) => reminder.time.valueOf()).sort(); - const sortedB = b.reminders.map((reminder) => reminder.time.valueOf()).sort(); - - for (let i = 0; i < sortedA.length; i++) { - if (sortedA[i] !== sortedB[i]) { - return false; - } - } - } - - return true; -} diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index 45893f7225..9533c9697b 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -2,7 +2,9 @@ * @jest-environment jsdom */ import moment from 'moment'; +import { ReminderList } from '../src/Reminders/Reminder'; import { Recurrence } from '../src/Recurrence'; +import { TIME_FORMATS } from '../src/Config/Settings'; import { RecurrenceBuilder } from './TestingTools/RecurrenceBuilder'; jest.mock('obsidian'); @@ -242,3 +244,114 @@ describe('identicalTo', () => { expect(date1Recurrence?.identicalTo(date2Recurrence)).toBe(false); }); }); + +describe('Recurrence - with reminders', () => { + it('creates a recurring instance with single 12h reminders', () => { + // Arrange + const recurrence = Recurrence.fromText({ + recurrenceRuleText: 'every week', + startDate: null, + scheduledDate: null, + dueDate: null, + reminders: new ReminderList([moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)]), + }); + + // Act + const next = recurrence!.next(); + + // Assert + expect( + next!.reminders!.isSame(new ReminderList([moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour)])), + ).toStrictEqual(true); + }); + + it('creates a recurring instance with single 24h reminders', () => { + // Arrange + const recurrence = Recurrence.fromText({ + recurrenceRuleText: 'every week', + startDate: null, + scheduledDate: null, + dueDate: null, + reminders: new ReminderList([moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)]), + }); + + // Act + const next = recurrence!.next(); + + // Assert + expect( + next!.reminders!.isSame(new ReminderList([moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour)])), + ).toStrictEqual(true); + }); + + it('creates a recurring instance with multple 12h reminders', () => { + // Arrange + const recurrence = Recurrence.fromText({ + recurrenceRuleText: 'every week', + startDate: null, + scheduledDate: null, + dueDate: null, + reminders: new ReminderList([ + moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour), + moment('2021-06-21', TIME_FORMATS.twelveHour), + moment('2021-07-19 3:00 pm', TIME_FORMATS.twelveHour), + ]), + }); + + // Act + const next = recurrence!.next(); + + // Assert + expect( + next!.reminders!.isSame( + new ReminderList([ + moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour), + moment('2021-06-28', TIME_FORMATS.twelveHour), + moment('2021-07-26 3:00 pm', TIME_FORMATS.twelveHour), + ]), + ), + ).toStrictEqual(true); + }); + + it('creates a recurring instance with multple 24h reminders', () => { + // Arrange + const recurrence = Recurrence.fromText({ + recurrenceRuleText: 'every week', + startDate: null, + scheduledDate: null, + dueDate: null, + reminders: new ReminderList([ + moment('2021-06-20 11:00', TIME_FORMATS.twentyFourHour), + moment('2021-06-21', TIME_FORMATS.twentyFourHour), + moment('2021-07-19 15:00', TIME_FORMATS.twentyFourHour), + ]), + }); + + // Act + const next = recurrence!.next(); + + // Assert + expect( + next!.reminders!.isSame( + new ReminderList([ + moment('2021-06-27 11:00', TIME_FORMATS.twentyFourHour), + moment('2021-06-28', TIME_FORMATS.twentyFourHour), + moment('2021-07-26 15:00', TIME_FORMATS.twentyFourHour), + ]), + ), + ).toStrictEqual(true); + }); + + it('differing only in reminder', () => { + const date1Recurrence = new RecurrenceBuilder().reminders(['2021-10-21']).build(); + + const date2Recurrence = new RecurrenceBuilder().reminders(['1998-03-13']).build(); + + const nullRecurrence = new RecurrenceBuilder().reminders([]).build(); + + expect(date1Recurrence?.identicalTo(date1Recurrence)).toBe(true); + expect(date1Recurrence?.identicalTo(date2Recurrence)).toBe(false); + expect(date1Recurrence?.identicalTo(nullRecurrence)).toBe(false); + expect(nullRecurrence?.identicalTo(date1Recurrence)).toBe(false); + }); +}); diff --git a/tests/TestingTools/RecurrenceBuilder.ts b/tests/TestingTools/RecurrenceBuilder.ts index cf53e05a51..bfaf654e41 100644 --- a/tests/TestingTools/RecurrenceBuilder.ts +++ b/tests/TestingTools/RecurrenceBuilder.ts @@ -2,6 +2,7 @@ import type { Moment } from 'moment'; import { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; +import { ReminderList, parseDateTimes } from '../../src/Reminders/Reminder'; /** * A fluent class for creating Recurrence objects for tests. @@ -20,7 +21,7 @@ export class RecurrenceBuilder { private _startDate: Moment | null = null; private _scheduledDate: Moment | null = null; private _dueDate: Moment | null = null; - private _reminders = null; + private _reminders: ReminderList | null = null; /** * Build a Recurrence @@ -63,6 +64,11 @@ export class RecurrenceBuilder { return this; } + public reminders(reminders: string[]): RecurrenceBuilder { + this._reminders = parseDateTimes(reminders); + return this; + } + private static parseDate(date: string | null): Moment | null { if (date) { return DateParser.parseDate(date); diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index a4ed15471d..ea1877fc7a 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -6,7 +6,7 @@ import type { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; import { StatusConfiguration, StatusType } from '../../src/StatusConfiguration'; import { TaskLocation } from '../../src/TaskLocation'; -import { Reminder, ReminderList, parseDateTime } from '../../src/Reminders/Reminder'; +import { ReminderList, parseDateTimes } from '../../src/Reminders/Reminder'; /** * A fluent class for creating tasks for tests. @@ -198,16 +198,7 @@ export class TaskBuilder { } public reminders(reminders: string[]): TaskBuilder { - const parsedReminders = new ReminderList(null); - for (const reminder of reminders) { - const reminderDate = TaskBuilder.parseReminder(reminder); - if (reminderDate) { - parsedReminders.reminders.push(reminderDate); - } else { - throw new Error(`TaskBuilder.parseReminder() was unable to parse: ${reminder}`); - } - } - this._reminders = parsedReminders; + this._reminders = parseDateTimes(reminders); return this; } @@ -233,11 +224,4 @@ export class TaskBuilder { return null; } } - - private static parseReminder(date: string | null): Reminder | null { - if (date) { - return parseDateTime(date); - } - return null; - } } From 0a938b32f33703a40bad7a66b143ba6a5bfecfca Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 12:45:29 +0100 Subject: [PATCH 48/93] refactor: Rename reminders to remind in Task and Recurrence Changing storing type to a single value will be done later. --- src/Commands/CreateOrEditTaskParser.ts | 4 +-- src/Query/Filter/ReminderDateField.ts | 4 +-- src/Recurrence.ts | 28 +++++++-------- src/Reminders/Notification.ts | 2 +- src/Suggestor/Suggestor.ts | 2 +- src/Task.ts | 12 +++---- src/TaskSerializer/DefaultTaskSerializer.ts | 8 ++--- src/TaskSerializer/index.ts | 2 +- src/ui/EditTask.svelte | 8 ++--- .../CustomMatchersForTaskSerializer.ts | 4 +-- tests/Query.test.ts | 4 +-- tests/Recurrence.test.ts | 36 +++++++++---------- tests/Reminder/Reminder.test.ts | 4 +-- .../DefaultTaskSerializer.test.ts | 8 ++--- tests/TaskSerializer/TaskSerializer.test.ts | 6 ++-- tests/TestingTools/RecurrenceBuilder.ts | 2 +- tests/TestingTools/TaskBuilder.ts | 2 +- 17 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/Commands/CreateOrEditTaskParser.ts b/src/Commands/CreateOrEditTaskParser.ts index a51b9aabb3..f45c31a2f1 100644 --- a/src/Commands/CreateOrEditTaskParser.ts +++ b/src/Commands/CreateOrEditTaskParser.ts @@ -60,7 +60,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta recurrence: null, blockLink: '', tags: [], - reminders: null, + reminder: null, originalMarkdown: '', scheduledDateIsInferred: false, }); @@ -96,7 +96,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta doneDate: null, recurrence: null, tags: [], - reminders: null, + reminder: null, originalMarkdown: '', // Not needed since the inferred status is always re-computed after submitting. scheduledDateIsInferred: false, diff --git a/src/Query/Filter/ReminderDateField.ts b/src/Query/Filter/ReminderDateField.ts index ce2c8f0f56..98b358b056 100644 --- a/src/Query/Filter/ReminderDateField.ts +++ b/src/Query/Filter/ReminderDateField.ts @@ -8,8 +8,8 @@ export class ReminderDateField extends DateField { } public date(task: Task): Moment | null { - if (task.reminders) { - return task.reminders.peek(); + if (task.reminder) { + return task.reminder.peek(); } else { return null; } diff --git a/src/Recurrence.ts b/src/Recurrence.ts index 4b02a236a8..8033c475d2 100644 --- a/src/Recurrence.ts +++ b/src/Recurrence.ts @@ -9,7 +9,7 @@ export class Recurrence { private readonly startDate: Moment | null; private readonly scheduledDate: Moment | null; private readonly dueDate: Moment | null; - private readonly reminders: ReminderList | null; + private readonly reminder: ReminderList | null; /** * The reference date is used to calculate future occurrences. @@ -33,7 +33,7 @@ export class Recurrence { startDate, scheduledDate, dueDate, - reminders, + reminder, }: { rrule: RRule; baseOnToday: boolean; @@ -41,7 +41,7 @@ export class Recurrence { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminders: ReminderList | null; + reminder: ReminderList | null; }) { this.rrule = rrule; this.baseOnToday = baseOnToday; @@ -49,7 +49,7 @@ export class Recurrence { this.startDate = startDate; this.scheduledDate = scheduledDate; this.dueDate = dueDate; - this.reminders = reminders; + this.reminder = reminder; } public static fromText({ @@ -57,13 +57,13 @@ export class Recurrence { startDate, scheduledDate, dueDate, - reminders, + reminder, }: { recurrenceRuleText: string; startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminders: ReminderList | null; + reminder: ReminderList | null; }): Recurrence | null { try { const match = recurrenceRuleText.match(/^([a-zA-Z0-9, !]+?)( when done)?$/i); @@ -86,8 +86,8 @@ export class Recurrence { referenceDate = window.moment(scheduledDate); } else if (startDate) { referenceDate = window.moment(startDate); - } else if (reminders?.peek()) { - referenceDate = window.moment(reminders.peek()?.format('YYYY-MM-DD')); + } else if (reminder?.peek()) { + referenceDate = window.moment(reminder.peek()?.format('YYYY-MM-DD')); } if (!baseOnToday && referenceDate !== null) { @@ -104,7 +104,7 @@ export class Recurrence { startDate, scheduledDate, dueDate, - reminders, + reminder: reminder, }); } } catch (error) { @@ -130,7 +130,7 @@ export class Recurrence { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminders: ReminderList | null; + reminder: ReminderList | null; } | null { const next = this.nextReferenceDate(); @@ -169,9 +169,9 @@ export class Recurrence { // Rounding days to handle cross daylight-savings-time recurrences. dueDate.add(Math.round(originalDifference.asDays()), 'days'); } - if (this.reminders) { + if (this.reminder) { reminders = new ReminderList(null); - this.reminders.reminders.forEach((reminder) => { + this.reminder.reminders.forEach((reminder) => { const originalDifference = window.moment.duration(reminder.time.diff(this.referenceDate)); const remTime = window.moment(next); @@ -187,7 +187,7 @@ export class Recurrence { startDate, scheduledDate, dueDate, - reminders, + reminder: reminders, }; } @@ -210,7 +210,7 @@ export class Recurrence { return false; } - if (!isRemindersSame(this.reminders, other.reminders)) { + if (!isRemindersSame(this.reminder, other.reminder)) { return false; } diff --git a/src/Reminders/Notification.ts b/src/Reminders/Notification.ts index 3de4801a26..eb5a0facec 100644 --- a/src/Reminders/Notification.ts +++ b/src/Reminders/Notification.ts @@ -89,7 +89,7 @@ export class TaskNotification { reminderSettings.dateTimeFormat, ); - task.reminders?.reminders.forEach((rDate) => { + task.reminder?.reminders.forEach((rDate) => { const curTime = window.moment(); if (TaskNotification.shouldNotifiy(rDate, dailyReminderTime, curTime)) { rDate.notified = true; diff --git a/src/Suggestor/Suggestor.ts b/src/Suggestor/Suggestor.ts index e6d3710e78..f52a4a5017 100644 --- a/src/Suggestor/Suggestor.ts +++ b/src/Suggestor/Suggestor.ts @@ -262,7 +262,7 @@ function addRecurrenceSuggestions(line: string, cursorPos: number, settings: Set startDate: null, scheduledDate: null, dueDate: null, - reminders: null, + reminder: null, })?.toText(); if (parsedRecurrence) { const appendedText = `${recurrencePrefix} ${parsedRecurrence} `; diff --git a/src/Task.ts b/src/Task.ts index d3a0448e00..05a060746c 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -103,7 +103,7 @@ export class Task { public readonly taskLocation: TaskLocation; public readonly tags: string[]; - public readonly reminders: ReminderList | null; + public readonly reminder: ReminderList | null; public readonly priority: Priority; @@ -142,7 +142,7 @@ export class Task { recurrence, blockLink, tags, - reminders, + reminder, originalMarkdown, scheduledDateIsInferred, }: { @@ -160,7 +160,7 @@ export class Task { recurrence: Recurrence | null; blockLink: string; tags: string[] | []; - reminders: ReminderList | null; + reminder: ReminderList | null; originalMarkdown: string; scheduledDateIsInferred: boolean; }) { @@ -171,7 +171,7 @@ export class Task { this.taskLocation = taskLocation; this.tags = tags; - this.reminders = reminders; + this.reminder = reminder; this.priority = priority; @@ -313,7 +313,7 @@ export class Task { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminders: ReminderList | null; + reminder: ReminderList | null; } | null = null; if (newStatus.isCompleted()) { @@ -512,7 +512,7 @@ export class Task { } // compare reminders - if (!isRemindersSame(this.reminders, other.reminders)) { + if (!isRemindersSame(this.reminder, other.reminder)) { return false; } diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index d5492e976d..06c64889b6 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -145,10 +145,10 @@ export class DefaultTaskSerializer implements TaskSerializer { ? ' ' + dueDateSymbol : ` ${dueDateSymbol} ${task.dueDate.format(TaskRegularExpressions.dateFormat)}`; case 'reminders': - if (!task.reminders || task.reminders.reminders.length <= 0) return ''; + if (!task.reminder || task.reminder.reminders.length <= 0) return ''; return layout.options.shortMode ? ' ' + reminderDateSymbol - : ` ${reminderDateSymbol} ${task.reminders.toString()}`; + : ` ${reminderDateSymbol} ${task.reminder.toString()}`; case 'recurrenceRule': if (!task.recurrence) return ''; return layout.options.shortMode @@ -302,7 +302,7 @@ export class DefaultTaskSerializer implements TaskSerializer { startDate, scheduledDate, dueDate, - reminders: rList, + reminder: rList, }); } // Add back any trailing tags to the description. We removed them so we can parse the rest of the @@ -321,7 +321,7 @@ export class DefaultTaskSerializer implements TaskSerializer { doneDate, recurrence, tags: Task.extractHashtags(line), - reminders: rList, + reminder: rList, }; } } diff --git a/src/TaskSerializer/index.ts b/src/TaskSerializer/index.ts index e58d6be6de..301f67c2d6 100644 --- a/src/TaskSerializer/index.ts +++ b/src/TaskSerializer/index.ts @@ -20,7 +20,7 @@ export type TaskDetails = Writeable< | 'doneDate' | 'recurrence' | 'tags' - | 'reminders' + | 'reminder' > >; diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index 1d308564ef..d81ad21a04 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -207,7 +207,7 @@ startDate: null, scheduledDate: null, dueDate: null, - reminders: null, + reminder: null, })?.toText(); if (!recurrenceFromText) { parsedRecurrence = 'invalid recurrence rule'; @@ -257,7 +257,7 @@ ? task.scheduledDate.format('YYYY-MM-DD') : '', dueDate: task.dueDate ? task.dueDate.format('YYYY-MM-DD') : '', - reminderDate: task.reminders?.peek() ? task.reminders?.peek()!.format(reminderSettings.dateTimeFormat) : '', + reminderDate: task.reminder?.peek() ? task.reminder?.peek()!.format(reminderSettings.dateTimeFormat) : '', doneDate: task.doneDate ? task.doneDate.format('YYYY-MM-DD') : '', forwardOnly: true, }; @@ -315,7 +315,7 @@ startDate, scheduledDate, dueDate, - reminders: reminderDate ? new ReminderList([reminderDate]) : null, + reminder: reminderDate ? new ReminderList([reminderDate]) : null, }); } @@ -343,7 +343,7 @@ startDate, scheduledDate, dueDate, - reminders: reminderDate ? new ReminderList([reminderDate]) : null, + reminder: reminderDate ? new ReminderList([reminderDate]) : null, doneDate: window .moment(editableTask.doneDate, 'YYYY-MM-DD') .isValid() diff --git a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts index e13a563bfb..865d0cbede 100644 --- a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts +++ b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts @@ -80,7 +80,7 @@ function summarizeTaskDetails(t: TaskDetails | null): SummarizedTaskDetails | nu dueDate: t.dueDate?.format(TaskRegularExpressions.dateFormat) ?? null, doneDate: t.doneDate?.format(TaskRegularExpressions.dateFormat) ?? null, recurrence: t.recurrence?.toText() ?? null, - reminders: t.reminders?.toString() ?? null, + reminder: t.reminder?.toString() ?? null, }; } @@ -104,7 +104,7 @@ function tryBuildTaskDetails(t: object): TaskDetails | null { doneDate: null, recurrence: null, tags: [], - reminders: null, + reminder: null, ...t, }; if (!isTaskDetails(toReturn)) return null; diff --git a/tests/Query.test.ts b/tests/Query.test.ts index e4bbd22c30..49a9a39c2e 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -333,7 +333,7 @@ describe('Query', () => { recurrence: null, blockLink: '', tags: [], - reminders: null, + reminder: null, originalMarkdown: '', scheduledDateIsInferred: false, createdDate: null, @@ -352,7 +352,7 @@ describe('Query', () => { recurrence: null, blockLink: '', tags: [], - reminders: null, + reminder: null, originalMarkdown: '', scheduledDateIsInferred: false, createdDate: null, diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index 9533c9697b..ddad487321 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -18,7 +18,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: null, - reminders: null, + reminder: null, }); // Act @@ -29,7 +29,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: null, - reminders: null, + reminder: null, }); }); @@ -40,7 +40,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2022-01-31').startOf('day'), - reminders: null, + reminder: null, }); // Act @@ -59,7 +59,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2022-01-31').startOf('day'), - reminders: null, + reminder: null, }); // Act @@ -78,7 +78,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2023-12-31').startOf('day'), - reminders: null, + reminder: null, }); // Act @@ -97,7 +97,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2024-02-29').startOf('day'), - reminders: null, + reminder: null, }); // Act @@ -116,7 +116,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2020-03-31').startOf('day'), - reminders: null, + reminder: null, }); // Act @@ -135,7 +135,7 @@ describe('Recurrence', () => { startDate: null, scheduledDate: null, dueDate: moment('2020-01-31').startOf('day'), - reminders: null, + reminder: null, }); // Act @@ -158,7 +158,7 @@ describe('Recurrence - with invalid dates in tasks', () => { startDate: null, scheduledDate: null, dueDate: moment('2022-02-30').startOf('day'), // 30th February: invalid date - reminders: null, + reminder: null, }); // Assert @@ -179,7 +179,7 @@ describe('Recurrence - with invalid dates in tasks', () => { startDate: null, scheduledDate: moment('2022-02-30').startOf('day'), // 30th February: invalid date dueDate: moment('2022-02-27').startOf('day'), - reminders: null, + reminder: null, }); // Act @@ -253,7 +253,7 @@ describe('Recurrence - with reminders', () => { startDate: null, scheduledDate: null, dueDate: null, - reminders: new ReminderList([moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)]), + reminder: new ReminderList([moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)]), }); // Act @@ -261,7 +261,7 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminders!.isSame(new ReminderList([moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour)])), + next!.reminder!.isSame(new ReminderList([moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour)])), ).toStrictEqual(true); }); @@ -272,7 +272,7 @@ describe('Recurrence - with reminders', () => { startDate: null, scheduledDate: null, dueDate: null, - reminders: new ReminderList([moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)]), + reminder: new ReminderList([moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)]), }); // Act @@ -280,7 +280,7 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminders!.isSame(new ReminderList([moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour)])), + next!.reminder!.isSame(new ReminderList([moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour)])), ).toStrictEqual(true); }); @@ -291,7 +291,7 @@ describe('Recurrence - with reminders', () => { startDate: null, scheduledDate: null, dueDate: null, - reminders: new ReminderList([ + reminder: new ReminderList([ moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour), moment('2021-06-21', TIME_FORMATS.twelveHour), moment('2021-07-19 3:00 pm', TIME_FORMATS.twelveHour), @@ -303,7 +303,7 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminders!.isSame( + next!.reminder!.isSame( new ReminderList([ moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour), moment('2021-06-28', TIME_FORMATS.twelveHour), @@ -320,7 +320,7 @@ describe('Recurrence - with reminders', () => { startDate: null, scheduledDate: null, dueDate: null, - reminders: new ReminderList([ + reminder: new ReminderList([ moment('2021-06-20 11:00', TIME_FORMATS.twentyFourHour), moment('2021-06-21', TIME_FORMATS.twentyFourHour), moment('2021-07-19 15:00', TIME_FORMATS.twentyFourHour), @@ -332,7 +332,7 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminders!.isSame( + next!.reminder!.isSame( new ReminderList([ moment('2021-06-27 11:00', TIME_FORMATS.twentyFourHour), moment('2021-06-28', TIME_FORMATS.twentyFourHour), diff --git a/tests/Reminder/Reminder.test.ts b/tests/Reminder/Reminder.test.ts index 0f485cf207..38433bc3b3 100644 --- a/tests/Reminder/Reminder.test.ts +++ b/tests/Reminder/Reminder.test.ts @@ -72,7 +72,7 @@ describe('should parse task strings: ', () => { const line = '- [ ] #task Reminder at 13:57 ⏲️ 2023-05-03 1:57 pm'; const task = fromLine({ line: line }); - expect(task.reminders).not.toBeNull(); + expect(task.reminder).not.toBeNull(); }); it('valid task - in 24-hour format', () => { @@ -80,6 +80,6 @@ describe('should parse task strings: ', () => { const line = '- [ ] #task Reminder at 13:57 ⏲️ 2023-05-03 13:57'; const task = fromLine({ line: line }); - expect(task.reminders).not.toBeNull(); + expect(task.reminder).not.toBeNull(); }); }); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index 9eed6088a2..075cee92d4 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -98,7 +98,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ times.forEach((time) => { const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); expect(taskDetails).toMatchTaskDetails({ - reminders: new ReminderList([moment(time, TIME_FORMATS.twelveHour)]), + reminder: new ReminderList([moment(time, TIME_FORMATS.twelveHour)]), }); }); }); @@ -109,7 +109,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ times.forEach((time) => { const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); expect(taskDetails).toMatchTaskDetails({ - reminders: new ReminderList([moment(time, TIME_FORMATS.twentyFourHour)]), + reminder: new ReminderList([moment(time, TIME_FORMATS.twentyFourHour)]), }); }); }); @@ -120,7 +120,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ `${reminderDateSymbol} 2021-06-20 10:00 am, 2021-06-21, 2021-07-19 3:00 pm`, ); expect(taskDetails).toMatchTaskDetails({ - reminders: new ReminderList([ + reminder: new ReminderList([ moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour), moment('2021-06-21', TIME_FORMATS.twelveHour), moment('2021-07-19 3:00 pm', TIME_FORMATS.twelveHour), @@ -132,7 +132,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ setDateTimeFormat(TIME_FORMATS.twentyFourHour); const taskDetails = deserialize(`${reminderDateSymbol} 2021-06-20 10:00, 2021-06-21, 2021-07-19 16:00`); expect(taskDetails).toMatchTaskDetails({ - reminders: new ReminderList([ + reminder: new ReminderList([ moment('2021-06-20 10:00', TIME_FORMATS.twentyFourHour), moment('2021-06-21', TIME_FORMATS.twentyFourHour), moment('2021-07-19 16:00', TIME_FORMATS.twentyFourHour), diff --git a/tests/TaskSerializer/TaskSerializer.test.ts b/tests/TaskSerializer/TaskSerializer.test.ts index 463e3f0f1a..b1c98ff735 100644 --- a/tests/TaskSerializer/TaskSerializer.test.ts +++ b/tests/TaskSerializer/TaskSerializer.test.ts @@ -15,9 +15,9 @@ window.moment = moment; This file contains a tested, end-to-end example for implementing and using a {@link TaskSerializer}.
- This file should also contain any {@link TaskSerializer} tests that should be tested + This file should also contain any {@link TaskSerializer} tests that should be tested against all the {@link TaskSerializer}s defined in this repo. Tests that only - apply to one should be housed in that serializer's specific test file + apply to one should be housed in that serializer's specific test file */ describe('TaskSerializer Example', () => { @@ -68,7 +68,7 @@ describe('TaskSerializer Example', () => { scheduledDate: null, doneDate: null, recurrence: null, - reminders: null, + reminder: null, }; } diff --git a/tests/TestingTools/RecurrenceBuilder.ts b/tests/TestingTools/RecurrenceBuilder.ts index bfaf654e41..fe853a8349 100644 --- a/tests/TestingTools/RecurrenceBuilder.ts +++ b/tests/TestingTools/RecurrenceBuilder.ts @@ -40,7 +40,7 @@ export class RecurrenceBuilder { startDate: this._startDate, scheduledDate: this._scheduledDate, dueDate: this._dueDate, - reminders: this._reminders, + reminder: this._reminders, }) as Recurrence; } diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index ea1877fc7a..4d403159c9 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -85,7 +85,7 @@ export class TaskBuilder { recurrence: this._recurrence, blockLink: this._blockLink, tags: this._tags, - reminders: this._reminders, + reminder: this._reminders, originalMarkdown: '', scheduledDateIsInferred: this._scheduledDateIsInferred, }); From cee10e5a8578b2d0fd08d0bd707ea8bbf62d583d Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 12:47:53 +0100 Subject: [PATCH 49/93] refactor: Rename reminder field in RecurrenceBuilder.ts to singular --- tests/TestingTools/RecurrenceBuilder.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/TestingTools/RecurrenceBuilder.ts b/tests/TestingTools/RecurrenceBuilder.ts index fe853a8349..8c570f1f87 100644 --- a/tests/TestingTools/RecurrenceBuilder.ts +++ b/tests/TestingTools/RecurrenceBuilder.ts @@ -21,7 +21,7 @@ export class RecurrenceBuilder { private _startDate: Moment | null = null; private _scheduledDate: Moment | null = null; private _dueDate: Moment | null = null; - private _reminders: ReminderList | null = null; + private _reminder: ReminderList | null = null; /** * Build a Recurrence @@ -40,7 +40,7 @@ export class RecurrenceBuilder { startDate: this._startDate, scheduledDate: this._scheduledDate, dueDate: this._dueDate, - reminder: this._reminders, + reminder: this._reminder, }) as Recurrence; } @@ -65,7 +65,7 @@ export class RecurrenceBuilder { } public reminders(reminders: string[]): RecurrenceBuilder { - this._reminders = parseDateTimes(reminders); + this._reminder = parseDateTimes(reminders); return this; } From 7a7708bb5ea3f7a161353f6755b17bbef386e946 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 12:52:17 +0100 Subject: [PATCH 50/93] test: Remove the tests of multi-reminder capability Before converting to store at most one reminder. --- tests/Recurrence.test.ts | 58 ------------------- .../DefaultTaskSerializer.test.ts | 48 --------------- 2 files changed, 106 deletions(-) diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index ddad487321..008a3bff5f 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -284,64 +284,6 @@ describe('Recurrence - with reminders', () => { ).toStrictEqual(true); }); - it('creates a recurring instance with multple 12h reminders', () => { - // Arrange - const recurrence = Recurrence.fromText({ - recurrenceRuleText: 'every week', - startDate: null, - scheduledDate: null, - dueDate: null, - reminder: new ReminderList([ - moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour), - moment('2021-06-21', TIME_FORMATS.twelveHour), - moment('2021-07-19 3:00 pm', TIME_FORMATS.twelveHour), - ]), - }); - - // Act - const next = recurrence!.next(); - - // Assert - expect( - next!.reminder!.isSame( - new ReminderList([ - moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour), - moment('2021-06-28', TIME_FORMATS.twelveHour), - moment('2021-07-26 3:00 pm', TIME_FORMATS.twelveHour), - ]), - ), - ).toStrictEqual(true); - }); - - it('creates a recurring instance with multple 24h reminders', () => { - // Arrange - const recurrence = Recurrence.fromText({ - recurrenceRuleText: 'every week', - startDate: null, - scheduledDate: null, - dueDate: null, - reminder: new ReminderList([ - moment('2021-06-20 11:00', TIME_FORMATS.twentyFourHour), - moment('2021-06-21', TIME_FORMATS.twentyFourHour), - moment('2021-07-19 15:00', TIME_FORMATS.twentyFourHour), - ]), - }); - - // Act - const next = recurrence!.next(); - - // Assert - expect( - next!.reminder!.isSame( - new ReminderList([ - moment('2021-06-27 11:00', TIME_FORMATS.twentyFourHour), - moment('2021-06-28', TIME_FORMATS.twentyFourHour), - moment('2021-07-26 15:00', TIME_FORMATS.twentyFourHour), - ]), - ), - ).toStrictEqual(true); - }); - it('differing only in reminder', () => { const date1Recurrence = new RecurrenceBuilder().reminders(['2021-10-21']).build(); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index 075cee92d4..4435e15863 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -113,32 +113,6 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ }); }); }); - - it('should parse multiple 12h reminders', () => { - setDateTimeFormat(TIME_FORMATS.twelveHour); - const taskDetails = deserialize( - `${reminderDateSymbol} 2021-06-20 10:00 am, 2021-06-21, 2021-07-19 3:00 pm`, - ); - expect(taskDetails).toMatchTaskDetails({ - reminder: new ReminderList([ - moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour), - moment('2021-06-21', TIME_FORMATS.twelveHour), - moment('2021-07-19 3:00 pm', TIME_FORMATS.twelveHour), - ]), - }); - }); - - it('should parse multiple 24h reminders', () => { - setDateTimeFormat(TIME_FORMATS.twentyFourHour); - const taskDetails = deserialize(`${reminderDateSymbol} 2021-06-20 10:00, 2021-06-21, 2021-07-19 16:00`); - expect(taskDetails).toMatchTaskDetails({ - reminder: new ReminderList([ - moment('2021-06-20 10:00', TIME_FORMATS.twentyFourHour), - moment('2021-06-21', TIME_FORMATS.twentyFourHour), - moment('2021-07-19 16:00', TIME_FORMATS.twentyFourHour), - ]), - }); - }); }); describe('serialize', () => { @@ -228,27 +202,5 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ expect(serialized).toEqual(` ${reminderDateSymbol} ${time}`); }); }); - - it('should serialize a multiple 12h reminders', () => { - setDateTimeFormat(TIME_FORMATS.twelveHour); - const serialized = serialize( - new TaskBuilder() - .reminders(['2021-06-20 5:00 pm', '2021-06-21', '2021-06-20 10:00 am']) - .description('') - .build(), - ); - expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 5:00 pm, 2021-06-21, 2021-06-20 10:00 am`); - }); - - it('should serialize a multiple 24h reminders', () => { - setDateTimeFormat(TIME_FORMATS.twentyFourHour); - const serialized = serialize( - new TaskBuilder() - .reminders(['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']) - .description('') - .build(), - ); - expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 13:45, 2021-06-20 01:45, 2021-06-20`); - }); }); }); From d4bd23d54ddfd8921826bf1e12f7891cd6db8854 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 13:41:08 +0100 Subject: [PATCH 51/93] refactor!: Reinstate earlier regex for single date + time I do not yet understand the regex, and cannot get it to match valid dates and times in https://regex101.com, but the tests still pass so it's good enough for now. --- src/TaskSerializer/DefaultTaskSerializer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index 06c64889b6..604a92090b 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -67,7 +67,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u, doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, - reminderRegex: /⏲️ *((\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?\s*(?:,\s*)?)+)$/u, + reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?)/u, }, } as const; From 9c8bba8891ee9dcf8a9b870160cdab2d5f70bbbd Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 14:02:08 +0100 Subject: [PATCH 52/93] refactor: ReminderList constructor now only takes at most one Moment --- src/Reminders/Reminder.ts | 6 +++--- src/ui/EditTask.svelte | 4 ++-- tests/Recurrence.test.ts | 8 ++++---- tests/TaskSerializer/DefaultTaskSerializer.test.ts | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index 869542317d..7836904238 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -14,10 +14,10 @@ export class ReminderSettings { export class ReminderList { public reminders: Reminder[] = []; - constructor(times: Moment[] | null) { - times?.forEach((time) => { + constructor(time: Moment | null) { + if (time) { this.reminders.push(parseMoment(time)); - }); + } } public toString(): string { diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index d81ad21a04..f8f4d8930c 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -315,7 +315,7 @@ startDate, scheduledDate, dueDate, - reminder: reminderDate ? new ReminderList([reminderDate]) : null, + reminder: reminderDate ? new ReminderList(reminderDate) : null, }); } @@ -343,7 +343,7 @@ startDate, scheduledDate, dueDate, - reminder: reminderDate ? new ReminderList([reminderDate]) : null, + reminder: reminderDate ? new ReminderList(reminderDate) : null, doneDate: window .moment(editableTask.doneDate, 'YYYY-MM-DD') .isValid() diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index 008a3bff5f..cb9734ef59 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -253,7 +253,7 @@ describe('Recurrence - with reminders', () => { startDate: null, scheduledDate: null, dueDate: null, - reminder: new ReminderList([moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)]), + reminder: new ReminderList(moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)), }); // Act @@ -261,7 +261,7 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminder!.isSame(new ReminderList([moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour)])), + next!.reminder!.isSame(new ReminderList(moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour))), ).toStrictEqual(true); }); @@ -272,7 +272,7 @@ describe('Recurrence - with reminders', () => { startDate: null, scheduledDate: null, dueDate: null, - reminder: new ReminderList([moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)]), + reminder: new ReminderList(moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)), }); // Act @@ -280,7 +280,7 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminder!.isSame(new ReminderList([moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour)])), + next!.reminder!.isSame(new ReminderList(moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour))), ).toStrictEqual(true); }); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index 4435e15863..c991e75e7d 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -98,7 +98,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ times.forEach((time) => { const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); expect(taskDetails).toMatchTaskDetails({ - reminder: new ReminderList([moment(time, TIME_FORMATS.twelveHour)]), + reminder: new ReminderList(moment(time, TIME_FORMATS.twelveHour)), }); }); }); @@ -109,7 +109,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ times.forEach((time) => { const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); expect(taskDetails).toMatchTaskDetails({ - reminder: new ReminderList([moment(time, TIME_FORMATS.twentyFourHour)]), + reminder: new ReminderList(moment(time, TIME_FORMATS.twentyFourHour)), }); }); }); From b9ea9d83d8d18119418cfc9bd7e1ddb4243390a5 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 14:12:53 +0100 Subject: [PATCH 53/93] refactor!: Simplify emoji-date parsing as regex only matches 1 value --- src/Reminders/Reminder.ts | 4 ---- src/TaskSerializer/DefaultTaskSerializer.ts | 11 +++-------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index 7836904238..7ac2912ce9 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -32,10 +32,6 @@ export class ReminderList { return this.reminders[0].time; } - public push(reminder: Moment) { - this.reminders.push(parseMoment(reminder)); - } - isSame(other: ReminderList | null) { return isRemindersSame(this, other); } diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index 604a92090b..d1de0d69ea 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -282,14 +282,9 @@ export class DefaultTaskSerializer implements TaskSerializer { const reminderMatch = line.match(TaskFormatRegularExpressions.reminderRegex); if (reminderMatch !== null) { line = line.replace(TaskFormatRegularExpressions.reminderRegex, '').trim(); - const split = reminderMatch[1].split(','); - rList = new ReminderList(null); - split.forEach((reminderDate) => { - reminderDate = reminderDate.trim(); // Remove any extra spaces - if (rList) { - rList.push(window.moment(reminderDate, reminderSettings.dateTimeFormat)); - } - }); + const reminderDate2 = reminderMatch[1]; + const reminder = window.moment(reminderDate2, reminderSettings.dateTimeFormat); + rList = new ReminderList(reminder); matched = true; } runs++; From 41398f18b5c9b8340bfe8fd87f784913d3f4df8a Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 16:52:46 +0100 Subject: [PATCH 54/93] refactor: RecurrenceBuilder.reminders() now takes only 1 date --- tests/Recurrence.test.ts | 6 +++--- tests/TestingTools/RecurrenceBuilder.ts | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index cb9734ef59..95e86c589f 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -285,11 +285,11 @@ describe('Recurrence - with reminders', () => { }); it('differing only in reminder', () => { - const date1Recurrence = new RecurrenceBuilder().reminders(['2021-10-21']).build(); + const date1Recurrence = new RecurrenceBuilder().reminders('2021-10-21').build(); - const date2Recurrence = new RecurrenceBuilder().reminders(['1998-03-13']).build(); + const date2Recurrence = new RecurrenceBuilder().reminders('1998-03-13').build(); - const nullRecurrence = new RecurrenceBuilder().reminders([]).build(); + const nullRecurrence = new RecurrenceBuilder().reminders('').build(); expect(date1Recurrence?.identicalTo(date1Recurrence)).toBe(true); expect(date1Recurrence?.identicalTo(date2Recurrence)).toBe(false); diff --git a/tests/TestingTools/RecurrenceBuilder.ts b/tests/TestingTools/RecurrenceBuilder.ts index 8c570f1f87..6e7d88e962 100644 --- a/tests/TestingTools/RecurrenceBuilder.ts +++ b/tests/TestingTools/RecurrenceBuilder.ts @@ -2,7 +2,7 @@ import type { Moment } from 'moment'; import { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; -import { ReminderList, parseDateTimes } from '../../src/Reminders/Reminder'; +import { ReminderList, parseDateTime } from '../../src/Reminders/Reminder'; /** * A fluent class for creating Recurrence objects for tests. @@ -64,8 +64,11 @@ export class RecurrenceBuilder { return this; } - public reminders(reminders: string[]): RecurrenceBuilder { - this._reminder = parseDateTimes(reminders); + public reminders(reminder: string): RecurrenceBuilder { + this._reminder = new ReminderList(null); + if (reminder.length > 0) { + this._reminder.reminders.push(parseDateTime(reminder)); + } return this; } From 76e90faae14e201ba121619ed72dfc89b6b37522 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 16:58:30 +0100 Subject: [PATCH 55/93] refactor: TaskBuilder.reminders() now takes only 1 date --- tests/Query/Filter/ReminderDateField.test.ts | 6 +++--- tests/Task.test.ts | 10 +++++----- tests/TaskSerializer/DefaultTaskSerializer.test.ts | 8 ++++---- tests/TestingTools/TaskBuilder.ts | 9 ++++++--- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index 8c57711320..9e74173baa 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -29,8 +29,8 @@ describe('sorting by reminder', () => { // These are minimal tests just to confirm basic behaviour is set up for this field. // Thorough testing is done in DueDateField.test.ts. - const date1 = new TaskBuilder().reminders(['2021-01-12']).build(); - const date2 = new TaskBuilder().reminders(['2022-12-23']).build(); + const date1 = new TaskBuilder().reminders('2021-01-12').build(); + const date2 = new TaskBuilder().reminders('2022-12-23').build(); it('sort by reminder', () => { expectTaskComparesBefore(new ReminderDateField().createNormalSorter(), date1, date2); @@ -49,7 +49,7 @@ describe('grouping by reminder date', () => { it('group by reminder date', () => { // Arrange const grouper = new ReminderDateField().createGrouper(); - const taskWithDate = new TaskBuilder().reminders(['1970-01-01']).build(); + const taskWithDate = new TaskBuilder().reminders('1970-01-01').build(); const taskWithoutDate = new TaskBuilder().build(); // Assert diff --git a/tests/Task.test.ts b/tests/Task.test.ts index d6c6ccbde7..2e46a27a82 100644 --- a/tests/Task.test.ts +++ b/tests/Task.test.ts @@ -1213,11 +1213,11 @@ describe('identicalTo', () => { }); it('should check reminders', () => { - const lhs = new TaskBuilder().reminders(['2023-03-07 09:25 am']); - expect(lhs).toBeIdenticalTo(new TaskBuilder().reminders(['2023-03-07 09:25 am'])); - expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders([])); - expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders(['2023-03-07'])); - expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders(['2023-03-07 09:27 am'])); + const lhs = new TaskBuilder().reminders('2023-03-07 09:25 am'); + expect(lhs).toBeIdenticalTo(new TaskBuilder().reminders('2023-03-07 09:25 am')); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders('')); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders('2023-03-07')); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders('2023-03-07 09:27 am')); }); }); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index c991e75e7d..f4d281037b 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -163,7 +163,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ }); it('should serialize a single reminder date', () => { - const serialized = serialize(new TaskBuilder().reminders(['2021-06-20']).description('').build()); + const serialized = serialize(new TaskBuilder().reminders('2021-06-20').description('').build()); expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20`); }); }); @@ -178,7 +178,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const times = ['2021-06-20 1:45 pm', '2021-06-20 1:45 am', '2021-06-20']; times.forEach((time) => { - const serialized = serialize(new TaskBuilder().reminders([time]).description('').build()); + const serialized = serialize(new TaskBuilder().reminders(time).description('').build()); expect(serialized).toEqual(` ${reminderDateSymbol} ${time}`); }); }); @@ -188,7 +188,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const times = ['2021-06-20 01:45 pm', '2021-06-20 1:45 PM', '2021-06-20 01:45 PM', '2021-06-20 1:45 p']; times.forEach((time) => { - const serialized = serialize(new TaskBuilder().reminders([time]).description('').build()); + const serialized = serialize(new TaskBuilder().reminders(time).description('').build()); expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 1:45 pm`); }); }); @@ -198,7 +198,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; times.forEach((time) => { - const serialized = serialize(new TaskBuilder().reminders([time]).description('').build()); + const serialized = serialize(new TaskBuilder().reminders(time).description('').build()); expect(serialized).toEqual(` ${reminderDateSymbol} ${time}`); }); }); diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index 4d403159c9..9853a67bcf 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -6,7 +6,7 @@ import type { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; import { StatusConfiguration, StatusType } from '../../src/StatusConfiguration'; import { TaskLocation } from '../../src/TaskLocation'; -import { ReminderList, parseDateTimes } from '../../src/Reminders/Reminder'; +import { ReminderList, parseDateTime } from '../../src/Reminders/Reminder'; /** * A fluent class for creating tasks for tests. @@ -197,8 +197,11 @@ export class TaskBuilder { return this; } - public reminders(reminders: string[]): TaskBuilder { - this._reminders = parseDateTimes(reminders); + public reminders(reminder: string): TaskBuilder { + this._reminders = new ReminderList(null); + if (reminder.length > 0) { + this._reminders.reminders.push(parseDateTime(reminder)); + } return this; } From 93cb3f2b065dc50a867507dc6851bb6ece362520 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 16:59:41 +0100 Subject: [PATCH 56/93] refactor: Remove parseDateTimes() - no longer used. --- src/Reminders/Reminder.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index 7ac2912ce9..5afc4ba67c 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -67,19 +67,6 @@ export function parseDateTime(dateTime: string): Reminder { return parseMoment(reminder); } -export function parseDateTimes(dateTimes: string[]): ReminderList { - const parsedReminders = new ReminderList(null); - for (const reminder of dateTimes) { - const reminderDate = parseDateTime(reminder); - if (reminderDate) { - parsedReminders.reminders.push(reminderDate); - } else { - throw new Error(`TaskBuilder.parseReminder() was unable to parse: ${reminder}`); - } - } - return parsedReminders; -} - export function parseMoment(reminder: Moment): Reminder { if (reminder.format('h:mm a') === '12:00 am') { //aka .startOf(day) which is the default time for reminders From 1810893ad0b182cdd57ac3ae5719142fb2a93b56 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 17:28:06 +0100 Subject: [PATCH 57/93] test: Move 'differing only in reminder' test to correct section --- tests/Recurrence.test.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index 95e86c589f..a09596d515 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -243,6 +243,19 @@ describe('identicalTo', () => { expect(date1Recurrence?.identicalTo(date1Recurrence)).toBe(true); expect(date1Recurrence?.identicalTo(date2Recurrence)).toBe(false); }); + + it('differing only in reminder', () => { + const date1Recurrence = new RecurrenceBuilder().reminders('2021-10-21').build(); + + const date2Recurrence = new RecurrenceBuilder().reminders('1998-03-13').build(); + + const nullRecurrence = new RecurrenceBuilder().reminders('').build(); + + expect(date1Recurrence?.identicalTo(date1Recurrence)).toBe(true); + expect(date1Recurrence?.identicalTo(date2Recurrence)).toBe(false); + expect(date1Recurrence?.identicalTo(nullRecurrence)).toBe(false); + expect(nullRecurrence?.identicalTo(date1Recurrence)).toBe(false); + }); }); describe('Recurrence - with reminders', () => { @@ -283,17 +296,4 @@ describe('Recurrence - with reminders', () => { next!.reminder!.isSame(new ReminderList(moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour))), ).toStrictEqual(true); }); - - it('differing only in reminder', () => { - const date1Recurrence = new RecurrenceBuilder().reminders('2021-10-21').build(); - - const date2Recurrence = new RecurrenceBuilder().reminders('1998-03-13').build(); - - const nullRecurrence = new RecurrenceBuilder().reminders('').build(); - - expect(date1Recurrence?.identicalTo(date1Recurrence)).toBe(true); - expect(date1Recurrence?.identicalTo(date2Recurrence)).toBe(false); - expect(date1Recurrence?.identicalTo(nullRecurrence)).toBe(false); - expect(nullRecurrence?.identicalTo(date1Recurrence)).toBe(false); - }); }); From 0c009d50b3a2229667ce9855e0d517117ffb459c Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 17:38:44 +0100 Subject: [PATCH 58/93] test: Add some more checks in Recurrence.test.ts --- tests/Recurrence.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index a09596d515..a04b0a08cb 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -280,12 +280,14 @@ describe('Recurrence - with reminders', () => { it('creates a recurring instance with single 24h reminders', () => { // Arrange + const originalReminder = new ReminderList(moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)); + const originalReminderAsString = originalReminder.toString(); const recurrence = Recurrence.fromText({ recurrenceRuleText: 'every week', startDate: null, scheduledDate: null, dueDate: null, - reminder: new ReminderList(moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)), + reminder: originalReminder, }); // Act @@ -295,5 +297,9 @@ describe('Recurrence - with reminders', () => { expect( next!.reminder!.isSame(new ReminderList(moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour))), ).toStrictEqual(true); + expect(next!.reminder!.toString()).toStrictEqual('2021-06-27 1:00 pm'); + + // Confirm that the original date has not been modified + expect(originalReminder.toString()).toStrictEqual(originalReminderAsString); }); }); From eb53efb98fbee32a151f987c55931574be00a8d8 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 17:46:17 +0100 Subject: [PATCH 59/93] refactor: Rework Recurrence.next() for single reminder --- src/Recurrence.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Recurrence.ts b/src/Recurrence.ts index 8033c475d2..7a28e1d617 100644 --- a/src/Recurrence.ts +++ b/src/Recurrence.ts @@ -170,16 +170,20 @@ export class Recurrence { dueDate.add(Math.round(originalDifference.asDays()), 'days'); } if (this.reminder) { - reminders = new ReminderList(null); - this.reminder.reminders.forEach((reminder) => { + if (this.reminder.reminders.length > 1) { + throw Error('this should not happen: too many reminders in Reccurence'); + } + if (this.reminder.reminders.length === 1) { + const reminder = this.reminder.reminders[0]; const originalDifference = window.moment.duration(reminder.time.diff(this.referenceDate)); const remTime = window.moment(next); remTime.add(Math.floor(originalDifference.asDays()), 'days'); // add back time remTime.set({ hour: reminder.time.hour(), minute: reminder.time.minute() }); - reminders!.reminders.push(new Reminder(remTime, reminder.type)); - }); + reminders = new ReminderList(null); + reminders.reminders.push(new Reminder(remTime, reminder.type)); + } } } From a874a494068291441be6f928b7a7657233c318e4 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 18:43:53 +0100 Subject: [PATCH 60/93] refactor!: Now only support a single reminder --- src/Query/Filter/ReminderDateField.ts | 2 +- src/Recurrence.ts | 29 +++++++++---------- src/Reminders/Notification.ts | 5 ++-- src/Reminders/Reminder.ts | 23 +++++++++++++++ src/Task.ts | 11 +++---- src/TaskSerializer/DefaultTaskSerializer.ts | 10 +++---- src/ui/EditTask.svelte | 9 +++--- tests/Recurrence.test.ts | 10 +++---- .../DefaultTaskSerializer.test.ts | 6 ++-- tests/TestingTools/RecurrenceBuilder.ts | 9 +++--- tests/TestingTools/TaskBuilder.ts | 9 +++--- 11 files changed, 74 insertions(+), 49 deletions(-) diff --git a/src/Query/Filter/ReminderDateField.ts b/src/Query/Filter/ReminderDateField.ts index 98b358b056..b77315f17a 100644 --- a/src/Query/Filter/ReminderDateField.ts +++ b/src/Query/Filter/ReminderDateField.ts @@ -9,7 +9,7 @@ export class ReminderDateField extends DateField { public date(task: Task): Moment | null { if (task.reminder) { - return task.reminder.peek(); + return task.reminder.time; } else { return null; } diff --git a/src/Recurrence.ts b/src/Recurrence.ts index 7a28e1d617..81e067bd09 100644 --- a/src/Recurrence.ts +++ b/src/Recurrence.ts @@ -1,7 +1,7 @@ import type { Moment } from 'moment'; import { RRule } from 'rrule'; import { compareByDate } from './lib/DateTools'; -import { Reminder, ReminderList, isRemindersSame } from './Reminders/Reminder'; +import { Reminder, isReminderSame } from './Reminders/Reminder'; export class Recurrence { private readonly rrule: RRule; @@ -9,7 +9,7 @@ export class Recurrence { private readonly startDate: Moment | null; private readonly scheduledDate: Moment | null; private readonly dueDate: Moment | null; - private readonly reminder: ReminderList | null; + private readonly reminder: Reminder | null; /** * The reference date is used to calculate future occurrences. @@ -41,7 +41,7 @@ export class Recurrence { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminder: ReminderList | null; + reminder: Reminder | null; }) { this.rrule = rrule; this.baseOnToday = baseOnToday; @@ -63,7 +63,7 @@ export class Recurrence { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminder: ReminderList | null; + reminder: Reminder | null; }): Recurrence | null { try { const match = recurrenceRuleText.match(/^([a-zA-Z0-9, !]+?)( when done)?$/i); @@ -86,8 +86,8 @@ export class Recurrence { referenceDate = window.moment(scheduledDate); } else if (startDate) { referenceDate = window.moment(startDate); - } else if (reminder?.peek()) { - referenceDate = window.moment(reminder.peek()?.format('YYYY-MM-DD')); + } else if (reminder) { + referenceDate = window.moment(reminder.time.format('YYYY-MM-DD')); } if (!baseOnToday && referenceDate !== null) { @@ -130,7 +130,7 @@ export class Recurrence { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminder: ReminderList | null; + reminder: Reminder | null; } | null { const next = this.nextReferenceDate(); @@ -140,7 +140,7 @@ export class Recurrence { let startDate: Moment | null = null; let scheduledDate: Moment | null = null; let dueDate: Moment | null = null; - let reminders: ReminderList | null = null; + let reminders: Reminder | null = null; // Only if a reference date is given. A reference date will exist if at // least one of the other dates is set. @@ -170,19 +170,16 @@ export class Recurrence { dueDate.add(Math.round(originalDifference.asDays()), 'days'); } if (this.reminder) { - if (this.reminder.reminders.length > 1) { - throw Error('this should not happen: too many reminders in Reccurence'); - } - if (this.reminder.reminders.length === 1) { - const reminder = this.reminder.reminders[0]; + { + // TODO Remove braces - added to minimise diffs in large change + const reminder = this.reminder; // TODO Inline reminder const originalDifference = window.moment.duration(reminder.time.diff(this.referenceDate)); const remTime = window.moment(next); remTime.add(Math.floor(originalDifference.asDays()), 'days'); // add back time remTime.set({ hour: reminder.time.hour(), minute: reminder.time.minute() }); - reminders = new ReminderList(null); - reminders.reminders.push(new Reminder(remTime, reminder.type)); + reminders = new Reminder(remTime, reminder.type); } } } @@ -214,7 +211,7 @@ export class Recurrence { return false; } - if (!isRemindersSame(this.reminder, other.reminder)) { + if (!isReminderSame(this.reminder, other.reminder)) { return false; } diff --git a/src/Reminders/Notification.ts b/src/Reminders/Notification.ts index eb5a0facec..77f66783f4 100644 --- a/src/Reminders/Notification.ts +++ b/src/Reminders/Notification.ts @@ -89,13 +89,14 @@ export class TaskNotification { reminderSettings.dateTimeFormat, ); - task.reminder?.reminders.forEach((rDate) => { + if (task.reminder) { + const rDate = task.reminder; // TODO Inline rDate const curTime = window.moment(); if (TaskNotification.shouldNotifiy(rDate, dailyReminderTime, curTime)) { rDate.notified = true; this.show(task); } - }); + } } } diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index 5afc4ba67c..dbebf934af 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -59,6 +59,10 @@ export class Reminder { } return this.time.format(reminderSettings.dateTimeFormat); } + + isSame(other: Reminder | null) { + return isReminderSame(this, other); + } } export function parseDateTime(dateTime: string): Reminder { @@ -76,6 +80,25 @@ export function parseMoment(reminder: Moment): Reminder { } } +// TODO Add detailed tests +// TODO Move to Reminder class +export function isReminderSame(a: Reminder | null, b: Reminder | null) { + if (a === null && b !== null) { + return false; + } else if (a !== null && b === null) { + return false; + } else if (a !== null && b !== null) { + if (!a.time.isSame(b.time)) { + return false; + } + if (a.type != b.type) { + return false; + } + } + + return true; +} + export function isRemindersSame(a: ReminderList | null, b: ReminderList | null) { if (a === null && b !== null) { return false; diff --git a/src/Task.ts b/src/Task.ts index 05a060746c..ea04274f1b 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -10,8 +10,9 @@ import { renderTaskLine } from './TaskLineRenderer'; import type { TaskLineRenderDetails } from './TaskLineRenderer'; import { DateFallback } from './DateFallback'; import { compareByDate } from './lib/DateTools'; -import { type ReminderList, isRemindersSame } from './Reminders/Reminder'; +import type { Reminder } from './Reminders/Reminder'; import { replaceTaskWithTasks } from './File'; +import { isReminderSame } from './Reminders/Reminder'; /** * When sorting, make sure low always comes after none. This way any tasks with low will be below any exiting @@ -103,7 +104,7 @@ export class Task { public readonly taskLocation: TaskLocation; public readonly tags: string[]; - public readonly reminder: ReminderList | null; + public readonly reminder: Reminder | null; public readonly priority: Priority; @@ -160,7 +161,7 @@ export class Task { recurrence: Recurrence | null; blockLink: string; tags: string[] | []; - reminder: ReminderList | null; + reminder: Reminder | null; originalMarkdown: string; scheduledDateIsInferred: boolean; }) { @@ -313,7 +314,7 @@ export class Task { startDate: Moment | null; scheduledDate: Moment | null; dueDate: Moment | null; - reminder: ReminderList | null; + reminder: Reminder | null; } | null = null; if (newStatus.isCompleted()) { @@ -512,7 +513,7 @@ export class Task { } // compare reminders - if (!isRemindersSame(this.reminder, other.reminder)) { + if (!isReminderSame(this.reminder, other.reminder)) { return false; } diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index d1de0d69ea..9e95a5fde4 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -1,6 +1,6 @@ import type { Moment } from 'moment'; import { getSettings } from '../Config/Settings'; -import { ReminderList } from '../Reminders/Reminder'; +import { Reminder, parseMoment } from '../Reminders/Reminder'; import { TaskLayout } from '../TaskLayout'; import type { TaskLayoutComponent } from '../TaskLayout'; import { Recurrence } from '../Recurrence'; @@ -144,8 +144,8 @@ export class DefaultTaskSerializer implements TaskSerializer { return layout.options.shortMode ? ' ' + dueDateSymbol : ` ${dueDateSymbol} ${task.dueDate.format(TaskRegularExpressions.dateFormat)}`; - case 'reminders': - if (!task.reminder || task.reminder.reminders.length <= 0) return ''; + case 'reminders': // TODO Rename to singular + if (!task.reminder) return ''; return layout.options.shortMode ? ' ' + reminderDateSymbol : ` ${reminderDateSymbol} ${task.reminder.toString()}`; @@ -201,7 +201,7 @@ export class DefaultTaskSerializer implements TaskSerializer { let scheduledDate: Moment | null = null; let dueDate: Moment | null = null; let doneDate: Moment | null = null; - let rList: ReminderList | null = null; + let rList: Reminder | null = null; // TODO Rename to reminder let createdDate: Moment | null = null; let recurrenceRule: string = ''; let recurrence: Recurrence | null = null; @@ -284,7 +284,7 @@ export class DefaultTaskSerializer implements TaskSerializer { line = line.replace(TaskFormatRegularExpressions.reminderRegex, '').trim(); const reminderDate2 = reminderMatch[1]; const reminder = window.moment(reminderDate2, reminderSettings.dateTimeFormat); - rList = new ReminderList(reminder); + rList = parseMoment(reminder); matched = true; } runs++; diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index f8f4d8930c..422c75e101 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -7,7 +7,7 @@ import { Status } from '../Status'; import { Priority, Task } from '../Task'; import { doAutocomplete } from '../DateAbbreviations'; - import { ReminderList } from '../Reminders/Reminder'; + import { parseMoment } from '../Reminders/Reminder'; // These exported variables are passed in as props by TaskModal.onOpen(): export let task: Task; @@ -257,7 +257,7 @@ ? task.scheduledDate.format('YYYY-MM-DD') : '', dueDate: task.dueDate ? task.dueDate.format('YYYY-MM-DD') : '', - reminderDate: task.reminder?.peek() ? task.reminder?.peek()!.format(reminderSettings.dateTimeFormat) : '', + reminderDate: task.reminder? task.reminder!.time.format(reminderSettings.dateTimeFormat) : '', doneDate: task.doneDate ? task.doneDate.format('YYYY-MM-DD') : '', forwardOnly: true, }; @@ -306,6 +306,7 @@ const dueDate = parseTypedDateForSaving(editableTask.dueDate); + // TODO Add tests for all the reminder handling inside the Edit modal tests const reminderDate = parseTypedDateForSaving(editableTask.reminderDate); let recurrence: Recurrence | null = null; @@ -315,7 +316,7 @@ startDate, scheduledDate, dueDate, - reminder: reminderDate ? new ReminderList(reminderDate) : null, + reminder: reminderDate ? parseMoment(reminderDate) : null, }); } @@ -343,7 +344,7 @@ startDate, scheduledDate, dueDate, - reminder: reminderDate ? new ReminderList(reminderDate) : null, + reminder: reminderDate ? parseMoment(reminderDate) : null, doneDate: window .moment(editableTask.doneDate, 'YYYY-MM-DD') .isValid() diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index a04b0a08cb..8f0d83de59 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -2,7 +2,7 @@ * @jest-environment jsdom */ import moment from 'moment'; -import { ReminderList } from '../src/Reminders/Reminder'; +import { parseMoment } from '../src/Reminders/Reminder'; import { Recurrence } from '../src/Recurrence'; import { TIME_FORMATS } from '../src/Config/Settings'; import { RecurrenceBuilder } from './TestingTools/RecurrenceBuilder'; @@ -266,7 +266,7 @@ describe('Recurrence - with reminders', () => { startDate: null, scheduledDate: null, dueDate: null, - reminder: new ReminderList(moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)), + reminder: parseMoment(moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)), }); // Act @@ -274,13 +274,13 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminder!.isSame(new ReminderList(moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour))), + next!.reminder!.isSame(parseMoment(moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour))), ).toStrictEqual(true); }); it('creates a recurring instance with single 24h reminders', () => { // Arrange - const originalReminder = new ReminderList(moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)); + const originalReminder = parseMoment(moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)); const originalReminderAsString = originalReminder.toString(); const recurrence = Recurrence.fromText({ recurrenceRuleText: 'every week', @@ -295,7 +295,7 @@ describe('Recurrence - with reminders', () => { // Assert expect( - next!.reminder!.isSame(new ReminderList(moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour))), + next!.reminder!.isSame(parseMoment(moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour))), ).toStrictEqual(true); expect(next!.reminder!.toString()).toStrictEqual('2021-06-27 1:00 pm'); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index f4d281037b..6cd282d6ce 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -8,7 +8,7 @@ import { DefaultTaskSerializer } from '../../src/TaskSerializer'; import { RecurrenceBuilder } from '../TestingTools/RecurrenceBuilder'; import { DEFAULT_SYMBOLS, type DefaultTaskSerializerSymbols } from '../../src/TaskSerializer/DefaultTaskSerializer'; import { TaskBuilder } from '../TestingTools/TaskBuilder'; -import { ReminderList } from '../../src/Reminders/Reminder'; +import { parseMoment } from '../../src/Reminders/Reminder'; import { setDateTimeFormat } from '../TestHelpers'; jest.mock('obsidian'); @@ -98,7 +98,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ times.forEach((time) => { const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); expect(taskDetails).toMatchTaskDetails({ - reminder: new ReminderList(moment(time, TIME_FORMATS.twelveHour)), + reminder: parseMoment(moment(time, TIME_FORMATS.twelveHour)), }); }); }); @@ -109,7 +109,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ times.forEach((time) => { const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); expect(taskDetails).toMatchTaskDetails({ - reminder: new ReminderList(moment(time, TIME_FORMATS.twentyFourHour)), + reminder: parseMoment(moment(time, TIME_FORMATS.twentyFourHour)), }); }); }); diff --git a/tests/TestingTools/RecurrenceBuilder.ts b/tests/TestingTools/RecurrenceBuilder.ts index 6e7d88e962..940302b061 100644 --- a/tests/TestingTools/RecurrenceBuilder.ts +++ b/tests/TestingTools/RecurrenceBuilder.ts @@ -2,7 +2,7 @@ import type { Moment } from 'moment'; import { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; -import { ReminderList, parseDateTime } from '../../src/Reminders/Reminder'; +import { Reminder, parseDateTime } from '../../src/Reminders/Reminder'; /** * A fluent class for creating Recurrence objects for tests. @@ -21,7 +21,7 @@ export class RecurrenceBuilder { private _startDate: Moment | null = null; private _scheduledDate: Moment | null = null; private _dueDate: Moment | null = null; - private _reminder: ReminderList | null = null; + private _reminder: Reminder | null = null; /** * Build a Recurrence @@ -65,9 +65,10 @@ export class RecurrenceBuilder { } public reminders(reminder: string): RecurrenceBuilder { - this._reminder = new ReminderList(null); if (reminder.length > 0) { - this._reminder.reminders.push(parseDateTime(reminder)); + this._reminder = parseDateTime(reminder); + } else { + this._reminder = null; } return this; } diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index 9853a67bcf..5c6949b1f7 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -6,7 +6,7 @@ import type { Recurrence } from '../../src/Recurrence'; import { DateParser } from '../../src/Query/DateParser'; import { StatusConfiguration, StatusType } from '../../src/StatusConfiguration'; import { TaskLocation } from '../../src/TaskLocation'; -import { ReminderList, parseDateTime } from '../../src/Reminders/Reminder'; +import { Reminder, parseDateTime } from '../../src/Reminders/Reminder'; /** * A fluent class for creating tasks for tests. @@ -40,7 +40,7 @@ export class TaskBuilder { private _scheduledDate: Moment | null = null; private _dueDate: Moment | null = null; private _doneDate: Moment | null = null; - private _reminders: ReminderList | null = null; + private _reminders: Reminder | null = null; // TODO Rename to singular private _recurrence: Recurrence | null = null; private _blockLink: string = ''; @@ -198,9 +198,10 @@ export class TaskBuilder { } public reminders(reminder: string): TaskBuilder { - this._reminders = new ReminderList(null); if (reminder.length > 0) { - this._reminders.reminders.push(parseDateTime(reminder)); + this._reminders = parseDateTime(reminder); + } else { + this._reminders = null; } return this; } From 6eff445e008812a5b695e89cde9801732fc7964f Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 18:45:53 +0100 Subject: [PATCH 61/93] refactor: Remove ReminderList class as no longer used --- src/Reminders/Reminder.ts | 49 --------------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index dbebf934af..37740355c5 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -11,32 +11,6 @@ export class ReminderSettings { constructor() {} } -export class ReminderList { - public reminders: Reminder[] = []; - - constructor(time: Moment | null) { - if (time) { - this.reminders.push(parseMoment(time)); - } - } - - public toString(): string { - return this.reminders.map((reminder) => `${reminder.toString()}`).join(', '); - } - - // TODO only used in ReminderDateField & Edit modal need way to deal with modal multiple reminders - public peek(): Moment | null { - if (this.reminders.length === 0) { - return null; - } - return this.reminders[0].time; - } - - isSame(other: ReminderList | null) { - return isRemindersSame(this, other); - } -} - export enum ReminderType { Date, DateTime, @@ -98,26 +72,3 @@ export function isReminderSame(a: Reminder | null, b: Reminder | null) { return true; } - -export function isRemindersSame(a: ReminderList | null, b: ReminderList | null) { - if (a === null && b !== null) { - return false; - } else if (a !== null && b === null) { - return false; - } else if (a !== null && b !== null) { - if (a.reminders.length !== b.reminders.length) { - return false; - } - - const sortedA = a.reminders.map((reminder) => reminder.time.valueOf()).sort(); - const sortedB = b.reminders.map((reminder) => reminder.time.valueOf()).sort(); - - for (let i = 0; i < sortedA.length; i++) { - if (sortedA[i] !== sortedB[i]) { - return false; - } - } - } - - return true; -} From 38ce5b6d9323e2fa4e003190614aa040c31fe549 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 19:28:23 +0100 Subject: [PATCH 62/93] comment: Add some TODOs of things to check --- src/Query/Filter/ReminderDateField.ts | 1 + src/Query/Query.ts | 4 ++-- src/Reminders/Components/Reminder.svelte | 4 ++-- src/Reminders/Reminder.ts | 9 ++++++--- src/Task.ts | 2 ++ src/TaskSerializer/DataviewTaskSerializer.ts | 2 ++ src/TaskSerializer/DefaultTaskSerializer.ts | 4 +++- src/main.ts | 1 + tests/Query.test.ts | 1 + tests/Query/Filter/ReminderDateField.test.ts | 2 ++ tests/TaskSerializer/TaskSerializer.test.ts | 1 + 11 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/Query/Filter/ReminderDateField.ts b/src/Query/Filter/ReminderDateField.ts index b77315f17a..243adf34b1 100644 --- a/src/Query/Filter/ReminderDateField.ts +++ b/src/Query/Filter/ReminderDateField.ts @@ -2,6 +2,7 @@ import type { Moment } from 'moment'; import type { Task } from '../../Task'; import { DateField } from './DateField'; +// TODO Think about how this handles times - can users query times? export class ReminderDateField extends DateField { public fieldName(): string { return 'reminder'; diff --git a/src/Query/Query.ts b/src/Query/Query.ts index 15b70202c5..eb2da6c5d8 100644 --- a/src/Query/Query.ts +++ b/src/Query/Query.ts @@ -20,7 +20,7 @@ export class Query implements IQuery { private _grouping: Grouper[] = []; private readonly hideOptionsRegexp = - /^(hide|show) (task count|backlink|priority|created date|start date|scheduled date|done date|due date|recurrence rule|edit button|urgency|reminder date|reminders)/; + /^(hide|show) (task count|backlink|priority|created date|start date|scheduled date|done date|due date|recurrence rule|edit button|urgency|reminder date|reminders)/; // TODO Remove reminders private readonly shortModeRegexp = /^short/; private readonly explainQueryRegexp = /^explain/; @@ -193,7 +193,7 @@ export class Query implements IQuery { this._layoutOptions.hideDueDate = hide; break; case 'reminder date': - case 'reminders': + case 'reminders': // TODO Remove reminders this._layoutOptions.hideReminders = hide; break; case 'done date': diff --git a/src/Reminders/Components/Reminder.svelte b/src/Reminders/Components/Reminder.svelte index b331fd80e4..d8db028846 100644 --- a/src/Reminders/Components/Reminder.svelte +++ b/src/Reminders/Components/Reminder.svelte @@ -27,7 +27,7 @@

@@ -48,7 +48,7 @@ {#each laters as i} - + {/each}
diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index 37740355c5..cca7c0ccb6 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -3,9 +3,9 @@ import { TIME_FORMATS, getSettings } from '../Config/Settings'; export class ReminderSettings { notificationTitle: string = 'Task Reminder'; - dateFormat: string = 'YYYY-MM-DD'; - dateTimeFormat: string = TIME_FORMATS.twelveHour; - dailyReminderTime: string = '9:00 am'; + dateFormat: string = 'YYYY-MM-DD'; // TODO Do not put format strings in user settings + dateTimeFormat: string = TIME_FORMATS.twelveHour; // TODO Do not put format strings in user settings - give this format an alias/name and use that in settings + dailyReminderTime: string = '9:00 am'; // TODO Use 24 hour clock for this setting refreshIntervalMilliseconds: number = 5 * 1000; constructor() {} @@ -34,17 +34,20 @@ export class Reminder { return this.time.format(reminderSettings.dateTimeFormat); } + // TODO Rename this to identicalTo() - for consistency with similar methods...?? Check how Task does it isSame(other: Reminder | null) { return isReminderSame(this, other); } } +// TODO Move this to a named constructor export function parseDateTime(dateTime: string): Reminder { const reminderSettings = getSettings().reminderSettings; const reminder = window.moment(dateTime, reminderSettings.dateTimeFormat); return parseMoment(reminder); } +// TODO Move this to a named constructor export function parseMoment(reminder: Moment): Reminder { if (reminder.format('h:mm a') === '12:00 am') { //aka .startOf(day) which is the default time for reminders diff --git a/src/Task.ts b/src/Task.ts index ea04274f1b..95204e256d 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -365,6 +365,8 @@ export class Task { } // toggle task status and update + // TODO Understand why this method exists. + // TODO Check that this method has tests. public toggleUpdate() { const newTasks = this.toggle(); diff --git a/src/TaskSerializer/DataviewTaskSerializer.ts b/src/TaskSerializer/DataviewTaskSerializer.ts index cde38a76ad..22007bd6b1 100644 --- a/src/TaskSerializer/DataviewTaskSerializer.ts +++ b/src/TaskSerializer/DataviewTaskSerializer.ts @@ -80,6 +80,8 @@ export const DATAVIEW_SYMBOLS = { dueDateRegex: toInlineFieldRegex(/due:: *(\d{4}-\d{2}-\d{2})/), doneDateRegex: toInlineFieldRegex(/completion:: *(\d{4}-\d{2}-\d{2})/), recurrenceRegex: toInlineFieldRegex(/repeat:: *([a-zA-Z0-9, !]+)/), + // TODO Remove the duplication of regex from emoji parsing code + // TODO Add tests and then actually hook up reminder reading in dataview format reminderRegex: toInlineFieldRegex( /remind:: *((\d{4}-\d{2}-\d{2}(?: \d{1,2}:\d{2} (?:am|pm|PM|AM))?\s*(?:,\s*)?)+)\b/, ), diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index 9e95a5fde4..a4082beb0e 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -67,6 +67,8 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u, doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, + // TODO Too complex Break this down, so it is easy to understand. + // TODO Does not work with Am. Add 'i' to make it case-insensitive. reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?)/u, }, } as const; @@ -210,7 +212,7 @@ export class DefaultTaskSerializer implements TaskSerializer { // (e.g. #tag1 #tag2), they do not have to all trail all task components, // but eventually we want to paste them back to the task description at the end let trailingTags = ''; - const { reminderSettings } = getSettings(); + const { reminderSettings } = getSettings(); // TODO Add an abstraction for reminder settings // Add a "max runs" failsafe to never end in an endless loop: const maxRuns = 20; let runs = 0; diff --git a/src/main.ts b/src/main.ts index 9ad36dc09f..1217611c40 100644 --- a/src/main.ts +++ b/src/main.ts @@ -62,6 +62,7 @@ export default class TasksPlugin extends Plugin { if (this.taskNotification && this.cache) { this.registerInterval(this.taskNotification.watcher(this.cache) ?? 0); } + // TODO Is it possible for this.cache to not yet be set up? }); } diff --git a/tests/Query.test.ts b/tests/Query.test.ts index 49a9a39c2e..1aea9b41d8 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -418,6 +418,7 @@ describe('Query', () => { ], }, ], + // TODO Move these new tests to ReminderDateField tests [ 'by reminder date presence', { diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index 9e74173baa..237b9f9feb 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -29,6 +29,8 @@ describe('sorting by reminder', () => { // These are minimal tests just to confirm basic behaviour is set up for this field. // Thorough testing is done in DueDateField.test.ts. + // TODO Needs to test that sorting also honours the time value, so multiple reminders on same date are sorted correctly. + const date1 = new TaskBuilder().reminders('2021-01-12').build(); const date2 = new TaskBuilder().reminders('2022-12-23').build(); diff --git a/tests/TaskSerializer/TaskSerializer.test.ts b/tests/TaskSerializer/TaskSerializer.test.ts index b1c98ff735..fa154f2e9e 100644 --- a/tests/TaskSerializer/TaskSerializer.test.ts +++ b/tests/TaskSerializer/TaskSerializer.test.ts @@ -104,6 +104,7 @@ describe('TaskSerializer Example', () => { }); }); + // TODO Figure out why this changed. it('should parse a priority and description', () => { expect(ts.deserialize('1 Wobble')).toMatchTaskDetails({ priority: Priority.High, From 160007bf01e2e48803fa04de2c0d5b930d800489 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 20:43:22 +0100 Subject: [PATCH 63/93] comment: Add more TODOs of things to check --- src/Reminders/Notification.ts | 3 +++ src/Task.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Reminders/Notification.ts b/src/Reminders/Notification.ts index 77f66783f4..38594c5932 100644 --- a/src/Reminders/Notification.ts +++ b/src/Reminders/Notification.ts @@ -127,12 +127,15 @@ export class TaskNotification { ).open(); } + // TODO How does this work with recurring tasks? private onDone(task: Task) { if (!task.status.isCompleted()) { task.toggleUpdate(); } } + // TODO Understand and test this. + // TODO What happens if this is invoked in a way that fails to find the task??? private async onOpenFile(task: Task) { const result = await getTaskLineAndFile(task, app.vault); if (result) { diff --git a/src/Task.ts b/src/Task.ts index 95204e256d..41c4000442 100644 --- a/src/Task.ts +++ b/src/Task.ts @@ -104,7 +104,7 @@ export class Task { public readonly taskLocation: TaskLocation; public readonly tags: string[]; - public readonly reminder: Reminder | null; + public readonly reminder: Reminder | null; // TODO Move this to after doneDate?? public readonly priority: Priority; From 52ae155d348737a004fc79df4dfeb60871dd04d2 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Tue, 16 May 2023 22:33:18 +0100 Subject: [PATCH 64/93] docs: Minor updates to Task-Reminders.md --- docs/advanced/Task-Reminders.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/advanced/Task-Reminders.md b/docs/advanced/Task-Reminders.md index 355efced6d..00ea80989f 100644 --- a/docs/advanced/Task-Reminders.md +++ b/docs/advanced/Task-Reminders.md @@ -1,17 +1,17 @@ --- -publish: false +publish: true --- -# Notifications +# Tasks Reminders #plugin/reminder -Within Tasks, reminder notifications can be set using the standard Tasks format `⏲️ YYYY-MM-DD` for daily notifications at a set or by specifying the hour `⏲️ YYYY-MM-DD h:mm a`. Multiple reminders can be set by separating secondary reminders with a comma: `⏲️ 2000-03-24, 2000-03-28 10:05 am, 2000-03-31` +Within Tasks, reminder notifications can be set using the standard Tasks format `⏲️ YYYY-MM-DD` for daily notifications at a set or by specifying the hour `⏲️ YYYY-MM-DD h:mm a`. -## Limitnations +## Limitations -- It's not posible to set a reminder for midnight 12:00 am. This due to a limination with how tasks creates dates using momentjs which sets the defaults time to midnight when one isn't provided. -- System notifications don't work on Mobile because Obsidian doesn't provide an API +- It's not possible to set a reminder for midnight 12:00 am. This due to a limitation with how tasks creates dates using `momentjs` which sets the defaults time to midnight when one isn't provided. +- System notifications don't work on Mobile because Obsidian doesn't provide an API. ## How to complete the reminder From d65a0bbf8f5fa673a985be63658952784aeaa7c6 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 17:45:35 +0100 Subject: [PATCH 65/93] docs: Add Reminder to Dates page --- docs/Getting Started/Dates.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/Getting Started/Dates.md b/docs/Getting Started/Dates.md index c4d9f35070..897dc010ff 100644 --- a/docs/Getting Started/Dates.md +++ b/docs/Getting Started/Dates.md @@ -81,6 +81,28 @@ starts before tomorrow --- +### Reminder date and time + +You can ask Tasks for a reminder to do a task. And unlike all the other dates, reminders can have times. + +Reminder dates use a timer clock emoji: ⏲ + +```markdown +- [ ] take out the trash ⏲ 2021-04-09 9:50 am +``` + +This differs from the [[Notifications|Reminders plugin]], which uses ⏰. + +For more information on the Reminders facility in Tasks, see [[Task-Reminders]]. + +> [!warning] +> The time format for task reminders may change. + +> [!released] +> Reminder support was introduced in Tasks X.Y.Z. + +--- + ## Track task histories This section explains the types of dates that Tasks can add for you automatically. From 0c764f91a3ac604c22e9af3409595236ae86ac79 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 17:46:16 +0100 Subject: [PATCH 66/93] docs: Page about Reminders plugin links to the Tasks reminders page --- docs/Advanced/Notifications.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Advanced/Notifications.md b/docs/Advanced/Notifications.md index 02f88d8daf..a1a4169f22 100644 --- a/docs/Advanced/Notifications.md +++ b/docs/Advanced/Notifications.md @@ -6,6 +6,9 @@ publish: true #plugin/reminder +> [!Tip] +> Since Tasks X.Y.Z, the Tasks plugin has built-in support for reminders. See [[Task-Reminders]] and [[Dates#Reminder date and time]]. + Within Tasks, notifications can be made possible by utilizing [obsidian-reminder](https://github.com/uphy/obsidian-reminder). This utilizes the standard Tasks date (as the due date) and can be extended with an additional reminder date by including a ⏰ and a date/time in the format `⏰ YYYY-MM-DD HH:MM`. Further, a default reminder can be enabled based on the Tasks' 'Due Date'. From b562031c5eeb42ecf0f101fed59c44f320d66624 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 17:46:39 +0100 Subject: [PATCH 67/93] test: Add tests that show that sorting by reminders honours the time --- tests/Query/Filter/ReminderDateField.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index 237b9f9feb..c61204d1cf 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -33,9 +33,14 @@ describe('sorting by reminder', () => { const date1 = new TaskBuilder().reminders('2021-01-12').build(); const date2 = new TaskBuilder().reminders('2022-12-23').build(); + const date3 = new TaskBuilder().reminders('2022-12-23 09:27').build(); + const date4 = new TaskBuilder().reminders('2022-12-23 13:59').build(); it('sort by reminder', () => { - expectTaskComparesBefore(new ReminderDateField().createNormalSorter(), date1, date2); + const sorter = new ReminderDateField().createNormalSorter(); + expectTaskComparesBefore(sorter, date1, date2); + expectTaskComparesBefore(sorter, date2, date3); + expectTaskComparesBefore(sorter, date3, date4); }); it('sort by reminder reverse', () => { From 16eef0341aeb5db1a8dae219ede501f3e664fa53 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 17:49:04 +0100 Subject: [PATCH 68/93] docs: Note the release version of Reminders facility --- docs/advanced/Task-Reminders.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/advanced/Task-Reminders.md b/docs/advanced/Task-Reminders.md index 00ea80989f..bc432e6902 100644 --- a/docs/advanced/Task-Reminders.md +++ b/docs/advanced/Task-Reminders.md @@ -6,6 +6,9 @@ publish: true #plugin/reminder +> [!released] +> Reminder support was introduced in Tasks X.Y.Z. + Within Tasks, reminder notifications can be set using the standard Tasks format `⏲️ YYYY-MM-DD` for daily notifications at a set or by specifying the hour `⏲️ YYYY-MM-DD h:mm a`. ## Limitations From 96e9af32ea03b21f719fd0a3b46960ee75d2bfc6 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 17:52:31 +0100 Subject: [PATCH 69/93] docs: Document 'sort by reminder' --- docs/Queries/Sorting.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Queries/Sorting.md b/docs/Queries/Sorting.md index 8d252de129..00ba12a6a1 100644 --- a/docs/Queries/Sorting.md +++ b/docs/Queries/Sorting.md @@ -35,13 +35,15 @@ You can sort tasks by the following properties. 1. `created` (the date when the task was created) 1. `start` (the date when the task starts) 1. `scheduled` (the date when the task is scheduled) +1. `reminder` (the reminder date and optional time) 1. `due` (the date when the task is due) 1. `done` (the date when the task was done) 1. `happens` (the earliest of start date, scheduled date, and due date) > [!released] `sort by happens` was introduced in Tasks 1.21.0.
-`sort by created` was introduced in Tasks 2.0.0. +`sort by created` was introduced in Tasks 2.0.0.
+`sort by reminder` was introduced in Tasks X.Y.Z. ### Task statuses From 4c7067e470f7c7b1b324303228ce8d2563e79c91 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 18:42:11 +0100 Subject: [PATCH 70/93] test: Remove a TODO that I have now done. --- tests/Query/Filter/ReminderDateField.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index c61204d1cf..72069cfbf0 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -29,8 +29,8 @@ describe('sorting by reminder', () => { // These are minimal tests just to confirm basic behaviour is set up for this field. // Thorough testing is done in DueDateField.test.ts. - // TODO Needs to test that sorting also honours the time value, so multiple reminders on same date are sorted correctly. - + // Because Reminder is the first field to support times, we do need to test + // that sorting takes account of differences in reminder time. const date1 = new TaskBuilder().reminders('2021-01-12').build(); const date2 = new TaskBuilder().reminders('2022-12-23').build(); const date3 = new TaskBuilder().reminders('2022-12-23 09:27').build(); From 56c04be3ae613f7035dc6e44a86fc471dfa9947c Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 19:31:05 +0100 Subject: [PATCH 71/93] test: Remove tests that use twelveHour format This is part of moving to 24-hour format only, for consistency with the Reminders plugin. --- tests/Recurrence.test.ts | 19 ---------- tests/Reminder/Reminder.test.ts | 27 -------------- .../DefaultTaskSerializer.test.ts | 37 ------------------- 3 files changed, 83 deletions(-) diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index 8f0d83de59..6ca41a0d98 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -259,25 +259,6 @@ describe('identicalTo', () => { }); describe('Recurrence - with reminders', () => { - it('creates a recurring instance with single 12h reminders', () => { - // Arrange - const recurrence = Recurrence.fromText({ - recurrenceRuleText: 'every week', - startDate: null, - scheduledDate: null, - dueDate: null, - reminder: parseMoment(moment('2021-06-20 10:00 am', TIME_FORMATS.twelveHour)), - }); - - // Act - const next = recurrence!.next(); - - // Assert - expect( - next!.reminder!.isSame(parseMoment(moment('2021-06-27 10:00 am', TIME_FORMATS.twelveHour))), - ).toStrictEqual(true); - }); - it('creates a recurring instance with single 24h reminders', () => { // Arrange const originalReminder = parseMoment(moment('2021-06-20 13:00', TIME_FORMATS.twentyFourHour)); diff --git a/tests/Reminder/Reminder.test.ts b/tests/Reminder/Reminder.test.ts index 38433bc3b3..4dca6d74a8 100644 --- a/tests/Reminder/Reminder.test.ts +++ b/tests/Reminder/Reminder.test.ts @@ -16,13 +16,6 @@ function checkParsedDateTime(input: string, output: string) { } describe('should parse Moment() dates & times as reminder: ', () => { - it('test Moment() datetime 12 hr', () => { - const reminder = moment('2023-04-30 11:44 am', TIME_FORMATS.twelveHour); - expect(parseMoment(reminder)).toStrictEqual( - new Reminder(moment('2023-04-30 11:44 am', TIME_FORMATS.twelveHour), ReminderType.DateTime), - ); - }); - it('test Moment() datetime 24hr', () => { const reminder = moment('2023-04-30 13:45', TIME_FORMATS.twentyFourHour); expect(parseMoment(reminder)).toStrictEqual( @@ -41,18 +34,6 @@ describe('should parse dates & times string as reminder: ', () => { resetSettings(); }); - it('test 12-hour format', () => { - setDateTimeFormat(TIME_FORMATS.twelveHour); - - checkParsedDateTime('2023-01-15', '2023-01-15'); - checkParsedDateTime('2023-01-15 1:45 am', '2023-01-15 1:45 am'); - checkParsedDateTime('12/13/2019', 'Invalid date'); - checkParsedDateTime('2024-01-15 13:45', '2024-01-15 1:45 pm'); // 12-hour format reads 24-hour OK - checkParsedDateTime('2023-01-15 1:45', '2023-01-15 1:45 am'); // forgeting am/pm defaults to am - checkParsedDateTime('2023-01-15 1:45 p', '2023-01-15 1:45 pm'); // can handle partial am/pm - checkParsedDateTime('2023-01-15 01:45 pm', '2023-01-15 1:45 pm'); // can handle leading zero - }); - it('test 24-hour format', () => { setDateTimeFormat(TIME_FORMATS.twentyFourHour); @@ -67,14 +48,6 @@ describe('should parse task strings: ', () => { resetSettings(); }); - it('valid task - in 12-hour format', () => { - setDateTimeFormat(TIME_FORMATS.twelveHour); - - const line = '- [ ] #task Reminder at 13:57 ⏲️ 2023-05-03 1:57 pm'; - const task = fromLine({ line: line }); - expect(task.reminder).not.toBeNull(); - }); - it('valid task - in 24-hour format', () => { setDateTimeFormat(TIME_FORMATS.twentyFourHour); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index 6cd282d6ce..be420d4a1f 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -86,23 +86,6 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ resetSettings(); }); - it('should parse a single 12h reminder', () => { - setDateTimeFormat(TIME_FORMATS.twelveHour); - const times = [ - '2021-06-20 1:45 pm', - '2021-06-20 1:45 am', - '2021-06-20 01:45 PM', - '2021-06-20 01:45 AM', - '2021-06-20', - ]; - times.forEach((time) => { - const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); - expect(taskDetails).toMatchTaskDetails({ - reminder: parseMoment(moment(time, TIME_FORMATS.twelveHour)), - }); - }); - }); - it('should parse a single 24h reminder', () => { setDateTimeFormat(TIME_FORMATS.twentyFourHour); const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; @@ -173,26 +156,6 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ resetSettings(); }); - it('should serialize a single 12h reminder', () => { - setDateTimeFormat(TIME_FORMATS.twelveHour); - const times = ['2021-06-20 1:45 pm', '2021-06-20 1:45 am', '2021-06-20']; - - times.forEach((time) => { - const serialized = serialize(new TaskBuilder().reminders(time).description('').build()); - expect(serialized).toEqual(` ${reminderDateSymbol} ${time}`); - }); - }); - - it('should serialize malformed 12h reminders', () => { - setDateTimeFormat(TIME_FORMATS.twelveHour); - const times = ['2021-06-20 01:45 pm', '2021-06-20 1:45 PM', '2021-06-20 01:45 PM', '2021-06-20 1:45 p']; - - times.forEach((time) => { - const serialized = serialize(new TaskBuilder().reminders(time).description('').build()); - expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20 1:45 pm`); - }); - }); - it('should serialize a single 24h reminder', () => { setDateTimeFormat(TIME_FORMATS.twentyFourHour); const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; From 2fa9f9e7e817b0770cc7bbb9e0a944789992b7d5 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 19:48:40 +0100 Subject: [PATCH 72/93] feat!: Make the default reminder format use 24-hour clock This is moving towards consistency with the Reminders plugin. --- src/Reminders/Reminder.ts | 4 ++-- tests/Recurrence.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index cca7c0ccb6..31d4789b16 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -4,8 +4,8 @@ import { TIME_FORMATS, getSettings } from '../Config/Settings'; export class ReminderSettings { notificationTitle: string = 'Task Reminder'; dateFormat: string = 'YYYY-MM-DD'; // TODO Do not put format strings in user settings - dateTimeFormat: string = TIME_FORMATS.twelveHour; // TODO Do not put format strings in user settings - give this format an alias/name and use that in settings - dailyReminderTime: string = '9:00 am'; // TODO Use 24 hour clock for this setting + dateTimeFormat: string = TIME_FORMATS.twentyFourHour; // TODO Remove this option from settings + dailyReminderTime: string = '09:00'; refreshIntervalMilliseconds: number = 5 * 1000; constructor() {} diff --git a/tests/Recurrence.test.ts b/tests/Recurrence.test.ts index 6ca41a0d98..cb5a33f821 100644 --- a/tests/Recurrence.test.ts +++ b/tests/Recurrence.test.ts @@ -278,7 +278,7 @@ describe('Recurrence - with reminders', () => { expect( next!.reminder!.isSame(parseMoment(moment('2021-06-27 13:00', TIME_FORMATS.twentyFourHour))), ).toStrictEqual(true); - expect(next!.reminder!.toString()).toStrictEqual('2021-06-27 1:00 pm'); + expect(next!.reminder!.toString()).toStrictEqual('2021-06-27 13:00'); // Confirm that the original date has not been modified expect(originalReminder.toString()).toStrictEqual(originalReminderAsString); From e12f8811e93de1f5273bff04df7a7065f5f8bda5 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 19:53:23 +0100 Subject: [PATCH 73/93] feat!: Remove the 'Reminder Format' from settings UI As only 24-hour format is now supported. --- src/Config/SettingsTab.ts | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/Config/SettingsTab.ts b/src/Config/SettingsTab.ts index b21e0eea85..0ff60e50d3 100644 --- a/src/Config/SettingsTab.ts +++ b/src/Config/SettingsTab.ts @@ -5,7 +5,7 @@ import { StatusRegistry } from '../StatusRegistry'; import { Status } from '../Status'; import type { StatusCollection } from '../StatusCollection'; import * as Themes from './Themes'; -import { type HeadingState, TASK_FORMATS, TIME_FORMATS } from './Settings'; +import { type HeadingState, TASK_FORMATS } from './Settings'; import { getSettings, isFeatureEnabled, updateGeneralSetting, updateSettings } from './Settings'; import { GlobalFilter } from './GlobalFilter'; import { StatusSettings } from './StatusSettings'; @@ -227,33 +227,11 @@ export class SettingsTab extends PluginSettingTab { containerEl.createEl('h4', { text: 'Reminder Settings' }); // --------------------------------------------------------------------------- - new Setting(containerEl) - .setName('Reminder Format') - .setDesc( - SettingsTab.createFragmentWithHTML( - '

Do not change if reminders already exist, otherwise you will not be notified unless manually converted to new format.

' + - '

The format that Tasks uses to read and write reminders time i.e 12hr 6:00 pm or 24hr 18:00.

', - ), - ) - .addDropdown((dropdown) => { - let k: keyof typeof TIME_FORMATS; - for (k in TIME_FORMATS) { - dropdown.addOption(TIME_FORMATS[k], k); - } - const settings = getSettings().reminderSettings; - - dropdown.setValue(settings.dateTimeFormat).onChange(async (value) => { - settings.dateTimeFormat = value; - updateSettings({ reminderSettings: settings }); - await this.plugin.saveSettings(); - }); - }); - new Setting(containerEl) .setName('Daily Reminder Time') .setDesc( SettingsTab.createFragmentWithHTML( - '

When daily reminders should be triggered. Should be in same format as above i.e 12 or 24hr.

', + '

When daily reminders should be triggered. Should be in the 24-hour clock, for example 09:00 or 21:00.

', ), ) .addText((text) => { From 561a40cf74036044ca5351fcc97f1a7ebde2e844 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 19:58:37 +0100 Subject: [PATCH 74/93] test: Remove test helper setDateTimeFormat() as only 1 format now --- tests/Reminder/Reminder.test.ts | 6 +----- tests/TaskSerializer/DefaultTaskSerializer.test.ts | 3 --- tests/TestHelpers.ts | 7 ------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/Reminder/Reminder.test.ts b/tests/Reminder/Reminder.test.ts index 4dca6d74a8..c97f592829 100644 --- a/tests/Reminder/Reminder.test.ts +++ b/tests/Reminder/Reminder.test.ts @@ -5,7 +5,7 @@ jest.mock('obsidian'); import moment from 'moment'; import { TIME_FORMATS, resetSettings } from '../../src/Config/Settings'; import { Reminder, ReminderType, parseDateTime, parseMoment } from '../../src/Reminders/Reminder'; -import { fromLine, setDateTimeFormat } from '../TestHelpers'; +import { fromLine } from '../TestHelpers'; window.moment = moment; @@ -35,8 +35,6 @@ describe('should parse dates & times string as reminder: ', () => { }); it('test 24-hour format', () => { - setDateTimeFormat(TIME_FORMATS.twentyFourHour); - checkParsedDateTime('2023-01-15', '2023-01-15'); checkParsedDateTime('2023-01-15 13:45', '2023-01-15 13:45'); checkParsedDateTime('2023-01-15 1:45 pm', '2023-01-15 01:45'); // the pm & leading 0 are ignored @@ -49,8 +47,6 @@ describe('should parse task strings: ', () => { }); it('valid task - in 24-hour format', () => { - setDateTimeFormat(TIME_FORMATS.twentyFourHour); - const line = '- [ ] #task Reminder at 13:57 ⏲️ 2023-05-03 13:57'; const task = fromLine({ line: line }); expect(task.reminder).not.toBeNull(); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index be420d4a1f..43a182c592 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -9,7 +9,6 @@ import { RecurrenceBuilder } from '../TestingTools/RecurrenceBuilder'; import { DEFAULT_SYMBOLS, type DefaultTaskSerializerSymbols } from '../../src/TaskSerializer/DefaultTaskSerializer'; import { TaskBuilder } from '../TestingTools/TaskBuilder'; import { parseMoment } from '../../src/Reminders/Reminder'; -import { setDateTimeFormat } from '../TestHelpers'; jest.mock('obsidian'); window.moment = moment; @@ -87,7 +86,6 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ }); it('should parse a single 24h reminder', () => { - setDateTimeFormat(TIME_FORMATS.twentyFourHour); const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; times.forEach((time) => { const taskDetails = deserialize(`${reminderDateSymbol} ${time}`); @@ -157,7 +155,6 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ }); it('should serialize a single 24h reminder', () => { - setDateTimeFormat(TIME_FORMATS.twentyFourHour); const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; times.forEach((time) => { diff --git a/tests/TestHelpers.ts b/tests/TestHelpers.ts index 5f2a4216c6..bbe4ebf720 100644 --- a/tests/TestHelpers.ts +++ b/tests/TestHelpers.ts @@ -1,4 +1,3 @@ -import { getSettings, updateSettings } from '../src/Config/Settings'; import { Task } from '../src/Task'; import { TaskLocation } from '../src/TaskLocation'; @@ -33,9 +32,3 @@ export function createTasksFromMarkdown(tasksAsMarkdown: string, path: string, p } return tasks; } - -export function setDateTimeFormat(dateTimeFormat: string) { - const settings = getSettings().reminderSettings; - settings.dateTimeFormat = dateTimeFormat; - updateSettings({ reminderSettings: settings }); -} From dac37cc83e2f93d5d83ca29764cd851282a29fc3 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 20:06:10 +0100 Subject: [PATCH 75/93] refactor: Get TIME_FORMATS.twentyFourHour directly instead of via settings. It is now the only allowed value in settings anyway. --- src/Reminders/Notification.ts | 4 ++-- src/Reminders/Reminder.ts | 5 ++--- src/TaskSerializer/DefaultTaskSerializer.ts | 5 ++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Reminders/Notification.ts b/src/Reminders/Notification.ts index 38594c5932..0b6e605ace 100644 --- a/src/Reminders/Notification.ts +++ b/src/Reminders/Notification.ts @@ -1,6 +1,6 @@ import { App, Modal } from 'obsidian'; import type { Moment } from 'moment'; -import { getSettings } from '../Config/Settings'; +import { TIME_FORMATS, getSettings } from '../Config/Settings'; import type { Task } from '../Task'; import { Query } from '../Query/Query'; import { sameDateTime } from '../lib/DateTools'; @@ -86,7 +86,7 @@ export class TaskNotification { for (const task of reminderTasks) { const dailyReminderTime = window.moment( `${window.moment().format('YYYY-MM-DD')} ${reminderSettings.dailyReminderTime}`, - reminderSettings.dateTimeFormat, + TIME_FORMATS.twentyFourHour, ); if (task.reminder) { diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index 31d4789b16..bf15b9fe4e 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -31,7 +31,7 @@ export class Reminder { if (this.type === ReminderType.Date) { return this.time.format(reminderSettings.dateFormat); } - return this.time.format(reminderSettings.dateTimeFormat); + return this.time.format(TIME_FORMATS.twentyFourHour); } // TODO Rename this to identicalTo() - for consistency with similar methods...?? Check how Task does it @@ -42,8 +42,7 @@ export class Reminder { // TODO Move this to a named constructor export function parseDateTime(dateTime: string): Reminder { - const reminderSettings = getSettings().reminderSettings; - const reminder = window.moment(dateTime, reminderSettings.dateTimeFormat); + const reminder = window.moment(dateTime, TIME_FORMATS.twentyFourHour); return parseMoment(reminder); } diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index a4082beb0e..a65c090038 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -1,5 +1,5 @@ import type { Moment } from 'moment'; -import { getSettings } from '../Config/Settings'; +import { TIME_FORMATS } from '../Config/Settings'; import { Reminder, parseMoment } from '../Reminders/Reminder'; import { TaskLayout } from '../TaskLayout'; import type { TaskLayoutComponent } from '../TaskLayout'; @@ -212,7 +212,6 @@ export class DefaultTaskSerializer implements TaskSerializer { // (e.g. #tag1 #tag2), they do not have to all trail all task components, // but eventually we want to paste them back to the task description at the end let trailingTags = ''; - const { reminderSettings } = getSettings(); // TODO Add an abstraction for reminder settings // Add a "max runs" failsafe to never end in an endless loop: const maxRuns = 20; let runs = 0; @@ -285,7 +284,7 @@ export class DefaultTaskSerializer implements TaskSerializer { if (reminderMatch !== null) { line = line.replace(TaskFormatRegularExpressions.reminderRegex, '').trim(); const reminderDate2 = reminderMatch[1]; - const reminder = window.moment(reminderDate2, reminderSettings.dateTimeFormat); + const reminder = window.moment(reminderDate2, TIME_FORMATS.twentyFourHour); rList = parseMoment(reminder); matched = true; } From 752ab370675a0cfc5ea8c024c0ea59ca298d1878 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 20:13:52 +0100 Subject: [PATCH 76/93] refactor: Get TIME_FORMATS.twentyFourHour directly instead of via settings. It is now the only allowed value in settings anyway. --- src/ui/EditTask.svelte | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index 422c75e101..2cff2736c8 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -2,7 +2,7 @@ import * as chrono from 'chrono-node'; import { onMount } from 'svelte'; import { Recurrence } from '../Recurrence'; - import { getSettings, TASK_FORMATS } from '../Config/Settings'; + import { getSettings, TASK_FORMATS, TIME_FORMATS} from '../Config/Settings'; import { GlobalFilter } from '../Config/GlobalFilter'; import { Status } from '../Status'; import { Priority, Task } from '../Task'; @@ -120,7 +120,6 @@ typedDate: string, forwardDate: Date | undefined = undefined, ): string { - const { reminderSettings } = getSettings(); if (!typedDate) { return `no ${fieldName} date`; } @@ -129,7 +128,7 @@ }); if (parsed !== null) { if (fieldName === 'reminder') { - return window.moment(parsed).format(reminderSettings.dateTimeFormat); + return window.moment(parsed).format(TIME_FORMATS.twentyFourHour); }else{ return window.moment(parsed).format('YYYY-MM-DD'); } @@ -224,7 +223,7 @@ } onMount(() => { - const { provideAccessKeys, reminderSettings } = getSettings(); + const { provideAccessKeys } = getSettings(); withAccessKeys = provideAccessKeys; const description = GlobalFilter.removeAsWordFrom(task.description); // If we're displaying to the user the description without the global filter (i.e. it was removed in the method @@ -257,7 +256,7 @@ ? task.scheduledDate.format('YYYY-MM-DD') : '', dueDate: task.dueDate ? task.dueDate.format('YYYY-MM-DD') : '', - reminderDate: task.reminder? task.reminder!.time.format(reminderSettings.dateTimeFormat) : '', + reminderDate: task.reminder? task.reminder!.time.format(TIME_FORMATS.twentyFourHour) : '', doneDate: task.doneDate ? task.doneDate.format('YYYY-MM-DD') : '', forwardOnly: true, }; From b79f4972ea78e9c6b2b92628ade219050862f2a4 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 20:19:09 +0100 Subject: [PATCH 77/93] refactor: Remove unused setting dateTimeFormat Code now uses TIME_FORMATS.twentyFourHour directly instead. This is part of moving towards compatibility with the Reminders plugin --- src/Reminders/Reminder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Reminders/Reminder.ts b/src/Reminders/Reminder.ts index bf15b9fe4e..ac35fde2a9 100644 --- a/src/Reminders/Reminder.ts +++ b/src/Reminders/Reminder.ts @@ -4,7 +4,6 @@ import { TIME_FORMATS, getSettings } from '../Config/Settings'; export class ReminderSettings { notificationTitle: string = 'Task Reminder'; dateFormat: string = 'YYYY-MM-DD'; // TODO Do not put format strings in user settings - dateTimeFormat: string = TIME_FORMATS.twentyFourHour; // TODO Remove this option from settings dailyReminderTime: string = '09:00'; refreshIntervalMilliseconds: number = 5 * 1000; From 41d1b129764fd1b6bbd6ecd3c96eefde525245db Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 20:23:34 +0100 Subject: [PATCH 78/93] refactor: Simplify emoji-based reminderRegex, to only match 24 hour times --- src/TaskSerializer/DefaultTaskSerializer.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index a65c090038..85744f6a78 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -67,9 +67,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u, doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, - // TODO Too complex Break this down, so it is easy to understand. - // TODO Does not work with Am. Add 'i' to make it case-insensitive. - reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}(?:\s(?:am|pm|AM|PM))?)?)/u, + reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)/u, }, } as const; From abb5d2023f6eec8bcc1b7a4c89929253516db94d Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 20:28:21 +0100 Subject: [PATCH 79/93] docs: Update docs now times are always 24-hour clock. --- docs/Getting Started/Dates.md | 5 +---- docs/advanced/Task-Reminders.md | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Getting Started/Dates.md b/docs/Getting Started/Dates.md index 897dc010ff..cb41511dce 100644 --- a/docs/Getting Started/Dates.md +++ b/docs/Getting Started/Dates.md @@ -88,16 +88,13 @@ You can ask Tasks for a reminder to do a task. And unlike all the other dates, r Reminder dates use a timer clock emoji: ⏲ ```markdown -- [ ] take out the trash ⏲ 2021-04-09 9:50 am +- [ ] take out the trash ⏲ 2021-04-09 09:50 ``` This differs from the [[Notifications|Reminders plugin]], which uses ⏰. For more information on the Reminders facility in Tasks, see [[Task-Reminders]]. -> [!warning] -> The time format for task reminders may change. - > [!released] > Reminder support was introduced in Tasks X.Y.Z. diff --git a/docs/advanced/Task-Reminders.md b/docs/advanced/Task-Reminders.md index bc432e6902..bb57ed046a 100644 --- a/docs/advanced/Task-Reminders.md +++ b/docs/advanced/Task-Reminders.md @@ -9,7 +9,10 @@ publish: true > [!released] > Reminder support was introduced in Tasks X.Y.Z. -Within Tasks, reminder notifications can be set using the standard Tasks format `⏲️ YYYY-MM-DD` for daily notifications at a set or by specifying the hour `⏲️ YYYY-MM-DD h:mm a`. +Within Tasks, reminder notifications can be set using either of two formats: + +- the standard Tasks format `⏲️ YYYY-MM-DD` for daily notifications at a set time +- or by specifying the time as well, in 24-hour clock `⏲️ YYYY-MM-DD HH:mm`. ## Limitations From 1d18976c5d490c76e1b499da807a1094f638c8da Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 20:50:04 +0100 Subject: [PATCH 80/93] =?UTF-8?q?feat!:=20Use=20=E2=8F=B0=20for=20reminder?= =?UTF-8?q?s=20for=20eventual=20consistency=20with=20Reminders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Getting Started/Dates.md | 4 +-- docs/advanced/Task-Reminders.md | 4 +-- src/TaskSerializer/DefaultTaskSerializer.ts | 4 +-- tests/Query.test.ts | 28 ++++++++++----------- tests/Reminder/Reminder.test.ts | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/Getting Started/Dates.md b/docs/Getting Started/Dates.md index cb41511dce..928e81b0d8 100644 --- a/docs/Getting Started/Dates.md +++ b/docs/Getting Started/Dates.md @@ -85,10 +85,10 @@ starts before tomorrow You can ask Tasks for a reminder to do a task. And unlike all the other dates, reminders can have times. -Reminder dates use a timer clock emoji: ⏲ +Reminder dates use an alarm clock emoji: ⏰ ```markdown -- [ ] take out the trash ⏲ 2021-04-09 09:50 +- [ ] take out the trash ⏰ 2021-04-09 09:50 ``` This differs from the [[Notifications|Reminders plugin]], which uses ⏰. diff --git a/docs/advanced/Task-Reminders.md b/docs/advanced/Task-Reminders.md index bb57ed046a..4f0fd1f7d7 100644 --- a/docs/advanced/Task-Reminders.md +++ b/docs/advanced/Task-Reminders.md @@ -11,8 +11,8 @@ publish: true Within Tasks, reminder notifications can be set using either of two formats: -- the standard Tasks format `⏲️ YYYY-MM-DD` for daily notifications at a set time -- or by specifying the time as well, in 24-hour clock `⏲️ YYYY-MM-DD HH:mm`. +- the standard Tasks format `⏰️ YYYY-MM-DD` for daily notifications at a set time +- or by specifying the time as well, in 24-hour clock `⏰️ YYYY-MM-DD HH:mm`. ## Limitations diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts index 85744f6a78..56b631d814 100644 --- a/src/TaskSerializer/DefaultTaskSerializer.ts +++ b/src/TaskSerializer/DefaultTaskSerializer.ts @@ -56,7 +56,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateSymbol: '📅', doneDateSymbol: '✅', recurrenceSymbol: '🔁', - reminderDateSymbol: '⏲️', + reminderDateSymbol: '⏰️', TaskFormatRegularExpressions: { // The following regex's end with `$` because they will be matched and // removed from the end until none are left. @@ -67,7 +67,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = { dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u, doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u, recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu, - reminderRegex: /⏲️ *(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)/u, + reminderRegex: /⏰️ *(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)/u, }, } as const; diff --git a/tests/Query.test.ts b/tests/Query.test.ts index 1aea9b41d8..34551a0697 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -425,12 +425,12 @@ describe('Query', () => { filters: ['has reminder date'], tasks: [ '- [ ] task 1', - '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏲️ 2022-04-20', - '- [ ] task 3 ⏲️ 2022-04-20', + '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏰️ 2022-04-20', + '- [ ] task 3 ⏰️ 2022-04-20', ], expectedResult: [ - '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏲️ 2022-04-20', - '- [ ] task 3 ⏲️ 2022-04-20', + '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏰️ 2022-04-20', + '- [ ] task 3 ⏰️ 2022-04-20', ], }, ], @@ -476,8 +476,8 @@ describe('Query', () => { filters: ['no reminder date'], tasks: [ '- [ ] task 1', - '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏲️ 2022-04-20', - '- [ ] task 3 ⏲️ 2022-04-20', + '- [ ] task 2 🛫 2022-04-20 ⏳ 2022-04-20 ⏰️ 2022-04-20', + '- [ ] task 3 ⏰️ 2022-04-20', ], expectedResult: ['- [ ] task 1'], }, @@ -517,11 +517,11 @@ describe('Query', () => { filters: ['reminder before 2022-04-20'], tasks: [ '- [ ] task 1', - '- [ ] task 2 ⏲️ 2022-04-15', - '- [ ] task 3 ⏲️ 2022-04-20', - '- [ ] task 4 ⏲️ 2022-04-25', + '- [ ] task 2 ⏰️ 2022-04-15', + '- [ ] task 3 ⏰️ 2022-04-20', + '- [ ] task 4 ⏰️ 2022-04-25', ], - expectedResult: ['- [ ] task 2 ⏲️ 2022-04-15'], + expectedResult: ['- [ ] task 2 ⏰️ 2022-04-15'], }, ], [ @@ -548,11 +548,11 @@ describe('Query', () => { filters: ['"reminder after 2022-04-20" AND "not done"'], tasks: [ '- [ ] task 1', - '- [ ] task 2 ⏲️ 2022-04-20', - '- [x] task 3 ⏲️ 2022-04-21', - '- [ ] task 4 ⏲️ 2022-04-21', + '- [ ] task 2 ⏰️ 2022-04-20', + '- [x] task 3 ⏰️ 2022-04-21', + '- [ ] task 4 ⏰️ 2022-04-21', ], - expectedResult: ['- [ ] task 4 ⏲️ 2022-04-21'], + expectedResult: ['- [ ] task 4 ⏰️ 2022-04-21'], }, ], ])('should support reminder filter %s', (_, { tasks: allTaskLines, filters, expectedResult }) => { diff --git a/tests/Reminder/Reminder.test.ts b/tests/Reminder/Reminder.test.ts index c97f592829..627b1552e4 100644 --- a/tests/Reminder/Reminder.test.ts +++ b/tests/Reminder/Reminder.test.ts @@ -47,7 +47,7 @@ describe('should parse task strings: ', () => { }); it('valid task - in 24-hour format', () => { - const line = '- [ ] #task Reminder at 13:57 ⏲️ 2023-05-03 13:57'; + const line = '- [ ] #task Reminder at 13:57 ⏰️ 2023-05-03 13:57'; const task = fromLine({ line: line }); expect(task.reminder).not.toBeNull(); }); From a59923061f406b13c09e88c7d7a9d92f71e84798 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 21:05:43 +0100 Subject: [PATCH 81/93] fix: Use consistent & unique reminder shortcut in modal --- src/ui/EditTask.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte index 2cff2736c8..6506867fcd 100644 --- a/src/ui/EditTask.svelte +++ b/src/ui/EditTask.svelte @@ -469,7 +469,7 @@ - + {reminderDateSymbol} {@html parsedReminderDate} From 19ec47d0954db421af65b8d4fd78ff2dcae79878 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 21:10:02 +0100 Subject: [PATCH 82/93] test: Rename TaskBuilder.reminders() --- tests/Query/Filter/ReminderDateField.test.ts | 10 +++++----- tests/Task.test.ts | 10 +++++----- tests/TaskSerializer/DefaultTaskSerializer.test.ts | 4 ++-- tests/TestingTools/TaskBuilder.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index 72069cfbf0..418fc57b0c 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -31,10 +31,10 @@ describe('sorting by reminder', () => { // Because Reminder is the first field to support times, we do need to test // that sorting takes account of differences in reminder time. - const date1 = new TaskBuilder().reminders('2021-01-12').build(); - const date2 = new TaskBuilder().reminders('2022-12-23').build(); - const date3 = new TaskBuilder().reminders('2022-12-23 09:27').build(); - const date4 = new TaskBuilder().reminders('2022-12-23 13:59').build(); + const date1 = new TaskBuilder().reminder('2021-01-12').build(); + const date2 = new TaskBuilder().reminder('2022-12-23').build(); + const date3 = new TaskBuilder().reminder('2022-12-23 09:27').build(); + const date4 = new TaskBuilder().reminder('2022-12-23 13:59').build(); it('sort by reminder', () => { const sorter = new ReminderDateField().createNormalSorter(); @@ -56,7 +56,7 @@ describe('grouping by reminder date', () => { it('group by reminder date', () => { // Arrange const grouper = new ReminderDateField().createGrouper(); - const taskWithDate = new TaskBuilder().reminders('1970-01-01').build(); + const taskWithDate = new TaskBuilder().reminder('1970-01-01').build(); const taskWithoutDate = new TaskBuilder().build(); // Assert diff --git a/tests/Task.test.ts b/tests/Task.test.ts index 2e46a27a82..64035851e6 100644 --- a/tests/Task.test.ts +++ b/tests/Task.test.ts @@ -1213,11 +1213,11 @@ describe('identicalTo', () => { }); it('should check reminders', () => { - const lhs = new TaskBuilder().reminders('2023-03-07 09:25 am'); - expect(lhs).toBeIdenticalTo(new TaskBuilder().reminders('2023-03-07 09:25 am')); - expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders('')); - expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders('2023-03-07')); - expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminders('2023-03-07 09:27 am')); + const lhs = new TaskBuilder().reminder('2023-03-07 09:25 am'); + expect(lhs).toBeIdenticalTo(new TaskBuilder().reminder('2023-03-07 09:25 am')); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminder('')); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminder('2023-03-07')); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminder('2023-03-07 09:27 am')); }); }); diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts index 43a182c592..51b4168c4b 100644 --- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts +++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts @@ -144,7 +144,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ }); it('should serialize a single reminder date', () => { - const serialized = serialize(new TaskBuilder().reminders('2021-06-20').description('').build()); + const serialized = serialize(new TaskBuilder().reminder('2021-06-20').description('').build()); expect(serialized).toEqual(` ${reminderDateSymbol} 2021-06-20`); }); }); @@ -158,7 +158,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ const times = ['2021-06-20 13:45', '2021-06-20 01:45', '2021-06-20']; times.forEach((time) => { - const serialized = serialize(new TaskBuilder().reminders(time).description('').build()); + const serialized = serialize(new TaskBuilder().reminder(time).description('').build()); expect(serialized).toEqual(` ${reminderDateSymbol} ${time}`); }); }); diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index 5c6949b1f7..9005deb096 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -197,7 +197,7 @@ export class TaskBuilder { return this; } - public reminders(reminder: string): TaskBuilder { + public reminder(reminder: string): TaskBuilder { if (reminder.length > 0) { this._reminders = parseDateTime(reminder); } else { From 223e3f612c190f71499bbfbd17430ed95bdb5d78 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 21:15:52 +0100 Subject: [PATCH 83/93] test: Make TaskBuilder.reminder() consistent with date methods --- tests/Task.test.ts | 2 +- tests/TestingTools/TaskBuilder.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Task.test.ts b/tests/Task.test.ts index 64035851e6..e85fb20605 100644 --- a/tests/Task.test.ts +++ b/tests/Task.test.ts @@ -1215,7 +1215,7 @@ describe('identicalTo', () => { it('should check reminders', () => { const lhs = new TaskBuilder().reminder('2023-03-07 09:25 am'); expect(lhs).toBeIdenticalTo(new TaskBuilder().reminder('2023-03-07 09:25 am')); - expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminder('')); + expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminder(null)); expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminder('2023-03-07')); expect(lhs).not.toBeIdenticalTo(new TaskBuilder().reminder('2023-03-07 09:27 am')); }); diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts index 9005deb096..ae1ba59ce0 100644 --- a/tests/TestingTools/TaskBuilder.ts +++ b/tests/TestingTools/TaskBuilder.ts @@ -197,8 +197,8 @@ export class TaskBuilder { return this; } - public reminder(reminder: string): TaskBuilder { - if (reminder.length > 0) { + public reminder(reminder: string | null): TaskBuilder { + if (reminder) { this._reminders = parseDateTime(reminder); } else { this._reminders = null; From 129e64ba15decb73e41d6c567f9a136d8fe3c73f Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 21:46:49 +0100 Subject: [PATCH 84/93] test: Reimplement testTaskFilter() using custom matchers For more detailed output on failure. --- tests/TestingTools/FilterTestHelpers.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/TestingTools/FilterTestHelpers.ts b/tests/TestingTools/FilterTestHelpers.ts index 1babf70a1b..2047a142bb 100644 --- a/tests/TestingTools/FilterTestHelpers.ts +++ b/tests/TestingTools/FilterTestHelpers.ts @@ -25,9 +25,12 @@ export function testFilter(filter: FilterOrErrorMessage, taskBuilder: TaskBuilde * @param expected true if the task should match the filter, and false otherwise. */ export function testTaskFilter(filter: FilterOrErrorMessage, task: Task, expected: boolean) { - expect(filter.filterFunction).toBeDefined(); - expect(filter.error).toBeUndefined(); - expect(filter.filterFunction!(task)).toEqual(expected); + expect(filter).toBeValid(); + if (expected) { + expect(filter).toMatchTask(task); + } else { + expect(filter).not.toMatchTask(task); + } } /** From 13fc7cc78fc2f707efb2831e3535e4495a1c101a Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 21:47:46 +0100 Subject: [PATCH 85/93] test: When custom filter toMatchTask() fails, display instruction --- tests/CustomMatchers/CustomMatchersForFilters.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/CustomMatchers/CustomMatchersForFilters.ts b/tests/CustomMatchers/CustomMatchersForFilters.ts index c4c6c849a5..705f577a45 100644 --- a/tests/CustomMatchers/CustomMatchersForFilters.ts +++ b/tests/CustomMatchers/CustomMatchersForFilters.ts @@ -138,13 +138,17 @@ export function toMatchTask(filter: FilterOrErrorMessage, task: Task) { const matches = filter.filterFunction!(task); if (!matches) { return { - message: () => `unexpected failure to match task: ${task.toFileLineString()}`, + message: () => `unexpected failure to match +task: "${task.toFileLineString()}" +with filter: "${filter.instruction}"`, pass: false, }; } return { - message: () => `filter should not have matched task: ${task.toFileLineString()}`, + message: () => `filter should not have matched +task: "${task.toFileLineString()}" +with filter: "${filter.instruction}"`, pass: true, }; } From bf6075fdcc915cd9ad1f414a9b9f420409e0f7af Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 21:49:42 +0100 Subject: [PATCH 86/93] test: Show issues with times in reminder filters & tasks This definitely needs more work. Users will be confused and miss out on tasks they will probably think should match. --- tests/Query/Filter/ReminderDateField.test.ts | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index 418fc57b0c..bfc82891a7 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -5,9 +5,51 @@ import moment from 'moment'; import { ReminderDateField } from '../../../src/Query/Filter/ReminderDateField'; import { TaskBuilder } from '../../TestingTools/TaskBuilder'; import { expectTaskComparesAfter, expectTaskComparesBefore } from '../../CustomMatchers/CustomMatchersForSorting'; +import type { FilterOrErrorMessage } from '../../../src/Query/Filter/Filter'; +import { testFilter } from '../../TestingTools/FilterTestHelpers'; window.moment = moment; +function testTaskFilterForTaskWithReminderDate( + filter: FilterOrErrorMessage, + reminderDateTime: string | null, + expected: boolean, +) { + const builder = new TaskBuilder(); + testFilter(filter, builder.reminder(reminderDateTime), expected); +} + +describe('reminder date', () => { + afterAll(() => { + jest.useRealTimers(); + }); + + it('by reminder date (on) - with filter containing date only', () => { + // Arrange + const filter = new ReminderDateField().createFilterOrErrorMessage('reminder on 2022-04-20'); + + // Act, Assert + testTaskFilterForTaskWithReminderDate(filter, null, false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-15', false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 09:15', false); // TODO should be true, I think + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-25', false); + }); + + it('by reminder date (on) - with filter containing date and time', () => { + // Arrange + const filter = new ReminderDateField().createFilterOrErrorMessage('reminder on 2022-04-20 15:43'); + + // Act, Assert + testTaskFilterForTaskWithReminderDate(filter, null, false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-15', false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20', true); // TODO should be false, I think + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 09:15', false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 15:43', false); // TODO should be true, I think + testTaskFilterForTaskWithReminderDate(filter, '2022-04-25', false); + }); +}); + describe('explain reminder date queries', () => { it('should explain explicit date', () => { const filterOrMessage = new ReminderDateField().createFilterOrErrorMessage('reminder before 2023-01-02'); From c90236fbaec4363d6c0fdf4b452d6e53056a0109 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 22:26:22 +0100 Subject: [PATCH 87/93] fix: Filtering of task reminders strips off time, so that searches work. I will have to document that it's not possible to search for tasks with reminders at a particular time of day. --- src/Query/Filter/ReminderDateField.ts | 49 +++++++++++++++++++- tests/Query/Filter/ReminderDateField.test.ts | 12 +++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/Query/Filter/ReminderDateField.ts b/src/Query/Filter/ReminderDateField.ts index 243adf34b1..26b7afa3c6 100644 --- a/src/Query/Filter/ReminderDateField.ts +++ b/src/Query/Filter/ReminderDateField.ts @@ -1,14 +1,50 @@ import type { Moment } from 'moment'; import type { Task } from '../../Task'; +import type { Comparator } from '../Sorter'; +import { compareByDate } from '../../lib/DateTools'; import { DateField } from './DateField'; -// TODO Think about how this handles times - can users query times? +/** + * ReminderDateField provides filter, sorting and grouping of tasks by their reminder value. + * + * **Filtering** by reminder ignores times completely. Only the dates in the task reminders + * and in the filter line are used. It is not possible to search for reminders at a particular + * time of day. + * + * **Sorting** by reminder does use the reminder times, if supplied. + */ export class ReminderDateField extends DateField { public fieldName(): string { return 'reminder'; } + /** + * Return the reminder date **with any time stripped off**. + * + * In order for filtering by reminder to work correctly, this returns the reminder date + * with any time stripped off, because the date-filtering works in whole days currently, + * due to all the other date fields not supporting time. + * + * If you want the reminder date and time, use {@link dateWithTime}. + * @param task + * @see dateWithTime + */ public date(task: Task): Moment | null { + if (task.reminder) { + return task.reminder.time.startOf('day'); + } else { + return null; + } + } + + /** + * Return the reminder date including its time. + * + * If you want just the date, with time stripped off, use {@link date}. + * @param task + * @see date + */ + public dateWithTime(task: Task): Moment | null { if (task.reminder) { return task.reminder.time; } else { @@ -19,4 +55,15 @@ export class ReminderDateField extends DateField { protected filterResultIfFieldMissing() { return false; } + + /** + * Return a function to compare two Task objects by their reminder. + * + * @note This does use any time on reminders, for more precise sorting. + */ + public comparator(): Comparator { + return (a: Task, b: Task) => { + return compareByDate(this.dateWithTime(a), this.dateWithTime(b)); + }; + } } diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index bfc82891a7..76a2b63a60 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -31,7 +31,7 @@ describe('reminder date', () => { // Act, Assert testTaskFilterForTaskWithReminderDate(filter, null, false); testTaskFilterForTaskWithReminderDate(filter, '2022-04-15', false); - testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 09:15', false); // TODO should be true, I think + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 09:15', true); testTaskFilterForTaskWithReminderDate(filter, '2022-04-20', true); testTaskFilterForTaskWithReminderDate(filter, '2022-04-25', false); }); @@ -43,9 +43,13 @@ describe('reminder date', () => { // Act, Assert testTaskFilterForTaskWithReminderDate(filter, null, false); testTaskFilterForTaskWithReminderDate(filter, '2022-04-15', false); - testTaskFilterForTaskWithReminderDate(filter, '2022-04-20', true); // TODO should be false, I think - testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 09:15', false); - testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 15:43', false); // TODO should be true, I think + + // Filter matches whole day, even though it was supplied with a time, so reminders + // with any time on the filter's day should match. + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 09:15', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 15:43', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-25', false); }); }); From 638575be81783f20ee3a0c1c2abb47bcc18a626f Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 22:28:52 +0100 Subject: [PATCH 88/93] test: Show that times on reminder filters are ignored --- tests/Query/Filter/ReminderDateField.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index 76a2b63a60..6fc781154f 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -60,6 +60,11 @@ describe('explain reminder date queries', () => { expect(filterOrMessage).toHaveExplanation('reminder date is before 2023-01-02 (Monday 2nd January 2023)'); }); + it('should show that times in reminder filters are ignored', () => { + const filterOrMessage = new ReminderDateField().createFilterOrErrorMessage('reminder on 2023-01-02 15:43'); + expect(filterOrMessage).toHaveExplanation('reminder date is on 2023-01-02 (Monday 2nd January 2023)'); + }); + it('implicit "on" gets added to explanation', () => { const filterOrMessage = new ReminderDateField().createFilterOrErrorMessage('reminder 2023-01-02'); expect(filterOrMessage).toHaveExplanation('reminder date is on 2023-01-02 (Monday 2nd January 2023)'); From 34e928abeab24c8e54a046fe9e5252c0428d973f Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Wed, 17 May 2023 22:51:39 +0100 Subject: [PATCH 89/93] refactor: Remove unused TIME_FORMATS.twelveHour --- src/Config/Settings.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Config/Settings.ts b/src/Config/Settings.ts index d7acef49cc..8ca07a6647 100644 --- a/src/Config/Settings.ts +++ b/src/Config/Settings.ts @@ -52,7 +52,6 @@ export type TASK_FORMATS = typeof TASK_FORMATS; // For convenience to make some // Time formats for the reminder settings. export const TIME_FORMATS = { - twelveHour: 'YYYY-MM-DD h:mm a', twentyFourHour: 'YYYY-MM-DD HH:mm', }; From 6315caf16f84d014ae4a08b3833b9272837dab44 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Sun, 28 May 2023 08:06:41 +0100 Subject: [PATCH 90/93] test: Sort new instructions in Query.test.ts --- tests/Query.test.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/Query.test.ts b/tests/Query.test.ts index 34551a0697..8999c63a89 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -50,11 +50,11 @@ describe('Query parsing', () => { 'has done date', 'has due date', 'has happens date', + 'has reminder date', 'has scheduled date', 'has start date', 'has tags', 'has tag', - 'has reminder date', 'heading does not include wibble', 'heading includes AND', // Verify Query doesn't confuse this with a boolean query 'heading includes wibble', @@ -64,11 +64,11 @@ describe('Query parsing', () => { 'no due date', 'no due date', 'no happens date', + 'no reminder date', 'no scheduled date', 'no start date', 'no tags', 'no tag', - 'no reminder date', 'not done', 'path does not include some/path', 'path includes AND', // Verify Query doesn't confuse this with a boolean query @@ -81,6 +81,13 @@ describe('Query parsing', () => { 'priority is none', 'recurrence does not include wednesday', 'recurrence includes wednesday', + 'reminder after 2021-12-27', + 'reminder after yesterday', + 'reminder before 2021-12-27', + 'reminder date is invalid', + 'reminder in 2021-12-27 2021-12-29', + 'reminder on 2021-12-27', + 'reminder this week', 'scheduled after 2021-12-27', 'scheduled before 2021-12-27', 'scheduled date is invalid', @@ -93,13 +100,6 @@ describe('Query parsing', () => { 'starts in 2021-12-27 2021-12-29', 'starts on 2021-12-27', 'starts this week', - 'reminder date is invalid', - 'reminder after 2021-12-27', - 'reminder before 2021-12-27', - 'reminder in 2021-12-27 2021-12-29', - 'reminder on 2021-12-27', - 'reminder this week', - 'reminder after yesterday', 'status.name includes cancelled', 'status.type is IN_PROGRESS', 'tag does not include #sometag', @@ -177,10 +177,10 @@ describe('Query parsing', () => { 'sort by path', 'sort by priority reverse', 'sort by priority', - 'sort by scheduled reverse', - 'sort by scheduled', 'sort by reminder reverse', 'sort by reminder', + 'sort by scheduled reverse', + 'sort by scheduled', 'sort by start reverse', 'sort by start', 'sort by status reverse', @@ -222,9 +222,9 @@ describe('Query parsing', () => { 'group by priority', 'group by recurrence', 'group by recurring', + 'group by reminder', 'group by root', 'group by scheduled', - 'group by reminder', 'group by start', 'group by status', 'group by status.name', @@ -254,9 +254,9 @@ describe('Query parsing', () => { 'hide edit button', 'hide priority', 'hide recurrence rule', - 'hide scheduled date', 'hide reminder date', 'hide reminders', + 'hide scheduled date', 'hide start date', 'hide task count', 'hide urgency', @@ -270,8 +270,8 @@ describe('Query parsing', () => { 'show edit button', 'show priority', 'show recurrence rule', - 'show scheduled date', 'show reminder date', + 'show scheduled date', 'show created date', 'show start date', 'show task count', From c43d5940be9441946285571431e3bb2043d22408 Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Sun, 28 May 2023 08:21:06 +0100 Subject: [PATCH 91/93] test: Move some tests from Query.test.ts to ReminderDateField.test.ts The presence and absence ones remain, for consistency with the other date fields. --- tests/Query.test.ts | 34 ------------------ tests/Query/Filter/ReminderDateField.test.ts | 38 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/tests/Query.test.ts b/tests/Query.test.ts index 8999c63a89..e0672d757b 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -418,7 +418,6 @@ describe('Query', () => { ], }, ], - // TODO Move these new tests to ReminderDateField tests [ 'by reminder date presence', { @@ -511,19 +510,6 @@ describe('Query', () => { expectedResult: ['- [ ] task 2 ⏳ 2022-04-15'], }, ], - [ - 'by reminder date (before)', // TODO Erik - { - filters: ['reminder before 2022-04-20'], - tasks: [ - '- [ ] task 1', - '- [ ] task 2 ⏰️ 2022-04-15', - '- [ ] task 3 ⏰️ 2022-04-20', - '- [ ] task 4 ⏰️ 2022-04-25', - ], - expectedResult: ['- [ ] task 2 ⏰️ 2022-04-15'], - }, - ], [ 'by done date (before)', { @@ -540,26 +526,6 @@ describe('Query', () => { }); }); - describe('filtering reminders', () => { - test.concurrent.each<[string, FilteringCase]>([ - [ - 'Reminder after AND not done', - { - filters: ['"reminder after 2022-04-20" AND "not done"'], - tasks: [ - '- [ ] task 1', - '- [ ] task 2 ⏰️ 2022-04-20', - '- [x] task 3 ⏰️ 2022-04-21', - '- [ ] task 4 ⏰️ 2022-04-21', - ], - expectedResult: ['- [ ] task 4 ⏰️ 2022-04-21'], - }, - ], - ])('should support reminder filter %s', (_, { tasks: allTaskLines, filters, expectedResult }) => { - shouldSupportFiltering(filters, allTaskLines, expectedResult); - }); - }); - describe('filtering with "happens"', () => { type HappensCase = { description: string; diff --git a/tests/Query/Filter/ReminderDateField.test.ts b/tests/Query/Filter/ReminderDateField.test.ts index 6fc781154f..946abe3e81 100644 --- a/tests/Query/Filter/ReminderDateField.test.ts +++ b/tests/Query/Filter/ReminderDateField.test.ts @@ -24,6 +24,44 @@ describe('reminder date', () => { jest.useRealTimers(); }); + it('by reminder date (before)', () => { + // Arrange + const filter = new ReminderDateField().createFilterOrErrorMessage('reminder before 2022-04-20 11:45'); + + // Act, Assert + testTaskFilterForTaskWithReminderDate(filter, null, false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-15', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-19 23:59', true); + + // Filter matches whole day, even though it was supplied with a time, so reminders + // with any time on the filter's day should match. + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20', false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 11:44', false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 11:45', false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 11:46', false); + + testTaskFilterForTaskWithReminderDate(filter, '2022-04-21', false); + }); + + it('by reminder date (after)', () => { + // Arrange + const filter = new ReminderDateField().createFilterOrErrorMessage('reminder after 2022-04-19 11:45'); + + // Act, Assert + testTaskFilterForTaskWithReminderDate(filter, null, false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-15', false); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-18 23:59', false); + + // Filter matches whole day, even though it was supplied with a time, so reminders + // with any time on the filter's day should match. + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 11:44', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 11:45', true); + testTaskFilterForTaskWithReminderDate(filter, '2022-04-20 11:46', true); + + testTaskFilterForTaskWithReminderDate(filter, '2022-04-21', true); + }); + it('by reminder date (on) - with filter containing date only', () => { // Arrange const filter = new ReminderDateField().createFilterOrErrorMessage('reminder on 2022-04-20'); From 8efadf6f6df714c1e80a0da6d9a24a2cc421da6e Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Sun, 28 May 2023 08:28:35 +0100 Subject: [PATCH 92/93] fix!!: Remove 'show/hide reminders' option As one reminder, at most, is now supported --- src/Query/Query.ts | 3 +-- tests/Query.test.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Query/Query.ts b/src/Query/Query.ts index eb2da6c5d8..f7059480e0 100644 --- a/src/Query/Query.ts +++ b/src/Query/Query.ts @@ -20,7 +20,7 @@ export class Query implements IQuery { private _grouping: Grouper[] = []; private readonly hideOptionsRegexp = - /^(hide|show) (task count|backlink|priority|created date|start date|scheduled date|done date|due date|recurrence rule|edit button|urgency|reminder date|reminders)/; // TODO Remove reminders + /^(hide|show) (task count|backlink|priority|created date|start date|scheduled date|done date|due date|recurrence rule|edit button|urgency|reminder date)/; private readonly shortModeRegexp = /^short/; private readonly explainQueryRegexp = /^explain/; @@ -193,7 +193,6 @@ export class Query implements IQuery { this._layoutOptions.hideDueDate = hide; break; case 'reminder date': - case 'reminders': // TODO Remove reminders this._layoutOptions.hideReminders = hide; break; case 'done date': diff --git a/tests/Query.test.ts b/tests/Query.test.ts index e0672d757b..934324abb3 100644 --- a/tests/Query.test.ts +++ b/tests/Query.test.ts @@ -255,7 +255,6 @@ describe('Query parsing', () => { 'hide priority', 'hide recurrence rule', 'hide reminder date', - 'hide reminders', 'hide scheduled date', 'hide start date', 'hide task count', From be9ad29d2cc5e3090782d70f2513bfdb7467536e Mon Sep 17 00:00:00 2001 From: Clare Macrae Date: Sun, 28 May 2023 08:30:28 +0100 Subject: [PATCH 93/93] refactor: Rename reminder Layout Option to hideReminderDate As only at most one value is now supported. --- src/Query/Query.ts | 2 +- src/TaskLayout.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Query/Query.ts b/src/Query/Query.ts index f7059480e0..e012fec15f 100644 --- a/src/Query/Query.ts +++ b/src/Query/Query.ts @@ -193,7 +193,7 @@ export class Query implements IQuery { this._layoutOptions.hideDueDate = hide; break; case 'reminder date': - this._layoutOptions.hideReminders = hide; + this._layoutOptions.hideReminderDate = hide; break; case 'done date': this._layoutOptions.hideDoneDate = hide; diff --git a/src/TaskLayout.ts b/src/TaskLayout.ts index ce8ffcbb52..8640dac634 100644 --- a/src/TaskLayout.ts +++ b/src/TaskLayout.ts @@ -11,7 +11,7 @@ export class LayoutOptions { hideScheduledDate: boolean = false; hideDoneDate: boolean = false; hideDueDate: boolean = false; - hideReminders: boolean = false; + hideReminderDate: boolean = false; hideRecurrenceRule: boolean = false; hideEditButton: boolean = false; hideUrgency: boolean = true; @@ -103,7 +103,7 @@ export class TaskLayout { newComponents = removeIf(newComponents, layoutOptions.hideScheduledDate, 'scheduledDate'); newComponents = removeIf(newComponents, layoutOptions.hideDueDate, 'dueDate'); newComponents = removeIf(newComponents, layoutOptions.hideDoneDate, 'doneDate'); - newComponents = removeIf(newComponents, layoutOptions.hideReminders, 'reminders'); + newComponents = removeIf(newComponents, layoutOptions.hideReminderDate, 'reminders'); // The following components are handled in QueryRenderer.ts and thus are not part of the same flow that // hides TaskLayoutComponent items. However, we still want to have 'tasks-layout-hide' items for them // (see https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1866).