Files
obsidian-notif/src/reminders/ReminderModal.ts
T

177 lines
5.1 KiB
TypeScript
Raw Normal View History

2026-06-27 15:05:31 +08:00
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;
}
}