Compare commits
2 Commits
v1.0.9
...
2fad5d88ab
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fad5d88ab | |||
| 683c4ddafe |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,4 +21,6 @@ data.json
|
|||||||
|
|
||||||
# Development/Documentation (not for distribution)
|
# Development/Documentation (not for distribution)
|
||||||
RELEASE-GUIDE.md
|
RELEASE-GUIDE.md
|
||||||
|
ROADMAP.md
|
||||||
|
deploy-test.bat
|
||||||
.claude/
|
.claude/
|
||||||
@@ -169,7 +169,7 @@ When enabled, completed tasks are automatically appended to your daily note with
|
|||||||
- [x] Write project proposal | 💼 Work | ⏱️ 45min / 30min (15min over estimate) | ✅ 14:30
|
- [x] Write project proposal | 💼 Work | ⏱️ 45min / 30min (15min over estimate) | ✅ 14:30
|
||||||
```
|
```
|
||||||
|
|
||||||
**Requirements:** The core "Daily Notes" plugin must be enabled in Obsidian settings. Focus Task respects your Daily Notes configuration (folder, date format, and template).
|
**Requirements:** The core "Daily Notes" plugin must be enabled in Obsidian settings. Immerse respects your Daily Notes configuration (folder, date format, and template).
|
||||||
|
|
||||||
### Lists
|
### Lists
|
||||||
Customize your task lists with:
|
Customize your task lists with:
|
||||||
@@ -182,7 +182,7 @@ Default lists: Work 💼, Personal 🏠, Learning 📚
|
|||||||
## 🎨 Customization
|
## 🎨 Customization
|
||||||
|
|
||||||
### Adding Custom Lists
|
### Adding Custom Lists
|
||||||
1. Go to Settings → Focus Task → Lists
|
1. Go to Settings → Immerse → Lists
|
||||||
2. Click "+ Add List"
|
2. Click "+ Add List"
|
||||||
3. Set the name, emoji, and color
|
3. Set the name, emoji, and color
|
||||||
4. Click Save
|
4. Click Save
|
||||||
|
|||||||
146
main.js
146
main.js
@@ -46,7 +46,10 @@ var DEFAULT_SETTINGS = {
|
|||||||
autoStartBreak: true,
|
autoStartBreak: true,
|
||||||
tickSoundEnabled: false,
|
tickSoundEnabled: false,
|
||||||
// Daily note logging
|
// Daily note logging
|
||||||
logToDaily: false
|
logToDaily: false,
|
||||||
|
// Task reminders
|
||||||
|
enableReminders: true,
|
||||||
|
defaultReminderMinutes: 15
|
||||||
};
|
};
|
||||||
var DEFAULT_DATA = {
|
var DEFAULT_DATA = {
|
||||||
tasks: [],
|
tasks: [],
|
||||||
@@ -90,8 +93,12 @@ var QuickAddTaskModal = class extends import_obsidian.Modal {
|
|||||||
super(app);
|
super(app);
|
||||||
this.taskText = "";
|
this.taskText = "";
|
||||||
this.selectedList = "work";
|
this.selectedList = "work";
|
||||||
|
this.scheduledDate = "";
|
||||||
|
this.scheduledTime = "";
|
||||||
|
this.reminderMinutes = 0;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.estimatedMinutes = plugin.settings.defaultEstimateMinutes;
|
this.estimatedMinutes = plugin.settings.defaultEstimateMinutes;
|
||||||
|
this.reminderMinutes = plugin.settings.defaultReminderMinutes;
|
||||||
if (plugin.settings.lists.length > 0) {
|
if (plugin.settings.lists.length > 0) {
|
||||||
this.selectedList = plugin.settings.lists[0].id;
|
this.selectedList = plugin.settings.lists[0].id;
|
||||||
}
|
}
|
||||||
@@ -137,6 +144,26 @@ var QuickAddTaskModal = class extends import_obsidian.Modal {
|
|||||||
dropdown.setValue(this.selectedList);
|
dropdown.setValue(this.selectedList);
|
||||||
dropdown.onChange((value) => this.selectedList = value);
|
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 buttonContainer = contentEl.createEl("div", { cls: "immerse-modal-buttons" });
|
||||||
const cancelBtn = buttonContainer.createEl("button", { text: "Cancel", cls: "immerse-btn" });
|
const cancelBtn = buttonContainer.createEl("button", { text: "Cancel", cls: "immerse-btn" });
|
||||||
cancelBtn.addEventListener("click", () => this.close());
|
cancelBtn.addEventListener("click", () => this.close());
|
||||||
@@ -146,6 +173,15 @@ var QuickAddTaskModal = class extends import_obsidian.Modal {
|
|||||||
submitTask() {
|
submitTask() {
|
||||||
if (this.taskText.trim()) {
|
if (this.taskText.trim()) {
|
||||||
const task = this.plugin.createTask(this.taskText, this.estimatedMinutes, this.selectedList);
|
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);
|
this.plugin.addTask(task);
|
||||||
new import_obsidian.Notice("\u2705 Task added!");
|
new import_obsidian.Notice("\u2705 Task added!");
|
||||||
this.close();
|
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.setValue(this.task.notes).onChange((value) => this.task.notes = value);
|
||||||
textarea.inputEl.rows = 4;
|
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) {
|
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)}`);
|
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) {
|
renderTaskItem(container, task) {
|
||||||
const list = this.plugin.settings.lists.find((l) => l.id === task.list);
|
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", {
|
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" });
|
const checkbox = taskEl.createEl("div", { cls: "immerse-checkbox" });
|
||||||
checkbox.innerHTML = task.completed ? "\u2713" : "";
|
checkbox.innerHTML = task.completed ? "\u2713" : "";
|
||||||
@@ -495,6 +555,21 @@ var ImmerseView = class extends import_obsidian2.ItemView {
|
|||||||
actualSpan.addClass("immerse-overtime-text");
|
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" });
|
const actions = taskEl.createEl("div", { cls: "immerse-task-actions" });
|
||||||
if (!task.completed) {
|
if (!task.completed) {
|
||||||
const startBtn = actions.createEl("button", { cls: "immerse-task-btn", attr: { "aria-label": "Start Pomodoro" } });
|
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;
|
this.secondsWorkedOnCurrentTask = 0;
|
||||||
// Status bar element
|
// Status bar element
|
||||||
this.statusBarEl = null;
|
this.statusBarEl = null;
|
||||||
|
// Reminder system
|
||||||
|
this.reminderCheckInterval = null;
|
||||||
|
this.notifiedReminders = /* @__PURE__ */ new Set();
|
||||||
}
|
}
|
||||||
|
// Track which reminders have been shown
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadAllData();
|
await this.loadAllData();
|
||||||
this.checkDailyReset();
|
this.checkDailyReset();
|
||||||
@@ -587,9 +666,13 @@ var ImmersePlugin = class extends import_obsidian3.Plugin {
|
|||||||
});
|
});
|
||||||
this.addSettingTab(new ImmerseSettingTab(this.app, this));
|
this.addSettingTab(new ImmerseSettingTab(this.app, this));
|
||||||
this.createStatusBar();
|
this.createStatusBar();
|
||||||
|
if (this.settings.enableReminders) {
|
||||||
|
this.startReminderSystem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
this.stopTimer();
|
this.stopTimer();
|
||||||
|
this.stopReminderSystem();
|
||||||
}
|
}
|
||||||
async loadAllData() {
|
async loadAllData() {
|
||||||
const loaded = await this.loadData();
|
const loaded = await this.loadData();
|
||||||
@@ -914,7 +997,7 @@ var ImmersePlugin = class extends import_obsidian3.Plugin {
|
|||||||
// ============ Status Bar Timer ============
|
// ============ Status Bar Timer ============
|
||||||
createStatusBar() {
|
createStatusBar() {
|
||||||
this.statusBarEl = this.addStatusBarItem();
|
this.statusBarEl = this.addStatusBarItem();
|
||||||
this.statusBarEl.addClass("focus-task-status-bar");
|
this.statusBarEl.addClass("immerse-status-bar");
|
||||||
this.updateStatusBar();
|
this.updateStatusBar();
|
||||||
this.statusBarEl.addEventListener("click", () => {
|
this.statusBarEl.addEventListener("click", () => {
|
||||||
this.activateView();
|
this.activateView();
|
||||||
@@ -929,10 +1012,63 @@ var ImmersePlugin = class extends import_obsidian3.Plugin {
|
|||||||
const timeStr = this.formatTime(this.currentTimerSeconds);
|
const timeStr = this.formatTime(this.currentTimerSeconds);
|
||||||
const icon = this.isTimerRunning ? "\u25B6" : "\u23F8";
|
const icon = this.isTimerRunning ? "\u25B6" : "\u23F8";
|
||||||
this.statusBarEl.setText(`\u26A1 ${icon} ${timeStr} - ${taskName}${task && task.text.length > 20 ? "..." : ""}`);
|
this.statusBarEl.setText(`\u26A1 ${icon} ${timeStr} - ${taskName}${task && task.text.length > 20 ? "..." : ""}`);
|
||||||
this.statusBarEl.addClass("focus-task-status-active");
|
this.statusBarEl.addClass("immerse-status-active");
|
||||||
} else {
|
} else {
|
||||||
this.statusBarEl.setText("\u26A1 Immerse");
|
this.statusBarEl.setText("\u26A1 Immerse");
|
||||||
this.statusBarEl.removeClass("focus-task-status-active");
|
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 ============
|
// ============ Sounds & Celebrations ============
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ async function packageRelease() {
|
|||||||
console.log(`\nFiles included:`);
|
console.log(`\nFiles included:`);
|
||||||
FILES_TO_INCLUDE.forEach(file => console.log(` - ${file}`));
|
FILES_TO_INCLUDE.forEach(file => console.log(` - ${file}`));
|
||||||
console.log(`\n💡 Tip: You can now upload these files from the '${RELEASE_DIR}' directory to your Gitea release.`);
|
console.log(`\n💡 Tip: You can now upload these files from the '${RELEASE_DIR}' directory to your Gitea release.`);
|
||||||
console.log(`💡 Tip: To create a zip, run: cd ${RELEASE_DIR} && zip -r ../focus-task-${manifest.version}.zip *`);
|
console.log(`💡 Tip: To create a zip, run: cd ${RELEASE_DIR} && zip -r ../immerse-${manifest.version}.zip *`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error packaging release:', error);
|
console.error('❌ Error packaging release:', error);
|
||||||
|
|||||||
120
src/main.ts
120
src/main.ts
@@ -9,9 +9,9 @@ import {
|
|||||||
} from 'obsidian';
|
} from 'obsidian';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FocusTask,
|
ImmerseTask,
|
||||||
FocusTaskSettings,
|
ImmerseSettings,
|
||||||
FocusTaskData,
|
ImmerseData,
|
||||||
DEFAULT_SETTINGS,
|
DEFAULT_SETTINGS,
|
||||||
DEFAULT_DATA,
|
DEFAULT_DATA,
|
||||||
VIEW_TYPE_IMMERSE,
|
VIEW_TYPE_IMMERSE,
|
||||||
@@ -26,8 +26,8 @@ import { QuickAddTaskModal } from './modals';
|
|||||||
// ============ Main Plugin Class ============
|
// ============ Main Plugin Class ============
|
||||||
|
|
||||||
export default class ImmersePlugin extends Plugin {
|
export default class ImmersePlugin extends Plugin {
|
||||||
settings: FocusTaskSettings;
|
settings: ImmerseSettings;
|
||||||
data: FocusTaskData;
|
data: ImmerseData;
|
||||||
|
|
||||||
// Timer state
|
// Timer state
|
||||||
timerInterval: number | null = null;
|
timerInterval: number | null = null;
|
||||||
@@ -48,6 +48,10 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
// Status bar element
|
// Status bar element
|
||||||
statusBarEl: HTMLElement | null = null;
|
statusBarEl: HTMLElement | null = null;
|
||||||
|
|
||||||
|
// Reminder system
|
||||||
|
private reminderCheckInterval: number | null = null;
|
||||||
|
private notifiedReminders: Set<string> = new Set(); // Track which reminders have been shown
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadAllData();
|
await this.loadAllData();
|
||||||
|
|
||||||
@@ -108,10 +112,16 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
|
|
||||||
// Create status bar timer
|
// Create status bar timer
|
||||||
this.createStatusBar();
|
this.createStatusBar();
|
||||||
|
|
||||||
|
// Start reminder checking system
|
||||||
|
if (this.settings.enableReminders) {
|
||||||
|
this.startReminderSystem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
this.stopTimer();
|
this.stopTimer();
|
||||||
|
this.stopReminderSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAllData() {
|
async loadAllData() {
|
||||||
@@ -183,7 +193,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
|
|
||||||
// ============ Task Management ============
|
// ============ Task Management ============
|
||||||
|
|
||||||
createTask(text: string, estimatedMinutes: number = this.settings.defaultEstimateMinutes, list: string = 'work'): FocusTask {
|
createTask(text: string, estimatedMinutes: number = this.settings.defaultEstimateMinutes, list: string = 'work'): ImmerseTask {
|
||||||
return {
|
return {
|
||||||
id: this.generateId(),
|
id: this.generateId(),
|
||||||
text,
|
text,
|
||||||
@@ -201,13 +211,13 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
addTask(task: FocusTask) {
|
addTask(task: ImmerseTask) {
|
||||||
this.data.tasks.push(task);
|
this.data.tasks.push(task);
|
||||||
this.saveAllData();
|
this.saveAllData();
|
||||||
this.refreshView();
|
this.refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTask(taskId: string, updates: Partial<FocusTask>) {
|
updateTask(taskId: string, updates: Partial<ImmerseTask>) {
|
||||||
const task = this.data.tasks.find(t => t.id === taskId);
|
const task = this.data.tasks.find(t => t.id === taskId);
|
||||||
if (task) {
|
if (task) {
|
||||||
Object.assign(task, updates);
|
Object.assign(task, updates);
|
||||||
@@ -578,7 +588,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
|
|
||||||
createStatusBar() {
|
createStatusBar() {
|
||||||
this.statusBarEl = this.addStatusBarItem();
|
this.statusBarEl = this.addStatusBarItem();
|
||||||
this.statusBarEl.addClass('focus-task-status-bar');
|
this.statusBarEl.addClass('immerse-status-bar');
|
||||||
this.updateStatusBar();
|
this.updateStatusBar();
|
||||||
|
|
||||||
// Click to open panel
|
// Click to open panel
|
||||||
@@ -597,16 +607,94 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
const icon = this.isTimerRunning ? '▶' : '⏸';
|
const icon = this.isTimerRunning ? '▶' : '⏸';
|
||||||
|
|
||||||
this.statusBarEl.setText(`⚡ ${icon} ${timeStr} - ${taskName}${task && task.text.length > 20 ? '...' : ''}`);
|
this.statusBarEl.setText(`⚡ ${icon} ${timeStr} - ${taskName}${task && task.text.length > 20 ? '...' : ''}`);
|
||||||
this.statusBarEl.addClass('focus-task-status-active');
|
this.statusBarEl.addClass('immerse-status-active');
|
||||||
} else {
|
} else {
|
||||||
this.statusBarEl.setText('⚡ Immerse');
|
this.statusBarEl.setText('⚡ Immerse');
|
||||||
this.statusBarEl.removeClass('focus-task-status-active');
|
this.statusBarEl.removeClass('immerse-status-active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ Reminder System ============
|
||||||
|
|
||||||
|
startReminderSystem() {
|
||||||
|
// Check for reminders every 30 seconds
|
||||||
|
this.reminderCheckInterval = window.setInterval(() => {
|
||||||
|
this.checkReminders();
|
||||||
|
}, 30000);
|
||||||
|
// Also check immediately
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Check each incomplete task with scheduling
|
||||||
|
this.data.tasks
|
||||||
|
.filter(task => !task.completed && task.scheduledDate && task.scheduledTime)
|
||||||
|
.forEach(task => {
|
||||||
|
const reminderKey = `${task.id}-${task.scheduledDate}-${task.scheduledTime}`;
|
||||||
|
|
||||||
|
// Skip if we already notified for this reminder
|
||||||
|
if (this.notifiedReminders.has(reminderKey)) return;
|
||||||
|
|
||||||
|
// Parse the scheduled date and time
|
||||||
|
const scheduledDateTime = new Date(`${task.scheduledDate}T${task.scheduledTime}`);
|
||||||
|
const scheduledTime = scheduledDateTime.getTime();
|
||||||
|
|
||||||
|
// Check for overdue tasks (scheduled time has passed)
|
||||||
|
if (currentTime > scheduledTime) {
|
||||||
|
this.showOverdueNotice(task);
|
||||||
|
this.notifiedReminders.add(reminderKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for tasks with reminders set
|
||||||
|
if (task.reminderMinutes) {
|
||||||
|
const reminderTime = scheduledTime - (task.reminderMinutes * 60 * 1000);
|
||||||
|
|
||||||
|
// Show reminder if it's past the reminder time but before the scheduled time
|
||||||
|
if (currentTime >= reminderTime) {
|
||||||
|
this.showReminder(task);
|
||||||
|
this.notifiedReminders.add(reminderKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showReminder(task: ImmerseTask) {
|
||||||
|
const timeStr = task.scheduledTime;
|
||||||
|
new Notice(`🔔 Reminder: "${task.text}" is scheduled for ${timeStr}`, 8000);
|
||||||
|
|
||||||
|
// Play alert sound if enabled
|
||||||
|
if (this.settings.enableSounds) {
|
||||||
|
this.playAlertSound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showOverdueNotice(task: ImmerseTask) {
|
||||||
|
const dateStr = task.scheduledDate;
|
||||||
|
const timeStr = task.scheduledTime;
|
||||||
|
new Notice(`⚠️ Overdue: "${task.text}" was scheduled for ${dateStr} ${timeStr}`, 10000);
|
||||||
|
|
||||||
|
// Play alert sound if enabled
|
||||||
|
if (this.settings.enableSounds) {
|
||||||
|
this.playAlertSound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ Sounds & Celebrations ============
|
// ============ Sounds & Celebrations ============
|
||||||
|
|
||||||
showCelebration(task: FocusTask) {
|
showCelebration(task: ImmerseTask) {
|
||||||
let messages = CELEBRATION_MESSAGES;
|
let messages = CELEBRATION_MESSAGES;
|
||||||
let extraMessage = '';
|
let extraMessage = '';
|
||||||
|
|
||||||
@@ -673,7 +761,7 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
|
|
||||||
// ============ Daily Note Logging ============
|
// ============ Daily Note Logging ============
|
||||||
|
|
||||||
async logTaskToDailyNote(task: FocusTask) {
|
async logTaskToDailyNote(task: ImmerseTask) {
|
||||||
try {
|
try {
|
||||||
const list = this.settings.lists.find(l => l.id === task.list);
|
const list = this.settings.lists.find(l => l.id === task.list);
|
||||||
|
|
||||||
@@ -862,15 +950,15 @@ export default class ImmersePlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTasksByList(listId: string): FocusTask[] {
|
getTasksByList(listId: string): ImmerseTask[] {
|
||||||
return this.data.tasks.filter(t => t.list === listId);
|
return this.data.tasks.filter(t => t.list === listId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPendingTasks(): FocusTask[] {
|
getPendingTasks(): ImmerseTask[] {
|
||||||
return this.data.tasks.filter(t => !t.completed);
|
return this.data.tasks.filter(t => !t.completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTodaysTasks(): FocusTask[] {
|
getTodaysTasks(): ImmerseTask[] {
|
||||||
const today = new Date().toDateString();
|
const today = new Date().toDateString();
|
||||||
return this.data.tasks.filter(t => {
|
return this.data.tasks.filter(t => {
|
||||||
if (t.scheduledDate === today) return true;
|
if (t.scheduledDate === today) return true;
|
||||||
|
|||||||
101
src/modals.ts
101
src/modals.ts
@@ -5,7 +5,7 @@ import {
|
|||||||
Setting,
|
Setting,
|
||||||
} from 'obsidian';
|
} from 'obsidian';
|
||||||
|
|
||||||
import { FocusTask } from './types';
|
import { ImmerseTask } from './types';
|
||||||
import ImmersePlugin from './main';
|
import ImmersePlugin from './main';
|
||||||
|
|
||||||
// ============ Quick Add Task Modal ============
|
// ============ Quick Add Task Modal ============
|
||||||
@@ -15,11 +15,15 @@ export class QuickAddTaskModal extends Modal {
|
|||||||
taskText: string = '';
|
taskText: string = '';
|
||||||
estimatedMinutes: number;
|
estimatedMinutes: number;
|
||||||
selectedList: string = 'work';
|
selectedList: string = 'work';
|
||||||
|
scheduledDate: string = '';
|
||||||
|
scheduledTime: string = '';
|
||||||
|
reminderMinutes: number = 0;
|
||||||
|
|
||||||
constructor(app: App, plugin: ImmersePlugin) {
|
constructor(app: App, plugin: ImmersePlugin) {
|
||||||
super(app);
|
super(app);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.estimatedMinutes = plugin.settings.defaultEstimateMinutes;
|
this.estimatedMinutes = plugin.settings.defaultEstimateMinutes;
|
||||||
|
this.reminderMinutes = plugin.settings.defaultReminderMinutes;
|
||||||
if (plugin.settings.lists.length > 0) {
|
if (plugin.settings.lists.length > 0) {
|
||||||
this.selectedList = plugin.settings.lists[0].id;
|
this.selectedList = plugin.settings.lists[0].id;
|
||||||
}
|
}
|
||||||
@@ -82,6 +86,45 @@ export class QuickAddTaskModal extends Modal {
|
|||||||
dropdown.onChange(value => this.selectedList = value);
|
dropdown.onChange(value => this.selectedList = value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Scheduled date
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName('📅 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';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scheduled time
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName('⏰ Scheduled Time')
|
||||||
|
.setDesc('Optional: What time?')
|
||||||
|
.addText(text => {
|
||||||
|
text.setPlaceholder('HH:mm')
|
||||||
|
.setValue(this.scheduledTime)
|
||||||
|
.onChange(value => this.scheduledTime = value);
|
||||||
|
text.inputEl.type = 'time';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reminder
|
||||||
|
if (this.plugin.settings.enableReminders) {
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName('🔔 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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
const buttonContainer = contentEl.createEl('div', { cls: 'immerse-modal-buttons' });
|
const buttonContainer = contentEl.createEl('div', { cls: 'immerse-modal-buttons' });
|
||||||
|
|
||||||
@@ -95,6 +138,16 @@ export class QuickAddTaskModal extends Modal {
|
|||||||
submitTask() {
|
submitTask() {
|
||||||
if (this.taskText.trim()) {
|
if (this.taskText.trim()) {
|
||||||
const task = this.plugin.createTask(this.taskText, this.estimatedMinutes, this.selectedList);
|
const task = this.plugin.createTask(this.taskText, this.estimatedMinutes, this.selectedList);
|
||||||
|
// Add scheduling data if provided
|
||||||
|
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);
|
this.plugin.addTask(task);
|
||||||
new Notice('✅ Task added!');
|
new Notice('✅ Task added!');
|
||||||
this.close();
|
this.close();
|
||||||
@@ -113,9 +166,9 @@ export class QuickAddTaskModal extends Modal {
|
|||||||
|
|
||||||
export class EditTaskModal extends Modal {
|
export class EditTaskModal extends Modal {
|
||||||
plugin: ImmersePlugin;
|
plugin: ImmersePlugin;
|
||||||
task: FocusTask;
|
task: ImmerseTask;
|
||||||
|
|
||||||
constructor(app: App, plugin: ImmersePlugin, task: FocusTask) {
|
constructor(app: App, plugin: ImmersePlugin, task: ImmerseTask) {
|
||||||
super(app);
|
super(app);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.task = { ...task };
|
this.task = { ...task };
|
||||||
@@ -177,6 +230,48 @@ export class EditTaskModal extends Modal {
|
|||||||
textarea.inputEl.rows = 4;
|
textarea.inputEl.rows = 4;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Scheduled date
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName('📅 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 || undefined);
|
||||||
|
text.inputEl.type = 'date';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scheduled time
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName('⏰ Scheduled Time')
|
||||||
|
.setDesc('Optional: What time?')
|
||||||
|
.addText(text => {
|
||||||
|
text.setPlaceholder('HH:mm')
|
||||||
|
.setValue(this.task.scheduledTime || '')
|
||||||
|
.onChange(value => this.task.scheduledTime = value || undefined);
|
||||||
|
text.inputEl.type = 'time';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reminder
|
||||||
|
if (this.plugin.settings.enableReminders) {
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName('🔔 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 : undefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Show actual time if task has been worked on
|
// Show actual time if task has been worked on
|
||||||
if (this.task.actualMinutes > 0) {
|
if (this.task.actualMinutes > 0) {
|
||||||
new Setting(contentEl)
|
new Setting(contentEl)
|
||||||
|
|||||||
22
src/types.ts
22
src/types.ts
@@ -1,6 +1,6 @@
|
|||||||
// ============ Types & Interfaces ============
|
// ============ Types & Interfaces ============
|
||||||
|
|
||||||
export interface FocusTask {
|
export interface ImmerseTask {
|
||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
@@ -10,7 +10,9 @@ export interface FocusTask {
|
|||||||
completedAt?: number;
|
completedAt?: number;
|
||||||
list: string;
|
list: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
scheduledDate?: string;
|
scheduledDate?: string; // Date in YYYY-MM-DD format
|
||||||
|
scheduledTime?: string; // Time in HH:mm format (24-hour)
|
||||||
|
reminderMinutes?: number; // Minutes before scheduled time to remind (0 = no reminder)
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ export interface TaskList {
|
|||||||
icon: string;
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FocusTaskSettings {
|
export interface ImmerseSettings {
|
||||||
pomodoroWorkMinutes: number;
|
pomodoroWorkMinutes: number;
|
||||||
pomodoroBreakMinutes: number;
|
pomodoroBreakMinutes: number;
|
||||||
longBreakMinutes: number;
|
longBreakMinutes: number;
|
||||||
@@ -34,10 +36,13 @@ export interface FocusTaskSettings {
|
|||||||
tickSoundEnabled: boolean;
|
tickSoundEnabled: boolean;
|
||||||
// Daily note logging
|
// Daily note logging
|
||||||
logToDaily: boolean;
|
logToDaily: boolean;
|
||||||
|
// Task reminders
|
||||||
|
enableReminders: boolean;
|
||||||
|
defaultReminderMinutes: number; // Default minutes before task to remind
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FocusTaskData {
|
export interface ImmerseData {
|
||||||
tasks: FocusTask[];
|
tasks: ImmerseTask[];
|
||||||
completedToday: number;
|
completedToday: number;
|
||||||
totalFocusMinutesToday: number;
|
totalFocusMinutesToday: number;
|
||||||
streak: number;
|
streak: number;
|
||||||
@@ -45,7 +50,7 @@ export interface FocusTaskData {
|
|||||||
pomodorosCompleted: number;
|
pomodorosCompleted: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: FocusTaskSettings = {
|
export const DEFAULT_SETTINGS: ImmerseSettings = {
|
||||||
pomodoroWorkMinutes: 25,
|
pomodoroWorkMinutes: 25,
|
||||||
pomodoroBreakMinutes: 5,
|
pomodoroBreakMinutes: 5,
|
||||||
longBreakMinutes: 15,
|
longBreakMinutes: 15,
|
||||||
@@ -62,9 +67,12 @@ export const DEFAULT_SETTINGS: FocusTaskSettings = {
|
|||||||
tickSoundEnabled: false,
|
tickSoundEnabled: false,
|
||||||
// Daily note logging
|
// Daily note logging
|
||||||
logToDaily: false,
|
logToDaily: false,
|
||||||
|
// Task reminders
|
||||||
|
enableReminders: true,
|
||||||
|
defaultReminderMinutes: 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_DATA: FocusTaskData = {
|
export const DEFAULT_DATA: ImmerseData = {
|
||||||
tasks: [],
|
tasks: [],
|
||||||
completedToday: 0,
|
completedToday: 0,
|
||||||
totalFocusMinutesToday: 0,
|
totalFocusMinutesToday: 0,
|
||||||
|
|||||||
27
src/view.ts
27
src/view.ts
@@ -3,7 +3,7 @@ import {
|
|||||||
WorkspaceLeaf,
|
WorkspaceLeaf,
|
||||||
} from 'obsidian';
|
} from 'obsidian';
|
||||||
|
|
||||||
import { VIEW_TYPE_IMMERSE, FocusTask } from './types';
|
import { VIEW_TYPE_IMMERSE, ImmerseTask } from './types';
|
||||||
import { QuickAddTaskModal, EditTaskModal } from './modals';
|
import { QuickAddTaskModal, EditTaskModal } from './modals';
|
||||||
import ImmersePlugin from './main';
|
import ImmersePlugin from './main';
|
||||||
|
|
||||||
@@ -324,11 +324,15 @@ export class ImmerseView extends ItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTaskItem(container: Element, task: FocusTask) {
|
renderTaskItem(container: Element, task: ImmerseTask) {
|
||||||
const list = this.plugin.settings.lists.find(l => l.id === task.list);
|
const list = this.plugin.settings.lists.find(l => l.id === task.list);
|
||||||
|
|
||||||
|
// Check if task is overdue
|
||||||
|
const isOverdue = !task.completed && task.scheduledDate && task.scheduledTime &&
|
||||||
|
new Date(`${task.scheduledDate}T${task.scheduledTime}`).getTime() < Date.now();
|
||||||
|
|
||||||
const taskEl = container.createEl('div', {
|
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' : ''}`
|
||||||
});
|
});
|
||||||
|
|
||||||
// Checkbox
|
// Checkbox
|
||||||
@@ -372,6 +376,23 @@ export class ImmerseView extends ItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show scheduled date/time if set
|
||||||
|
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(`⚠️ OVERDUE: ${dateStr}${timeStr ? ' ' + timeStr : ''}`);
|
||||||
|
} else {
|
||||||
|
scheduleSpan.setText(`📅 ${dateStr}${timeStr ? ' ' + timeStr : ''}`);
|
||||||
|
}
|
||||||
|
if (task.reminderMinutes) {
|
||||||
|
scheduleSpan.title = `Reminder set for ${task.reminderMinutes} min before`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
const actions = taskEl.createEl('div', { cls: 'immerse-task-actions' });
|
const actions = taskEl.createEl('div', { cls: 'immerse-task-actions' });
|
||||||
|
|
||||||
|
|||||||
27
styles.css
27
styles.css
@@ -464,6 +464,33 @@
|
|||||||
color: var(--ft-warning);
|
color: var(--ft-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.immerse-schedule-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--ft-primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-schedule-badge.overdue {
|
||||||
|
background: var(--ft-danger);
|
||||||
|
animation: pulse-overdue 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-overdue {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.immerse-task-item.overdue {
|
||||||
|
border-left: 3px solid var(--ft-danger);
|
||||||
|
}
|
||||||
|
|
||||||
/* Task Actions */
|
/* Task Actions */
|
||||||
.immerse-task-actions {
|
.immerse-task-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user