Initial commit

This commit is contained in:
2026-02-19 16:35:20 -08:00
parent dc2fa22c4d
commit 2936f7d359
18 changed files with 2727 additions and 93 deletions

View File

@@ -0,0 +1,117 @@
import { ItemView, TFile, WorkspaceLeaf } from 'obsidian';
import type { TodoItem } from '../core/types';
import { parseTodos } from '../core/todo-parser';
import { toggleTodo } from '../core/todo-transformer';
import { createTodoItemEl } from './todo-item-component';
export const TODO_VIEW_TYPE = 'todo-tracker-view';
export class TodoSidebarView extends ItemView {
private currentFile: TFile | null = null;
constructor(leaf: WorkspaceLeaf) {
super(leaf);
}
getViewType(): string {
return TODO_VIEW_TYPE;
}
getDisplayText(): string {
return 'Todo Tracker';
}
getIcon(): string {
return 'check-square';
}
async onOpen(): Promise<void> {
await this.refresh();
// Listen for active leaf changes
this.registerEvent(
this.app.workspace.on('active-leaf-change', () => {
this.refresh();
})
);
// Listen for file modifications
this.registerEvent(
this.app.vault.on('modify', (file) => {
if (this.currentFile && file.path === this.currentFile.path) {
this.refresh();
}
})
);
}
async onClose(): Promise<void> {
// Cleanup is handled automatically by registerEvent
}
async refresh(): Promise<void> {
const activeFile = this.app.workspace.getActiveFile();
// Check if it's a markdown file
if (!activeFile || activeFile.extension !== 'md') {
this.currentFile = null;
this.renderEmpty('Open a markdown file to see its todos');
return;
}
this.currentFile = activeFile;
const content = await this.app.vault.read(activeFile);
const { todos } = parseTodos(content);
if (todos.length === 0) {
this.renderEmpty('No todos in this note');
return;
}
this.renderTodos(todos, activeFile);
}
private renderEmpty(message: string): void {
const container = this.contentEl;
container.empty();
container.addClass('todo-tracker-container');
const emptyEl = container.createDiv({ cls: 'todo-tracker-empty' });
emptyEl.setText(message);
}
private renderTodos(todos: TodoItem[], file: TFile): void {
const container = this.contentEl;
container.empty();
container.addClass('todo-tracker-container');
// Header with file name
const headerEl = container.createDiv({ cls: 'todo-tracker-header' });
headerEl.setText(file.basename);
// Todo list
const listEl = container.createEl('ul', { cls: 'todo-tracker-list' });
for (const todo of todos) {
const itemEl = createTodoItemEl(listEl, todo, {
onToggle: () => this.handleToggle(file, todo),
onMoveClick: () => this.handleMoveClick(todo, file),
});
listEl.appendChild(itemEl);
}
}
private async handleToggle(file: TFile, todo: TodoItem): Promise<void> {
await this.app.vault.process(file, (content) => {
return toggleTodo(content, todo.lineNumber);
});
}
private handleMoveClick(todo: TodoItem, file: TFile): void {
// Import dynamically to avoid circular dependencies
import('../modals/note-select-modal').then(({ NoteSelectModal }) => {
new NoteSelectModal(this.app, todo, file).open();
});
}
}