Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Reminders fields and Notifications #1925

Draft
wants to merge 97 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
61eb448
System Notifications
Erik-Handeland Apr 11, 2023
24047ba
Update .gitignore
Erik-Handeland Apr 11, 2023
6fbcf75
obsidian modal
Erik-Handeland Apr 12, 2023
c609564
switched from interface to class
Erik-Handeland Apr 13, 2023
cd1819b
Reminder parsing and queries + model field
Erik-Handeland Apr 16, 2023
836bd1e
reminders watcher
Erik-Handeland Apr 17, 2023
a29ad68
hooked up tasks to notification modals
Erik-Handeland Apr 19, 2023
b51b204
Time support
Erik-Handeland Apr 20, 2023
d759ba5
Delete .vscode directory
Erik-Handeland Apr 20, 2023
528fc5b
Update .gitignore
Erik-Handeland Apr 20, 2023
edd333c
Delete src/Query/.vscode directory
Erik-Handeland Apr 20, 2023
71736c9
todos
Erik-Handeland Apr 21, 2023
fa7152b
Merge branch 'reminders' of https://github.com/Erik-Handeland/obsidia…
Erik-Handeland Apr 21, 2023
a4c1945
clean up
Erik-Handeland Apr 21, 2023
d91b4b3
hide reminder date
Erik-Handeland Apr 21, 2023
101b398
added back callback functions
Erik-Handeland Apr 21, 2023
7560b6b
Recurrences now support reminders
Erik-Handeland Apr 24, 2023
ccf8e87
Update Recurrence.ts
Erik-Handeland Apr 24, 2023
7da073f
migrated last reminderDate to reminder[]
Erik-Handeland Apr 26, 2023
7dee6c5
Update Recurrence.ts
Erik-Handeland Apr 26, 2023
8ba4542
cleanup
Erik-Handeland Apr 26, 2023
c596d5c
move from array of Reminder to single Reminders that holds an array o…
Erik-Handeland Apr 29, 2023
be6bfc3
merge main
Erik-Handeland Apr 29, 2023
e88f2cd
openFile callback added
Erik-Handeland Apr 29, 2023
e8aac99
starter documentation
Erik-Handeland Apr 29, 2023
fe065c5
Update Reminders.ts
Erik-Handeland Apr 29, 2023
01e37ca
basic tests
Erik-Handeland Apr 30, 2023
44a1486
Update ReminderList.ts
Erik-Handeland Apr 30, 2023
9565076
refactored ReminderList
Erik-Handeland Apr 30, 2023
110381e
Update Query.test.ts
Erik-Handeland Apr 30, 2023
0f285bd
reminders now uses settings panel
Erik-Handeland Apr 30, 2023
01e0bc5
clean up
Erik-Handeland Apr 30, 2023
44fdfcc
fix recurrence bug & serializer bug
Erik-Handeland Apr 30, 2023
d0e8550
cleaned up notifications
Erik-Handeland Apr 30, 2023
bb6b711
notifications trigger more accurately
Erik-Handeland Apr 30, 2023
9068c75
more tests
Erik-Handeland Apr 30, 2023
c8a755a
Clean up based on git comments
Erik-Handeland May 2, 2023
a21c7b1
Rename notification.ts to Notification.ts
Erik-Handeland May 3, 2023
2e5ea71
Rename Notification.ts to Notification.ts
Erik-Handeland May 3, 2023
187d6e7
Rename Reminder.ts to Reminder.ts
Erik-Handeland May 3, 2023
e770b95
Rename Icon.svelte to Icon.svelte
Erik-Handeland May 3, 2023
378638f
Rename Markdown.svelte to Markdown.svelte
Erik-Handeland May 3, 2023
3731393
Rename Reminder.svelte to Reminder.svelte
Erik-Handeland May 3, 2023
10fa48a
Rename folders and remove main.css
Erik-Handeland May 3, 2023
807a584
Fixed testing issue
Erik-Handeland May 6, 2023
d08fdef
fixed regex and added tests from claremacrae
Erik-Handeland May 6, 2023
24af196
more serialize tests
Erik-Handeland May 6, 2023
a227f0b
query reminder tests
Erik-Handeland May 7, 2023
06482ec
added Recurrence reminder tests
Erik-Handeland May 7, 2023
0a938b3
refactor: Rename reminders to remind in Task and Recurrence
claremacrae May 16, 2023
cee10e5
refactor: Rename reminder field in RecurrenceBuilder.ts to singular
claremacrae May 16, 2023
7a7708b
test: Remove the tests of multi-reminder capability
claremacrae May 16, 2023
d4bd23d
refactor!: Reinstate earlier regex for single date + time
claremacrae May 16, 2023
9c8bba8
refactor: ReminderList constructor now only takes at most one Moment
claremacrae May 16, 2023
b9ea9d8
refactor!: Simplify emoji-date parsing as regex only matches 1 value
claremacrae May 16, 2023
41398f1
refactor: RecurrenceBuilder.reminders() now takes only 1 date
claremacrae May 16, 2023
76e90fa
refactor: TaskBuilder.reminders() now takes only 1 date
claremacrae May 16, 2023
93cb3f2
refactor: Remove parseDateTimes() - no longer used.
claremacrae May 16, 2023
1810893
test: Move 'differing only in reminder' test to correct section
claremacrae May 16, 2023
0c009d5
test: Add some more checks in Recurrence.test.ts
claremacrae May 16, 2023
eb53efb
refactor: Rework Recurrence.next() for single reminder
claremacrae May 16, 2023
a874a49
refactor!: Now only support a single reminder
claremacrae May 16, 2023
6eff445
refactor: Remove ReminderList class as no longer used
claremacrae May 16, 2023
38ce5b6
comment: Add some TODOs of things to check
claremacrae May 16, 2023
160007b
comment: Add more TODOs of things to check
claremacrae May 16, 2023
52ae155
docs: Minor updates to Task-Reminders.md
claremacrae May 16, 2023
d65a0bb
docs: Add Reminder to Dates page
claremacrae May 17, 2023
0c764f9
docs: Page about Reminders plugin links to the Tasks reminders page
claremacrae May 17, 2023
b562031
test: Add tests that show that sorting by reminders honours the time
claremacrae May 17, 2023
16eef03
docs: Note the release version of Reminders facility
claremacrae May 17, 2023
96e9af3
docs: Document 'sort by reminder'
claremacrae May 17, 2023
4c7067e
test: Remove a TODO that I have now done.
claremacrae May 17, 2023
56c04be
test: Remove tests that use twelveHour format
claremacrae May 17, 2023
2fa9f9e
feat!: Make the default reminder format use 24-hour clock
claremacrae May 17, 2023
e12f881
feat!: Remove the 'Reminder Format' from settings UI
claremacrae May 17, 2023
561a40c
test: Remove test helper setDateTimeFormat() as only 1 format now
claremacrae May 17, 2023
dac37cc
refactor: Get TIME_FORMATS.twentyFourHour directly instead of via set…
claremacrae May 17, 2023
752ab37
refactor: Get TIME_FORMATS.twentyFourHour directly instead of via set…
claremacrae May 17, 2023
b79f497
refactor: Remove unused setting dateTimeFormat
claremacrae May 17, 2023
41d1b12
refactor: Simplify emoji-based reminderRegex, to only match 24 hour t…
claremacrae May 17, 2023
abb5d20
docs: Update docs now times are always 24-hour clock.
claremacrae May 17, 2023
1d18976
feat!: Use ⏰ for reminders for eventual consistency with Reminders
claremacrae May 17, 2023
a599230
fix: Use consistent & unique reminder shortcut in modal
claremacrae May 17, 2023
19ec47d
test: Rename TaskBuilder.reminders()
claremacrae May 17, 2023
223e3f6
test: Make TaskBuilder.reminder() consistent with date methods
claremacrae May 17, 2023
129e64b
test: Reimplement testTaskFilter() using custom matchers
claremacrae May 17, 2023
13fc7cc
test: When custom filter toMatchTask() fails, display instruction
claremacrae May 17, 2023
bf6075f
test: Show issues with times in reminder filters & tasks
claremacrae May 17, 2023
c90236f
fix: Filtering of task reminders strips off time, so that searches work.
claremacrae May 17, 2023
638575b
test: Show that times on reminder filters are ignored
claremacrae May 17, 2023
34e928a
refactor: Remove unused TIME_FORMATS.twelveHour
claremacrae May 17, 2023
6315caf
test: Sort new instructions in Query.test.ts
claremacrae May 28, 2023
c43d594
test: Move some tests from Query.test.ts to ReminderDateField.test.ts
claremacrae May 28, 2023
8efadf6
fix!!: Remove 'show/hide reminders' option
claremacrae May 28, 2023
be9ad29
refactor: Rename reminder Layout Option to hideReminderDate
claremacrae May 28, 2023
9297016
Merge branch 'main' into user-Erik-Handeland-reminders
claremacrae May 28, 2023
0497244
Merge branch 'main' into user-Erik-Handeland-reminders
claremacrae May 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ yarn-error.log

# Backup files.
*.bak

# vscode files
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
5 changes: 0 additions & 5 deletions .vscode/extensions.json

This file was deleted.

3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

25 changes: 25 additions & 0 deletions docs/advanced/Task-Reminders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
publish: false
---

# Notifications

<span class="related-pages">#plugin/reminder</span>

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`

## Limitnations

- 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

## How to complete the reminder

The reminder date doesn't change when completing the task, the date will change only when you complete it from the reminder popup or from the notification.

![image](https://user-images.githubusercontent.com/38974541/143463881-e4af4b91-426f-48e8-938e-4a1053b06677.png)
![image](https://user-images.githubusercontent.com/38974541/143464983-542675ae-a467-41c0-aaca-1075c42f8328.png)

## Acknowledgment

This feature was created using code from uphy's [obsidian-reminder](https://github.com/uphy/obsidian-reminder) plugin.
Erik-Handeland marked this conversation as resolved.
Show resolved Hide resolved
27 changes: 27 additions & 0 deletions main.css
Erik-Handeland marked this conversation as resolved.
Show resolved Hide resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Commands/CreateOrEditTaskParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta
recurrence: null,
blockLink: '',
tags: [],
reminders: null,
originalMarkdown: '',
scheduledDateIsInferred: false,
});
Expand Down Expand Up @@ -95,6 +96,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta
doneDate: null,
recurrence: null,
tags: [],
reminders: null,
originalMarkdown: '',
// Not needed since the inferred status is always re-computed after submitting.
scheduledDateIsInferred: false,
Expand Down
10 changes: 10 additions & 0 deletions src/Config/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Status } from '../Status';
import { DefaultTaskSerializer, type TaskSerializer } from '../TaskSerializer';
import type { SuggestionBuilder } from '../Suggestor';
import { DataviewTaskSerializer } from '../TaskSerializer/DataviewTaskSerializer';
import { ReminderSettings } from '../Reminders/Reminder';
import { DebugSettings } from './DebugSettings';
import { StatusSettings } from './StatusSettings';
import { Feature } from './Feature';
Expand Down Expand Up @@ -49,6 +50,12 @@ export const TASK_FORMATS = {

export type TASK_FORMATS = typeof TASK_FORMATS; // For convenience to make some typing easier

// Time formats for the reminder settings.
export const TIME_FORMATS = {
twelveHour: 'YYYY-MM-DD h:mm a',
twentyFourHour: 'YYYY-MM-DD HH:mm',
};
Erik-Handeland marked this conversation as resolved.
Show resolved Hide resolved

export interface Settings {
globalQuery: string;
globalFilter: string;
Expand All @@ -73,6 +80,8 @@ export interface Settings {
// dynamically generated.
generalSettings: SettingsMap;

reminderSettings: ReminderSettings;

// Tracks the stage of the headings in the settings UI.
headingOpened: HeadingState;
debugSettings: DebugSettings;
Expand Down Expand Up @@ -104,6 +113,7 @@ const defaultSettings: Settings = {
},
headingOpened: {},
debugSettings: new DebugSettings(),
reminderSettings: new ReminderSettings(),
};

let settings: Settings = { ...defaultSettings };
Expand Down
64 changes: 63 additions & 1 deletion src/Config/SettingsTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from './Settings';
import { type HeadingState, TASK_FORMATS, TIME_FORMATS } from './Settings';
import { getSettings, isFeatureEnabled, updateGeneralSetting, updateSettings } from './Settings';
import { GlobalFilter } from './GlobalFilter';
import { StatusSettings } from './StatusSettings';
Expand Down Expand Up @@ -223,6 +223,68 @@ export class SettingsTab extends PluginSettingTab {
});
});

// ---------------------------------------------------------------------------
containerEl.createEl('h4', { text: 'Reminder Settings' });
// ---------------------------------------------------------------------------

new Setting(containerEl)
.setName('Reminder Format')
.setDesc(
SettingsTab.createFragmentWithHTML(
'<p class="tasks-setting-important">Do not change if reminders already exist, otherwise you will not be notified unless manually converted to new format.</p>' +
'<p>The format that Tasks uses to read and write reminders time i.e 12hr 6:00 pm or 24hr 18:00.</p>',
),
)
.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(
'<p>When daily reminders should be triggered. Should be in same format as above i.e 12 or 24hr.</p>',
),
)
.addText((text) => {
const settings = getSettings().reminderSettings;
text.setPlaceholder('10')
.setValue(settings.dailyReminderTime)
.onChange(async (value) => {
settings.dailyReminderTime = value;
updateSettings({ reminderSettings: settings });
await this.plugin.saveSettings();
});
});

new Setting(containerEl)
.setName('Check Reminders interval')
.setDesc(
SettingsTab.createFragmentWithHTML('<p>How often Tasks should check for reminders in Seconds.</p>'),
)
.addSlider((slider) => {
const settings = getSettings().reminderSettings;
slider
.setLimits(1, 30, 1)
.setValue(settings.refreshIntervalMilliseconds / 1000) // convert from miliseconds to seconds
.setDynamicTooltip()
.onChange(async (value) => {
settings.refreshIntervalMilliseconds = value * 1000; // convert from seconds to miliseconds
updateSettings({ reminderSettings: settings });
await this.plugin.saveSettings();
});
});

// ---------------------------------------------------------------------------
containerEl.createEl('h4', { text: 'Auto-suggest Settings' });
// ---------------------------------------------------------------------------
Expand Down
21 changes: 21 additions & 0 deletions src/Query/Filter/ReminderDateField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Moment } from 'moment';
import type { Task } from '../../Task';
import { DateField } from './DateField';

export class ReminderDateField extends DateField {
public fieldName(): string {
return 'reminder';
}

public date(task: Task): Moment | null {
if (task.reminders) {
return task.reminders.peek();
} else {
return null;
}
}
Erik-Handeland marked this conversation as resolved.
Show resolved Hide resolved

protected filterResultIfFieldMissing() {
return false;
}
}
2 changes: 2 additions & 0 deletions src/Query/FilterParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,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';
Expand Down Expand Up @@ -45,6 +46,7 @@ const fieldCreators: EndsWith<BooleanField> = [
() => new ScheduledDateField(),
() => new DueDateField(),
() => new DoneDateField(),
() => new ReminderDateField(),
() => new PathField(),
() => new FolderField(),
() => new RootField(),
Expand Down
6 changes: 5 additions & 1 deletion src/Query/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)/;
/^(hide|show) (task count|backlink|priority|created date|start date|scheduled date|done date|due date|recurrence rule|edit button|urgency|reminder date|reminders)/;
private readonly shortModeRegexp = /^short/;
private readonly explainQueryRegexp = /^explain/;

Expand Down Expand Up @@ -192,6 +192,10 @@ export class Query implements IQuery {
case 'due date':
this._layoutOptions.hideDueDate = hide;
break;
case 'reminder date':
case 'reminders':
this._layoutOptions.hideReminders = hide;
break;
case 'done date':
this._layoutOptions.hideDoneDate = hide;
break;
Expand Down
30 changes: 29 additions & 1 deletion src/Recurrence.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { Moment } from 'moment';
import { RRule } from 'rrule';
import { compareByDate } from './lib/DateTools';
import { compareByDate, isRemindersSame } from './lib/DateTools';
import { Reminder, ReminderList } from './Reminders/Reminder';

export class Recurrence {
private readonly rrule: RRule;
private readonly baseOnToday: boolean;
private readonly startDate: Moment | null;
private readonly scheduledDate: Moment | null;
private readonly dueDate: Moment | null;
private readonly reminders: ReminderList | null;

/**
* The reference date is used to calculate future occurrences.
Expand All @@ -31,32 +33,37 @@ export class Recurrence {
startDate,
scheduledDate,
dueDate,
reminders,
}: {
rrule: RRule;
baseOnToday: boolean;
referenceDate: Moment | null;
startDate: Moment | null;
scheduledDate: Moment | null;
dueDate: Moment | null;
reminders: ReminderList | null;
}) {
this.rrule = rrule;
this.baseOnToday = baseOnToday;
this.referenceDate = referenceDate;
this.startDate = startDate;
this.scheduledDate = scheduledDate;
this.dueDate = dueDate;
this.reminders = reminders;
}

public static fromText({
recurrenceRuleText,
startDate,
scheduledDate,
dueDate,
reminders,
}: {
recurrenceRuleText: string;
startDate: Moment | null;
scheduledDate: Moment | null;
dueDate: Moment | null;
reminders: ReminderList | null;
}): Recurrence | null {
try {
const match = recurrenceRuleText.match(/^([a-zA-Z0-9, !]+?)( when done)?$/i);
Expand Down Expand Up @@ -95,6 +102,7 @@ export class Recurrence {
startDate,
scheduledDate,
dueDate,
reminders,
});
}
} catch (error) {
Expand All @@ -120,6 +128,7 @@ export class Recurrence {
startDate: Moment | null;
scheduledDate: Moment | null;
dueDate: Moment | null;
reminders: ReminderList | null;
} | null {
const next = this.nextReferenceDate();

Expand All @@ -129,6 +138,7 @@ export class Recurrence {
let startDate: Moment | null = null;
let scheduledDate: Moment | null = null;
let dueDate: Moment | null = null;
let reminders: ReminderList | null = null;

// Only if a reference date is given. A reference date will exist if at
// least one of the other dates is set.
Expand Down Expand Up @@ -157,12 +167,26 @@ 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) => {
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));
});
}
}

return {
startDate,
scheduledDate,
dueDate,
reminders,
};
}

Expand All @@ -185,6 +209,10 @@ export class Recurrence {
return false;
}

if (!isRemindersSame(this.reminders, other.reminders)) {
return false;
}

return this.toText() === other.toText(); // this also checks baseOnToday
}

Expand Down
1 change: 1 addition & 0 deletions src/Suggestor/Suggestor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ function addRecurrenceSuggestions(line: string, cursorPos: number, settings: Set
startDate: null,
scheduledDate: null,
dueDate: null,
reminders: null,
})?.toText();
if (parsedRecurrence) {
const appendedText = `${recurrencePrefix} ${parsedRecurrence} `;
Expand Down
Loading