Initial commit

This commit is contained in:
2025-12-17 20:36:13 +01:00
parent d2485cec73
commit a265c31460
14 changed files with 1045 additions and 0 deletions

347
view.ts Normal file
View File

@@ -0,0 +1,347 @@
import { ItemView, WorkspaceLeaf, Modal, App, Setting } from "obsidian";
import TaskWeaverPlugin from "./main";
import { Task, Category } from "./types";
import { formatDuration, generateUniqueId } from "./utils";
export const VIEW_TYPE_TASKWEAVER = "taskweaver-view";
export class TaskWeaverView extends ItemView {
plugin: TaskWeaverPlugin;
private updateInterval: number | null = null;
constructor(leaf: WorkspaceLeaf, plugin: TaskWeaverPlugin) {
super(leaf);
this.plugin = plugin;
}
getViewType(): string {
return VIEW_TYPE_TASKWEAVER;
}
getDisplayText(): string {
return "TaskWeaver";
}
getIcon(): string {
return "clock";
}
async onOpen(): Promise<void> {
this.render();
this.updateInterval = window.setInterval(() => {
this.updateTimers();
}, 1000);
}
async onClose(): Promise<void> {
if (this.updateInterval !== null) {
window.clearInterval(this.updateInterval);
}
}
render(): void {
const container = this.containerEl.children[1] as HTMLElement;
container.empty();
container.addClass("taskweaver-container");
const headerDiv = container.createDiv({ cls: "taskweaver-header" });
headerDiv.createEl("h4", { text: "TaskWeaver" });
const buttonContainer = headerDiv.createDiv({ cls: "taskweaver-button-group" });
const addTaskBtn = buttonContainer.createEl("button", {
text: "Add Task",
cls: "taskweaver-btn"
});
addTaskBtn.onclick = () => this.openAddTaskModal();
const manageCategoriesBtn = buttonContainer.createEl("button", {
text: "Categories",
cls: "taskweaver-btn"
});
manageCategoriesBtn.onclick = () => this.openManageCategoriesModal();
const categoriesMap = new Map(this.plugin.settings.categories.map(c => [c.id, c]));
this.plugin.settings.categories.forEach(category => {
const categoryTasks = this.plugin.settings.tasks.filter(t => t.categoryId === category.id && !t.completed);
if (categoryTasks.length > 0) {
this.renderCategory(container, category, categoryTasks);
}
});
const completedTasks = this.plugin.settings.tasks.filter(t => t.completed);
if (completedTasks.length > 0) {
const completedSection = container.createDiv({ cls: "taskweaver-category" });
const completedHeader = completedSection.createDiv({ cls: "taskweaver-category-header" });
completedHeader.createEl("span", { text: "✓ Completed", cls: "taskweaver-category-name" });
completedTasks.forEach(task => {
const category = categoriesMap.get(task.categoryId);
this.renderTask(completedSection, task, category);
});
}
}
renderCategory(container: HTMLElement, category: Category, tasks: Task[]): void {
const categoryDiv = container.createDiv({ cls: "taskweaver-category" });
const categoryHeader = categoryDiv.createDiv({ cls: "taskweaver-category-header" });
categoryHeader.style.borderLeftColor = category.color;
const categoryName = categoryHeader.createSpan({ cls: "taskweaver-category-name" });
if (category.emoji) {
categoryName.createSpan({ text: `${category.emoji} `, cls: "taskweaver-emoji" });
}
categoryName.createSpan({ text: category.name });
tasks.forEach(task => {
this.renderTask(categoryDiv, task, category);
});
}
renderTask(container: HTMLElement, task: Task, category?: Category): void {
const taskDiv = container.createDiv({ cls: "taskweaver-task" });
const taskInfo = taskDiv.createDiv({ cls: "taskweaver-task-info" });
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 taskTitle = taskInfo.createSpan({
text: task.title,
cls: task.completed ? "taskweaver-task-title taskweaver-task-completed" : "taskweaver-task-title"
});
const taskControls = taskDiv.createDiv({ cls: "taskweaver-task-controls" });
const timerSpan = taskControls.createSpan({
cls: "taskweaver-timer",
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);
};
const deleteBtn = taskControls.createEl("button", {
text: "🗑",
cls: "taskweaver-delete-btn"
});
deleteBtn.onclick = async () => {
await this.plugin.deleteTask(task.id);
};
}
}
updateTimers(): void {
const container = this.containerEl.children[1] as HTMLElement;
const timerElements = container.querySelectorAll(".taskweaver-timer");
timerElements.forEach((element) => {
const taskId = element.getAttribute("data-task-id");
if (taskId) {
const task = this.plugin.settings.tasks.find(t => t.id === taskId);
if (task) {
element.setText(formatDuration(this.plugin.getTaskElapsed(task)));
}
}
});
}
openAddTaskModal(): void {
new AddTaskModal(this.app, this.plugin, (title, categoryId) => {
this.plugin.addTask(title, categoryId);
}).open();
}
openManageCategoriesModal(): void {
new ManageCategoriesModal(this.app, this.plugin).open();
}
}
class AddTaskModal extends Modal {
plugin: TaskWeaverPlugin;
onSubmit: (title: string, categoryId: string) => void;
constructor(app: App, plugin: TaskWeaverPlugin, onSubmit: (title: string, categoryId: string) => void) {
super(app);
this.plugin = plugin;
this.onSubmit = onSubmit;
}
onOpen(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl("h3", { text: "Add New Task" });
let taskTitle = "";
let selectedCategoryId = this.plugin.settings.categories[0]?.id || "";
new Setting(contentEl)
.setName("Task title")
.addText(text => text
.setPlaceholder("Enter task title")
.onChange(value => {
taskTitle = value;
}));
if (this.plugin.settings.categories.length === 0) {
contentEl.createEl("p", {
text: "Please create a category first",
cls: "taskweaver-warning"
});
new Setting(contentEl)
.addButton(btn => btn
.setButtonText("Create Category")
.setCta()
.onClick(() => {
this.close();
new ManageCategoriesModal(this.app, this.plugin).open();
}));
} else {
new Setting(contentEl)
.setName("Category")
.addDropdown(dropdown => {
this.plugin.settings.categories.forEach(cat => {
const label = cat.emoji ? `${cat.emoji} ${cat.name}` : cat.name;
dropdown.addOption(cat.id, label);
});
dropdown.setValue(selectedCategoryId);
dropdown.onChange(value => {
selectedCategoryId = value;
});
});
new Setting(contentEl)
.addButton(btn => btn
.setButtonText("Cancel")
.onClick(() => {
this.close();
}))
.addButton(btn => btn
.setButtonText("Add")
.setCta()
.onClick(() => {
if (taskTitle && selectedCategoryId) {
this.onSubmit(taskTitle, selectedCategoryId);
this.close();
}
}));
}
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}
class ManageCategoriesModal extends Modal {
plugin: TaskWeaverPlugin;
constructor(app: App, plugin: TaskWeaverPlugin) {
super(app);
this.plugin = plugin;
}
onOpen(): void {
this.render();
}
render(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl("h3", { text: "Manage Categories" });
const categoriesList = contentEl.createDiv({ cls: "taskweaver-categories-list" });
this.plugin.settings.categories.forEach(category => {
const categoryItem = categoriesList.createDiv({ cls: "taskweaver-category-item" });
const colorPreview = categoryItem.createDiv({ cls: "taskweaver-color-preview" });
colorPreview.style.backgroundColor = category.color;
const categoryInfo = categoryItem.createDiv({ cls: "taskweaver-category-info" });
if (category.emoji) {
categoryInfo.createSpan({ text: `${category.emoji} `, cls: "taskweaver-emoji" });
}
categoryInfo.createSpan({ text: category.name });
const deleteBtn = categoryItem.createEl("button", {
text: "Delete",
cls: "taskweaver-btn-small"
});
deleteBtn.onclick = async () => {
await this.plugin.deleteCategory(category.id);
this.render();
};
});
contentEl.createEl("h4", { text: "Add New Category" });
let categoryName = "";
let categoryColor = "#4a9eff";
let categoryEmoji = "";
new Setting(contentEl)
.setName("Category name")
.addText(text => text
.setPlaceholder("e.g., Work, Personal")
.onChange(value => {
categoryName = value;
}));
new Setting(contentEl)
.setName("Color")
.addText(text => text
.setValue(categoryColor)
.setPlaceholder("#4a9eff")
.onChange(value => {
categoryColor = value;
}));
new Setting(contentEl)
.setName("Emoji (optional)")
.addText(text => text
.setPlaceholder("💼")
.onChange(value => {
categoryEmoji = value;
}));
new Setting(contentEl)
.addButton(btn => btn
.setButtonText("Close")
.onClick(() => {
this.close();
}))
.addButton(btn => btn
.setButtonText("Add Category")
.setCta()
.onClick(async () => {
if (categoryName) {
await this.plugin.addCategory(categoryName, categoryColor, categoryEmoji);
this.render();
}
}));
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}