feat: Add task scheduling and reminders system
Add comprehensive scheduling functionality with reminder notifications: - Add scheduledDate, scheduledTime, and reminderMinutes fields to ImmerseTask - Add enableReminders and defaultReminderMinutes to plugin settings - Implement reminder notification system with 30-second background checks - Add overdue task detection with visual indicators - Add native HTML5 date/time pickers in Quick Add and Edit modals - Add reminder dropdown with 5/10/15/30/60 minute options - Display scheduled date/time with blue badge (📅) in task list - Show pulsing red "OVERDUE" badge (⚠️) for past-due tasks - Add red left border highlight for overdue tasks - Implement startup check for due/overdue tasks when plugin loads - Show overdue notices when Obsidian opens if tasks are past due - Play alert sounds for reminders (if sounds enabled) - Track shown reminders to prevent duplicate notifications
This commit is contained in:
140
main.js
140
main.js
@@ -46,7 +46,10 @@ var DEFAULT_SETTINGS = {
|
||||
autoStartBreak: true,
|
||||
tickSoundEnabled: false,
|
||||
// Daily note logging
|
||||
logToDaily: false
|
||||
logToDaily: false,
|
||||
// Task reminders
|
||||
enableReminders: true,
|
||||
defaultReminderMinutes: 15
|
||||
};
|
||||
var DEFAULT_DATA = {
|
||||
tasks: [],
|
||||
@@ -90,8 +93,12 @@ var QuickAddTaskModal = class extends import_obsidian.Modal {
|
||||
super(app);
|
||||
this.taskText = "";
|
||||
this.selectedList = "work";
|
||||
this.scheduledDate = "";
|
||||
this.scheduledTime = "";
|
||||
this.reminderMinutes = 0;
|
||||
this.plugin = plugin;
|
||||
this.estimatedMinutes = plugin.settings.defaultEstimateMinutes;
|
||||
this.reminderMinutes = plugin.settings.defaultReminderMinutes;
|
||||
if (plugin.settings.lists.length > 0) {
|
||||
this.selectedList = plugin.settings.lists[0].id;
|
||||
}
|
||||
@@ -137,6 +144,26 @@ var QuickAddTaskModal = class extends import_obsidian.Modal {
|
||||
dropdown.setValue(this.selectedList);
|
||||
dropdown.onChange((value) => this.selectedList = value);
|
||||
});
|
||||
new import_obsidian.Setting(contentEl).setName("\u{1F4C5} Scheduled Date").setDesc("Optional: When do you plan to work on this?").addText((text) => {
|
||||
text.setPlaceholder("YYYY-MM-DD").setValue(this.scheduledDate).onChange((value) => this.scheduledDate = value);
|
||||
text.inputEl.type = "date";
|
||||
});
|
||||
new import_obsidian.Setting(contentEl).setName("\u23F0 Scheduled Time").setDesc("Optional: What time?").addText((text) => {
|
||||
text.setPlaceholder("HH:mm").setValue(this.scheduledTime).onChange((value) => this.scheduledTime = value);
|
||||
text.inputEl.type = "time";
|
||||
});
|
||||
if (this.plugin.settings.enableReminders) {
|
||||
new import_obsidian.Setting(contentEl).setName("\u{1F514} Reminder").setDesc("Remind me before the scheduled time").addDropdown((dropdown) => {
|
||||
dropdown.addOption("0", "No reminder");
|
||||
dropdown.addOption("5", "5 minutes before");
|
||||
dropdown.addOption("10", "10 minutes before");
|
||||
dropdown.addOption("15", "15 minutes before");
|
||||
dropdown.addOption("30", "30 minutes before");
|
||||
dropdown.addOption("60", "1 hour before");
|
||||
dropdown.setValue(this.reminderMinutes.toString());
|
||||
dropdown.onChange((value) => this.reminderMinutes = parseInt(value));
|
||||
});
|
||||
}
|
||||
const buttonContainer = contentEl.createEl("div", { cls: "immerse-modal-buttons" });
|
||||
const cancelBtn = buttonContainer.createEl("button", { text: "Cancel", cls: "immerse-btn" });
|
||||
cancelBtn.addEventListener("click", () => this.close());
|
||||
@@ -146,6 +173,15 @@ var QuickAddTaskModal = class extends import_obsidian.Modal {
|
||||
submitTask() {
|
||||
if (this.taskText.trim()) {
|
||||
const task = this.plugin.createTask(this.taskText, this.estimatedMinutes, this.selectedList);
|
||||
if (this.scheduledDate) {
|
||||
task.scheduledDate = this.scheduledDate;
|
||||
}
|
||||
if (this.scheduledTime) {
|
||||
task.scheduledTime = this.scheduledTime;
|
||||
}
|
||||
if (this.reminderMinutes > 0 && this.scheduledDate && this.scheduledTime) {
|
||||
task.reminderMinutes = this.reminderMinutes;
|
||||
}
|
||||
this.plugin.addTask(task);
|
||||
new import_obsidian.Notice("\u2705 Task added!");
|
||||
this.close();
|
||||
@@ -201,6 +237,29 @@ var EditTaskModal = class extends import_obsidian.Modal {
|
||||
textarea.setValue(this.task.notes).onChange((value) => this.task.notes = value);
|
||||
textarea.inputEl.rows = 4;
|
||||
});
|
||||
new import_obsidian.Setting(contentEl).setName("\u{1F4C5} Scheduled Date").setDesc("Optional: When do you plan to work on this?").addText((text) => {
|
||||
text.setPlaceholder("YYYY-MM-DD").setValue(this.task.scheduledDate || "").onChange((value) => this.task.scheduledDate = value || void 0);
|
||||
text.inputEl.type = "date";
|
||||
});
|
||||
new import_obsidian.Setting(contentEl).setName("\u23F0 Scheduled Time").setDesc("Optional: What time?").addText((text) => {
|
||||
text.setPlaceholder("HH:mm").setValue(this.task.scheduledTime || "").onChange((value) => this.task.scheduledTime = value || void 0);
|
||||
text.inputEl.type = "time";
|
||||
});
|
||||
if (this.plugin.settings.enableReminders) {
|
||||
new import_obsidian.Setting(contentEl).setName("\u{1F514} Reminder").setDesc("Remind me before the scheduled time").addDropdown((dropdown) => {
|
||||
dropdown.addOption("0", "No reminder");
|
||||
dropdown.addOption("5", "5 minutes before");
|
||||
dropdown.addOption("10", "10 minutes before");
|
||||
dropdown.addOption("15", "15 minutes before");
|
||||
dropdown.addOption("30", "30 minutes before");
|
||||
dropdown.addOption("60", "1 hour before");
|
||||
dropdown.setValue((this.task.reminderMinutes || 0).toString());
|
||||
dropdown.onChange((value) => {
|
||||
const minutes = parseInt(value);
|
||||
this.task.reminderMinutes = minutes > 0 ? minutes : void 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
if (this.task.actualMinutes > 0) {
|
||||
new import_obsidian.Setting(contentEl).setName("Time Tracked").setDesc(`You've worked on this task for ${this.plugin.formatTimeHuman(this.task.actualMinutes)}`);
|
||||
}
|
||||
@@ -459,8 +518,9 @@ var ImmerseView = class extends import_obsidian2.ItemView {
|
||||
}
|
||||
renderTaskItem(container, task) {
|
||||
const list = this.plugin.settings.lists.find((l) => l.id === task.list);
|
||||
const isOverdue = !task.completed && task.scheduledDate && task.scheduledTime && new Date(`${task.scheduledDate}T${task.scheduledTime}`).getTime() < Date.now();
|
||||
const taskEl = container.createEl("div", {
|
||||
cls: `immerse-task-item ${task.completed ? "completed" : ""} ${task.isActive ? "active" : ""}`
|
||||
cls: `immerse-task-item ${task.completed ? "completed" : ""} ${task.isActive ? "active" : ""} ${isOverdue ? "overdue" : ""}`
|
||||
});
|
||||
const checkbox = taskEl.createEl("div", { cls: "immerse-checkbox" });
|
||||
checkbox.innerHTML = task.completed ? "\u2713" : "";
|
||||
@@ -495,6 +555,21 @@ var ImmerseView = class extends import_obsidian2.ItemView {
|
||||
actualSpan.addClass("immerse-overtime-text");
|
||||
}
|
||||
}
|
||||
if (task.scheduledDate) {
|
||||
const scheduleSpan = taskMeta.createEl("span", {
|
||||
cls: `immerse-schedule-badge ${isOverdue ? "overdue" : ""}`
|
||||
});
|
||||
const dateStr = task.scheduledDate;
|
||||
const timeStr = task.scheduledTime || "";
|
||||
if (isOverdue) {
|
||||
scheduleSpan.setText(`\u26A0\uFE0F OVERDUE: ${dateStr}${timeStr ? " " + timeStr : ""}`);
|
||||
} else {
|
||||
scheduleSpan.setText(`\u{1F4C5} ${dateStr}${timeStr ? " " + timeStr : ""}`);
|
||||
}
|
||||
if (task.reminderMinutes) {
|
||||
scheduleSpan.title = `Reminder set for ${task.reminderMinutes} min before`;
|
||||
}
|
||||
}
|
||||
const actions = taskEl.createEl("div", { cls: "immerse-task-actions" });
|
||||
if (!task.completed) {
|
||||
const startBtn = actions.createEl("button", { cls: "immerse-task-btn", attr: { "aria-label": "Start Pomodoro" } });
|
||||
@@ -544,7 +619,11 @@ var ImmersePlugin = class extends import_obsidian3.Plugin {
|
||||
this.secondsWorkedOnCurrentTask = 0;
|
||||
// Status bar element
|
||||
this.statusBarEl = null;
|
||||
// Reminder system
|
||||
this.reminderCheckInterval = null;
|
||||
this.notifiedReminders = /* @__PURE__ */ new Set();
|
||||
}
|
||||
// Track which reminders have been shown
|
||||
async onload() {
|
||||
await this.loadAllData();
|
||||
this.checkDailyReset();
|
||||
@@ -587,9 +666,13 @@ var ImmersePlugin = class extends import_obsidian3.Plugin {
|
||||
});
|
||||
this.addSettingTab(new ImmerseSettingTab(this.app, this));
|
||||
this.createStatusBar();
|
||||
if (this.settings.enableReminders) {
|
||||
this.startReminderSystem();
|
||||
}
|
||||
}
|
||||
onunload() {
|
||||
this.stopTimer();
|
||||
this.stopReminderSystem();
|
||||
}
|
||||
async loadAllData() {
|
||||
const loaded = await this.loadData();
|
||||
@@ -935,6 +1018,59 @@ var ImmersePlugin = class extends import_obsidian3.Plugin {
|
||||
this.statusBarEl.removeClass("immerse-status-active");
|
||||
}
|
||||
}
|
||||
// ============ Reminder System ============
|
||||
startReminderSystem() {
|
||||
this.reminderCheckInterval = window.setInterval(() => {
|
||||
this.checkReminders();
|
||||
}, 3e4);
|
||||
this.checkReminders();
|
||||
}
|
||||
stopReminderSystem() {
|
||||
if (this.reminderCheckInterval) {
|
||||
window.clearInterval(this.reminderCheckInterval);
|
||||
this.reminderCheckInterval = null;
|
||||
}
|
||||
}
|
||||
checkReminders() {
|
||||
if (!this.settings.enableReminders)
|
||||
return;
|
||||
const now = new Date();
|
||||
const currentTime = now.getTime();
|
||||
this.data.tasks.filter((task) => !task.completed && task.scheduledDate && task.scheduledTime).forEach((task) => {
|
||||
const reminderKey = `${task.id}-${task.scheduledDate}-${task.scheduledTime}`;
|
||||
if (this.notifiedReminders.has(reminderKey))
|
||||
return;
|
||||
const scheduledDateTime = new Date(`${task.scheduledDate}T${task.scheduledTime}`);
|
||||
const scheduledTime = scheduledDateTime.getTime();
|
||||
if (currentTime > scheduledTime) {
|
||||
this.showOverdueNotice(task);
|
||||
this.notifiedReminders.add(reminderKey);
|
||||
return;
|
||||
}
|
||||
if (task.reminderMinutes) {
|
||||
const reminderTime = scheduledTime - task.reminderMinutes * 60 * 1e3;
|
||||
if (currentTime >= reminderTime) {
|
||||
this.showReminder(task);
|
||||
this.notifiedReminders.add(reminderKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
showReminder(task) {
|
||||
const timeStr = task.scheduledTime;
|
||||
new import_obsidian3.Notice(`\u{1F514} Reminder: "${task.text}" is scheduled for ${timeStr}`, 8e3);
|
||||
if (this.settings.enableSounds) {
|
||||
this.playAlertSound();
|
||||
}
|
||||
}
|
||||
showOverdueNotice(task) {
|
||||
const dateStr = task.scheduledDate;
|
||||
const timeStr = task.scheduledTime;
|
||||
new import_obsidian3.Notice(`\u26A0\uFE0F Overdue: "${task.text}" was scheduled for ${dateStr} ${timeStr}`, 1e4);
|
||||
if (this.settings.enableSounds) {
|
||||
this.playAlertSound();
|
||||
}
|
||||
}
|
||||
// ============ Sounds & Celebrations ============
|
||||
showCelebration(task) {
|
||||
let messages = CELEBRATION_MESSAGES;
|
||||
|
||||
Reference in New Issue
Block a user