/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/main.ts var main_exports = {}; __export(main_exports, { default: () => FocusTaskPlugin }); module.exports = __toCommonJS(main_exports); var import_obsidian3 = require("obsidian"); // src/types.ts var DEFAULT_SETTINGS = { pomodoroWorkMinutes: 25, pomodoroBreakMinutes: 5, longBreakMinutes: 15, longBreakInterval: 4, enableSounds: true, enableCelebrations: true, defaultEstimateMinutes: 30, lists: [ { id: "work", name: "Work", color: "#6366f1", icon: "\u{1F4BC}" }, { id: "personal", name: "Personal", color: "#22c55e", icon: "\u{1F3E0}" }, { id: "learning", name: "Learning", color: "#f59e0b", icon: "\u{1F4DA}" } ], autoStartBreak: false, tickSoundEnabled: false }; var DEFAULT_DATA = { tasks: [], completedToday: 0, totalFocusMinutesToday: 0, streak: 0, lastActiveDate: "", pomodorosCompleted: 0 }; var VIEW_TYPE_FOCUS_TASK = "focus-task-view"; var CELEBRATION_MESSAGES = [ { emoji: "\u{1F4A5}", message: "Crushed it!" }, { emoji: "\u{1F525}", message: "On fire!" }, { emoji: "\u26A1", message: "Lightning fast!" }, { emoji: "\u{1F3AF}", message: "Bullseye!" }, { emoji: "\u{1F680}", message: "Blasting through!" }, { emoji: "\u{1F4AA}", message: "Strong work!" }, { emoji: "\u{1F31F}", message: "Stellar!" }, { emoji: "\u2728", message: "Brilliant!" }, { emoji: "\u{1F3C6}", message: "Champion!" }, { emoji: "\u{1F389}", message: "Well done!" } ]; var EARLY_FINISH_MESSAGES = [ { emoji: "\u26A1", message: "Speed demon! Finished early!" }, { emoji: "\u{1F3CE}\uFE0F", message: "Faster than expected!" }, { emoji: "\u{1F3AF}", message: "Under budget! Nice work!" } ]; var OVERTIME_MESSAGES = [ { emoji: "\u{1F4AA}", message: "Persistence pays off!" }, { emoji: "\u{1F3C3}", message: "Marathon runner!" }, { emoji: "\u{1F525}", message: "The grind is real!" } ]; // src/view.ts var import_obsidian2 = require("obsidian"); // src/modals.ts var import_obsidian = require("obsidian"); var QuickAddTaskModal = class extends import_obsidian.Modal { constructor(app, plugin) { super(app); this.taskText = ""; this.selectedList = "work"; this.plugin = plugin; this.estimatedMinutes = plugin.settings.defaultEstimateMinutes; if (plugin.settings.lists.length > 0) { this.selectedList = plugin.settings.lists[0].id; } } onOpen() { const { contentEl } = this; contentEl.addClass("focus-task-modal"); contentEl.createEl("h2", { text: "\u26A1 Add New Task" }); new import_obsidian.Setting(contentEl).setName("Task").addText((text) => { text.setPlaceholder("What do you need to do?").onChange((value) => this.taskText = value); text.inputEl.focus(); text.inputEl.addEventListener("keydown", (e) => { if (e.key === "Enter" && this.taskText.trim()) { this.submitTask(); } }); }); new import_obsidian.Setting(contentEl).setName("Estimated Time").setDesc("How long do you think this will take?").addDropdown((dropdown) => { const options = { "5": "5 min", "10": "10 min", "15": "15 min", "20": "20 min", "25": "25 min (1 pomodoro)", "30": "30 min", "45": "45 min", "50": "50 min (2 pomodoros)", "60": "1 hour", "90": "1.5 hours", "120": "2 hours", "180": "3 hours" }; Object.entries(options).forEach(([value, label]) => { dropdown.addOption(value, label); }); dropdown.setValue(this.estimatedMinutes.toString()); dropdown.onChange((value) => this.estimatedMinutes = parseInt(value)); }); new import_obsidian.Setting(contentEl).setName("List").addDropdown((dropdown) => { this.plugin.settings.lists.forEach((list) => { dropdown.addOption(list.id, `${list.icon} ${list.name}`); }); dropdown.setValue(this.selectedList); dropdown.onChange((value) => this.selectedList = value); }); const buttonContainer = contentEl.createEl("div", { cls: "focus-task-modal-buttons" }); const cancelBtn = buttonContainer.createEl("button", { text: "Cancel", cls: "focus-task-btn" }); cancelBtn.addEventListener("click", () => this.close()); const addBtn = buttonContainer.createEl("button", { text: "Add Task", cls: "focus-task-btn focus-task-btn-primary" }); addBtn.addEventListener("click", () => this.submitTask()); } submitTask() { if (this.taskText.trim()) { const task = this.plugin.createTask(this.taskText, this.estimatedMinutes, this.selectedList); this.plugin.addTask(task); new import_obsidian.Notice("\u2705 Task added!"); this.close(); } else { new import_obsidian.Notice("Please enter a task description"); } } onClose() { const { contentEl } = this; contentEl.empty(); } }; var EditTaskModal = class extends import_obsidian.Modal { constructor(app, plugin, task) { super(app); this.plugin = plugin; this.task = { ...task }; } onOpen() { const { contentEl } = this; contentEl.addClass("focus-task-modal"); contentEl.createEl("h2", { text: "\u270F\uFE0F Edit Task" }); new import_obsidian.Setting(contentEl).setName("Task").addText((text) => text.setValue(this.task.text).onChange((value) => this.task.text = value)); new import_obsidian.Setting(contentEl).setName("Estimated Time").addDropdown((dropdown) => { const options = { "5": "5 min", "10": "10 min", "15": "15 min", "20": "20 min", "25": "25 min", "30": "30 min", "45": "45 min", "50": "50 min", "60": "1 hour", "90": "1.5 hours", "120": "2 hours", "180": "3 hours" }; Object.entries(options).forEach(([value, label]) => { dropdown.addOption(value, label); }); dropdown.setValue(this.task.estimatedMinutes.toString()); dropdown.onChange((value) => this.task.estimatedMinutes = parseInt(value)); }); new import_obsidian.Setting(contentEl).setName("List").addDropdown((dropdown) => { this.plugin.settings.lists.forEach((list) => { dropdown.addOption(list.id, `${list.icon} ${list.name}`); }); dropdown.setValue(this.task.list); dropdown.onChange((value) => this.task.list = value); }); new import_obsidian.Setting(contentEl).setName("Notes").setDesc("Add any additional details or links").addTextArea((textarea) => { textarea.setValue(this.task.notes).onChange((value) => this.task.notes = value); textarea.inputEl.rows = 4; }); if (this.task.actualMinutes > 0) { new import_obsidian.Setting(contentEl).setName("Time Tracked").setDesc(`You've worked on this task for ${this.plugin.formatTimeHuman(this.task.actualMinutes)}`); } const buttonContainer = contentEl.createEl("div", { cls: "focus-task-modal-buttons" }); const cancelBtn = buttonContainer.createEl("button", { text: "Cancel", cls: "focus-task-btn" }); cancelBtn.addEventListener("click", () => this.close()); const saveBtn = buttonContainer.createEl("button", { text: "Save", cls: "focus-task-btn focus-task-btn-primary" }); saveBtn.addEventListener("click", () => { this.plugin.updateTask(this.task.id, this.task); new import_obsidian.Notice("\u2705 Task updated!"); this.close(); }); } onClose() { const { contentEl } = this; contentEl.empty(); } }; // src/view.ts var FocusTaskView = class extends import_obsidian2.ItemView { constructor(leaf, plugin) { super(leaf); this.currentFilter = "all"; // References to elements that need frequent updates this.timerTimeEl = null; this.progressBarEl = null; this.actualTimeEl = null; this.pauseBtnEl = null; this.plugin = plugin; } getViewType() { return VIEW_TYPE_FOCUS_TASK; } getDisplayText() { return "Focus Task"; } getIcon() { return "zap"; } async onOpen() { this.refresh(); } // Light update - only updates timer display without rebuilding DOM updateTimerDisplay() { if (!this.timerTimeEl) return; this.timerTimeEl.textContent = this.plugin.formatTime(this.plugin.currentTimerSeconds); if (this.progressBarEl) { let progressPercent = 0; if (this.plugin.isBreakMode) { const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60; progressPercent = (breakDuration - this.plugin.currentTimerSeconds) / breakDuration * 100; } else { 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 (this.actualTimeEl && this.plugin.activeTaskId) { const task = this.plugin.data.tasks.find((t) => t.id === this.plugin.activeTaskId); if (task) { this.actualTimeEl.textContent = `Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}`; } } } refresh() { const container = this.containerEl.children[1]; container.empty(); container.addClass("focus-task-container"); this.timerTimeEl = null; this.progressBarEl = null; this.actualTimeEl = null; this.pauseBtnEl = null; this.renderHeader(container); this.renderStatsBar(container); this.renderActiveTask(container); this.renderTaskList(container); } renderHeader(container) { const header = container.createEl("div", { cls: "focus-task-header" }); const titleSection = header.createEl("div", { cls: "focus-task-title-section" }); titleSection.createEl("h2", { text: "\u26A1 Focus Task", cls: "focus-task-title" }); const today = new Date(); const dateStr = today.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" }); titleSection.createEl("div", { text: dateStr, cls: "focus-task-date" }); const actions = header.createEl("div", { cls: "focus-task-header-actions" }); const addBtn = actions.createEl("button", { cls: "focus-task-btn focus-task-btn-primary" }); addBtn.innerHTML = "+ Add Task"; addBtn.addEventListener("click", () => { new QuickAddTaskModal(this.app, this.plugin).open(); }); } renderStatsBar(container) { const stats = this.plugin.getStats(); const statsBar = container.createEl("div", { cls: "focus-task-stats-bar" }); const statItems = [ { label: "Pending", value: stats.pendingCount.toString(), icon: "\u{1F4CB}" }, { label: "Done Today", value: stats.completedToday.toString(), icon: "\u2705" }, { label: "Focus Time", value: this.plugin.formatTimeHuman(stats.totalFocusMinutesToday), icon: "\u23F1\uFE0F" }, { label: "Streak", value: `${stats.streak} days`, icon: "\u{1F525}" } ]; statItems.forEach((stat) => { const item = statsBar.createEl("div", { cls: "focus-task-stat-item" }); item.createEl("div", { cls: "focus-task-stat-icon", text: stat.icon }); item.createEl("div", { cls: "focus-task-stat-value", text: stat.value }); item.createEl("div", { cls: "focus-task-stat-label", text: stat.label }); }); } renderActiveTask(container) { const activeSection = container.createEl("div", { cls: "focus-task-active-section" }); if (this.plugin.activeTaskId) { const task = this.plugin.data.tasks.find((t) => t.id === this.plugin.activeTaskId); if (task) { activeSection.addClass("focus-task-has-active"); const activeCard = activeSection.createEl("div", { cls: "focus-task-active-card" }); if (this.plugin.isBreakMode) { activeCard.addClass("focus-task-break-card"); activeCard.createEl("div", { cls: "focus-task-active-label", text: "\u2615 BREAK TIME" }); } else { activeCard.createEl("div", { cls: "focus-task-active-label", text: "\u{1F3AF} FOCUSING ON" }); } activeCard.createEl("div", { cls: "focus-task-active-task-name", text: task.text }); const timerDisplay = activeCard.createEl("div", { cls: "focus-task-timer-display" }); this.timerTimeEl = timerDisplay.createEl("span", { cls: "focus-task-timer-time", text: this.plugin.formatTime(this.plugin.currentTimerSeconds) }); const progressWrap = activeCard.createEl("div", { cls: "focus-task-progress-wrap" }); this.progressBarEl = progressWrap.createEl("div", { cls: "focus-task-progress-bar" }); let progressPercent = 0; if (this.plugin.isBreakMode) { const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60; progressPercent = (breakDuration - this.plugin.currentTimerSeconds) / breakDuration * 100; } else { 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("focus-task-overtime"); if (!this.plugin.isBreakMode) { const timeInfo = activeCard.createEl("div", { cls: "focus-task-time-info" }); timeInfo.createEl("span", { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` }); this.actualTimeEl = timeInfo.createEl("span", { text: `Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}` }); } const controls = activeCard.createEl("div", { cls: "focus-task-active-controls" }); this.pauseBtnEl = controls.createEl("button", { cls: "focus-task-btn focus-task-btn-secondary" }); this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? "\u23F8 Pause" : "\u25B6 Resume"; this.pauseBtnEl.addEventListener("click", () => this.plugin.toggleTimer()); if (!this.plugin.isBreakMode) { const completeBtn = controls.createEl("button", { cls: "focus-task-btn focus-task-btn-success" }); completeBtn.innerHTML = "\u2713 Complete"; completeBtn.addEventListener("click", () => this.plugin.completeTask(task.id)); } const stopBtn = controls.createEl("button", { cls: "focus-task-btn focus-task-btn-danger" }); stopBtn.innerHTML = "\u2715 Stop"; stopBtn.addEventListener("click", () => this.plugin.stopTimer()); if (this.plugin.isBreakMode) { const skipBreakBtn = controls.createEl("button", { cls: "focus-task-btn" }); skipBreakBtn.innerHTML = "\u23ED Skip Break"; skipBreakBtn.addEventListener("click", () => { this.plugin.isBreakMode = false; this.plugin.stopTimer(); this.refresh(); }); } } } else { const startPrompt = activeSection.createEl("div", { cls: "focus-task-start-prompt" }); startPrompt.createEl("div", { cls: "focus-task-prompt-icon", text: "\u26A1" }); startPrompt.createEl("div", { cls: "focus-task-prompt-text", text: "Ready to focus?" }); startPrompt.createEl("div", { cls: "focus-task-prompt-hint", text: "Click \u25B6 on a task to start a Pomodoro session" }); } } renderTaskList(container) { const listSection = container.createEl("div", { cls: "focus-task-list-section" }); const filters = listSection.createEl("div", { cls: "focus-task-filters" }); const filterOptions = [ { id: "all", label: "All Tasks" }, { id: "today", label: "Today" }, { id: "completed", label: "Completed" }, ...this.plugin.settings.lists.map((l) => ({ id: l.id, label: `${l.icon} ${l.name}` })) ]; filterOptions.forEach((opt) => { const btn = filters.createEl("button", { cls: `focus-task-filter-btn ${this.currentFilter === opt.id ? "active" : ""}`, text: opt.label }); btn.addEventListener("click", () => { this.currentFilter = opt.id; this.refresh(); }); }); const taskList = listSection.createEl("div", { cls: "focus-task-task-list" }); let tasks = this.plugin.data.tasks; if (this.currentFilter === "today") { tasks = this.plugin.getTodaysTasks(); } else if (this.currentFilter === "completed") { tasks = this.plugin.data.tasks.filter((t) => t.completed); } else if (this.currentFilter !== "all") { tasks = this.plugin.getTasksByList(this.currentFilter); } tasks = [...tasks].sort((a, b) => { if (a.completed !== b.completed) return a.completed ? 1 : -1; return b.createdAt - a.createdAt; }); if (tasks.length === 0) { const emptyState = taskList.createEl("div", { cls: "focus-task-empty-state" }); emptyState.createEl("div", { cls: "focus-task-empty-icon", text: "\u{1F4DD}" }); emptyState.createEl("div", { cls: "focus-task-empty-text", text: "No tasks yet" }); emptyState.createEl("div", { cls: "focus-task-empty-hint", text: "Add a task to get started!" }); } else { tasks.forEach((task) => this.renderTaskItem(taskList, task)); } } renderTaskItem(container, task) { const list = this.plugin.settings.lists.find((l) => l.id === task.list); const taskEl = container.createEl("div", { cls: `focus-task-task-item ${task.completed ? "completed" : ""} ${task.isActive ? "active" : ""}` }); const checkbox = taskEl.createEl("div", { cls: "focus-task-checkbox" }); checkbox.innerHTML = task.completed ? "\u2713" : ""; checkbox.style.borderColor = (list == null ? void 0 : list.color) || "#6366f1"; if (task.completed) { checkbox.style.backgroundColor = (list == null ? void 0 : list.color) || "#6366f1"; checkbox.style.color = "white"; } checkbox.addEventListener("click", (e) => { e.stopPropagation(); if (!task.completed) { this.plugin.completeTask(task.id); } }); const content = taskEl.createEl("div", { cls: "focus-task-task-content" }); const taskHeader = content.createEl("div", { cls: "focus-task-task-header" }); taskHeader.createEl("span", { cls: "focus-task-task-text", text: task.text }); if (list) { const listBadge = taskHeader.createEl("span", { cls: "focus-task-list-badge", text: `${list.icon} ${list.name}` }); listBadge.style.backgroundColor = list.color + "20"; listBadge.style.color = list.color; } const taskMeta = content.createEl("div", { cls: "focus-task-task-meta" }); taskMeta.createEl("span", { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` }); if (task.actualMinutes > 0) { const actualSpan = taskMeta.createEl("span"); actualSpan.setText(`Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}`); if (task.actualMinutes > task.estimatedMinutes) { actualSpan.addClass("focus-task-overtime-text"); } } const actions = taskEl.createEl("div", { cls: "focus-task-task-actions" }); if (!task.completed) { const startBtn = actions.createEl("button", { cls: "focus-task-task-btn", attr: { "aria-label": "Start Pomodoro" } }); startBtn.innerHTML = "\u25B6"; startBtn.addEventListener("click", (e) => { e.stopPropagation(); this.plugin.startPomodoro(task.id); }); const stopwatchBtn = actions.createEl("button", { cls: "focus-task-task-btn", attr: { "aria-label": "Start Stopwatch" } }); stopwatchBtn.innerHTML = "\u23F1"; stopwatchBtn.addEventListener("click", (e) => { e.stopPropagation(); this.plugin.startTimer(task.id); }); } const editBtn = actions.createEl("button", { cls: "focus-task-task-btn", attr: { "aria-label": "Edit" } }); editBtn.innerHTML = "\u270F\uFE0F"; editBtn.addEventListener("click", (e) => { e.stopPropagation(); new EditTaskModal(this.app, this.plugin, task).open(); }); const deleteBtn = actions.createEl("button", { cls: "focus-task-task-btn focus-task-delete-btn", attr: { "aria-label": "Delete" } }); deleteBtn.innerHTML = "\u{1F5D1}"; deleteBtn.addEventListener("click", (e) => { e.stopPropagation(); this.plugin.deleteTask(task.id); }); } }; // src/main.ts var FocusTaskPlugin = class extends import_obsidian3.Plugin { constructor() { super(...arguments); // Timer state this.timerInterval = null; this.currentTimerSeconds = 0; this.isTimerRunning = false; this.isBreakMode = false; this.activeTaskId = null; this.pomodoroCount = 0; // Focus time tracking (in seconds for accuracy) this.focusSecondsToday = 0; // Status bar element this.statusBarEl = null; } async onload() { await this.loadAllData(); this.checkDailyReset(); this.registerView( VIEW_TYPE_FOCUS_TASK, (leaf) => new FocusTaskView(leaf, this) ); this.addRibbonIcon("zap", "Open Focus Task", () => { this.activateView(); }); this.addCommand({ id: "open-focus-task", name: "Open Focus Task Panel", callback: () => this.activateView() }); this.addCommand({ id: "quick-add-task", name: "Quick Add Task", callback: () => new QuickAddTaskModal(this.app, this).open() }); this.addCommand({ id: "start-focus-mode", name: "Start Focus Mode on Next Task", callback: () => this.startFocusOnNextTask() }); this.addCommand({ id: "toggle-timer", name: "Toggle Timer (Play/Pause)", callback: () => this.toggleTimer() }); this.addCommand({ id: "complete-current-task", name: "Complete Current Task", callback: () => this.completeActiveTask() }); this.addSettingTab(new FocusTaskSettingTab(this.app, this)); this.createStatusBar(); } onunload() { this.stopTimer(); } async loadAllData() { const loaded = await this.loadData(); this.data = Object.assign({}, DEFAULT_DATA, (loaded == null ? void 0 : loaded.data) || {}); this.settings = Object.assign({}, DEFAULT_SETTINGS, (loaded == null ? void 0 : loaded.settings) || {}); this.focusSecondsToday = (this.data.totalFocusMinutesToday || 0) * 60; } async saveAllData() { this.data.totalFocusMinutesToday = Math.floor(this.focusSecondsToday / 60); await this.saveData({ settings: this.settings, data: this.data }); } checkDailyReset() { const today = new Date().toDateString(); if (this.data.lastActiveDate !== today) { const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); if (this.data.lastActiveDate === yesterday.toDateString()) { this.data.streak++; } else if (this.data.lastActiveDate !== today) { this.data.streak = 0; } this.data.completedToday = 0; this.data.totalFocusMinutesToday = 0; this.focusSecondsToday = 0; this.data.lastActiveDate = today; this.saveAllData(); } } async activateView() { const { workspace } = this.app; let leaf = null; const leaves = workspace.getLeavesOfType(VIEW_TYPE_FOCUS_TASK); if (leaves.length > 0) { leaf = leaves[0]; } else { leaf = workspace.getRightLeaf(false); if (leaf) { await leaf.setViewState({ type: VIEW_TYPE_FOCUS_TASK, active: true }); } } if (leaf) { workspace.revealLeaf(leaf); } } // ============ Task Management ============ createTask(text, estimatedMinutes = this.settings.defaultEstimateMinutes, list = "work") { return { id: this.generateId(), text, completed: false, estimatedMinutes, actualMinutes: 0, createdAt: Date.now(), list, notes: "", isActive: false }; } generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } addTask(task) { this.data.tasks.push(task); this.saveAllData(); this.refreshView(); } updateTask(taskId, updates) { const task = this.data.tasks.find((t) => t.id === taskId); if (task) { Object.assign(task, updates); this.saveAllData(); this.refreshView(); } } deleteTask(taskId) { this.data.tasks = this.data.tasks.filter((t) => t.id !== taskId); if (this.activeTaskId === taskId) { this.stopTimer(); this.activeTaskId = null; } this.saveAllData(); this.refreshView(); } completeTask(taskId) { const task = this.data.tasks.find((t) => t.id === taskId); if (task && !task.completed) { task.completed = true; task.completedAt = Date.now(); task.isActive = false; this.data.completedToday++; this.data.lastActiveDate = new Date().toDateString(); if (this.settings.enableCelebrations) { this.showCelebration(task); } if (this.settings.enableSounds) { this.playCompletionSound(); } if (this.activeTaskId === taskId) { this.stopTimer(); this.activeTaskId = null; } this.saveAllData(); this.refreshView(); } } completeActiveTask() { if (this.activeTaskId) { this.completeTask(this.activeTaskId); } else { new import_obsidian3.Notice("No active task to complete"); } } // ============ Timer Management ============ startTimer(taskId) { const task = this.data.tasks.find((t) => t.id === taskId); if (!task) return; this.stopTimer(); this.activeTaskId = taskId; task.isActive = true; this.isBreakMode = false; this.currentTimerSeconds = 0; this.isTimerRunning = true; this.refreshView(); this.updateStatusBar(); this.timerInterval = window.setInterval(() => { this.currentTimerSeconds++; task.actualMinutes = Math.floor(this.currentTimerSeconds / 60); this.focusSecondsToday++; this.updateStatusBar(); this.updateTimerDisplay(); if (this.currentTimerSeconds === task.estimatedMinutes * 60) { if (this.settings.enableSounds) { this.playAlertSound(); } new import_obsidian3.Notice(`\u23F0 Time's up for: ${task.text}`); } }, 1e3); this.saveAllData(); } startPomodoro(taskId) { const task = this.data.tasks.find((t) => t.id === taskId); if (!task) return; this.stopTimer(); this.activeTaskId = taskId; task.isActive = true; this.isBreakMode = false; this.currentTimerSeconds = this.settings.pomodoroWorkMinutes * 60; this.isTimerRunning = true; this.refreshView(); this.updateStatusBar(); let secondsWorked = 0; this.timerInterval = window.setInterval(() => { this.currentTimerSeconds--; if (!this.isBreakMode) { secondsWorked++; task.actualMinutes = Math.floor(secondsWorked / 60); this.focusSecondsToday++; } this.updateStatusBar(); this.updateTimerDisplay(); if (this.currentTimerSeconds <= 0) { this.handlePomodoroEnd(); } }, 1e3); } handlePomodoroEnd() { if (!this.isBreakMode) { this.pomodoroCount++; this.data.pomodorosCompleted++; if (this.settings.enableSounds) { this.playAlertSound(); } new import_obsidian3.Notice("\u{1F345} Pomodoro complete! Time for a break."); if (this.settings.autoStartBreak) { this.startBreak(); } else { this.stopTimer(); } } else { if (this.settings.enableSounds) { this.playAlertSound(); } new import_obsidian3.Notice("\u26A1 Break over! Ready to focus?"); this.isBreakMode = false; this.stopTimer(); } this.saveAllData(); } startBreak() { this.isBreakMode = true; const isLongBreak = this.pomodoroCount % this.settings.longBreakInterval === 0; this.currentTimerSeconds = (isLongBreak ? this.settings.longBreakMinutes : this.settings.pomodoroBreakMinutes) * 60; new import_obsidian3.Notice(isLongBreak ? "\u2615 Long break time!" : "\u2615 Short break time!"); this.refreshView(); if (!this.timerInterval) { this.timerInterval = window.setInterval(() => { this.currentTimerSeconds--; this.updateStatusBar(); this.updateTimerDisplay(); if (this.currentTimerSeconds <= 0) { this.handlePomodoroEnd(); } }, 1e3); } this.updateStatusBar(); } toggleTimer() { if (this.isTimerRunning && this.timerInterval) { window.clearInterval(this.timerInterval); this.timerInterval = null; this.isTimerRunning = false; } else if (this.activeTaskId) { this.isTimerRunning = true; const task = this.data.tasks.find((t) => t.id === this.activeTaskId); this.timerInterval = window.setInterval(() => { this.currentTimerSeconds--; if (task && !this.isBreakMode) { task.actualMinutes = Math.floor((this.settings.pomodoroWorkMinutes * 60 - this.currentTimerSeconds) / 60); this.focusSecondsToday++; } this.updateStatusBar(); this.updateTimerDisplay(); if (this.currentTimerSeconds <= 0) { this.handlePomodoroEnd(); } }, 1e3); } else { new import_obsidian3.Notice("No active task. Select a task first."); } this.updateStatusBar(); this.refreshView(); } stopTimer() { if (this.timerInterval) { window.clearInterval(this.timerInterval); this.timerInterval = null; } if (this.activeTaskId) { const task = this.data.tasks.find((t) => t.id === this.activeTaskId); if (task) { task.isActive = false; } } this.isTimerRunning = false; this.activeTaskId = null; this.updateStatusBar(); this.saveAllData(); this.refreshView(); } startFocusOnNextTask() { const pendingTasks = this.data.tasks.filter((t) => !t.completed); if (pendingTasks.length > 0) { this.startPomodoro(pendingTasks[0].id); } else { new import_obsidian3.Notice("No pending tasks. Add a task first!"); } } // ============ Status Bar Timer ============ createStatusBar() { this.statusBarEl = this.addStatusBarItem(); this.statusBarEl.addClass("focus-task-status-bar"); this.updateStatusBar(); this.statusBarEl.addEventListener("click", () => { this.activateView(); }); } updateStatusBar() { if (!this.statusBarEl) return; if (this.activeTaskId) { const task = this.data.tasks.find((t) => t.id === this.activeTaskId); const taskName = this.isBreakMode ? "\u2615 Break" : (task == null ? void 0 : task.text.substring(0, 20)) || "Task"; const timeStr = this.formatTime(this.currentTimerSeconds); const icon = this.isTimerRunning ? "\u25B6" : "\u23F8"; this.statusBarEl.setText(`\u26A1 ${icon} ${timeStr} - ${taskName}${task && task.text.length > 20 ? "..." : ""}`); this.statusBarEl.addClass("focus-task-status-active"); } else { this.statusBarEl.setText("\u26A1 Focus Task"); this.statusBarEl.removeClass("focus-task-status-active"); } } // ============ Sounds & Celebrations ============ showCelebration(task) { let messages = CELEBRATION_MESSAGES; let extraMessage = ""; if (task.actualMinutes < task.estimatedMinutes) { const saved = task.estimatedMinutes - task.actualMinutes; messages = EARLY_FINISH_MESSAGES; extraMessage = ` (${saved} min early!)`; } else if (task.actualMinutes > task.estimatedMinutes * 1.5) { messages = OVERTIME_MESSAGES; } const celebration = messages[Math.floor(Math.random() * messages.length)]; new import_obsidian3.Notice(`${celebration.emoji} ${celebration.message}${extraMessage}`); } playCompletionSound() { try { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.setValueAtTime(800, audioContext.currentTime); oscillator.frequency.setValueAtTime(1e3, audioContext.currentTime + 0.1); oscillator.frequency.setValueAtTime(1200, audioContext.currentTime + 0.2); gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.3); } catch (e) { console.log("Audio not available"); } } playAlertSound() { try { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.setValueAtTime(440, audioContext.currentTime); gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); for (let i = 0; i < 3; i++) { oscillator.frequency.setValueAtTime(440, audioContext.currentTime + i * 0.3); oscillator.frequency.setValueAtTime(550, audioContext.currentTime + i * 0.3 + 0.15); } gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.9); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.9); } catch (e) { console.log("Audio not available"); } } // ============ Utilities ============ formatTime(seconds) { const absSeconds = Math.abs(seconds); const mins = Math.floor(absSeconds / 60); const secs = absSeconds % 60; const sign = seconds < 0 ? "-" : ""; return `${sign}${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; } formatTimeHuman(minutes) { if (minutes < 60) return `${minutes}min`; const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins > 0 ? `${hours}hr ${mins}min` : `${hours}hr`; } refreshView() { const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_FOCUS_TASK); leaves.forEach((leaf) => { if (leaf.view instanceof FocusTaskView) { leaf.view.refresh(); } }); } // Light refresh - only updates timer display without rebuilding DOM updateTimerDisplay() { const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_FOCUS_TASK); leaves.forEach((leaf) => { if (leaf.view instanceof FocusTaskView) { leaf.view.updateTimerDisplay(); } }); } getTasksByList(listId) { return this.data.tasks.filter((t) => t.list === listId); } getPendingTasks() { return this.data.tasks.filter((t) => !t.completed); } getTodaysTasks() { const today = new Date().toDateString(); return this.data.tasks.filter((t) => { if (t.scheduledDate === today) return true; if (!t.scheduledDate && !t.completed) return true; return false; }); } getStats() { const pending = this.getPendingTasks(); const totalEstimate = pending.reduce((sum, t) => sum + t.estimatedMinutes, 0); const completedTasks = this.data.tasks.filter((t) => t.completed); const avgAccuracy = completedTasks.length > 0 ? completedTasks.reduce((sum, t) => sum + t.estimatedMinutes / Math.max(t.actualMinutes, 1), 0) / completedTasks.length : 1; return { pendingCount: pending.length, completedToday: this.data.completedToday, totalEstimatedMinutes: totalEstimate, totalFocusMinutesToday: Math.floor(this.focusSecondsToday / 60), streak: this.data.streak, pomodorosCompleted: this.data.pomodorosCompleted, avgAccuracy: Math.round(avgAccuracy * 100) }; } }; var FocusTaskSettingTab = class extends import_obsidian3.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h1", { text: "\u26A1 Focus Task Settings" }); containerEl.createEl("h2", { text: "\u{1F345} Pomodoro Timer" }); new import_obsidian3.Setting(containerEl).setName("Work Duration").setDesc("Length of each work session in minutes").addSlider((slider) => slider.setLimits(5, 60, 5).setValue(this.plugin.settings.pomodoroWorkMinutes).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.pomodoroWorkMinutes = value; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Short Break Duration").setDesc("Length of short breaks in minutes").addSlider((slider) => slider.setLimits(1, 15, 1).setValue(this.plugin.settings.pomodoroBreakMinutes).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.pomodoroBreakMinutes = value; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Long Break Duration").setDesc("Length of long breaks in minutes").addSlider((slider) => slider.setLimits(5, 30, 5).setValue(this.plugin.settings.longBreakMinutes).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.longBreakMinutes = value; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Long Break Interval").setDesc("Number of pomodoros before a long break").addSlider((slider) => slider.setLimits(2, 6, 1).setValue(this.plugin.settings.longBreakInterval).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.longBreakInterval = value; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Auto-start Breaks").setDesc("Automatically start break timer after work session").addToggle((toggle) => toggle.setValue(this.plugin.settings.autoStartBreak).onChange(async (value) => { this.plugin.settings.autoStartBreak = value; await this.plugin.saveAllData(); })); containerEl.createEl("h2", { text: "\u2699\uFE0F General" }); new import_obsidian3.Setting(containerEl).setName("Default Time Estimate").setDesc("Default estimated time for new tasks in minutes").addSlider((slider) => slider.setLimits(5, 120, 5).setValue(this.plugin.settings.defaultEstimateMinutes).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.defaultEstimateMinutes = value; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Enable Sounds").setDesc("Play sounds for timer completion and task completion").addToggle((toggle) => toggle.setValue(this.plugin.settings.enableSounds).onChange(async (value) => { this.plugin.settings.enableSounds = value; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Enable Celebrations").setDesc("Show celebration messages when completing tasks").addToggle((toggle) => toggle.setValue(this.plugin.settings.enableCelebrations).onChange(async (value) => { this.plugin.settings.enableCelebrations = value; await this.plugin.saveAllData(); })); containerEl.createEl("h2", { text: "\u{1F4CB} Lists" }); this.plugin.settings.lists.forEach((list, index) => { new import_obsidian3.Setting(containerEl).setName(`${list.icon} ${list.name}`).addText((text) => text.setValue(list.name).setPlaceholder("List name").onChange(async (value) => { this.plugin.settings.lists[index].name = value; await this.plugin.saveAllData(); })).addText((text) => text.setValue(list.icon).setPlaceholder("Emoji").onChange(async (value) => { this.plugin.settings.lists[index].icon = value; await this.plugin.saveAllData(); })).addColorPicker((picker) => picker.setValue(list.color).onChange(async (value) => { this.plugin.settings.lists[index].color = value; await this.plugin.saveAllData(); })).addButton((btn) => btn.setIcon("trash").setTooltip("Delete list").onClick(async () => { this.plugin.settings.lists.splice(index, 1); await this.plugin.saveAllData(); this.display(); })); }); new import_obsidian3.Setting(containerEl).addButton((btn) => btn.setButtonText("+ Add List").onClick(async () => { this.plugin.settings.lists.push({ id: this.plugin.generateId(), name: "New List", color: "#6366f1", icon: "\u{1F4C1}" }); await this.plugin.saveAllData(); this.display(); })); containerEl.createEl("h2", { text: "\u{1F4D6} About" }); const aboutDiv = containerEl.createDiv({ cls: "focus-task-about" }); aboutDiv.innerHTML = `
Focus Task is heavily inspired by Blitzit, a fantastic productivity app that combines task management with focused time tracking.
This plugin brings similar functionality directly into Obsidian, allowing you to manage tasks, use the Pomodoro technique, and track your productivity without leaving your notes.
`; } };