5 Commits

Author SHA1 Message Date
fe42d42500 Release v1.1.5: Bug Fixes & Report Enhancements
This release addresses multiple critical bugs and enhances the reporting experience.

## Bug Fixes
- Fixed: Actual time tracking now persists correctly across Pomodoro breaks
- Fixed: Timer actual time no longer resets to 0 when resuming work after breaks
- Fixed: Stopwatch mode timer bar now displays correctly
- Fixed: Daily stats counter no longer increments when canceling/stopping tasks
- Fixed: Report quick select buttons now update date fields automatically
- Fixed: Today's focus time calculation corrected (previous day restart issue resolved)

## Enhancements
- Added visual glow animation to active quick filter buttons in Reports
- Quick filter buttons now show active state with pulsing effect
- Date inputs update automatically when clicking quick filter options
- Improved UX with visual feedback for selected time ranges

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 20:06:56 +01:00
087d22f1fd fix: Stopwatch mode and daily stats improvements
Bug Fixes:
- Removed progress bar from stopwatch mode (only shows in pomodoro/break)
- Fixed daily stats not resetting at midnight when app stays open
- Fixed focus time tracking to only count completed tasks, not stopped sessions

Improvements:
- Added daily reset check interval (checks every 60 seconds)
- Added daily reset check on app visibility change
- Focus time now accurately reflects completed work only
- Session time tracking separated from daily focus time

Technical:
- Added dailyResetCheckInterval for periodic day change detection
- Added sessionStartSeconds to track focus time per session
- Removed real-time focus time updates from timer intervals
- Focus time only added in completeTask() method
- Progress bar conditionally rendered based on isStopwatchMode flag
2025-11-28 18:40:08 +01:00
a74fd244b0 Updated README.md 2025-11-26 09:47:32 +01:00
7fbb789fdd Updated README.md 2025-11-26 09:46:01 +01:00
8d6a20ff05 Release v1.1.4: Pomodoro & Stopwatch Mode Streamlining
Streamlined timer modes with bug fixes and code cleanup:

Bug Fixes:
- Fixed "POMODORO COMPLETE" showing in stopwatch mode
- Fixed break timer showing pomodoro notices in stopwatch mode
- Fixed pause/resume resetting timer to 0 in stopwatch mode
- Fixed "Continue Working" button appearing in stopwatch mode
- Fixed "Skip Break" button changing to "Continue" incorrectly

Improvements:
- Stopwatch mode now completely independent from pomodoro workflow
- Removed break functionality from stopwatch mode (only Pause, Complete, Stop buttons)
- Stopwatch mode maintains "FOCUSING ON" label throughout session
- Cleaned up dead code (wasStopwatchBeforeBreak flag and related logic)
- Simplified break resume logic (always returns to pomodoro mode)

Technical:
- Added isStopwatchMode flag for proper mode tracking
- Fixed toggleTimer to handle stopwatch count-up vs pomodoro countdown
- Removed unnecessary break integration code from stopwatch workflow
2025-11-25 21:41:12 +01:00
9 changed files with 218 additions and 76 deletions

View File

@@ -4,7 +4,7 @@ A powerful task management and focus timer plugin for [Obsidian](https://obsidia
![Immerse Banner](https://img.shields.io/badge/Obsidian-Plugin-7c3aed?style=for-the-badge&logo=obsidian&logoColor=white) ![Immerse Banner](https://img.shields.io/badge/Obsidian-Plugin-7c3aed?style=for-the-badge&logo=obsidian&logoColor=white)
![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge) ![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)
![Version](https://img.shields.io/badge/Version-1.1.3-blue?style=for-the-badge) ![Version](https://img.shields.io/badge/Version-1.1.5-blue?style=for-the-badge)
## 🎯 Overview ## 🎯 Overview
@@ -82,10 +82,6 @@ All settings available in Settings → Immerse:
- Daily note integration (auto-log completed tasks) - Daily note integration (auto-log completed tasks)
- Custom lists with names, emojis, and colors - Custom lists with names, emojis, and colors
## 🙏 Credits
Heavily inspired by [Blitzit](https://www.blitzit.app/) - a fantastic productivity app. Immerse brings that experience into Obsidian.
## 🤝 Contributing ## 🤝 Contributing
Contributions welcome! Fork, create a feature branch, and open a PR. Contributions welcome! Fork, create a feature branch, and open a PR.
@@ -107,5 +103,7 @@ MIT License - see [LICENSE](LICENSE) file.
**Links:** [Repository](https://git.cribdev.com/crib/immerse) • [Issues](https://git.cribdev.com/crib/immerse/issues) • [Blitzit](https://www.blitzit.app/) **Links:** [Repository](https://git.cribdev.com/crib/immerse) • [Issues](https://git.cribdev.com/crib/immerse/issues) • [Blitzit](https://www.blitzit.app/)
<p align="center"> <p align="center">
<em>Made with ❤️ for the Obsidian community • Inspired by Blitzit ⚡</em> Made with ❤️ for the Obsidian community
<br><br>
<em>✨ Coded with assistance from <a href="https://claude.ai">Claude.ai</a></em>
</p> </p>

108
main.js
View File

@@ -749,7 +749,12 @@ var ImmerseView = class extends import_obsidian2.ItemView {
const breakLabel = this.plugin.currentTimerSeconds > 0 ? "\u2615 BREAK TIME" : "\u2728 BREAK COMPLETE"; const breakLabel = this.plugin.currentTimerSeconds > 0 ? "\u2615 BREAK TIME" : "\u2728 BREAK COMPLETE";
activeCard.createEl("div", { cls: "immerse-active-label", text: breakLabel }); activeCard.createEl("div", { cls: "immerse-active-label", text: breakLabel });
} else { } else {
const workLabel = this.plugin.currentTimerSeconds > 0 ? "\u{1F3AF} FOCUSING ON" : "\u{1F345} POMODORO COMPLETE"; let workLabel;
if (this.plugin.currentTimerSeconds > 0 || this.plugin.isStopwatchMode) {
workLabel = "\u{1F3AF} FOCUSING ON";
} else {
workLabel = "\u{1F345} POMODORO COMPLETE";
}
activeCard.createEl("div", { cls: "immerse-active-label", text: workLabel }); activeCard.createEl("div", { cls: "immerse-active-label", text: workLabel });
} }
activeCard.createEl("div", { cls: "immerse-active-task-name", text: task.text }); activeCard.createEl("div", { cls: "immerse-active-task-name", text: task.text });
@@ -758,19 +763,21 @@ var ImmerseView = class extends import_obsidian2.ItemView {
cls: "immerse-timer-time", cls: "immerse-timer-time",
text: this.plugin.formatTime(this.plugin.currentTimerSeconds) text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
}); });
const progressWrap = activeCard.createEl("div", { cls: "immerse-progress-wrap" }); if (!this.plugin.isStopwatchMode) {
this.progressBarEl = progressWrap.createEl("div", { cls: "immerse-progress-bar" }); const progressWrap = activeCard.createEl("div", { cls: "immerse-progress-wrap" });
let progressPercent = 0; this.progressBarEl = progressWrap.createEl("div", { cls: "immerse-progress-bar" });
if (this.plugin.isBreakMode) { let progressPercent = 0;
const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60; if (this.plugin.isBreakMode) {
progressPercent = (breakDuration - this.plugin.currentTimerSeconds) / breakDuration * 100; const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60;
} else { progressPercent = (breakDuration - this.plugin.currentTimerSeconds) / breakDuration * 100;
const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; } else {
progressPercent = (workDuration - this.plugin.currentTimerSeconds) / workDuration * 100; const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60;
progressPercent = (workDuration - this.plugin.currentTimerSeconds) / workDuration * 100;
}
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
if (progressPercent >= 100)
this.progressBarEl.addClass("immerse-overtime");
} }
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
if (progressPercent >= 100)
this.progressBarEl.addClass("immerse-overtime");
if (!this.plugin.isBreakMode) { if (!this.plugin.isBreakMode) {
const timeInfo = activeCard.createEl("div", { cls: "immerse-time-info" }); const timeInfo = activeCard.createEl("div", { cls: "immerse-time-info" });
timeInfo.createEl("span", { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` }); timeInfo.createEl("span", { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` });
@@ -809,7 +816,7 @@ var ImmerseView = class extends import_obsidian2.ItemView {
}); });
} }
} else { } else {
if (this.plugin.currentTimerSeconds > 0) { if (this.plugin.currentTimerSeconds > 0 || this.plugin.isStopwatchMode) {
this.pauseBtnEl = controls.createEl("button", { cls: "immerse-btn immerse-btn-secondary" }); this.pauseBtnEl = controls.createEl("button", { cls: "immerse-btn immerse-btn-secondary" });
this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? "\u23F8 Pause" : "\u25B6 Resume"; this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? "\u23F8 Pause" : "\u25B6 Resume";
this.pauseBtnEl.addEventListener("click", () => this.plugin.toggleTimer()); this.pauseBtnEl.addEventListener("click", () => this.plugin.toggleTimer());
@@ -975,6 +982,10 @@ var ReportView = class extends import_obsidian3.ItemView {
constructor(leaf, plugin) { constructor(leaf, plugin) {
super(leaf); super(leaf);
this.selectedListIds = []; this.selectedListIds = [];
this.activeQuickFilter = "Last 7 days";
// Track active filter
this.startDateInput = null;
this.endDateInput = null;
this.plugin = plugin; this.plugin = plugin;
const today = new Date(); const today = new Date();
this.endDate = today.toISOString().split("T")[0]; this.endDate = today.toISOString().split("T")[0];
@@ -1016,12 +1027,20 @@ var ReportView = class extends import_obsidian3.ItemView {
const filtersSection = container.createEl("div", { cls: "immerse-report-filters" }); const filtersSection = container.createEl("div", { cls: "immerse-report-filters" });
const dateRow = filtersSection.createEl("div", { cls: "immerse-report-filter-row" }); const dateRow = filtersSection.createEl("div", { cls: "immerse-report-filter-row" });
new import_obsidian3.Setting(dateRow).setName("Start Date").addText((text) => { new import_obsidian3.Setting(dateRow).setName("Start Date").addText((text) => {
text.setValue(this.startDate).onChange((value) => this.startDate = value); text.setValue(this.startDate).onChange((value) => {
this.startDate = value;
this.activeQuickFilter = null;
});
text.inputEl.type = "date"; text.inputEl.type = "date";
this.startDateInput = text.inputEl;
}); });
new import_obsidian3.Setting(dateRow).setName("End Date").addText((text) => { new import_obsidian3.Setting(dateRow).setName("End Date").addText((text) => {
text.setValue(this.endDate).onChange((value) => this.endDate = value); text.setValue(this.endDate).onChange((value) => {
this.endDate = value;
this.activeQuickFilter = null;
});
text.inputEl.type = "date"; text.inputEl.type = "date";
this.endDateInput = text.inputEl;
}); });
const quickFilters = filtersSection.createEl("div", { cls: "immerse-report-quick-filters" }); const quickFilters = filtersSection.createEl("div", { cls: "immerse-report-quick-filters" });
quickFilters.createEl("span", { text: "Quick select: ", cls: "immerse-filter-label" }); quickFilters.createEl("span", { text: "Quick select: ", cls: "immerse-filter-label" });
@@ -1036,12 +1055,24 @@ var ReportView = class extends import_obsidian3.ItemView {
text: filter.label, text: filter.label,
cls: "immerse-quick-filter-btn" cls: "immerse-quick-filter-btn"
}); });
if (filter.label === this.activeQuickFilter) {
btn.addClass("active");
}
btn.addEventListener("click", () => { btn.addEventListener("click", () => {
const today = new Date(); const today = new Date();
this.endDate = today.toISOString().split("T")[0]; this.endDate = today.toISOString().split("T")[0];
const startDate = new Date(today); const startDate = new Date(today);
startDate.setDate(startDate.getDate() - filter.days); startDate.setDate(startDate.getDate() - filter.days);
this.startDate = startDate.toISOString().split("T")[0]; this.startDate = startDate.toISOString().split("T")[0];
if (this.startDateInput)
this.startDateInput.value = this.startDate;
if (this.endDateInput)
this.endDateInput.value = this.endDate;
this.activeQuickFilter = filter.label;
quickFilters.querySelectorAll(".immerse-quick-filter-btn").forEach((b) => {
b.removeClass("active");
});
btn.addClass("active");
this.renderReport(container); this.renderReport(container);
}); });
}); });
@@ -1250,6 +1281,7 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.currentTimerSeconds = 0; this.currentTimerSeconds = 0;
this.isTimerRunning = false; this.isTimerRunning = false;
this.isBreakMode = false; this.isBreakMode = false;
this.isStopwatchMode = false;
this.activeTaskId = null; this.activeTaskId = null;
this.pomodoroCount = 0; this.pomodoroCount = 0;
// Timestamp-based tracking for reliable background timing // Timestamp-based tracking for reliable background timing
@@ -1258,19 +1290,29 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
// Focus time tracking (in seconds for accuracy) // Focus time tracking (in seconds for accuracy)
this.focusSecondsToday = 0; this.focusSecondsToday = 0;
this.secondsWorkedOnCurrentTask = 0; this.secondsWorkedOnCurrentTask = 0;
this.sessionStartSeconds = 0;
// Track seconds at start of current session
// Status bar element // Status bar element
this.statusBarEl = null; this.statusBarEl = null;
// Reminder system // Reminder system
this.reminderCheckInterval = null; this.reminderCheckInterval = null;
this.notifiedReminders = /* @__PURE__ */ new Set(); this.notifiedReminders = /* @__PURE__ */ new Set();
// Track which reminders have been shown
// Daily reset check interval
this.dailyResetCheckInterval = null;
} }
// Track which reminders have been shown
async onload() { async onload() {
await this.loadAllData(); await this.loadAllData();
this.checkDailyReset(); this.checkDailyReset();
this.dailyResetCheckInterval = window.setInterval(() => {
this.checkDailyReset();
}, 6e4);
document.addEventListener("visibilitychange", () => { document.addEventListener("visibilitychange", () => {
if (!document.hidden && this.isTimerRunning) { if (!document.hidden) {
this.syncTimerFromTimestamp(); this.checkDailyReset();
if (this.isTimerRunning) {
this.syncTimerFromTimestamp();
}
} }
}); });
this.registerView( this.registerView(
@@ -1323,6 +1365,10 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
onunload() { onunload() {
this.stopTimer(); this.stopTimer();
this.stopReminderSystem(); this.stopReminderSystem();
if (this.dailyResetCheckInterval) {
window.clearInterval(this.dailyResetCheckInterval);
this.dailyResetCheckInterval = null;
}
} }
async loadAllData() { async loadAllData() {
const loaded = await this.loadData(); const loaded = await this.loadData();
@@ -1434,6 +1480,10 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
task.isActive = false; task.isActive = false;
this.data.completedToday++; this.data.completedToday++;
this.data.lastActiveDate = new Date().toDateString(); this.data.lastActiveDate = new Date().toDateString();
if (this.activeTaskId === taskId && this.sessionStartSeconds !== void 0) {
const sessionTime = this.secondsWorkedOnCurrentTask - this.sessionStartSeconds;
this.focusSecondsToday += sessionTime;
}
this.archiveCompletedTask(task); this.archiveCompletedTask(task);
this.updateDailyStats(task); this.updateDailyStats(task);
if (this.settings.enableCelebrations) { if (this.settings.enableCelebrations) {
@@ -1652,12 +1702,14 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.activeTaskId = taskId; this.activeTaskId = taskId;
task.isActive = true; task.isActive = true;
this.isBreakMode = false; this.isBreakMode = false;
this.isStopwatchMode = true;
this.currentTimerSeconds = 0; this.currentTimerSeconds = 0;
this.isTimerRunning = true; this.isTimerRunning = true;
this.secondsWorkedOnCurrentTask = task.actualMinutes * 60; this.secondsWorkedOnCurrentTask = task.actualMinutes * 60;
this.timerStartTimestamp = Date.now(); this.timerStartTimestamp = Date.now();
this.pausedTimeRemaining = 0; this.pausedTimeRemaining = 0;
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask;
let alertShown = false; let alertShown = false;
this.refreshView(); this.refreshView();
this.updateStatusBar(); this.updateStatusBar();
@@ -1668,8 +1720,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.currentTimerSeconds = elapsedSeconds; this.currentTimerSeconds = elapsedSeconds;
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
if (!alertShown && this.currentTimerSeconds >= task.estimatedMinutes * 60) { if (!alertShown && this.currentTimerSeconds >= task.estimatedMinutes * 60) {
@@ -1690,12 +1740,14 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.activeTaskId = taskId; this.activeTaskId = taskId;
task.isActive = true; task.isActive = true;
this.isBreakMode = false; this.isBreakMode = false;
this.isStopwatchMode = false;
this.currentTimerSeconds = this.settings.pomodoroWorkMinutes * 60; this.currentTimerSeconds = this.settings.pomodoroWorkMinutes * 60;
this.isTimerRunning = true; this.isTimerRunning = true;
this.secondsWorkedOnCurrentTask = Math.floor(task.actualMinutes * 60); this.secondsWorkedOnCurrentTask = Math.floor(task.actualMinutes * 60);
this.timerStartTimestamp = Date.now(); this.timerStartTimestamp = Date.now();
this.pausedTimeRemaining = this.currentTimerSeconds; this.pausedTimeRemaining = this.currentTimerSeconds;
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask;
this.refreshView(); this.refreshView();
this.updateStatusBar(); this.updateStatusBar();
this.timerInterval = window.setInterval(() => { this.timerInterval = window.setInterval(() => {
@@ -1709,8 +1761,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
if (task.actualMinutes !== actualMinutes) { if (task.actualMinutes !== actualMinutes) {
task.actualMinutes = actualMinutes; task.actualMinutes = actualMinutes;
} }
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
} }
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
@@ -1788,16 +1838,18 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
const now = Date.now(); const now = Date.now();
const elapsedMs = now - this.timerStartTimestamp; const elapsedMs = now - this.timerStartTimestamp;
const elapsedSeconds = Math.floor(elapsedMs / 1e3); const elapsedSeconds = Math.floor(elapsedMs / 1e3);
this.currentTimerSeconds = Math.max(0, this.pausedTimeRemaining - elapsedSeconds); if (this.pausedTimeRemaining === 0 || this.isStopwatchMode) {
this.currentTimerSeconds = this.pausedTimeRemaining + elapsedSeconds;
} else {
this.currentTimerSeconds = Math.max(0, this.pausedTimeRemaining - elapsedSeconds);
}
if (task && !this.isBreakMode) { if (task && !this.isBreakMode) {
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
} }
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
if (this.currentTimerSeconds <= 0) { if (this.currentTimerSeconds <= 0 && !this.isStopwatchMode) {
this.handlePomodoroEnd(); this.handlePomodoroEnd();
} }
}, 1e3); }, 1e3);
@@ -1822,8 +1874,10 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
} }
} }
this.isTimerRunning = false; this.isTimerRunning = false;
this.isStopwatchMode = false;
this.activeTaskId = null; this.activeTaskId = null;
this.secondsWorkedOnCurrentTask = 0; this.secondsWorkedOnCurrentTask = 0;
this.sessionStartSeconds = 0;
this.timerStartTimestamp = 0; this.timerStartTimestamp = 0;
this.pausedTimeRemaining = 0; this.pausedTimeRemaining = 0;
this.updateStatusBar(); this.updateStatusBar();

View File

@@ -1,7 +1,7 @@
{ {
"id": "immerse", "id": "immerse",
"name": "Immerse", "name": "Immerse",
"version": "1.1.3", "version": "1.1.5",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "A Blitzit-inspired task management and focus timer plugin. Plan your day, track time with Pomodoro technique, and crush your tasks with satisfying checkoffs.", "description": "A Blitzit-inspired task management and focus timer plugin. Plan your day, track time with Pomodoro technique, and crush your tasks with satisfying checkoffs.",
"author": "Crib", "author": "Crib",

View File

@@ -1,6 +1,6 @@
{ {
"name": "immerse", "name": "immerse",
"version": "1.1.3", "version": "1.1.5",
"description": "A Blitzit-inspired task management and focus timer plugin for Obsidian", "description": "A Blitzit-inspired task management and focus timer plugin for Obsidian",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@@ -35,6 +35,7 @@ export default class ImmersePlugin extends Plugin {
currentTimerSeconds: number = 0; currentTimerSeconds: number = 0;
isTimerRunning: boolean = false; isTimerRunning: boolean = false;
isBreakMode: boolean = false; isBreakMode: boolean = false;
isStopwatchMode: boolean = false;
activeTaskId: string | null = null; activeTaskId: string | null = null;
pomodoroCount: number = 0; pomodoroCount: number = 0;
@@ -45,6 +46,7 @@ export default class ImmersePlugin extends Plugin {
// Focus time tracking (in seconds for accuracy) // Focus time tracking (in seconds for accuracy)
private focusSecondsToday: number = 0; private focusSecondsToday: number = 0;
private secondsWorkedOnCurrentTask: number = 0; private secondsWorkedOnCurrentTask: number = 0;
private sessionStartSeconds: number = 0; // Track seconds at start of current session
// Status bar element // Status bar element
statusBarEl: HTMLElement | null = null; statusBarEl: HTMLElement | null = null;
@@ -53,16 +55,28 @@ export default class ImmersePlugin extends Plugin {
private reminderCheckInterval: number | null = null; private reminderCheckInterval: number | null = null;
private notifiedReminders: Set<string> = new Set(); // Track which reminders have been shown private notifiedReminders: Set<string> = new Set(); // Track which reminders have been shown
// Daily reset check interval
private dailyResetCheckInterval: number | null = null;
async onload() { async onload() {
await this.loadAllData(); await this.loadAllData();
// Check and reset daily stats // Check and reset daily stats
this.checkDailyReset(); this.checkDailyReset();
// Start daily reset check interval (check every 60 seconds)
this.dailyResetCheckInterval = window.setInterval(() => {
this.checkDailyReset();
}, 60000);
// Handle visibility changes to sync timer when app comes back to foreground // Handle visibility changes to sync timer when app comes back to foreground
document.addEventListener('visibilitychange', () => { document.addEventListener('visibilitychange', () => {
if (!document.hidden && this.isTimerRunning) { if (!document.hidden) {
this.syncTimerFromTimestamp(); // Check for day change when app becomes visible
this.checkDailyReset();
if (this.isTimerRunning) {
this.syncTimerFromTimestamp();
}
} }
}); });
@@ -134,6 +148,12 @@ export default class ImmersePlugin extends Plugin {
onunload() { onunload() {
this.stopTimer(); this.stopTimer();
this.stopReminderSystem(); this.stopReminderSystem();
// Stop daily reset check interval
if (this.dailyResetCheckInterval) {
window.clearInterval(this.dailyResetCheckInterval);
this.dailyResetCheckInterval = null;
}
} }
async loadAllData() { async loadAllData() {
@@ -278,6 +298,12 @@ export default class ImmersePlugin extends Plugin {
this.data.completedToday++; this.data.completedToday++;
this.data.lastActiveDate = new Date().toDateString(); this.data.lastActiveDate = new Date().toDateString();
// Add focus time from this session (only on completion)
if (this.activeTaskId === taskId && this.sessionStartSeconds !== undefined) {
const sessionTime = this.secondsWorkedOnCurrentTask - this.sessionStartSeconds;
this.focusSecondsToday += sessionTime;
}
// Archive task for historical reporting // Archive task for historical reporting
this.archiveCompletedTask(task); this.archiveCompletedTask(task);
@@ -551,6 +577,7 @@ export default class ImmersePlugin extends Plugin {
this.activeTaskId = taskId; this.activeTaskId = taskId;
task.isActive = true; task.isActive = true;
this.isBreakMode = false; this.isBreakMode = false;
this.isStopwatchMode = true;
this.currentTimerSeconds = 0; this.currentTimerSeconds = 0;
this.isTimerRunning = true; this.isTimerRunning = true;
this.secondsWorkedOnCurrentTask = task.actualMinutes * 60; this.secondsWorkedOnCurrentTask = task.actualMinutes * 60;
@@ -561,6 +588,7 @@ export default class ImmersePlugin extends Plugin {
// Store initial values // Store initial values
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time
let alertShown = false; let alertShown = false;
// Full refresh to show the active task card // Full refresh to show the active task card
@@ -581,10 +609,6 @@ export default class ImmersePlugin extends Plugin {
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
// Update focus time
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
// Light update - only timer display, no full refresh // Light update - only timer display, no full refresh
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
@@ -610,6 +634,7 @@ export default class ImmersePlugin extends Plugin {
this.activeTaskId = taskId; this.activeTaskId = taskId;
task.isActive = true; task.isActive = true;
this.isBreakMode = false; this.isBreakMode = false;
this.isStopwatchMode = false;
this.currentTimerSeconds = this.settings.pomodoroWorkMinutes * 60; this.currentTimerSeconds = this.settings.pomodoroWorkMinutes * 60;
this.isTimerRunning = true; this.isTimerRunning = true;
@@ -623,6 +648,7 @@ export default class ImmersePlugin extends Plugin {
// Store the initial seconds worked to calculate delta // Store the initial seconds worked to calculate delta
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time
// Full refresh to show the active task card // Full refresh to show the active task card
this.refreshView(); this.refreshView();
@@ -645,10 +671,6 @@ export default class ImmersePlugin extends Plugin {
if (task.actualMinutes !== actualMinutes) { if (task.actualMinutes !== actualMinutes) {
task.actualMinutes = actualMinutes; task.actualMinutes = actualMinutes;
} }
// Update focus time based on elapsed seconds
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
} }
// Light update - only timer display, no full refresh // Light update - only timer display, no full refresh
@@ -766,24 +788,26 @@ export default class ImmersePlugin extends Plugin {
const elapsedMs = now - this.timerStartTimestamp; const elapsedMs = now - this.timerStartTimestamp;
const elapsedSeconds = Math.floor(elapsedMs / 1000); const elapsedSeconds = Math.floor(elapsedMs / 1000);
// Update timer (countdown from paused position) // Update timer based on mode
this.currentTimerSeconds = Math.max(0, this.pausedTimeRemaining - elapsedSeconds); if (this.pausedTimeRemaining === 0 || this.isStopwatchMode) {
// Stopwatch mode - count up from paused position
this.currentTimerSeconds = this.pausedTimeRemaining + elapsedSeconds;
} else {
// Countdown mode (pomodoro/break) - count down from paused position
this.currentTimerSeconds = Math.max(0, this.pausedTimeRemaining - elapsedSeconds);
}
if (task && !this.isBreakMode) { if (task && !this.isBreakMode) {
// Update actual time worked // Update actual time worked
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
// Update focus time
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
} }
// Light update - only timer display // Light update - only timer display
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
if (this.currentTimerSeconds <= 0) { if (this.currentTimerSeconds <= 0 && !this.isStopwatchMode) {
this.handlePomodoroEnd(); this.handlePomodoroEnd();
} }
}, 1000); }, 1000);
@@ -814,8 +838,10 @@ export default class ImmersePlugin extends Plugin {
} }
this.isTimerRunning = false; this.isTimerRunning = false;
this.isStopwatchMode = false;
this.activeTaskId = null; this.activeTaskId = null;
this.secondsWorkedOnCurrentTask = 0; this.secondsWorkedOnCurrentTask = 0;
this.sessionStartSeconds = 0;
this.timerStartTimestamp = 0; this.timerStartTimestamp = 0;
this.pausedTimeRemaining = 0; this.pausedTimeRemaining = 0;
this.updateStatusBar(); this.updateStatusBar();

View File

@@ -14,6 +14,9 @@ export class ReportView extends ItemView {
startDate: string; startDate: string;
endDate: string; endDate: string;
selectedListIds: string[] = []; selectedListIds: string[] = [];
activeQuickFilter: string | null = 'Last 7 days'; // Track active filter
startDateInput: HTMLInputElement | null = null;
endDateInput: HTMLInputElement | null = null;
constructor(leaf: WorkspaceLeaf, plugin: ImmersePlugin) { constructor(leaf: WorkspaceLeaf, plugin: ImmersePlugin) {
super(leaf); super(leaf);
@@ -83,16 +86,24 @@ export class ReportView extends ItemView {
.setName('Start Date') .setName('Start Date')
.addText(text => { .addText(text => {
text.setValue(this.startDate) text.setValue(this.startDate)
.onChange(value => this.startDate = value); .onChange(value => {
this.startDate = value;
this.activeQuickFilter = null; // Clear active filter when manually changing dates
});
text.inputEl.type = 'date'; text.inputEl.type = 'date';
this.startDateInput = text.inputEl; // Store reference
}); });
new Setting(dateRow) new Setting(dateRow)
.setName('End Date') .setName('End Date')
.addText(text => { .addText(text => {
text.setValue(this.endDate) text.setValue(this.endDate)
.onChange(value => this.endDate = value); .onChange(value => {
this.endDate = value;
this.activeQuickFilter = null; // Clear active filter when manually changing dates
});
text.inputEl.type = 'date'; text.inputEl.type = 'date';
this.endDateInput = text.inputEl; // Store reference
}); });
// Quick filters // Quick filters
@@ -111,12 +122,33 @@ export class ReportView extends ItemView {
text: filter.label, text: filter.label,
cls: 'immerse-quick-filter-btn' cls: 'immerse-quick-filter-btn'
}); });
// Set active class if this is the default active filter
if (filter.label === this.activeQuickFilter) {
btn.addClass('active');
}
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
// Update dates
const today = new Date(); const today = new Date();
this.endDate = today.toISOString().split('T')[0]; this.endDate = today.toISOString().split('T')[0];
const startDate = new Date(today); const startDate = new Date(today);
startDate.setDate(startDate.getDate() - filter.days); startDate.setDate(startDate.getDate() - filter.days);
this.startDate = startDate.toISOString().split('T')[0]; this.startDate = startDate.toISOString().split('T')[0];
// Update date inputs
if (this.startDateInput) this.startDateInput.value = this.startDate;
if (this.endDateInput) this.endDateInput.value = this.endDate;
// Update active filter
this.activeQuickFilter = filter.label;
// Update button states
quickFilters.querySelectorAll('.immerse-quick-filter-btn').forEach(b => {
b.removeClass('active');
});
btn.addClass('active');
this.renderReport(container); this.renderReport(container);
}); });
}); });

View File

@@ -154,7 +154,13 @@ export class ImmerseView extends ItemView {
const breakLabel = this.plugin.currentTimerSeconds > 0 ? '☕ BREAK TIME' : '✨ BREAK COMPLETE'; const breakLabel = this.plugin.currentTimerSeconds > 0 ? '☕ BREAK TIME' : '✨ BREAK COMPLETE';
activeCard.createEl('div', { cls: 'immerse-active-label', text: breakLabel }); activeCard.createEl('div', { cls: 'immerse-active-label', text: breakLabel });
} else { } else {
const workLabel = this.plugin.currentTimerSeconds > 0 ? '🎯 FOCUSING ON' : '🍅 POMODORO COMPLETE'; // Determine label based on whether timer is active and mode (stopwatch vs pomodoro)
let workLabel: string;
if (this.plugin.currentTimerSeconds > 0 || this.plugin.isStopwatchMode) {
workLabel = '🎯 FOCUSING ON';
} else {
workLabel = '🍅 POMODORO COMPLETE';
}
activeCard.createEl('div', { cls: 'immerse-active-label', text: workLabel }); activeCard.createEl('div', { cls: 'immerse-active-label', text: workLabel });
} }
@@ -167,24 +173,26 @@ export class ImmerseView extends ItemView {
text: this.plugin.formatTime(this.plugin.currentTimerSeconds) text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
}); });
// Progress bar - store reference for updates // Progress bar - only show in pomodoro/break mode, not stopwatch
const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' }); if (!this.plugin.isStopwatchMode) {
this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' }); const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' });
this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' });
let progressPercent = 0; let progressPercent = 0;
if (this.plugin.isBreakMode) { if (this.plugin.isBreakMode) {
const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0
? this.plugin.settings.longBreakMinutes * 60 ? this.plugin.settings.longBreakMinutes * 60
: this.plugin.settings.pomodoroBreakMinutes * 60; : this.plugin.settings.pomodoroBreakMinutes * 60;
progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100; progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100;
} else { } else {
const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60;
progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100; progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100;
}
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime');
} }
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime');
// Time info - store reference for actual time updates // Time info - store reference for actual time updates
if (!this.plugin.isBreakMode) { if (!this.plugin.isBreakMode) {
const timeInfo = activeCard.createEl('div', { cls: 'immerse-time-info' }); const timeInfo = activeCard.createEl('div', { cls: 'immerse-time-info' });
@@ -234,8 +242,8 @@ export class ImmerseView extends ItemView {
} }
} else { } else {
// Work mode controls // Work mode controls
if (this.plugin.currentTimerSeconds > 0) { if (this.plugin.currentTimerSeconds > 0 || this.plugin.isStopwatchMode) {
// Work session still running // Work session still running (or stopwatch mode active)
this.pauseBtnEl = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' }); this.pauseBtnEl = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' });
this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? '⏸ Pause' : '▶ Resume'; this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? '⏸ Pause' : '▶ Resume';
this.pauseBtnEl.addEventListener('click', () => this.plugin.toggleTimer()); this.pauseBtnEl.addEventListener('click', () => this.plugin.toggleTimer());
@@ -248,7 +256,7 @@ export class ImmerseView extends ItemView {
stopBtn.innerHTML = '✕ Stop'; stopBtn.innerHTML = '✕ Stop';
stopBtn.addEventListener('click', () => this.plugin.stopTimer()); stopBtn.addEventListener('click', () => this.plugin.stopTimer());
} else { } else {
// Work session finished - show break and completion options // Pomodoro session finished - show break and completion options
const startBreakBtn = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' }); const startBreakBtn = controls.createEl('button', { cls: 'immerse-btn immerse-btn-secondary' });
startBreakBtn.innerHTML = '☕ Start Break'; startBreakBtn.innerHTML = '☕ Start Break';
startBreakBtn.addEventListener('click', () => this.plugin.startBreak()); startBreakBtn.addEventListener('click', () => this.plugin.startBreak());

View File

@@ -757,15 +757,38 @@
border: 1px solid var(--ft-border); border: 1px solid var(--ft-border);
color: var(--ft-text); color: var(--ft-text);
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.3s ease;
font-size: 13px; font-size: 13px;
white-space: nowrap; white-space: nowrap;
position: relative;
} }
.immerse-quick-filter-btn:hover { .immerse-quick-filter-btn:hover {
background: var(--ft-primary); background: var(--ft-primary);
color: white; color: white;
border-color: var(--ft-primary); border-color: var(--ft-primary);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}
.immerse-quick-filter-btn.active {
background: var(--ft-primary);
color: white;
border-color: var(--ft-primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
0 0 20px rgba(99, 102, 241, 0.4);
animation: pulse-glow 2s ease-in-out infinite;
}
@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
0 0 20px rgba(99, 102, 241, 0.4);
}
50% {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3),
0 0 25px rgba(99, 102, 241, 0.6);
}
} }
.immerse-report-generate-btn { .immerse-report-generate-btn {

View File

@@ -5,5 +5,6 @@
"1.0.7": "0.15.0", "1.0.7": "0.15.0",
"1.0.8": "0.15.0", "1.0.8": "0.15.0",
"1.0.9": "0.15.0", "1.0.9": "0.15.0",
"1.1.3": "0.15.0" "1.1.3": "0.15.0",
"1.1.4": "0.15.0"
} }