feat: integrate Milkdown editor for note-taking functionality

- Added Milkdown dependencies: @milkdown/kit, @milkdown/react, @milkdown/theme-nord.
- Implemented MilkdownEditor component for rich text editing in notes.
- Updated /notes/$noteId route to include editable title and auto-saving functionality.
- Enhanced UI with loading states, saving indicators, and delete confirmation dialog.
- Applied Milkdown-specific CSS overrides for consistent theming and styling.
- Improved note update logic with debounced saving and cleanup on unmount.
This commit is contained in:
Roberto Musso
2026-02-22 22:47:05 +01:00
parent 7860ca6ad1
commit 2308158976
9 changed files with 3016 additions and 34 deletions

View File

@@ -269,3 +269,29 @@
- `notes.create` returns `{ id }` which can be used directly for navigation in the `onSuccess` callback
- TanStack Router file-based routing: `notes.$noteId.tsx` generates `/notes/:noteId` route automatically — `Route.useParams()` provides typed `{ noteId }`
---
## 2026-02-22 - US-016
- What was implemented:
- Installed `@milkdown/kit`, `@milkdown/react`, `@milkdown/theme-nord` (following official Milkdown installation guide)
- Created `MilkdownEditor` wrapper component at `src/renderer/components/notes/MilkdownEditor.tsx`
- Uses official React recipe: `MilkdownProvider` + `useEditor` hook with `Editor.make()` configuring `commonmark`, `listener`, `history` plugins
- `listenerCtx.markdownUpdated()` fires onChange callback via stable `useRef` (avoids editor re-creation)
- `defaultValueCtx` sets initial markdown content from SQLite
- Rewrote `src/renderer/routes/notes.$noteId.tsx` with full editor page:
- Editable title: borderless shadcn/ui `Input` (border-0, shadow-none, focus-visible:ring-0), saves on blur via `notes.update({ id, title })`
- Auto-save: `onChange` from Milkdown triggers 500ms debounced `notes.update({ id, content })` via `useRef` + `setTimeout`/`clearTimeout`
- "Saving..." indicator: shadcn/ui `Badge` (variant=secondary) shown while debounce is pending, hidden on mutation `onSettled`
- Back button: shadcn/ui `Button` (variant=ghost, size=icon) with `ArrowLeft` Lucide icon, `window.history.back()`
- Loading/not-found states handled
- Added Milkdown/ProseMirror CSS overrides in `src/renderer/globals.css` using semantic color variables (`var(--foreground)`, `var(--muted)`, `var(--border)`, `var(--muted-foreground)`, `var(--primary)`)
- Typecheck passes (zero errors)
- Files changed: `src/renderer/components/notes/MilkdownEditor.tsx` (new), `src/renderer/routes/notes.$noteId.tsx`, `src/renderer/globals.css`, `package.json`, `package-lock.json`, `prd.json`, `progress.txt`
- **Learnings for future iterations:**
- `@milkdown/kit` is the recommended all-in-one package — it bundles core, preset-commonmark, plugin-listener, plugin-history, and utilities under sub-paths like `@milkdown/kit/core`, `@milkdown/kit/preset/commonmark`, etc.
- The `useEditor` hook from `@milkdown/react` takes `(root) => Editor.make()...` — the `root` param is the DOM element Milkdown manages, set via `ctx.set(rootCtx, root)`
- Use `useRef` for the onChange callback passed to `listenerCtx.markdownUpdated()` — this avoids re-creating the editor instance when the callback identity changes
- `listenerCtx.markdownUpdated((_ctx, markdown, prevMarkdown))` provides both current and previous markdown — compare them to avoid firing on no-op updates
- For debounced auto-save: `useRef<ReturnType<typeof setTimeout>>` + `clearTimeout`/`setTimeout` is simpler than external debounce libraries; cleanup in `useEffect` return prevents stale saves
- Nord theme (`@milkdown/theme-nord`) provides base ProseMirror structure; override with CSS using the app's semantic color variables for consistent theming
- Import both `@milkdown/theme-nord/style.css` and `@milkdown/kit/prose/view/style/prosemirror.css` for proper base styling
---