feat(pomodoro): enhance timer with estimation overrides and detailed cycle tracking

- Add user override interface for custom time estimation (H/M inputs with add/replace toggle)
- Implement override-only calculation when no notes are available
- Fix cycle tracking to update/reset on each calculation
- Enhance cycle progress display format: "Cycles X/X - Sessions X/X - XH/XM"
- Separate Pomodoro visibility from review buttons for persistent display
- Add comprehensive estimation logic supporting both note-based and override-only scenarios
- Improve UI styling with focus states, better visual hierarchy, and responsive layout
This commit is contained in:
dralkh 2025-10-04 13:50:48 +03:00
parent 52efa857af
commit 265b866d44
10 changed files with 894 additions and 104 deletions

337
main.js
View file

@ -4356,6 +4356,12 @@ var PomodoroUIManager = class {
this.pomodoroQuickLongInput = null;
this.pomodoroQuickSessionsInput = null;
this.pomodoroCalculationResultEl = null;
// New estimation and cycle tracking elements (moved to calculation panel)
this.pomodoroCycleProgressEl = null;
// User override input elements
this.pomodoroUserOverrideHoursInput = null;
this.pomodoroUserOverrideMinutesInput = null;
this.pomodoroUserAddToEstimationCheckbox = null;
// private isPomodoroSectionOpen: boolean = false; // No longer needed, section is always "open"
this.areButtonsVisible = true;
// For Play/Pause/Skip buttons
@ -4432,7 +4438,7 @@ var PomodoroUIManager = class {
* @param container The parent element to render into (this.pomodoroRootEl).
*/
renderPomodoroTimer(container) {
var _a, _b, _c, _d, _e;
var _a, _b, _c, _d, _e, _f;
let mainControlsRow = container.querySelector(".pomodoro-main-controls");
if (!mainControlsRow) {
mainControlsRow = container.createDiv("pomodoro-main-controls");
@ -4533,12 +4539,16 @@ var PomodoroUIManager = class {
(0, import_obsidian12.setIcon)(this.pomodoroSkipBtn, "skip-forward");
this.pomodoroSkipBtn.addEventListener("click", () => this.plugin.pomodoroService.skipSession());
}
if (!this.pomodoroCycleProgressEl || this.pomodoroCycleProgressEl.parentElement !== container) {
(_e = this.pomodoroCycleProgressEl) == null ? void 0 : _e.remove();
this.pomodoroCycleProgressEl = container.createDiv("pomodoro-cycle-progress");
}
let settingsPanelContainer = container.querySelector(".pomodoro-settings-panel-container");
if (!settingsPanelContainer) {
settingsPanelContainer = container.createDiv("pomodoro-settings-panel-container");
}
if (!this.pomodoroQuickSettingsPanelEl || this.pomodoroQuickSettingsPanelEl.parentElement !== settingsPanelContainer) {
(_e = this.pomodoroQuickSettingsPanelEl) == null ? void 0 : _e.remove();
(_f = this.pomodoroQuickSettingsPanelEl) == null ? void 0 : _f.remove();
this.pomodoroQuickSettingsPanelEl = settingsPanelContainer.createDiv("pomodoro-quick-settings-panel");
this.pomodoroQuickSettingsPanelEl.style.display = "none";
const createQuickSetting = (labelText, inputType = "number") => {
@ -4553,6 +4563,26 @@ var PomodoroUIManager = class {
this.pomodoroQuickLongInput = createQuickSetting("Long Break (min):");
this.pomodoroQuickSessionsInput = createQuickSetting("Sessions/Long Break:");
const buttonsContainer = this.pomodoroQuickSettingsPanelEl.createDiv({ cls: "pomodoro-quick-settings-buttons" });
const overrideContainer = this.pomodoroQuickSettingsPanelEl.createDiv("pomodoro-override-container");
const overrideLabel = overrideContainer.createEl("label", { text: "Override time (optional):", cls: "pomodoro-override-label" });
const overrideInputsContainer = overrideContainer.createDiv("pomodoro-override-inputs");
this.pomodoroUserOverrideHoursInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-hours" });
this.pomodoroUserOverrideHoursInput.setAttr("min", "0");
this.pomodoroUserOverrideHoursInput.setAttr("placeholder", "H");
this.pomodoroUserOverrideHoursInput.value = String(this.plugin.pluginState.pomodoroUserOverrideHours);
const hoursLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small");
hoursLabel.setText("h");
this.pomodoroUserOverrideMinutesInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-minutes" });
this.pomodoroUserOverrideMinutesInput.setAttr("min", "0");
this.pomodoroUserOverrideMinutesInput.setAttr("placeholder", "M");
this.pomodoroUserOverrideMinutesInput.value = String(this.plugin.pluginState.pomodoroUserOverrideMinutes);
const minutesLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small");
minutesLabel.setText("m");
const toggleContainer = overrideContainer.createDiv("pomodoro-override-toggle-container");
this.pomodoroUserAddToEstimationCheckbox = toggleContainer.createEl("input", { type: "checkbox", cls: "pomodoro-add-to-estimation" });
this.pomodoroUserAddToEstimationCheckbox.checked = this.plugin.pluginState.pomodoroUserAddToEstimation;
const toggleLabel = toggleContainer.createEl("label", { text: "Add to estimated time", cls: "pomodoro-toggle-label" });
toggleLabel.setAttribute("for", "pomodoro-add-to-estimation");
const calculateBtn = buttonsContainer.createEl("button", { text: "Calculate Reading Time", cls: "pomodoro-quick-calculate-btn" });
calculateBtn.addEventListener("click", async () => {
const settingsSaved = this._savePomodoroSettings();
@ -4571,54 +4601,60 @@ var PomodoroUIManager = class {
async calculateAndDisplayPomodoroEstimate() {
if (!this.plugin || !this.pomodoroCalculationResultEl)
return;
this.saveUserOverrideSettings();
const notesForEstimate = this.plugin.reviewController.getTodayNotes();
if (notesForEstimate.length === 0) {
const userOverrideHours = this.plugin.pluginState.pomodoroUserOverrideHours || 0;
const userOverrideMinutes = this.plugin.pluginState.pomodoroUserOverrideMinutes || 0;
const userOverrideTimeInMinutes = userOverrideHours * 60 + userOverrideMinutes;
if (notesForEstimate.length === 0 && userOverrideTimeInMinutes === 0) {
const activeDate = this.plugin.reviewController.getCurrentReviewDateOverride();
const message = activeDate ? `No notes scheduled for ${new Date(activeDate).toLocaleDateString()} to calculate.` : "No notes currently due to calculate.";
this.pomodoroCalculationResultEl.setText(message);
this.pomodoroCalculationResultEl.style.display = "block";
return;
}
let totalReadingTimeInSeconds = 0;
for (const note of notesForEstimate) {
totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path);
}
const totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60;
const settings = this.plugin.settings;
const workDuration = settings.pomodoroWorkDuration;
const shortBreakDuration = settings.pomodoroShortBreakDuration;
const longBreakDuration = settings.pomodoroLongBreakDuration;
const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak;
let pomodorosNeeded = 0;
let sessionsCompletedInCycle = 0;
let remainingReadingTimeMinutes = totalReadingTimeInMinutes;
let totalBreakTimeInMinutes = 0;
if (totalReadingTimeInMinutes === 0) {
this.pomodoroCalculationResultEl.setText("Estimated reading time is 0 minutes.");
const result = await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate);
if (!result) {
this.pomodoroCalculationResultEl.setText("Unable to calculate estimation.");
this.pomodoroCalculationResultEl.style.display = "block";
return;
}
while (remainingReadingTimeMinutes > 0) {
pomodorosNeeded++;
remainingReadingTimeMinutes -= workDuration;
sessionsCompletedInCycle++;
if (remainingReadingTimeMinutes <= 0)
break;
if (sessionsCompletedInCycle >= sessionsUntilLongBreak) {
totalBreakTimeInMinutes += longBreakDuration;
sessionsCompletedInCycle = 0;
} else {
totalBreakTimeInMinutes += shortBreakDuration;
}
}
const totalTimeWithBreaksMinutes = pomodorosNeeded * workDuration + totalBreakTimeInMinutes;
const { totalReadingTimeInSeconds, totalReadingTimeInMinutes, pomodorosNeeded, totalTimeWithBreaksMinutes } = result;
const addToEstimation = this.plugin.pluginState.pomodoroUserAddToEstimation || false;
const formattedTotalReadingTime = EstimationUtils.formatTime(totalReadingTimeInSeconds);
const formattedTotalTimeWithBreaks = EstimationUtils.formatTime(Math.ceil(totalTimeWithBreaksMinutes * 60));
this.pomodoroCalculationResultEl.empty();
this.pomodoroCalculationResultEl.createEl("p", { text: `Estimated reading time for ${notesForEstimate.length} note(s) in current view: ${formattedTotalReadingTime}.` });
if (notesForEstimate.length > 0 && totalReadingTimeInSeconds > 0 && (!userOverrideTimeInMinutes || addToEstimation)) {
let baseReadingTimeInSeconds = 0;
for (const note of notesForEstimate) {
baseReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path);
}
const formattedBaseReadingTime = EstimationUtils.formatTime(baseReadingTimeInSeconds);
this.pomodoroCalculationResultEl.createEl("p", { text: `Base reading time for ${notesForEstimate.length} note(s): ${formattedBaseReadingTime}.` });
} else if (notesForEstimate.length === 0 && userOverrideTimeInMinutes > 0) {
this.pomodoroCalculationResultEl.createEl("p", { text: `Using override time only (no notes).` });
}
if (userOverrideTimeInMinutes > 0) {
const overrideText = addToEstimation ? `Added ${userOverrideHours}h ${userOverrideMinutes}m override time.` : `Using ${userOverrideHours}h ${userOverrideMinutes}m override time (replacing estimate).`;
this.pomodoroCalculationResultEl.createEl("p", { text: overrideText, cls: "pomodoro-override-info" });
}
this.pomodoroCalculationResultEl.createEl("p", { text: `Requires ~${pomodorosNeeded} Pomodoro work session(s).` });
this.pomodoroCalculationResultEl.createEl("p", { text: `Total time with breaks: ~${formattedTotalTimeWithBreaks}.` });
this.pomodoroCalculationResultEl.style.display = "block";
this.updateCycleProgressDisplay();
}
/**
* Saves user override settings to plugin state
*/
saveUserOverrideSettings() {
var _a, _b, _c;
const hours = parseInt(((_a = this.pomodoroUserOverrideHoursInput) == null ? void 0 : _a.value) || "0");
const minutes = parseInt(((_b = this.pomodoroUserOverrideMinutesInput) == null ? void 0 : _b.value) || "0");
const addToEstimation = ((_c = this.pomodoroUserAddToEstimationCheckbox) == null ? void 0 : _c.checked) || false;
this.plugin.pluginState.pomodoroUserOverrideHours = hours;
this.plugin.pluginState.pomodoroUserOverrideMinutes = minutes;
this.plugin.pluginState.pomodoroUserAddToEstimation = addToEstimation;
this.plugin.savePluginData();
}
/**
* Updates the Pomodoro UI based on the current state from PomodoroService.
@ -4704,6 +4740,46 @@ var PomodoroUIManager = class {
if (this.pomodoroCalculationResultEl && ((_a = this.pomodoroQuickSettingsPanelEl) == null ? void 0 : _a.style.display) === "none") {
this.pomodoroCalculationResultEl.style.display = "none";
}
this.updateCycleProgressDisplay();
}
/**
* Updates the cycle progress display based on current state
*/
updateCycleProgressDisplay() {
if (!this.pomodoroCycleProgressEl)
return;
const cycleProgress = this.plugin.pomodoroService.getCycleProgress();
if (cycleProgress) {
const { current, total, workSessionsRemaining, totalWorkSessions, totalTimeMinutes } = cycleProgress;
const completedSessions = totalWorkSessions - workSessionsRemaining;
const totalHours = Math.floor(totalTimeMinutes / 60);
const totalMinutes = Math.round(totalTimeMinutes % 60);
const timeString = totalHours > 0 ? `${totalHours}H/${totalMinutes}M` : `${totalMinutes}M`;
this.pomodoroCycleProgressEl.setText(`Cycles ${current}/${total} - Sessions ${completedSessions}/${totalWorkSessions} - ${timeString}`);
this.pomodoroCycleProgressEl.style.display = "";
this.pomodoroCycleProgressEl.addClass("cycle-active");
} else {
this.pomodoroCycleProgressEl.style.display = "none";
}
}
/**
* Format time in seconds to a readable string
*/
formatTime(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor(totalSeconds % 3600 / 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
} else {
return `${minutes}m`;
}
}
/**
* Calculate and display estimation for current notes
*/
async calculateAndDisplayEstimation() {
const notesForEstimate = this.plugin.reviewController.getTodayNotes();
await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate);
}
};
@ -5174,6 +5250,26 @@ var ListViewRenderer = class {
// statsCountEl.setText(`${dueNotesForStats.length} notes - ${EstimationUtils.formatTime(totalTime)}${overdueNotes.length > 0 ? ` (${overdueNotes.length} overdue)` : ''}`);
// }
async _ensureAndUpdateReviewButtonsSection(container, notesForDisplay, selectedNotes) {
let pomodoroContainer = container.querySelector(".sidebar-pomodoro-section");
if (!pomodoroContainer) {
pomodoroContainer = container.createDiv("sidebar-pomodoro-section");
const pomodoroSectionContainerEl = pomodoroContainer.createDiv("sidebar-pomodoro-button-container");
}
if (this.pomodoroUIManager && pomodoroContainer) {
const pomodoroSectionContainerEl = pomodoroContainer.querySelector(".sidebar-pomodoro-button-container");
if (pomodoroSectionContainerEl) {
this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl);
if (this.plugin.settings.pomodoroEnabled) {
pomodoroContainer.style.display = "";
this.pomodoroUIManager.showPomodoroSection(true);
this.pomodoroUIManager.updatePomodoroUI();
this.pomodoroUIManager.calculateAndDisplayEstimation();
} else {
pomodoroContainer.style.display = "none";
this.pomodoroUIManager.showPomodoroSection(false);
}
}
}
let reviewButtonsContainer = container.querySelector(".review-buttons-container");
if (notesForDisplay.length > 0) {
if (!reviewButtonsContainer) {
@ -5187,7 +5283,6 @@ var ListViewRenderer = class {
nextNoteBtn.addEventListener("click", () => {
this.plugin.reviewController.navigateToNextNote();
});
reviewButtonsContainer.createDiv("sidebar-pomodoro-button-container");
const reviewCurrentBtn = reviewButtonsContainer.createEl("button", { text: "Review Current Note", title: "Review the currently open note if it's due", cls: "review-all-button" });
reviewCurrentBtn.addEventListener("click", () => {
this.plugin.reviewController.reviewCurrentNote();
@ -5204,16 +5299,6 @@ var ListViewRenderer = class {
}
}
reviewButtonsContainer.style.display = "";
const pomodoroSectionContainerEl = reviewButtonsContainer.querySelector(".sidebar-pomodoro-button-container");
if (this.pomodoroUIManager && pomodoroSectionContainerEl) {
this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl);
if (this.plugin.settings.pomodoroEnabled) {
this.pomodoroUIManager.showPomodoroSection(true);
this.pomodoroUIManager.updatePomodoroUI();
} else {
this.pomodoroUIManager.showPomodoroSection(false);
}
}
let bulkActionButtons = container.querySelector(".review-bulk-actions");
if (!bulkActionButtons) {
bulkActionButtons = container.createDiv("review-bulk-actions");
@ -6447,7 +6532,16 @@ var DEFAULT_PLUGIN_STATE_DATA = {
// Default to work duration
pomodoroSessionsCompletedInCycle: 0,
pomodoroIsRunning: false,
pomodoroEndTimeMs: null
pomodoroEndTimeMs: null,
// Pomodoro Estimation and Cycle Tracking Defaults
pomodoroEstimatedTotalCycles: null,
pomodoroEstimatedWorkSessions: null,
pomodoroIsEstimationActive: false,
pomodoroUserHasModifiedSettings: false,
// User Override Time Settings Defaults
pomodoroUserOverrideHours: 0,
pomodoroUserOverrideMinutes: 0,
pomodoroUserAddToEstimation: false
};
var DEFAULT_APP_DATA = {
settings: DEFAULT_SETTINGS,
@ -6501,7 +6595,7 @@ var SpaceforgeSettingTab = class extends import_obsidian17.PluginSettingTab {
const actionsContainer = containerEl.createEl("div", { cls: "sf-settings-actions" });
const exportBtn = actionsContainer.createEl("button", { text: "Export all data" });
exportBtn.addEventListener("click", () => {
var _a;
var _a, _b, _c, _d, _e, _f, _g, _h;
const pluginStateToExport = {
schedules: this.plugin.reviewScheduleService.schedules,
history: this.plugin.reviewHistoryService.history,
@ -6515,8 +6609,15 @@ var SpaceforgeSettingTab = class extends import_obsidian17.PluginSettingTab {
pomodoroTimeLeftInSeconds: this.plugin.pluginState.pomodoroTimeLeftInSeconds,
pomodoroSessionsCompletedInCycle: this.plugin.pluginState.pomodoroSessionsCompletedInCycle,
pomodoroIsRunning: this.plugin.pluginState.pomodoroIsRunning,
pomodoroEndTimeMs: (_a = this.plugin.pluginState.pomodoroEndTimeMs) != null ? _a : null
pomodoroEndTimeMs: (_a = this.plugin.pluginState.pomodoroEndTimeMs) != null ? _a : null,
// Add the missing field
pomodoroEstimatedTotalCycles: (_b = this.plugin.pluginState.pomodoroEstimatedTotalCycles) != null ? _b : null,
pomodoroEstimatedWorkSessions: (_c = this.plugin.pluginState.pomodoroEstimatedWorkSessions) != null ? _c : null,
pomodoroIsEstimationActive: (_d = this.plugin.pluginState.pomodoroIsEstimationActive) != null ? _d : false,
pomodoroUserHasModifiedSettings: (_e = this.plugin.pluginState.pomodoroUserHasModifiedSettings) != null ? _e : false,
pomodoroUserOverrideHours: (_f = this.plugin.pluginState.pomodoroUserOverrideHours) != null ? _f : 0,
pomodoroUserOverrideMinutes: (_g = this.plugin.pluginState.pomodoroUserOverrideMinutes) != null ? _g : 0,
pomodoroUserAddToEstimation: (_h = this.plugin.pluginState.pomodoroUserAddToEstimation) != null ? _h : false
};
const dataToExport = {
settings: this.plugin.settings,
@ -9570,10 +9671,21 @@ var PomodoroService = class {
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
}
updateDurations(work, short, long, sessions) {
const currentWork = this.settings.pomodoroWorkDuration;
const currentShort = this.settings.pomodoroShortBreakDuration;
const currentLong = this.settings.pomodoroLongBreakDuration;
const currentSessions = this.settings.pomodoroSessionsUntilLongBreak;
const userChangedSettings = work !== currentWork || short !== currentShort || long !== currentLong || sessions !== currentSessions;
this.settings.pomodoroWorkDuration = work;
this.settings.pomodoroShortBreakDuration = short;
this.settings.pomodoroLongBreakDuration = long;
this.settings.pomodoroSessionsUntilLongBreak = sessions;
if (userChangedSettings) {
this.state.pomodoroUserHasModifiedSettings = true;
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
}
const activeModesForDurationUpdate = ["work", "shortBreak", "longBreak"];
if (!this.state.pomodoroIsRunning && activeModesForDurationUpdate.includes(this.state.pomodoroCurrentMode)) {
this.resetTimeForMode(this.state.pomodoroCurrentMode);
@ -9581,6 +9693,126 @@ var PomodoroService = class {
this.plugin.savePluginData();
this.notifyUpdate();
}
/**
* Calculate and set estimation based on reading time
*/
async calculateEstimationFromNotes(notes) {
const userOverrideHours = this.state.pomodoroUserOverrideHours || 0;
const userOverrideMinutes = this.state.pomodoroUserOverrideMinutes || 0;
const userOverrideTimeInMinutes = userOverrideHours * 60 + userOverrideMinutes;
const addToEstimation = this.state.pomodoroUserAddToEstimation || false;
let totalReadingTimeInSeconds = 0;
let totalReadingTimeInMinutes = 0;
if (notes.length > 0) {
for (const note of notes) {
totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path);
}
totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60;
}
if (userOverrideTimeInMinutes > 0) {
if (addToEstimation) {
totalReadingTimeInMinutes += userOverrideTimeInMinutes;
} else {
totalReadingTimeInMinutes = userOverrideTimeInMinutes;
totalReadingTimeInSeconds = userOverrideTimeInMinutes * 60;
}
}
if (notes.length === 0 && userOverrideTimeInMinutes === 0) {
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
this.notifyUpdate();
return null;
}
const settings = this.settings;
const workDuration = settings.pomodoroWorkDuration;
const shortBreakDuration = settings.pomodoroShortBreakDuration;
const longBreakDuration = settings.pomodoroLongBreakDuration;
const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak;
if (totalReadingTimeInMinutes === 0 || workDuration === 0) {
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
this.notifyUpdate();
return null;
}
let pomodorosNeeded = 0;
let sessionsCompletedInCycle = 0;
let remainingReadingTimeMinutes = totalReadingTimeInMinutes;
let totalBreakTimeInMinutes = 0;
while (remainingReadingTimeMinutes > 0) {
pomodorosNeeded++;
remainingReadingTimeMinutes -= workDuration;
sessionsCompletedInCycle++;
if (remainingReadingTimeMinutes <= 0)
break;
if (sessionsCompletedInCycle >= sessionsUntilLongBreak) {
totalBreakTimeInMinutes += longBreakDuration;
sessionsCompletedInCycle = 0;
} else {
totalBreakTimeInMinutes += shortBreakDuration;
}
}
this.state.pomodoroEstimatedTotalCycles = Math.ceil(pomodorosNeeded / sessionsUntilLongBreak);
this.state.pomodoroEstimatedWorkSessions = pomodorosNeeded;
this.state.pomodoroIsEstimationActive = true;
this.plugin.savePluginData();
this.notifyUpdate();
return {
totalReadingTimeInSeconds,
totalReadingTimeInMinutes,
pomodorosNeeded,
totalTimeWithBreaksMinutes: pomodorosNeeded * workDuration + totalBreakTimeInMinutes
};
}
/**
* Get current cycle progress information
*/
getCycleProgress() {
if (!this.state.pomodoroIsEstimationActive || !this.state.pomodoroEstimatedWorkSessions) {
return null;
}
const currentCycle = Math.floor(this.state.pomodoroSessionsCompletedInCycle / this.settings.pomodoroSessionsUntilLongBreak) + 1;
const totalCycles = this.state.pomodoroEstimatedTotalCycles || 1;
const workSessionsRemaining = Math.max(0, this.state.pomodoroEstimatedWorkSessions - this.state.pomodoroSessionsCompletedInCycle);
const totalWorkSessions = this.state.pomodoroEstimatedWorkSessions;
const workDuration = this.settings.pomodoroWorkDuration;
const shortBreakDuration = this.settings.pomodoroShortBreakDuration;
const longBreakDuration = this.settings.pomodoroLongBreakDuration;
const sessionsUntilLongBreak = this.settings.pomodoroSessionsUntilLongBreak;
let totalBreakTime = 0;
let sessionsInCycle = 0;
for (let i = 0; i < totalWorkSessions; i++) {
sessionsInCycle++;
if (i < totalWorkSessions - 1) {
if (sessionsInCycle >= sessionsUntilLongBreak) {
totalBreakTime += longBreakDuration;
sessionsInCycle = 0;
} else {
totalBreakTime += shortBreakDuration;
}
}
}
const totalTimeMinutes = totalWorkSessions * workDuration + totalBreakTime;
return {
current: currentCycle,
total: totalCycles,
workSessionsRemaining,
totalWorkSessions,
totalTimeMinutes
};
}
/**
* Reset estimation state (call when user wants to clear estimation)
*/
resetEstimation() {
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
this.state.pomodoroUserHasModifiedSettings = false;
this.plugin.savePluginData();
this.notifyUpdate();
}
// --- Internal Logic ---
startTimerInterval() {
if (this.timerInterval !== null) {
@ -10073,7 +10305,7 @@ var SpaceforgePlugin = class extends import_obsidian26.Plugin {
}
}
async savePluginData() {
var _a, _b;
var _a, _b, _c, _d;
try {
if (!this.settings || typeof this.settings !== "object") {
this.settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
@ -10097,6 +10329,13 @@ var SpaceforgePlugin = class extends import_obsidian26.Plugin {
pomodoroIsRunning: typeof this.pluginState.pomodoroIsRunning === "boolean" ? this.pluginState.pomodoroIsRunning : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsRunning,
pomodoroEndTimeMs: (_b = this.pluginState.pomodoroEndTimeMs) != null ? _b : null,
// Add the missing field
pomodoroEstimatedTotalCycles: (_c = this.pluginState.pomodoroEstimatedTotalCycles) != null ? _c : DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedTotalCycles,
pomodoroEstimatedWorkSessions: (_d = this.pluginState.pomodoroEstimatedWorkSessions) != null ? _d : DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedWorkSessions,
pomodoroIsEstimationActive: typeof this.pluginState.pomodoroIsEstimationActive === "boolean" ? this.pluginState.pomodoroIsEstimationActive : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsEstimationActive,
pomodoroUserHasModifiedSettings: typeof this.pluginState.pomodoroUserHasModifiedSettings === "boolean" ? this.pluginState.pomodoroUserHasModifiedSettings : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserHasModifiedSettings,
pomodoroUserOverrideHours: typeof this.pluginState.pomodoroUserOverrideHours === "number" ? this.pluginState.pomodoroUserOverrideHours : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideHours,
pomodoroUserOverrideMinutes: typeof this.pluginState.pomodoroUserOverrideMinutes === "number" ? this.pluginState.pomodoroUserOverrideMinutes : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideMinutes,
pomodoroUserAddToEstimation: typeof this.pluginState.pomodoroUserAddToEstimation === "boolean" ? this.pluginState.pomodoroUserAddToEstimation : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserAddToEstimation,
version: this.manifest.version
};
this.pluginState = currentPluginState;

View file

@ -464,6 +464,13 @@ export default class SpaceforgePlugin extends Plugin {
pomodoroSessionsCompletedInCycle: this.pluginState.pomodoroSessionsCompletedInCycle || DEFAULT_PLUGIN_STATE_DATA.pomodoroSessionsCompletedInCycle,
pomodoroIsRunning: typeof this.pluginState.pomodoroIsRunning === 'boolean' ? this.pluginState.pomodoroIsRunning : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsRunning,
pomodoroEndTimeMs: this.pluginState.pomodoroEndTimeMs ?? null, // Add the missing field
pomodoroEstimatedTotalCycles: this.pluginState.pomodoroEstimatedTotalCycles ?? DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedTotalCycles,
pomodoroEstimatedWorkSessions: this.pluginState.pomodoroEstimatedWorkSessions ?? DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedWorkSessions,
pomodoroIsEstimationActive: typeof this.pluginState.pomodoroIsEstimationActive === 'boolean' ? this.pluginState.pomodoroIsEstimationActive : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsEstimationActive,
pomodoroUserHasModifiedSettings: typeof this.pluginState.pomodoroUserHasModifiedSettings === 'boolean' ? this.pluginState.pomodoroUserHasModifiedSettings : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserHasModifiedSettings,
pomodoroUserOverrideHours: typeof this.pluginState.pomodoroUserOverrideHours === 'number' ? this.pluginState.pomodoroUserOverrideHours : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideHours,
pomodoroUserOverrideMinutes: typeof this.pluginState.pomodoroUserOverrideMinutes === 'number' ? this.pluginState.pomodoroUserOverrideMinutes : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideMinutes,
pomodoroUserAddToEstimation: typeof this.pluginState.pomodoroUserAddToEstimation === 'boolean' ? this.pluginState.pomodoroUserAddToEstimation : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserAddToEstimation,
version: this.manifest.version,
};
this.pluginState = currentPluginState; // Update the live pluginState

View file

@ -23,6 +23,17 @@ export interface PluginStateData {
pomodoroSessionsCompletedInCycle: number;
pomodoroIsRunning: boolean;
pomodoroEndTimeMs: number | null; // Timestamp when the current session ends
// Pomodoro Estimation and Cycle Tracking
pomodoroEstimatedTotalCycles: number | null; // Total cycles calculated from reading time
pomodoroEstimatedWorkSessions: number | null; // Total work sessions calculated
pomodoroIsEstimationActive: boolean; // Whether estimation is currently active (not overridden by user)
pomodoroUserHasModifiedSettings: boolean; // Whether user has manually changed H/M settings
// User Override Time Settings
pomodoroUserOverrideHours: number; // User-specified hours for override
pomodoroUserOverrideMinutes: number; // User-specified minutes for override
pomodoroUserAddToEstimation: boolean; // Whether to add user time to estimation or replace it
}
/**
@ -44,6 +55,17 @@ export const DEFAULT_PLUGIN_STATE_DATA: PluginStateData = {
pomodoroSessionsCompletedInCycle: 0,
pomodoroIsRunning: false,
pomodoroEndTimeMs: null,
// Pomodoro Estimation and Cycle Tracking Defaults
pomodoroEstimatedTotalCycles: null,
pomodoroEstimatedWorkSessions: null,
pomodoroIsEstimationActive: false,
pomodoroUserHasModifiedSettings: false,
// User Override Time Settings Defaults
pomodoroUserOverrideHours: 0,
pomodoroUserOverrideMinutes: 0,
pomodoroUserAddToEstimation: false,
};
/**

3
note.md Normal file
View file

@ -0,0 +1,3 @@
bug postpone all postpones all notes not just one note. from that specific days on entry.
change due date red color to something more subtle

View file

@ -94,11 +94,27 @@ export class PomodoroService {
}
public updateDurations(work: number, short: number, long: number, sessions: number): void {
// Check if user is modifying from the current settings (indicating manual override)
const currentWork = this.settings.pomodoroWorkDuration;
const currentShort = this.settings.pomodoroShortBreakDuration;
const currentLong = this.settings.pomodoroLongBreakDuration;
const currentSessions = this.settings.pomodoroSessionsUntilLongBreak;
const userChangedSettings = work !== currentWork || short !== currentShort || long !== currentLong || sessions !== currentSessions;
this.settings.pomodoroWorkDuration = work;
this.settings.pomodoroShortBreakDuration = short;
this.settings.pomodoroLongBreakDuration = long;
this.settings.pomodoroSessionsUntilLongBreak = sessions;
// If user manually changed settings, mark as modified and deactivate estimation
if (userChangedSettings) {
this.state.pomodoroUserHasModifiedSettings = true;
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
}
// If current session is not running and its mode (which is an active timed mode) had its duration changed, update its timeLeft
const activeModesForDurationUpdate: PomodoroMode[] = ['work', 'shortBreak', 'longBreak'];
if (!this.state.pomodoroIsRunning && activeModesForDurationUpdate.includes(this.state.pomodoroCurrentMode)) {
@ -108,6 +124,165 @@ export class PomodoroService {
this.notifyUpdate();
}
/**
* Calculate and set estimation based on reading time
*/
public async calculateEstimationFromNotes(notes: any[]): Promise<{
totalReadingTimeInSeconds: number;
totalReadingTimeInMinutes: number;
pomodorosNeeded: number;
totalTimeWithBreaksMinutes: number;
} | null> {
// Apply user override if specified
const userOverrideHours = this.state.pomodoroUserOverrideHours || 0;
const userOverrideMinutes = this.state.pomodoroUserOverrideMinutes || 0;
const userOverrideTimeInMinutes = (userOverrideHours * 60) + userOverrideMinutes;
const addToEstimation = this.state.pomodoroUserAddToEstimation || false;
let totalReadingTimeInSeconds = 0;
let totalReadingTimeInMinutes = 0;
// Calculate base reading time from notes
if (notes.length > 0) {
for (const note of notes) {
totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path);
}
totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60;
}
// Apply user override
if (userOverrideTimeInMinutes > 0) {
if (addToEstimation) {
totalReadingTimeInMinutes += userOverrideTimeInMinutes;
} else {
totalReadingTimeInMinutes = userOverrideTimeInMinutes;
totalReadingTimeInSeconds = userOverrideTimeInMinutes * 60; // Update seconds too for consistency
}
}
// If no notes and no override, return null
if (notes.length === 0 && userOverrideTimeInMinutes === 0) {
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
this.notifyUpdate();
return null;
}
const settings = this.settings;
const workDuration = settings.pomodoroWorkDuration;
const shortBreakDuration = settings.pomodoroShortBreakDuration;
const longBreakDuration = settings.pomodoroLongBreakDuration;
const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak;
if (totalReadingTimeInMinutes === 0 || workDuration === 0) {
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
this.notifyUpdate();
return null;
}
let pomodorosNeeded = 0;
let sessionsCompletedInCycle = 0;
let remainingReadingTimeMinutes = totalReadingTimeInMinutes;
let totalBreakTimeInMinutes = 0;
while (remainingReadingTimeMinutes > 0) {
pomodorosNeeded++;
remainingReadingTimeMinutes -= workDuration;
sessionsCompletedInCycle++;
if (remainingReadingTimeMinutes <= 0) break;
if (sessionsCompletedInCycle >= sessionsUntilLongBreak) {
totalBreakTimeInMinutes += longBreakDuration;
sessionsCompletedInCycle = 0;
} else {
totalBreakTimeInMinutes += shortBreakDuration;
}
}
// Always update estimation when calculate is called (this resets the estimation)
this.state.pomodoroEstimatedTotalCycles = Math.ceil(pomodorosNeeded / sessionsUntilLongBreak);
this.state.pomodoroEstimatedWorkSessions = pomodorosNeeded;
this.state.pomodoroIsEstimationActive = true;
this.plugin.savePluginData();
this.notifyUpdate();
// Return calculation results for UI display
return {
totalReadingTimeInSeconds,
totalReadingTimeInMinutes,
pomodorosNeeded,
totalTimeWithBreaksMinutes: (pomodorosNeeded * workDuration) + totalBreakTimeInMinutes
};
}
/**
* Get current cycle progress information
*/
public getCycleProgress(): {
current: number;
total: number;
workSessionsRemaining: number;
totalWorkSessions: number;
totalTimeMinutes: number;
} | null {
if (!this.state.pomodoroIsEstimationActive || !this.state.pomodoroEstimatedWorkSessions) {
return null;
}
const currentCycle = Math.floor(this.state.pomodoroSessionsCompletedInCycle / this.settings.pomodoroSessionsUntilLongBreak) + 1;
const totalCycles = this.state.pomodoroEstimatedTotalCycles || 1;
const workSessionsRemaining = Math.max(0, this.state.pomodoroEstimatedWorkSessions - this.state.pomodoroSessionsCompletedInCycle);
const totalWorkSessions = this.state.pomodoroEstimatedWorkSessions;
// Calculate total time including breaks
const workDuration = this.settings.pomodoroWorkDuration;
const shortBreakDuration = this.settings.pomodoroShortBreakDuration;
const longBreakDuration = this.settings.pomodoroLongBreakDuration;
const sessionsUntilLongBreak = this.settings.pomodoroSessionsUntilLongBreak;
let totalBreakTime = 0;
let sessionsInCycle = 0;
for (let i = 0; i < totalWorkSessions; i++) {
sessionsInCycle++;
if (i < totalWorkSessions - 1) { // Don't add break after last session
if (sessionsInCycle >= sessionsUntilLongBreak) {
totalBreakTime += longBreakDuration;
sessionsInCycle = 0;
} else {
totalBreakTime += shortBreakDuration;
}
}
}
const totalTimeMinutes = (totalWorkSessions * workDuration) + totalBreakTime;
return {
current: currentCycle,
total: totalCycles,
workSessionsRemaining: workSessionsRemaining,
totalWorkSessions: totalWorkSessions,
totalTimeMinutes: totalTimeMinutes
};
}
/**
* Reset estimation state (call when user wants to clear estimation)
*/
public resetEstimation(): void {
this.state.pomodoroIsEstimationActive = false;
this.state.pomodoroEstimatedTotalCycles = null;
this.state.pomodoroEstimatedWorkSessions = null;
this.state.pomodoroUserHasModifiedSettings = false;
this.plugin.savePluginData();
this.notifyUpdate();
}
// --- Internal Logic ---

View file

@ -2802,6 +2802,16 @@ button.disabled:hover {
.pomodoro-visibility-toggle-container {
display: none !important;
}
.sidebar-pomodoro-section {
width: 100%;
margin-top: 8px;
margin-bottom: 8px;
padding: 8px;
background-color: var(--background-secondary);
border-radius: var(--radius-m);
border: 1px solid var(--background-modifier-border);
box-sizing: border-box;
}
.sidebar-pomodoro-section-container {
width: 100%;
margin-top: 8px;
@ -2983,14 +2993,99 @@ button.disabled:hover {
.pomodoro-calculation-result {
font-size: 0.9em;
margin-top: 8px;
padding: 8px;
padding: 10px;
background-color: var(--background-secondary-alt);
border-radius: var(--radius-s);
border: 1px solid var(--background-modifier-border);
line-height: 1.4;
}
.pomodoro-calculation-result p {
margin: 4px 0;
}
.pomodoro-cycle-progress {
font-size: 0.75em;
color: var(--text-muted);
text-align: center;
margin-top: 4px;
padding: 2px 6px;
border-radius: var(--radius-s);
transition: var(--sf-transition);
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pomodoro-cycle-progress.cycle-active {
color: var(--sf-info);
background-color: var(--background-modifier-hover);
font-weight: 500;
}
.pomodoro-override-container {
margin: 12px 0;
padding: 10px;
background-color: var(--background-secondary-alt);
border-radius: var(--radius-s);
border: 1px solid var(--background-modifier-border);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.pomodoro-override-label {
font-size: 0.9em;
color: var(--text-normal);
font-weight: 500;
margin-bottom: 6px;
display: block;
}
.pomodoro-override-inputs {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.pomodoro-override-hours,
.pomodoro-override-minutes {
width: 50px;
background-color: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
padding: 4px 6px;
text-align: center;
font-size: 0.9em;
}
.pomodoro-override-hours:focus,
.pomodoro-override-minutes:focus {
outline: none;
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2);
}
.pomodoro-override-label-small {
font-size: 0.8em;
color: var(--text-muted);
font-weight: 400;
}
.pomodoro-override-toggle-container {
display: flex;
align-items: center;
gap: 8px;
}
.pomodoro-add-to-estimation {
margin: 0;
}
.pomodoro-toggle-label {
font-size: 0.85em;
color: var(--text-normal);
cursor: pointer;
user-select: none;
}
.pomodoro-override-info {
color: var(--sf-info);
font-style: italic;
font-size: 0.9em;
margin-top: 4px;
padding: 4px 6px;
background-color: var(--background-primary);
border-radius: var(--radius-s);
border-left: 3px solid var(--sf-info);
}
/* styles/responsive.css */
@media (max-width: 768px) {

View file

@ -3,6 +3,17 @@
display: none !important; /* Hide the old toggle button */
}
.sidebar-pomodoro-section {
width: 100%; /* Make it fit the width of its parent in the sidebar */
margin-top: 8px; /* Space above Pomodoro section */
margin-bottom: 8px; /* Space below Pomodoro section */
padding: 8px; /* Padding inside the Pomodoro container */
background-color: var(--background-secondary); /* Match sidebar section bg */
border-radius: var(--radius-m); /* Consistent border radius */
border: 1px solid var(--background-modifier-border); /* Consistent border */
box-sizing: border-box; /* Ensure padding and border are within width */
}
.sidebar-pomodoro-section-container {
width: 100%; /* Make it fit the width of its parent in the sidebar */
margin-top: 8px; /* Space above Pomodoro section */
@ -207,11 +218,110 @@
.pomodoro-calculation-result {
font-size: 0.9em;
margin-top: 8px; /* Increased margin */
padding: 8px; /* Increased padding */
padding: 10px; /* Increased padding */
background-color: var(--background-secondary-alt);
border-radius: var(--radius-s);
border: 1px solid var(--background-modifier-border); /* Add border for definition */
line-height: 1.4;
}
.pomodoro-calculation-result p {
margin: 4px 0; /* Spacing for paragraphs within result */
}
/* Cycle Tracking Styles */
.pomodoro-cycle-progress {
font-size: 0.75em;
color: var(--text-muted);
text-align: center;
margin-top: 4px;
padding: 2px 6px;
border-radius: var(--radius-s);
transition: var(--sf-transition);
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pomodoro-cycle-progress.cycle-active {
color: var(--sf-info);
background-color: var(--background-modifier-hover);
font-weight: 500;
}
/* User Override Styles */
.pomodoro-override-container {
margin: 12px 0;
padding: 10px;
background-color: var(--background-secondary-alt);
border-radius: var(--radius-s);
border: 1px solid var(--background-modifier-border);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.pomodoro-override-label {
font-size: 0.9em;
color: var(--text-normal);
font-weight: 500;
margin-bottom: 6px;
display: block;
}
.pomodoro-override-inputs {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.pomodoro-override-hours,
.pomodoro-override-minutes {
width: 50px;
background-color: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
padding: 4px 6px;
text-align: center;
font-size: 0.9em;
}
.pomodoro-override-hours:focus,
.pomodoro-override-minutes:focus {
outline: none;
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2);
}
.pomodoro-override-label-small {
font-size: 0.8em;
color: var(--text-muted);
font-weight: 400;
}
.pomodoro-override-toggle-container {
display: flex;
align-items: center;
gap: 8px;
}
.pomodoro-add-to-estimation {
margin: 0;
}
.pomodoro-toggle-label {
font-size: 0.85em;
color: var(--text-normal);
cursor: pointer;
user-select: none;
}
.pomodoro-override-info {
color: var(--sf-info);
font-style: italic;
font-size: 0.9em;
margin-top: 4px;
padding: 4px 6px;
background-color: var(--background-primary);
border-radius: var(--radius-s);
border-left: 3px solid var(--sf-info);
}

View file

@ -99,6 +99,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
pomodoroSessionsCompletedInCycle: this.plugin.pluginState.pomodoroSessionsCompletedInCycle,
pomodoroIsRunning: this.plugin.pluginState.pomodoroIsRunning,
pomodoroEndTimeMs: this.plugin.pluginState.pomodoroEndTimeMs ?? null, // Add the missing field
pomodoroEstimatedTotalCycles: this.plugin.pluginState.pomodoroEstimatedTotalCycles ?? null,
pomodoroEstimatedWorkSessions: this.plugin.pluginState.pomodoroEstimatedWorkSessions ?? null,
pomodoroIsEstimationActive: this.plugin.pluginState.pomodoroIsEstimationActive ?? false,
pomodoroUserHasModifiedSettings: this.plugin.pluginState.pomodoroUserHasModifiedSettings ?? false,
pomodoroUserOverrideHours: this.plugin.pluginState.pomodoroUserOverrideHours ?? 0,
pomodoroUserOverrideMinutes: this.plugin.pluginState.pomodoroUserOverrideMinutes ?? 0,
pomodoroUserAddToEstimation: this.plugin.pluginState.pomodoroUserAddToEstimation ?? false,
};
const dataToExport: SpaceforgePluginData = {

View file

@ -125,6 +125,33 @@ export class ListViewRenderer {
// }
private async _ensureAndUpdateReviewButtonsSection(container: HTMLElement, notesForDisplay: ReviewSchedule[], selectedNotes: string[]): Promise<void> {
// --- Pomodoro Section (Always visible when enabled, independent of notes) ---
let pomodoroContainer = container.querySelector(".sidebar-pomodoro-section") as HTMLElement;
if (!pomodoroContainer) {
pomodoroContainer = container.createDiv("sidebar-pomodoro-section");
const pomodoroSectionContainerEl = pomodoroContainer.createDiv("sidebar-pomodoro-button-container");
}
// Update Pomodoro section visibility and content
if (this.pomodoroUIManager && pomodoroContainer) {
const pomodoroSectionContainerEl = pomodoroContainer.querySelector(".sidebar-pomodoro-button-container") as HTMLElement;
if (pomodoroSectionContainerEl) {
this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl);
if (this.plugin.settings.pomodoroEnabled) {
pomodoroContainer.style.display = '';
this.pomodoroUIManager.showPomodoroSection(true);
this.pomodoroUIManager.updatePomodoroUI();
// Automatically calculate estimation for current notes
this.pomodoroUIManager.calculateAndDisplayEstimation();
} else {
pomodoroContainer.style.display = 'none';
this.pomodoroUIManager.showPomodoroSection(false);
}
}
}
// --- Review Buttons Section (Only visible when there are notes) ---
let reviewButtonsContainer = container.querySelector(".review-buttons-container") as HTMLElement;
// Visibility of review buttons should depend on whether there are notes in the current context (notesForDisplay)
@ -138,8 +165,6 @@ export class ListViewRenderer {
const nextNoteBtn = navButtonsContainer.createEl("button", { text: "Next", title: "Navigate to Next Note", cls: "review-all-button" });
nextNoteBtn.addEventListener("click", () => { this.plugin.reviewController.navigateToNextNote(); });
reviewButtonsContainer.createDiv("sidebar-pomodoro-button-container"); // Placeholder for Pomodoro
const reviewCurrentBtn = reviewButtonsContainer.createEl("button", { text: "Review Current Note", title: "Review the currently open note if it's due", cls: "review-all-button" });
reviewCurrentBtn.addEventListener("click", () => { this.plugin.reviewController.reviewCurrentNote(); });
const reviewAllBtn = reviewButtonsContainer.createEl("button", { text: "Review All", title: "Start Reviewing All Due Notes", cls: "review-all-button" });
@ -151,18 +176,6 @@ export class ListViewRenderer {
}
}
reviewButtonsContainer.style.display = '';
// Pomodoro section
const pomodoroSectionContainerEl = reviewButtonsContainer.querySelector(".sidebar-pomodoro-button-container") as HTMLElement;
if (this.pomodoroUIManager && pomodoroSectionContainerEl) {
this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl); // This method will be refactored to be non-destructive
if (this.plugin.settings.pomodoroEnabled) {
this.pomodoroUIManager.showPomodoroSection(true); // Controls overall visibility
this.pomodoroUIManager.updatePomodoroUI(); // Updates internal state and button text
} else {
this.pomodoroUIManager.showPomodoroSection(false);
}
}
// Bulk action buttons
let bulkActionButtons = container.querySelector(".review-bulk-actions") as HTMLElement;

View file

@ -21,6 +21,14 @@ export class PomodoroUIManager {
private pomodoroQuickLongInput: HTMLInputElement | null = null;
private pomodoroQuickSessionsInput: HTMLInputElement | null = null;
private pomodoroCalculationResultEl: HTMLElement | null = null;
// New estimation and cycle tracking elements (moved to calculation panel)
private pomodoroCycleProgressEl: HTMLElement | null = null;
// User override input elements
private pomodoroUserOverrideHoursInput: HTMLInputElement | null = null;
private pomodoroUserOverrideMinutesInput: HTMLInputElement | null = null;
private pomodoroUserAddToEstimationCheckbox: HTMLInputElement | null = null;
// private isPomodoroSectionOpen: boolean = false; // No longer needed, section is always "open"
private areButtonsVisible: boolean = true; // For Play/Pause/Skip buttons
@ -237,6 +245,12 @@ export class PomodoroUIManager {
this.pomodoroSkipBtn.addEventListener("click", () => this.plugin.pomodoroService.skipSession());
}
// Cycle Progress Display (shows current cycle and remaining sessions)
if (!this.pomodoroCycleProgressEl || this.pomodoroCycleProgressEl.parentElement !== container) {
this.pomodoroCycleProgressEl?.remove();
this.pomodoroCycleProgressEl = container.createDiv("pomodoro-cycle-progress");
}
// Quick Settings Toggle Button - REMOVED
// if (!this.pomodoroQuickSettingsToggleBtn || this.pomodoroQuickSettingsToggleBtn.parentElement !== mainControlsRow) {
// this.pomodoroQuickSettingsToggleBtn?.remove();
@ -302,6 +316,35 @@ export class PomodoroUIManager {
// }
// });
// User override time inputs
const overrideContainer = this.pomodoroQuickSettingsPanelEl.createDiv("pomodoro-override-container");
const overrideLabel = overrideContainer.createEl("label", { text: "Override time (optional):", cls: "pomodoro-override-label" });
const overrideInputsContainer = overrideContainer.createDiv("pomodoro-override-inputs");
this.pomodoroUserOverrideHoursInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-hours" });
this.pomodoroUserOverrideHoursInput.setAttr("min", "0");
this.pomodoroUserOverrideHoursInput.setAttr("placeholder", "H");
this.pomodoroUserOverrideHoursInput.value = String(this.plugin.pluginState.pomodoroUserOverrideHours);
const hoursLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small");
hoursLabel.setText("h");
this.pomodoroUserOverrideMinutesInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-minutes" });
this.pomodoroUserOverrideMinutesInput.setAttr("min", "0");
this.pomodoroUserOverrideMinutesInput.setAttr("placeholder", "M");
this.pomodoroUserOverrideMinutesInput.value = String(this.plugin.pluginState.pomodoroUserOverrideMinutes);
const minutesLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small");
minutesLabel.setText("m");
// Add to estimation toggle
const toggleContainer = overrideContainer.createDiv("pomodoro-override-toggle-container");
this.pomodoroUserAddToEstimationCheckbox = toggleContainer.createEl("input", { type: "checkbox", cls: "pomodoro-add-to-estimation" });
this.pomodoroUserAddToEstimationCheckbox.checked = this.plugin.pluginState.pomodoroUserAddToEstimation;
const toggleLabel = toggleContainer.createEl("label", { text: "Add to estimated time", cls: "pomodoro-toggle-label" });
toggleLabel.setAttribute("for", "pomodoro-add-to-estimation");
const calculateBtn = buttonsContainer.createEl("button", { text: "Calculate Reading Time", cls: "pomodoro-quick-calculate-btn" });
calculateBtn.addEventListener("click", async () => {
const settingsSaved = this._savePomodoroSettings();
@ -323,10 +366,18 @@ export class PomodoroUIManager {
private async calculateAndDisplayPomodoroEstimate(): Promise<void> {
if (!this.plugin || !this.pomodoroCalculationResultEl) return;
// Save user override settings
this.saveUserOverrideSettings();
// Use notes from the review controller, which are context-aware (selected date or actual today)
const notesForEstimate = this.plugin.reviewController.getTodayNotes();
if (notesForEstimate.length === 0) {
// Check if we have either notes or user override
const userOverrideHours = this.plugin.pluginState.pomodoroUserOverrideHours || 0;
const userOverrideMinutes = this.plugin.pluginState.pomodoroUserOverrideMinutes || 0;
const userOverrideTimeInMinutes = (userOverrideHours * 60) + userOverrideMinutes;
if (notesForEstimate.length === 0 && userOverrideTimeInMinutes === 0) {
const activeDate = this.plugin.reviewController.getCurrentReviewDateOverride();
const message = activeDate
? `No notes scheduled for ${new Date(activeDate).toLocaleDateString()} to calculate.`
@ -336,54 +387,68 @@ export class PomodoroUIManager {
return;
}
let totalReadingTimeInSeconds = 0;
for (const note of notesForEstimate) {
totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path);
}
const totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60;
const settings = this.plugin.settings;
const workDuration = settings.pomodoroWorkDuration;
const shortBreakDuration = settings.pomodoroShortBreakDuration;
const longBreakDuration = settings.pomodoroLongBreakDuration;
const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak;
let pomodorosNeeded = 0;
let sessionsCompletedInCycle = 0;
let remainingReadingTimeMinutes = totalReadingTimeInMinutes;
let totalBreakTimeInMinutes = 0;
if (totalReadingTimeInMinutes === 0) {
this.pomodoroCalculationResultEl.setText("Estimated reading time is 0 minutes.");
this.pomodoroCalculationResultEl.style.display = 'block';
return;
// Use the PomodoroService to calculate estimation (this also resets and updates cycle tracking)
const result = await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate);
if (!result) {
this.pomodoroCalculationResultEl.setText("Unable to calculate estimation.");
this.pomodoroCalculationResultEl.style.display = 'block';
return;
}
while (remainingReadingTimeMinutes > 0) {
pomodorosNeeded++;
remainingReadingTimeMinutes -= workDuration;
sessionsCompletedInCycle++;
const { totalReadingTimeInSeconds, totalReadingTimeInMinutes, pomodorosNeeded, totalTimeWithBreaksMinutes } = result;
if (remainingReadingTimeMinutes <= 0) break;
if (sessionsCompletedInCycle >= sessionsUntilLongBreak) {
totalBreakTimeInMinutes += longBreakDuration;
sessionsCompletedInCycle = 0;
} else {
totalBreakTimeInMinutes += shortBreakDuration;
}
}
const totalTimeWithBreaksMinutes = (pomodorosNeeded * workDuration) + totalBreakTimeInMinutes;
// Get user override values for display (already declared above)
const addToEstimation = this.plugin.pluginState.pomodoroUserAddToEstimation || false;
const formattedTotalReadingTime = EstimationUtils.formatTime(totalReadingTimeInSeconds);
const formattedTotalTimeWithBreaks = EstimationUtils.formatTime(Math.ceil(totalTimeWithBreaksMinutes * 60));
this.pomodoroCalculationResultEl.empty();
this.pomodoroCalculationResultEl.createEl("p", { text: `Estimated reading time for ${notesForEstimate.length} note(s) in current view: ${formattedTotalReadingTime}.` });
// Show base reading time if we have notes and it wasn't completely overridden
if (notesForEstimate.length > 0 && totalReadingTimeInSeconds > 0 && (!userOverrideTimeInMinutes || addToEstimation)) {
// Calculate base reading time without overrides for display
let baseReadingTimeInSeconds = 0;
for (const note of notesForEstimate) {
baseReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path);
}
const formattedBaseReadingTime = EstimationUtils.formatTime(baseReadingTimeInSeconds);
this.pomodoroCalculationResultEl.createEl("p", { text: `Base reading time for ${notesForEstimate.length} note(s): ${formattedBaseReadingTime}.` });
} else if (notesForEstimate.length === 0 && userOverrideTimeInMinutes > 0) {
// Show message when using only override time
this.pomodoroCalculationResultEl.createEl("p", { text: `Using override time only (no notes).` });
}
// Show user override information if applicable
if (userOverrideTimeInMinutes > 0) {
const overrideText = addToEstimation
? `Added ${userOverrideHours}h ${userOverrideMinutes}m override time.`
: `Using ${userOverrideHours}h ${userOverrideMinutes}m override time (replacing estimate).`;
this.pomodoroCalculationResultEl.createEl("p", { text: overrideText, cls: "pomodoro-override-info" });
}
this.pomodoroCalculationResultEl.createEl("p", { text: `Requires ~${pomodorosNeeded} Pomodoro work session(s).` });
this.pomodoroCalculationResultEl.createEl("p", { text: `Total time with breaks: ~${formattedTotalTimeWithBreaks}.` });
this.pomodoroCalculationResultEl.style.display = 'block';
// Update the cycle progress display to show the new estimation
this.updateCycleProgressDisplay();
}
/**
* Saves user override settings to plugin state
*/
private saveUserOverrideSettings(): void {
const hours = parseInt(this.pomodoroUserOverrideHoursInput?.value || "0");
const minutes = parseInt(this.pomodoroUserOverrideMinutesInput?.value || "0");
const addToEstimation = this.pomodoroUserAddToEstimationCheckbox?.checked || false;
this.plugin.pluginState.pomodoroUserOverrideHours = hours;
this.plugin.pluginState.pomodoroUserOverrideMinutes = minutes;
this.plugin.pluginState.pomodoroUserAddToEstimation = addToEstimation;
this.plugin.savePluginData();
}
/**
@ -487,5 +552,59 @@ export class PomodoroUIManager {
if (this.pomodoroCalculationResultEl && this.pomodoroQuickSettingsPanelEl?.style.display === 'none') {
this.pomodoroCalculationResultEl.style.display = 'none';
}
// Update cycle progress display
this.updateCycleProgressDisplay();
}
}
/**
* Updates the cycle progress display based on current state
*/
private updateCycleProgressDisplay(): void {
if (!this.pomodoroCycleProgressEl) return;
const cycleProgress = this.plugin.pomodoroService.getCycleProgress();
if (cycleProgress) {
const { current, total, workSessionsRemaining, totalWorkSessions, totalTimeMinutes } = cycleProgress;
// Calculate completed sessions
const completedSessions = totalWorkSessions - workSessionsRemaining;
// Format total time
const totalHours = Math.floor(totalTimeMinutes / 60);
const totalMinutes = Math.round(totalTimeMinutes % 60);
const timeString = totalHours > 0 ? `${totalHours}H/${totalMinutes}M` : `${totalMinutes}M`;
this.pomodoroCycleProgressEl.setText(`Cycles ${current}/${total} - Sessions ${completedSessions}/${totalWorkSessions} - ${timeString}`);
this.pomodoroCycleProgressEl.style.display = '';
this.pomodoroCycleProgressEl.addClass('cycle-active');
} else {
this.pomodoroCycleProgressEl.style.display = 'none';
}
}
/**
* Format time in seconds to a readable string
*/
private formatTime(totalSeconds: number): string {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
} else {
return `${minutes}m`;
}
}
/**
* Calculate and display estimation for current notes
*/
public async calculateAndDisplayEstimation(): Promise<void> {
const notesForEstimate = this.plugin.reviewController.getTodayNotes();
await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate);
}
}