- parent_id on tasks; roots shown by default, others expandable - Expand/collapse per node with expand/collapse button - Sub button creates subtask directly from the row - Make subtask of button in edit dialog with searchable picker - Remove parent clears the parent link in the dialog - Flat mode toggle shows all tasks indented by depth - Tree filter shows nodes whose descendants match, not only direct matches - Deletion blocked if task has subtasks (client + server) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
44 lines
985 B
JavaScript
44 lines
985 B
JavaScript
import { mkdirSync } from 'node:fs';
|
|
import { Simple_KeyValue_Store } from './kv-store.mjs';
|
|
|
|
mkdirSync('./data', { recursive: true });
|
|
|
|
const store = new Simple_KeyValue_Store('./data/tasks.ndjson', {
|
|
auto_load: true,
|
|
auto_store: true,
|
|
debounce_flush_timeout: 5000,
|
|
});
|
|
|
|
// --- Tasks ---
|
|
|
|
export function list_tasks() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('task:')) {
|
|
result.push(store.get(key));
|
|
}
|
|
}
|
|
return result.sort((a, b) => b.created_at - a.created_at);
|
|
}
|
|
|
|
export function get_task(id) {
|
|
return store.get(`task:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_task(task) {
|
|
store.set(`task:${task.id}`, task);
|
|
}
|
|
|
|
export function delete_task(id) {
|
|
return store.delete(`task:${id}`);
|
|
}
|
|
|
|
export function has_child_tasks(parent_id) {
|
|
for (const [key] of store.data.entries()) {
|
|
if (!key.startsWith('task:')) { continue; }
|
|
const task = store.get(key);
|
|
if (task.parent_id === parent_id) { return true; }
|
|
}
|
|
return false;
|
|
}
|