diff --git a/src/components/NotesApp.tsx b/src/components/NotesApp.tsx index deee07b..4523a94 100644 --- a/src/components/NotesApp.tsx +++ b/src/components/NotesApp.tsx @@ -80,35 +80,40 @@ export function NotesApp() { }; return ( -
-
-
- +
+
+
+ setQuery(e.target.value)} - placeholder="Titel, Tags oder Notiztext …" + placeholder="Ohne Prefix: Titel & Tags · tag:x · title:y · content:z" autoComplete="off" + className="h-9" />
-
-
- - -
-
@@ -117,7 +122,7 @@ export function NotesApp() { {error ?

{error}

: null} {loading ?

Lade Snippets …

: null} -
+
{!loading && snippets.length === 0 ? (

Keine Snippets. Mit „Neu“ starten.

) : null} diff --git a/src/components/SnippetCard.tsx b/src/components/SnippetCard.tsx index 271ab9c..abbfc71 100644 --- a/src/components/SnippetCard.tsx +++ b/src/components/SnippetCard.tsx @@ -1,5 +1,5 @@ import { marked } from "marked"; -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import type { Snippet } from "@/types/snippet"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter } from "@/components/ui/card"; @@ -8,6 +8,12 @@ import { Textarea } from "@/components/ui/textarea"; marked.setOptions({ gfm: true }); +/** API `null` / fehlender Wert → leerer String, damit kontrollierte Inputs nie `null` anzeigen. */ +function stringFieldForForm(v: string | null | undefined): string { + if (v == null) return ""; + return typeof v === "string" ? v : ""; +} + function tagsFromString(s: string): string[] { return s .split(",") @@ -30,23 +36,36 @@ type Props = { }; export function SnippetCard({ snippet, onReplace, onRemove }: Props) { - const pristineEmpty = !snippet.body && !snippet.title && snippet.tags.length === 0; + const pristineEmpty = + !stringFieldForForm(snippet.body) && (snippet.title == null || snippet.title.trim() === "") && (snippet.tags?.length ?? 0) === 0; const [editing, setEditing] = useState(pristineEmpty); + const [showPreview, setShowPreview] = useState(false); const [dirty, setDirty] = useState(false); - const [body, setBody] = useState(snippet.body); - const [title, setTitle] = useState(snippet.title ?? ""); - const [tagsStr, setTagsStr] = useState(snippet.tags.join(", ")); + const [body, setBody] = useState(() => stringFieldForForm(snippet.body)); + const [title, setTitle] = useState(() => stringFieldForForm(snippet.title)); + const [tagsStr, setTagsStr] = useState(() => (snippet.tags ?? []).join(", ")); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); - const html = useMemo(() => marked.parse(editing ? body : snippet.body) as string, [editing, body, snippet.body]); + const html = useMemo( + () => marked.parse(editing ? body : stringFieldForForm(snippet.body)) as string, + [editing, body, snippet.body], + ); + + useEffect(() => { + if (editing) return; + setBody(stringFieldForForm(snippet.body)); + setTitle(stringFieldForForm(snippet.title)); + setTagsStr((snippet.tags ?? []).join(", ")); + }, [editing, snippet.id, snippet.updated_at, snippet.body, snippet.title, snippet.tags]); const startEdit = () => { setEditing(true); + setShowPreview(false); setDirty(false); - setBody(snippet.body); - setTitle(snippet.title ?? ""); - setTagsStr(snippet.tags.join(", ")); + setBody(stringFieldForForm(snippet.body)); + setTitle(stringFieldForForm(snippet.title)); + setTagsStr((snippet.tags ?? []).join(", ")); setError(null); }; @@ -103,45 +122,76 @@ export function SnippetCard({ snippet, onReplace, onRemove }: Props) { } setEditing(false); setDirty(false); - setBody(snippet.body); - setTitle(snippet.title ?? ""); - setTagsStr(snippet.tags.join(", ")); + setBody(stringFieldForForm(snippet.body)); + setTitle(stringFieldForForm(snippet.title)); + setTagsStr((snippet.tags ?? []).join(", ")); setError(null); }; const markDirty = () => setDirty(true); + const mdBodyClass = + "snippet-md max-h-[min(55vh,420px)] overflow-auto text-sm leading-relaxed [&_a]:text-primary [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-muted-foreground/40 [&_blockquote]:pl-3 [&_blockquote]:italic [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_h1]:mb-2 [&_h1]:text-xl [&_h1]:font-semibold [&_h2]:mb-2 [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:mb-1 [&_h3]:text-base [&_h3]:font-semibold [&_li]:ml-4 [&_ol]:list-decimal [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_ul]:list-disc"; + return ( - - -
+ + +
{editing ? ( -
-