diff --git a/bun.lock b/bun.lock index b04f809..d091cc9 100644 --- a/bun.lock +++ b/bun.lock @@ -103,6 +103,7 @@ "@codemirror/autocomplete": "^6.18.6", "@codemirror/commands": "^6.8.1", "@codemirror/language": "^6.11.1", + "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.37.2", "@lezer/generator": "^1.7.3", @@ -121,10 +122,9 @@ }, }, "packages/werewolf-ui": { - "name": "werewolfUI", + "name": "@workshop/werewolf-ui", "version": "0.1.0", "dependencies": { - "bun-plugin-tailwind": "^0.0.15", "hono": "^4.8.3", "lucide-static": "^0.525.0", "tailwindcss": "^4.0.6", @@ -146,6 +146,8 @@ "@codemirror/language": ["@codemirror/language@6.11.1", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ=="], + "@codemirror/search": ["@codemirror/search@6.5.11", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "crelt": "^1.0.5" } }, "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA=="], + "@codemirror/state": ["@codemirror/state@6.5.2", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA=="], "@codemirror/view": ["@codemirror/view@6.37.2", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw=="], @@ -218,6 +220,8 @@ "@workshop/todo": ["@workshop/todo@workspace:packages/todo"], + "@workshop/werewolf-ui": ["@workshop/werewolf-ui@workspace:packages/werewolf-ui"], + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], "ajv": ["ajv@6.10.0", "", { "dependencies": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg=="], @@ -730,8 +734,6 @@ "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], - "werewolfUI": ["werewolfUI@workspace:packages/werewolf-ui"], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -780,6 +782,14 @@ "@workshop/nano-remix/hono": ["hono@4.8.4", "", {}, "sha512-KOIBp1+iUs0HrKztM4EHiB2UtzZDTBihDtOF5K6+WaJjCPeaW4Q92R8j63jOhvJI5+tZSMuKD9REVEXXY9illg=="], + "@workshop/todo/@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + + "@workshop/todo/hono": ["hono@4.8.4", "", {}, "sha512-KOIBp1+iUs0HrKztM4EHiB2UtzZDTBihDtOF5K6+WaJjCPeaW4Q92R8j63jOhvJI5+tZSMuKD9REVEXXY9illg=="], + + "@workshop/werewolf-ui/@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + + "@workshop/werewolf-ui/hono": ["hono@4.8.4", "", {}, "sha512-KOIBp1+iUs0HrKztM4EHiB2UtzZDTBihDtOF5K6+WaJjCPeaW4Q92R8j63jOhvJI5+tZSMuKD9REVEXXY9illg=="], + "ajv/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="], "amqplib/readable-stream": ["readable-stream@1.1.14", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ=="], @@ -836,10 +846,6 @@ "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "werewolfUI/@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], - - "werewolfUI/hono": ["hono@4.8.4", "", {}, "sha512-KOIBp1+iUs0HrKztM4EHiB2UtzZDTBihDtOF5K6+WaJjCPeaW4Q92R8j63jOhvJI5+tZSMuKD9REVEXXY9illg=="], - "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -890,6 +896,10 @@ "@workshop/nano-remix/@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + "@workshop/todo/@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + + "@workshop/werewolf-ui/@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + "amqplib/readable-stream/inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "amqplib/readable-stream/string_decoder": ["string_decoder@0.10.31", "", {}, "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="], @@ -904,8 +914,6 @@ "morgan/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "werewolfUI/@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], - "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "@modelcontextprotocol/sdk/express/body-parser/bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], diff --git a/main.ts b/main.ts index ed15b90..33a3e26 100644 --- a/main.ts +++ b/main.ts @@ -27,7 +27,6 @@ try { await Promise.all([run(["bun", "run", "--filter=@workshop/http", "start"]), run(["bun", "bot:discord"])]) console.log("✅ All processes completed successfully") } catch (error) { - console.log(`🌭`, error.message) console.error("❌ One or more processes failed:", error) process.exit(1) } diff --git a/packages/nano-remix/src/buildRoute.ts b/packages/nano-remix/src/buildRoute.ts index 6d7f598..0ff12cd 100644 --- a/packages/nano-remix/src/buildRoute.ts +++ b/packages/nano-remix/src/buildRoute.ts @@ -9,12 +9,9 @@ type BuildRouteOptions = { export const buildRoute = async ({ distDir, routeName, filepath, force = false }: BuildRouteOptions) => { if (!force && !(await shouldRebuild(routeName, filepath, distDir))) { - console.log(`🌭 Skipping build for ${routeName} - up to date`) return } - console.log(`🌭 Building route ${routeName} from ${filepath}`) - const scriptPath = join(import.meta.dirname, "../scripts/build.ts") const proc = Bun.spawn({ diff --git a/packages/todo/package.json b/packages/todo/package.json index e1ff7bf..c374974 100644 --- a/packages/todo/package.json +++ b/packages/todo/package.json @@ -12,6 +12,7 @@ "@codemirror/autocomplete": "^6.18.6", "@codemirror/commands": "^6.8.1", "@codemirror/language": "^6.11.1", + "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.37.2", "@lezer/generator": "^1.7.3", diff --git a/packages/todo/src/autoTodoOnNewline.ts b/packages/todo/src/autoTodoOnNewline.ts index 0e750cb..aa6f528 100644 --- a/packages/todo/src/autoTodoOnNewline.ts +++ b/packages/todo/src/autoTodoOnNewline.ts @@ -44,7 +44,7 @@ const handleNewlineChange = (opts: HandleNewlineChangeOpts) => { userEvent: "delete", }) } else { - const prefix = `- [ ] ` + const prefix = `[ ] ` opts.update.view.dispatch({ changes: { from: insertPos, to: insertPos, insert: prefix }, selection: { anchor: insertPos + prefix.length }, diff --git a/packages/todo/src/todo.css b/packages/todo/src/todo.css index ff9c18c..bf79d12 100644 --- a/packages/todo/src/todo.css +++ b/packages/todo/src/todo.css @@ -4,11 +4,14 @@ transform-origin: center center; } -.todo-completed { - text-decoration: line-through !important; +.todo-completed * { + text-decoration: line-through; color: black !important; opacity: 0.5; +} +.todo-completed .todo-indent { + text-decoration: none #10B981 !important; } @keyframes todoCompleting { diff --git a/packages/todo/src/todo.test.ts b/packages/todo/src/todo.test.ts index f7e725b..ea08ed3 100644 --- a/packages/todo/src/todo.test.ts +++ b/packages/todo/src/todo.test.ts @@ -3,26 +3,26 @@ import { Todo } from "@/todo" import { test, expect } from "bun:test" test("parsing valid todos", () => { - expectTodo("- [ ] some words and that is it", { done: false, text: "some words and that is it" }) - expectTodo("- [x] has #some #tags and @2/5/25 5m", { + expectTodo("[ ] some words and that is it", { done: false, text: "some words and that is it" }) + expectTodo("[x] has #some #tags and @2/5/25 5m", { done: true, text: "has #some #tags and @2/5/25 5m", tags: ["some", "tags"], timeEstimate: 300, dueDate: DateTime.fromISO("2025-02-05"), }) - expectTodo("- [ ] works with iso dates @2055/01/2", { + expectTodo("[ ] works with iso dates @2055/01/2", { text: "works with iso dates @2055/01/2", dueDate: DateTime.fromISO("2055-01-02"), }) - expectTodo(" - [ ] handles nested todos", { text: "handles nested todos", indent: " " }) + expectTodo(" [ ] handles nested todos", { text: "handles nested todos", indent: " " }) }) test("todo nodes", () => { - const todo = Todo.parse(" - [ ] some #words and @2/2/25") + const todo = Todo.parse(" [ ] some #words and @2/2/25") expect(todo!.nodes).toEqual([ { type: "indent", content: " " }, - { type: "checkbox", content: "- [ ] ", checked: false }, + { type: "checkbox", content: "[ ] ", checked: false }, { type: "text", content: "some" }, { type: "whitespace", content: " " }, { type: "tag", content: "#words" }, @@ -34,18 +34,18 @@ test("todo nodes", () => { }) test("parsing invalid todos", () => { - expectInvalidTodo("- [ ]") - expectInvalidTodo(" -[x") - expectInvalidTodo("-[omg] some text") + expectInvalidTodo("[ ") + expectInvalidTodo(" -[]") + expectInvalidTodo("-[] some text") }) // Helpers test("todo to string", () => { - const todo = Todo.parse("- [ ] some #words and @2/2/25") - expect(todo!.toString()).toBe("- [ ] some #words and @2/2/25") + const todo = Todo.parse("[ ] some #words and @2/2/25") + expect(todo!.toString()).toBe("[ ] some #words and @2/2/25") - const nestedTodo = Todo.parse(" - [x] more #words and @2/2/25") - expect(nestedTodo!.toString()).toBe(" - [x] more #words and @2/2/25") + const nestedTodo = Todo.parse(" [x] more #words and @2/2/25") + expect(nestedTodo!.toString()).toBe(" [x] more #words and @2/2/25") }) const expectTodo = (input: string, expectation: Partial) => { diff --git a/packages/todo/src/todo.ts b/packages/todo/src/todo.ts index 907acf7..92efc07 100644 --- a/packages/todo/src/todo.ts +++ b/packages/todo/src/todo.ts @@ -8,7 +8,7 @@ export type TodoJSON = { children: TodoJSON[] } -export const checkboxRegex = /^\s*-\s*\[\s*(\w?)\s*\]\s*/ +export const checkboxRegex = /^\s*\[\s*(\w?)\s*\]\s*/ export class Todo { done: boolean @@ -28,7 +28,7 @@ export class Todo { } toString() { - let string = `${this.indent}- [${this.done ? "x" : " "}] ${this.text}` + let string = `${this.indent}[${this.done ? "x" : " "}] ${this.text}` this.children.forEach((child) => { string += "\n" + child.toString() }) diff --git a/packages/todo/src/todoEditor.tsx b/packages/todo/src/todoEditor.tsx index b73ecb7..60bd895 100644 --- a/packages/todo/src/todoEditor.tsx +++ b/packages/todo/src/todoEditor.tsx @@ -10,6 +10,7 @@ import { todoClickHandler } from "@/todoClickHandler" import { dateAutocompletion } from "@/dateAutocompletion" import { todoTimer } from "@/todoTimer" import { DateTime } from "luxon" +import { searchKeymap } from "@codemirror/search" import "./todo.css" @@ -44,6 +45,7 @@ const createEditorExtensions = ( keymap.of(historyKeymap), keymap.of(defaultKeymap), keymap.of(foldKeymap), + keymap.of(searchKeymap), ] } @@ -120,7 +122,7 @@ export const TodoEditor = ({ defaultValue, onChange }: TodoEditorProps) => { } const KeyboardShortcuts = ({ onClose }: { onClose: () => void }) => { - const shortcuts = buildKeyBindings(undefined as any).filter((k) => !k.hidden) + const shortcuts = buildKeyBindings(undefined as any).filter((k) => k.label) return (
diff --git a/packages/todo/src/todoKeymap.ts b/packages/todo/src/todoKeymap.ts index e332a54..5d27bdf 100644 --- a/packages/todo/src/todoKeymap.ts +++ b/packages/todo/src/todoKeymap.ts @@ -5,8 +5,9 @@ import { type RefObject } from "hono/jsx" import { parseTodoList, todoListToString } from "@/todoList" import { todoTimer } from "./todoTimer" import { triggerTodoCompletionEffect } from "./todoCompletion" +import { selectNextOccurrence } from "@codemirror/search" -export type Command = KeyBinding & { label: string; key: string; hidden?: boolean } +export type Command = KeyBinding & { label?: string; key: string } export const buildKeyBindings = ( filterElRef: RefObject, @@ -79,9 +80,7 @@ export const buildKeyBindings = ( }, }, { - label: "Stop the todo timer", key: "Escape", - hidden: true, run: (view: EditorView) => { const timerPlugin = view.plugin(todoTimer) if (timerPlugin?.currentWidget) { @@ -100,13 +99,11 @@ export const buildKeyBindings = ( }, }, { - label: "Indent the todo item", key: "Tab", preventDefault: true, run: indentMore, }, { - label: "Unindent the todo item", key: "Shift-Tab", preventDefault: true, run: indentLess, diff --git a/packages/todo/src/todoList.test.ts b/packages/todo/src/todoList.test.ts index 35b7b04..20eee53 100644 --- a/packages/todo/src/todoList.test.ts +++ b/packages/todo/src/todoList.test.ts @@ -30,13 +30,13 @@ test("todoListToString", () => { const result = todoListToString(todoList) const expected = `# Today -- [ ] a task with a #tag -- [x] a #tag and date @2/2/25 in a task +[ ] a task with a #tag +[x] a #tag and date @2/2/25 in a task # Tomorrow -- [ ] another task - - [ ] a subtask - - [x] completed -- [ ] a task with a @1/2/25 date +[ ] another task + [ ] a subtask + [x] completed +[ ] a task with a @1/2/25 date ` expect(result).toEqual(expected) @@ -44,15 +44,15 @@ test("todoListToString", () => { test("parseTodoList", () => { const todoListString = `# Today -- [ ] a task with a #tag -- [x] a #tag and date @2/2/25 in a task +[ ] a task with a #tag +[x] a #tag and date @2/2/25 in a task # Tomorrow -- [ ] another task - - [ ] a subtask - - [x] completed +[ ] another task + [ ] a subtask + [x] completed -- [ ] a task with a @1/2/25 date` +[ ] a task with a @1/2/25 date` const result = parseTodoList(todoListString) diff --git a/packages/todo/src/todoList.ts b/packages/todo/src/todoList.ts index 9168710..fc1bee8 100644 --- a/packages/todo/src/todoList.ts +++ b/packages/todo/src/todoList.ts @@ -66,7 +66,7 @@ export const todoListToString = (todoList: TodoList): string => { } const todoJsonToString = (todoOrString: Todo): string => { - let result = `${todoOrString.indent}- [${todoOrString.done ? "x" : " "}] ${todoOrString.text}\n` + let result = `${todoOrString.indent}[${todoOrString.done ? "x" : " "}] ${todoOrString.text}\n` todoOrString.children.forEach((child) => { result += todoJsonToString(child)