/* 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, // Daily note logging logToDaily: false, dailyNoteFormat: "YYYY-MM-DD", dailyNoteHeading: "## \u26A1 Completed Tasks" }; 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.settings.logToDaily) { this.logTaskToDailyNote(task); } 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"); } } // ============ Daily Note Logging ============ async logTaskToDailyNote(task) { try { const dailyNotePath = this.getDailyNotePath(); const list = this.settings.lists.find((l) => l.id === task.list); const timeDiff = task.actualMinutes - task.estimatedMinutes; let timeComparison = ""; if (timeDiff < 0) { timeComparison = `${Math.abs(timeDiff)}min under estimate \u2728`; } else if (timeDiff > 0) { timeComparison = `${timeDiff}min over estimate`; } else { timeComparison = `exactly on target \u{1F3AF}`; } const completedTime = new Date(task.completedAt || Date.now()).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }); const taskEntry = `- [x] ${task.text} | ${(list == null ? void 0 : list.icon) || "\u{1F4CB}"} ${(list == null ? void 0 : list.name) || "Task"} | \u23F1\uFE0F ${this.formatTimeHuman(task.actualMinutes)} / ${this.formatTimeHuman(task.estimatedMinutes)} (${timeComparison}) | \u2705 ${completedTime}`; await this.appendToDailyNote(dailyNotePath, taskEntry); } catch (e) { console.error("Failed to log task to daily note:", e); new import_obsidian3.Notice("Failed to log task to daily note"); } } getDailyNotePath() { const now = new Date(); const format = this.settings.dailyNoteFormat; const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, "0"); const day = now.getDate().toString().padStart(2, "0"); let filename = format.replace("YYYY", year.toString()).replace("MM", month).replace("DD", day); return `${filename}.md`; } async appendToDailyNote(path, content) { const { vault } = this.app; const heading = this.settings.dailyNoteHeading; let file = vault.getAbstractFileByPath(path); if (!file) { await vault.create(path, `# ${new Date().toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })} ${heading} ${content} `); new import_obsidian3.Notice("\u{1F4DD} Task logged to new daily note"); return; } if (!(file instanceof import_obsidian3.TFile)) { new import_obsidian3.Notice("Daily note path is not a file"); return; } let existingContent = await vault.read(file); if (existingContent.includes(heading)) { const headingIndex = existingContent.indexOf(heading); const afterHeading = headingIndex + heading.length; const nextHeadingMatch = existingContent.slice(afterHeading).match(/\n##? /); let insertPosition; if (nextHeadingMatch && nextHeadingMatch.index !== void 0) { insertPosition = afterHeading + nextHeadingMatch.index; } else { insertPosition = existingContent.length; } const before = existingContent.slice(0, insertPosition); const after = existingContent.slice(insertPosition); const newContent = before.trimEnd() + "\n" + content + "\n" + after.trimStart(); await vault.modify(file, newContent); } else { const newContent = existingContent.trimEnd() + "\n\n" + heading + "\n\n" + content + "\n"; await vault.modify(file, newContent); } new import_obsidian3.Notice("\u{1F4DD} Task logged to daily note"); } // ============ 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{1F4DD} Daily Note Integration" }); new import_obsidian3.Setting(containerEl).setName("Log completed tasks to daily note").setDesc("When you complete a task, add an entry to your daily note").addToggle((toggle) => toggle.setValue(this.plugin.settings.logToDaily).onChange(async (value) => { this.plugin.settings.logToDaily = value; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Daily note filename format").setDesc("Format for daily note filename (YYYY = year, MM = month, DD = day)").addText((text) => text.setPlaceholder("YYYY-MM-DD").setValue(this.plugin.settings.dailyNoteFormat).onChange(async (value) => { this.plugin.settings.dailyNoteFormat = value || "YYYY-MM-DD"; await this.plugin.saveAllData(); })); new import_obsidian3.Setting(containerEl).setName("Section heading").setDesc("Heading under which completed tasks will be logged").addText((text) => text.setPlaceholder("## \u26A1 Completed Tasks").setValue(this.plugin.settings.dailyNoteHeading).onChange(async (value) => { this.plugin.settings.dailyNoteHeading = value || "## \u26A1 Completed Tasks"; 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.

Source Code

`; } };