From 61fa2d39dcacbae7f6edba7560407524d7cfb5a6 Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Wed, 11 Feb 2026 06:30:17 -0500 Subject: [PATCH] separate command logic, add view context --- source/app.tsx | 58 +++++++++++++++++---------------- source/commands.ts | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 source/commands.ts diff --git a/source/app.tsx b/source/app.tsx index e670a27..441fa67 100644 --- a/source/app.tsx +++ b/source/app.tsx @@ -2,6 +2,7 @@ import React, {useState, useEffect, useCallback} from 'react'; import {Box, Text, useApp} from 'ink'; import TextInput from 'ink-text-input'; import {TodoistApi, type Task} from '@doist/todoist-api-typescript'; +import {commands} from './commands.js'; const token = process.env['TODOIST_API_TOKEN']; if (!token) { @@ -15,15 +16,25 @@ export default function App() { const {exit} = useApp(); const [tasks, setTasks] = useState([]); const [projects, setProjects] = useState>(new Map()); + + const [view, setView] = useState< + {type: 'filter'; query: string} | {type: 'project'; projectId: string} + >({ + type: 'filter', + query: 'today', + }); + const [loading, setLoading] = useState(true); const [input, setInput] = useState(''); const [message, setMessage] = useState(''); - const fetchTasks = useCallback(async () => { + const refresh = useCallback(async () => { setLoading(true); const [taskResponse, projectResponse] = await Promise.all([ - api.getTasksByFilter({query: 'today'}), + view.type === 'filter' + ? api.getTasksByFilter({query: view.query}) + : api.getTasks({projectId: view.projectId}), api.getProjects(), ]); @@ -34,39 +45,32 @@ export default function App() { setProjects(projectMap); setTasks(taskResponse.results); - setLoading(false); - }, []); + }, [view]); useEffect(() => { - fetchTasks(); - }, [fetchTasks]); + refresh(); + }, [refresh]); const handleSubmit = async (value: string) => { const trimmed = value.trim(); setInput(''); - if (trimmed.startsWith('done ')) { - // "done 2" → complete task #2 - const num = Number.parseInt(trimmed.slice(5), 10); - const task = tasks[num - 1]; // arrays are 0-indexed, display is 1-indexed - if (task) { - await api.closeTask(task.id); - setMessage(`Completed: ${task.content}`); - await fetchTasks(); // refresh the list - } else { - setMessage(`No task #${num}`); - } - } else if (trimmed.startsWith('add ')) { - const text = trimmed.slice(4); - await api.quickAddTask({text}); - setMessage(`Added: ${text}`); - await fetchTasks(); - } else if (trimmed === 'refresh') { - await fetchTasks(); - setMessage('Refreshed'); - } else if (trimmed === 'quit') { - exit(); + const ctx = { + api, + tasks, + projects, + setMessage, + refresh, + setView, + exit, + }; + + const command = commands.find(c => trimmed.startsWith(c.prefix)); + + if (command) { + const args = trimmed.slice(command.prefix.length); + await command.run(args, ctx); } else { setMessage(`Unknown command: ${trimmed}`); } diff --git a/source/commands.ts b/source/commands.ts new file mode 100644 index 0000000..a094fcd --- /dev/null +++ b/source/commands.ts @@ -0,0 +1,81 @@ +import {type TodoistApi, type Task} from '@doist/todoist-api-typescript'; + +export type CommandContext = { + api: TodoistApi; + tasks: Task[]; + projects: Map; + setMessage: (msg: string) => void; + + refresh: () => Promise; + setView: ( + view: + | {type: 'filter'; query: string} + | {type: 'project'; projectId: string}, + ) => void; + + exit: () => void; +}; +type Command = { + prefix: string; + run: (args: string, ctx: CommandContext) => Promise; +}; + +export const commands: Command[] = [ + { + prefix: 'done ', + run: async (args, {api, tasks, setMessage, refresh}) => { + const num = Number.parseInt(args, 10); + const task = tasks[num - 1]; + if (task) { + await api.closeTask(task.id); + setMessage(`Completed: ${task.content}`); + await refresh(); + } else { + setMessage(`No task #${num}`); + } + }, + }, + { + prefix: 'add ', + run: async (args, {api, setMessage, refresh}) => { + await api.quickAddTask({text: args}); + setMessage(`Added: ${args}`); + await refresh(); + }, + }, + { + prefix: 'refresh', + run: async (_args, {refresh, setMessage}) => { + await refresh(); + setMessage('Refreshed'); + }, + }, + { + prefix: 'project ', + run: async (args, {projects, setMessage, setView}) => { + const projectId = [...projects.entries()].find( + ([, n]) => n.toLowerCase() === args.toLowerCase(), + )?.[0]; + if (projectId) { + setView({type: 'project', projectId}); + setMessage(`Viewing project: ${args}`); + } else { + setMessage(`Project not found: ${args}`); + } + }, + }, + + { + prefix: 'today', + run: async (_args, {setView, setMessage}) => { + setView({type: 'filter', query: 'today'}); + setMessage('Viewing today'); + }, + }, + { + prefix: 'quit', + run: async (_args, {exit}) => { + exit(); + }, + }, +]; -- 2.51.2