diff --git a/src/app/App.tsx b/src/app/App.tsx index e0dae9c..60e2f89 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,9 +1,11 @@ import { Header } from "@/components/Header"; +import { Toaster } from "@/components/Toaster"; import { QueryProvider } from "@/providers/QueryProvider"; import { BrowserRouter, Route, Routes } from "react-router"; import { AddVault } from "./routes/AddVault"; import { Home } from "./routes/Home"; import { NoteEditor } from "./routes/NoteEditor"; +import { NoteNew } from "./routes/NoteNew"; import { NoteView } from "./routes/NoteView"; import { Notes } from "./routes/Notes"; import { OAuthCallback } from "./routes/OAuthCallback"; @@ -14,11 +16,13 @@ export function App() {
+
} /> } /> + } /> } /> } /> } /> diff --git a/src/app/routes/NoteEditor.tsx b/src/app/routes/NoteEditor.tsx index 863205f..f89ecc7 100644 --- a/src/app/routes/NoteEditor.tsx +++ b/src/app/routes/NoteEditor.tsx @@ -1,5 +1,7 @@ import { CodeMirrorEditor } from "@/components/CodeMirrorEditor"; +import { DeleteNoteButton } from "@/components/DeleteNoteButton"; import { MarkdownView, buildWikilinkResolver } from "@/components/MarkdownView"; +import { TagEditor, normalizeTag } from "@/components/TagEditor"; import { relativeTime } from "@/lib/time"; import { useNote, useUpdateNote, useVaultStore } from "@/lib/vault"; import { type UpdateNotePayload, VaultAuthError, VaultConflictError } from "@/lib/vault/client"; @@ -136,7 +138,7 @@ function EditorSurface({ note }: { note: Note }) { const pathChanged = draft.path !== baseline.path; const addTag = (raw: string) => { - const t = raw.trim().replace(/^#/, ""); + const t = normalizeTag(raw); if (!t) return; if (draft.tags.includes(t)) return; setDraft((d) => ({ ...d, tags: [...d.tags, t] })); @@ -169,6 +171,8 @@ function EditorSurface({ note }: { note: Note }) { )}
+ + - ))} - onInputChange(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === ",") { - e.preventDefault(); - onAdd(input); - } else if (e.key === "Backspace" && input === "" && tags.length > 0) { - onRemove(tags[tags.length - 1]!); - } - }} - onBlur={() => { - if (input.trim()) onAdd(input); - }} - placeholder="add tag…" - className="min-w-24 flex-1 rounded-md border border-transparent bg-transparent px-1 py-0.5 text-xs text-fg focus:border-border focus:outline-none" - aria-label="Add tag" - /> -
- ); -} - function ConflictBanner({ conflict, onReload, diff --git a/src/app/routes/NoteNew.test.tsx b/src/app/routes/NoteNew.test.tsx new file mode 100644 index 0000000..5eee759 --- /dev/null +++ b/src/app/routes/NoteNew.test.tsx @@ -0,0 +1,196 @@ +import { NoteNew } from "@/app/routes/NoteNew"; +import { useToastStore } from "@/lib/toast/store"; +import { useVaultStore } from "@/lib/vault/store"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { MemoryRouter, Route, Routes } from "react-router"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@/components/CodeMirrorEditor", () => ({ + CodeMirrorEditor: ({ + value, + onChange, + }: { value: string; onChange(next: string): void; onSave?(): void; onCancel?(): void }) => ( +