diff --git a/styles.css b/styles.css index 5224e0a..3ff0d7e 100644 --- a/styles.css +++ b/styles.css @@ -53,6 +53,98 @@ background-color: var(--background-modifier-error-hover); } +.taskweaver-active-timer { + background: linear-gradient(135deg, var(--background-secondary) 0%, var(--background-primary-alt) 100%); + border: 2px solid var(--interactive-accent); + border-radius: var(--radius-l); + padding: var(--size-4-4); + margin-bottom: var(--size-4-4); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.taskweaver-active-timer-header { + text-align: center; + margin-bottom: var(--size-4-2); +} + +.taskweaver-active-timer-label { + font-size: var(--font-ui-smaller); + text-transform: uppercase; + letter-spacing: 1px; + color: var(--text-muted); + font-weight: var(--font-semibold); +} + +.taskweaver-active-timer-title { + text-align: center; + font-size: var(--font-ui-large); + font-weight: var(--font-semibold); + color: var(--text-normal); + margin-bottom: var(--size-4-3); +} + +.taskweaver-active-timer-display { + text-align: center; + font-size: 48px; + font-family: var(--font-monospace); + font-weight: var(--font-bold); + color: var(--interactive-accent); + margin: var(--size-4-4) 0; + letter-spacing: 2px; +} + +.taskweaver-active-timer-controls { + display: flex; + gap: var(--size-4-2); + justify-content: center; + margin-top: var(--size-4-3); +} + +.taskweaver-timer-control-btn { + padding: var(--size-4-2) var(--size-4-4); + border: none; + border-radius: var(--radius-m); + cursor: pointer; + font-size: var(--font-ui-medium); + font-weight: var(--font-semibold); + transition: all 0.2s; + flex: 1; + max-width: 120px; +} + +.taskweaver-timer-control-btn.pause { + background-color: var(--color-orange); + color: white; +} + +.taskweaver-timer-control-btn.pause:hover { + background-color: var(--color-orange); + opacity: 0.8; + transform: translateY(-2px); +} + +.taskweaver-timer-control-btn.stop { + background-color: var(--background-modifier-error); + color: white; +} + +.taskweaver-timer-control-btn.stop:hover { + background-color: var(--background-modifier-error); + opacity: 0.8; + transform: translateY(-2px); +} + +.taskweaver-timer-control-btn.complete { + background-color: var(--color-green); + color: white; +} + +.taskweaver-timer-control-btn.complete:hover { + background-color: var(--color-green); + opacity: 0.8; + transform: translateY(-2px); +} + .taskweaver-category { margin-bottom: var(--size-4-3); background-color: var(--background-secondary); @@ -123,32 +215,47 @@ gap: var(--size-4-1); } -.taskweaver-timer { +.taskweaver-task-running { + opacity: 0.6; + background-color: var(--background-primary-alt) !important; +} + +.taskweaver-timer-small { font-family: var(--font-monospace); color: var(--text-muted); font-size: var(--font-ui-smaller); - min-width: 70px; + min-width: 60px; text-align: right; } -.taskweaver-timer-btn, +.taskweaver-start-btn { + background-color: var(--interactive-accent); + color: var(--text-on-accent); + border: none; + border-radius: var(--radius-s); + padding: var(--size-2-1) var(--size-4-2); + cursor: pointer; + font-size: var(--font-ui-small); + transition: all 0.2s; + font-weight: var(--font-semibold); +} + +.taskweaver-start-btn:hover { + background-color: var(--interactive-accent-hover); + transform: translateY(-1px); +} + .taskweaver-delete-btn { background: transparent; border: 1px solid var(--background-modifier-border); border-radius: var(--radius-s); padding: var(--size-2-1) var(--size-2-3); cursor: pointer; - font-size: var(--font-ui-small); - color: var(--text-normal); + font-size: var(--font-ui-smaller); + color: var(--text-muted); transition: all 0.2s; } -.taskweaver-timer-btn:hover { - background-color: var(--interactive-accent); - color: var(--text-on-accent); - border-color: var(--interactive-accent); -} - .taskweaver-delete-btn:hover { background-color: var(--background-modifier-error); color: var(--text-on-accent); diff --git a/utils.ts b/utils.ts index d7f6c1d..fc4c54e 100644 --- a/utils.ts +++ b/utils.ts @@ -1,4 +1,4 @@ -import { App, TFile } from "obsidian"; +import { App, TFile, Notice, moment } from "obsidian"; import { Task, Category } from "./types"; export function formatDuration(milliseconds: number): string { @@ -20,32 +20,64 @@ export function generateUniqueId(): string { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } -async function getDailyNote(app: App): Promise { +async function getDailyNote(app: App): Promise { const { vault } = app; - // @ts-ignore - Access internal daily notes API + // @ts-ignore - Access internal plugins const dailyNotesPlugin = app.internalPlugins.plugins["daily-notes"]; if (!dailyNotesPlugin || !dailyNotesPlugin.enabled) { - throw new Error("Daily notes plugin is not enabled. Please enable it in Settings → Core plugins."); + new Notice("Daily notes plugin is not enabled. Please enable it in Settings → Core plugins."); + return null; } - // @ts-ignore - Access daily notes interface - const { createDailyNote, getDailyNote, getAllDailyNotes } = app.internalPlugins.plugins["daily-notes"].instance; + // @ts-ignore - Access the daily notes instance + const dailyNotesInterface = dailyNotesPlugin.instance; - // @ts-ignore - const dailyNotes = getAllDailyNotes(); - // @ts-ignore - const today = window.moment(); - // @ts-ignore - let dailyNote = getDailyNote(today, dailyNotes); + if (!dailyNotesInterface) { + new Notice("Could not access daily notes interface"); + return null; + } - if (!dailyNote) { + try { // @ts-ignore - dailyNote = await createDailyNote(today); - } + const { format, folder, template } = dailyNotesInterface.options || {}; - return dailyNote; + const dateFormat = format || "YYYY-MM-DD"; + const dailyNotesFolder = folder || ""; + const today = moment().format(dateFormat); + + const fileName = `${today}.md`; + const filePath = dailyNotesFolder ? `${dailyNotesFolder}/${fileName}` : fileName; + + let dailyNote = vault.getAbstractFileByPath(filePath); + + if (!dailyNote || !(dailyNote instanceof TFile)) { + if (dailyNotesFolder && !vault.getAbstractFileByPath(dailyNotesFolder)) { + await vault.createFolder(dailyNotesFolder); + } + + let initialContent = ""; + if (template) { + const templateFile = vault.getAbstractFileByPath(template); + if (templateFile instanceof TFile) { + initialContent = await vault.read(templateFile); + } + } + + dailyNote = await vault.create(filePath, initialContent); + } + + if (dailyNote instanceof TFile) { + return dailyNote; + } + + return null; + } catch (error) { + console.error("Error getting daily note:", error); + new Notice("Error accessing daily note: " + error.message); + return null; + } } export async function logTaskToDailyNote( @@ -57,6 +89,11 @@ export async function logTaskToDailyNote( try { const dailyNote = await getDailyNote(app); + if (!dailyNote) { + new Notice("Could not find or create daily note"); + return; + } + const duration = formatDuration(task.totalElapsed); const logEntry = format .replace("{{title}}", task.title) @@ -67,8 +104,10 @@ export async function logTaskToDailyNote( const content = await app.vault.read(dailyNote); const newContent = content ? `${content}\n${logEntry}` : logEntry; await app.vault.modify(dailyNote, newContent); + + new Notice("Task logged to daily note"); } catch (error) { console.error("Failed to log task to daily note:", error); - throw error; + new Notice("Failed to log task: " + error.message); } } diff --git a/view.ts b/view.ts index 41d0699..4d282ad 100644 --- a/view.ts +++ b/view.ts @@ -63,6 +63,12 @@ export class TaskWeaverView extends ItemView { const categoriesMap = new Map(this.plugin.settings.categories.map(c => [c.id, c])); + const activeTask = this.plugin.settings.tasks.find(t => t.startTime !== null); + if (activeTask) { + const category = categoriesMap.get(activeTask.categoryId); + this.renderActiveTimer(container, activeTask, category); + } + this.plugin.settings.categories.forEach(category => { const categoryTasks = this.plugin.settings.tasks.filter(t => t.categoryId === category.id && !t.completed); @@ -84,6 +90,55 @@ export class TaskWeaverView extends ItemView { } } + renderActiveTimer(container: HTMLElement, task: Task, category?: Category): void { + const timerSection = container.createDiv({ cls: "taskweaver-active-timer" }); + + if (category) { + timerSection.style.borderColor = category.color; + } + + const timerHeader = timerSection.createDiv({ cls: "taskweaver-active-timer-header" }); + timerHeader.createEl("span", { text: "Active Task", cls: "taskweaver-active-timer-label" }); + + const taskTitleDiv = timerSection.createDiv({ cls: "taskweaver-active-timer-title" }); + if (category?.emoji) { + taskTitleDiv.createSpan({ text: `${category.emoji} `, cls: "taskweaver-emoji" }); + } + taskTitleDiv.createSpan({ text: task.title }); + + const timerDisplay = timerSection.createDiv({ + cls: "taskweaver-active-timer-display", + attr: { "data-task-id": task.id } + }); + timerDisplay.setText(formatDuration(this.plugin.getTaskElapsed(task))); + + const timerControls = timerSection.createDiv({ cls: "taskweaver-active-timer-controls" }); + + const pauseBtn = timerControls.createEl("button", { + text: "Pause", + cls: "taskweaver-timer-control-btn pause" + }); + pauseBtn.onclick = async () => { + await this.plugin.toggleTaskTimer(task.id); + }; + + const stopBtn = timerControls.createEl("button", { + text: "Stop", + cls: "taskweaver-timer-control-btn stop" + }); + stopBtn.onclick = async () => { + await this.plugin.toggleTaskTimer(task.id); + }; + + const completeBtn = timerControls.createEl("button", { + text: "Complete", + cls: "taskweaver-timer-control-btn complete" + }); + completeBtn.onclick = async () => { + await this.plugin.toggleTaskComplete(task.id); + }; + } + renderCategory(container: HTMLElement, category: Category, tasks: Task[]): void { const categoryDiv = container.createDiv({ cls: "taskweaver-category" }); @@ -104,14 +159,11 @@ export class TaskWeaverView extends ItemView { renderTask(container: HTMLElement, task: Task, category?: Category): void { const taskDiv = container.createDiv({ cls: "taskweaver-task" }); - const taskInfo = taskDiv.createDiv({ cls: "taskweaver-task-info" }); + if (task.startTime) { + taskDiv.addClass("taskweaver-task-running"); + } - const checkbox = taskInfo.createEl("input", { type: "checkbox" }); - checkbox.checked = task.completed; - checkbox.addClass("taskweaver-checkbox"); - checkbox.onclick = async () => { - await this.plugin.toggleTaskComplete(task.id); - }; + const taskInfo = taskDiv.createDiv({ cls: "taskweaver-task-info" }); const taskTitle = taskInfo.createSpan({ text: task.title, @@ -121,34 +173,54 @@ export class TaskWeaverView extends ItemView { const taskControls = taskDiv.createDiv({ cls: "taskweaver-task-controls" }); const timerSpan = taskControls.createSpan({ - cls: "taskweaver-timer", + cls: "taskweaver-timer-small", attr: { "data-task-id": task.id } }); timerSpan.setText(formatDuration(this.plugin.getTaskElapsed(task))); if (!task.completed) { - const timerBtn = taskControls.createEl("button", { - text: task.startTime ? "⏸" : "▶", - cls: "taskweaver-timer-btn" - }); - timerBtn.onclick = async () => { - await this.plugin.toggleTaskTimer(task.id); - }; + if (!task.startTime) { + const startBtn = taskControls.createEl("button", { + text: "Start", + cls: "taskweaver-start-btn" + }); + startBtn.onclick = async () => { + await this.plugin.toggleTaskTimer(task.id); + }; + } const deleteBtn = taskControls.createEl("button", { - text: "🗑", + text: "Delete", cls: "taskweaver-delete-btn" }); deleteBtn.onclick = async () => { await this.plugin.deleteTask(task.id); }; + } else { + const checkbox = taskControls.createEl("input", { type: "checkbox" }); + checkbox.checked = true; + checkbox.addClass("taskweaver-checkbox"); + checkbox.onclick = async () => { + await this.plugin.toggleTaskComplete(task.id); + }; } } updateTimers(): void { const container = this.containerEl.children[1] as HTMLElement; - const timerElements = container.querySelectorAll(".taskweaver-timer"); + const activeTimerDisplay = container.querySelector(".taskweaver-active-timer-display"); + if (activeTimerDisplay) { + const taskId = activeTimerDisplay.getAttribute("data-task-id"); + if (taskId) { + const task = this.plugin.settings.tasks.find(t => t.id === taskId); + if (task) { + activeTimerDisplay.setText(formatDuration(this.plugin.getTaskElapsed(task))); + } + } + } + + const timerElements = container.querySelectorAll(".taskweaver-timer-small"); timerElements.forEach((element) => { const taskId = element.getAttribute("data-task-id"); if (taskId) {