177 lines
5.1 KiB
TypeScript
177 lines
5.1 KiB
TypeScript
import { App, Modal, Notice, Setting } from "obsidian";
|
|
import { ParsedReminderInput } from "../types";
|
|
|
|
export class ReminderModal extends Modal {
|
|
private title = "";
|
|
private body = "";
|
|
private year = "";
|
|
private month = "";
|
|
private day = "";
|
|
private hour = "";
|
|
private minute = "";
|
|
private channels = "";
|
|
|
|
constructor(
|
|
app: App,
|
|
private defaultChannels: string[],
|
|
private onSubmit: (input: ParsedReminderInput) => Promise<void>,
|
|
initialTitle = "",
|
|
) {
|
|
super(app);
|
|
const initialDate = new Date(Date.now() + 60 * 60 * 1000);
|
|
this.title = initialTitle;
|
|
this.body = initialTitle;
|
|
this.channels = defaultChannels.join(", ");
|
|
this.year = String(initialDate.getFullYear());
|
|
this.month = String(initialDate.getMonth() + 1).padStart(2, "0");
|
|
this.day = String(initialDate.getDate()).padStart(2, "0");
|
|
this.hour = String(initialDate.getHours()).padStart(2, "0");
|
|
this.minute = String(Math.ceil(initialDate.getMinutes() / 5) * 5).padStart(2, "0");
|
|
if (this.minute === "60") {
|
|
this.minute = "00";
|
|
this.hour = String((initialDate.getHours() + 1) % 24).padStart(2, "0");
|
|
}
|
|
}
|
|
|
|
onOpen(): void {
|
|
const { contentEl } = this;
|
|
contentEl.empty();
|
|
contentEl.createEl("h2", { text: "Create reminder" });
|
|
|
|
this.renderReminderRow(contentEl);
|
|
|
|
new Setting(contentEl)
|
|
.setName("Details")
|
|
.setDesc("Optional longer message.")
|
|
.addTextArea((text) => {
|
|
text.setValue(this.body);
|
|
text.inputEl.rows = 3;
|
|
text.onChange((value) => {
|
|
this.body = value;
|
|
});
|
|
});
|
|
|
|
new Setting(contentEl)
|
|
.setName("Channels")
|
|
.setDesc("Comma separated provider IDs or types. Leave empty to use all enabled providers.")
|
|
.addText((text) => text
|
|
.setValue(this.channels)
|
|
.onChange((value) => {
|
|
this.channels = value;
|
|
}));
|
|
|
|
new Setting(contentEl)
|
|
.addButton((button) => button
|
|
.setButtonText("Create")
|
|
.setCta()
|
|
.onClick(async () => {
|
|
const dueAt = this.getSelectedDate();
|
|
if (!this.title.trim() || !dueAt) {
|
|
new Notice("Title and valid due date are required.");
|
|
return;
|
|
}
|
|
|
|
await this.onSubmit({
|
|
title: this.title.trim(),
|
|
body: this.body.trim(),
|
|
dueAt: dueAt.toISOString(),
|
|
channels: this.channels.split(",").map((value) => value.trim()).filter(Boolean),
|
|
});
|
|
this.close();
|
|
}));
|
|
}
|
|
|
|
private renderReminderRow(container: HTMLElement): void {
|
|
const setting = new Setting(container)
|
|
.setName("Reminder")
|
|
.setDesc("Text, date, and time.");
|
|
|
|
setting.addText((text) => {
|
|
text.setPlaceholder("Reminder text");
|
|
text.setValue(this.title);
|
|
text.onChange((value) => {
|
|
this.title = value;
|
|
if (!this.body || this.body === this.title) {
|
|
this.body = value;
|
|
}
|
|
});
|
|
});
|
|
|
|
setting.addDropdown((dropdown) => {
|
|
for (const year of this.range(new Date().getFullYear(), new Date().getFullYear() + 3)) {
|
|
dropdown.addOption(String(year), String(year));
|
|
}
|
|
dropdown.setValue(this.year);
|
|
dropdown.onChange((value) => {
|
|
this.year = value;
|
|
this.ensureValidDay();
|
|
});
|
|
});
|
|
|
|
setting.addDropdown((dropdown) => {
|
|
for (const month of this.range(1, 12)) {
|
|
const value = String(month).padStart(2, "0");
|
|
dropdown.addOption(value, value);
|
|
}
|
|
dropdown.setValue(this.month);
|
|
dropdown.onChange((value) => {
|
|
this.month = value;
|
|
this.ensureValidDay();
|
|
});
|
|
});
|
|
|
|
setting.addDropdown((dropdown) => {
|
|
for (const day of this.range(1, 31)) {
|
|
const value = String(day).padStart(2, "0");
|
|
dropdown.addOption(value, value);
|
|
}
|
|
dropdown.setValue(this.day);
|
|
dropdown.onChange((value) => {
|
|
this.day = value;
|
|
});
|
|
});
|
|
|
|
setting.addDropdown((dropdown) => {
|
|
for (const hour of this.range(0, 23)) {
|
|
const value = String(hour).padStart(2, "0");
|
|
dropdown.addOption(value, value);
|
|
}
|
|
dropdown.setValue(this.hour);
|
|
dropdown.onChange((value) => {
|
|
this.hour = value;
|
|
});
|
|
});
|
|
|
|
setting.addDropdown((dropdown) => {
|
|
for (const minute of this.range(0, 55, 5)) {
|
|
const value = String(minute).padStart(2, "0");
|
|
dropdown.addOption(value, value);
|
|
}
|
|
dropdown.setValue(this.minute);
|
|
dropdown.onChange((value) => {
|
|
this.minute = value;
|
|
});
|
|
});
|
|
}
|
|
|
|
private getSelectedDate(): Date | null {
|
|
const date = new Date(`${this.year}-${this.month}-${this.day}T${this.hour}:${this.minute}:00`);
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
}
|
|
|
|
private ensureValidDay(): void {
|
|
const maxDay = new Date(Number(this.year), Number(this.month), 0).getDate();
|
|
if (Number(this.day) > maxDay) {
|
|
this.day = String(maxDay).padStart(2, "0");
|
|
}
|
|
}
|
|
|
|
private range(from: number, to: number, step = 1): number[] {
|
|
const values: number[] = [];
|
|
for (let value = from; value <= to; value += step) {
|
|
values.push(value);
|
|
}
|
|
return values;
|
|
}
|
|
}
|