Jacob Schmidt 54bef33f0b Refactor frontend state to store-first architecture
- add and expand feature stores for entries, fragments, todos, lists, settings

- move CRUD logic into store helpers and simplify component state handling

- update SidePanel + button to create section-specific items

- switch fragment UX to view-first with edit/create modes

- add and update docs for store-based state management

- remove deprecated account route
2026-02-25 20:52:46 -06:00

100 lines
3.4 KiB
TypeScript

import { writable } from "svelte/store";
export type TodoItem = { id: number; text: string; done: boolean };
export type TodoListMeta = { id: string; label: string };
const initialTodoListMeta: TodoListMeta[] = [
{ id: "todos/today", label: "Today" },
{ id: "todos/scheduled", label: "Scheduled" },
{ id: "todos/completed", label: "Completed" }
];
export const todoListsStore = writable<TodoListMeta[]>(initialTodoListMeta);
export const todosStore = writable<Record<string, TodoItem[]>>({
"todos/today": [
{ id: 1, text: "Finalize journal sidebar interactions", done: false },
{ id: 2, text: "Review fragment taxonomy updates", done: false },
{ id: 3, text: "Capture daily notes summary", done: false }
],
"todos/scheduled": [
{ id: 4, text: "Tuesday: sync settings to backend store", done: false },
{ id: 5, text: "Thursday: polish editor keyboard shortcuts", done: false },
{ id: 6, text: "Friday: QA fragment and todo workflows", done: false }
],
"todos/completed": [
{ id: 7, text: "Replaced navbar profile with settings shortcut", done: true },
{ id: 8, text: "Centered modal presentation", done: true },
{ id: 9, text: "Added fragment creation form mode", done: true }
]
});
export function serializeTodoList(title: string, todos: TodoItem[]): string {
const heading = title?.trim() ? `# ${title}` : "# To-Do List";
const lines = todos.map((todo) => `- [${todo.done ? "x" : " "}] ${todo.text}`);
return `${heading}\n\n${lines.join("\n")}`;
}
export function createTodoId(): number {
return Date.now() + Math.floor(Math.random() * 1000);
}
export function parseTodoList(content: string): TodoItem[] {
const lines = content.replace(/\r\n/g, "\n").split("\n");
const parsed: TodoItem[] = [];
for (const line of lines) {
const match = line.match(/^- \[( |x)\]\s+(.+)$/i);
if (!match) continue;
parsed.push({
id: createTodoId(),
text: match[2].trim(),
done: match[1].toLowerCase() === "x"
});
}
return parsed;
}
export function getOrCreateTodoList(
lists: Record<string, TodoItem[]>,
documentId: string,
fallbackContent: string
): { lists: Record<string, TodoItem[]>; todos: TodoItem[] } {
const existing = lists[documentId];
if (existing) {
return { lists, todos: existing };
}
const parsed = parseTodoList(fallbackContent);
return { lists: { ...lists, [documentId]: parsed }, todos: parsed };
}
export function setTodoList(
lists: Record<string, TodoItem[]>,
documentId: string,
todos: TodoItem[]
): Record<string, TodoItem[]> {
return { ...lists, [documentId]: todos };
}
export function addTodoItem(todos: TodoItem[], text: string): TodoItem[] {
return [{ id: createTodoId(), text: text.trim(), done: false }, ...todos];
}
export function toggleTodoItem(todos: TodoItem[], id: number): TodoItem[] {
return todos.map((todo) => (todo.id === id ? { ...todo, done: !todo.done } : todo));
}
export function updateTodoItemText(todos: TodoItem[], id: number, text: string): TodoItem[] {
return todos.map((todo) => (todo.id === id ? { ...todo, text: text.trim() } : todo));
}
export function removeTodoItem(todos: TodoItem[], id: number): TodoItem[] {
return todos.filter((todo) => todo.id !== id);
}
export function createTodoListDraft(): { meta: TodoListMeta; items: TodoItem[] } {
const id = `todos/list-${Date.now()}`;
return {
meta: { id, label: "New List" },
items: []
};
}