diff --git a/frontend/src/lib/components/CommentThread.svelte b/frontend/src/lib/components/CommentThread.svelte index 00ca5ab3..8725bd09 100644 --- a/frontend/src/lib/components/CommentThread.svelte +++ b/frontend/src/lib/components/CommentThread.svelte @@ -13,6 +13,7 @@ type Props = { initialComments?: Comment[]; loadOnMount?: boolean; canComment: boolean; + currentUserId: string | null; quotedText?: string | null; showCompose?: boolean; onCountChange?: (count: number) => void; @@ -25,6 +26,7 @@ let { initialComments = [], loadOnMount = false, canComment, + currentUserId = null, quotedText = null, showCompose = true, onCountChange @@ -44,6 +46,8 @@ let comments: Comment[] = $state(untrack(() => [...initialComments])); let newText: string = $state(''); let posting: boolean = $state(false); let newMentionCandidates: MentionDTO[] = $state([]); +let editingId: string | null = $state(null); +let editText: string = $state(''); const commentsBase = $derived( blockId @@ -78,6 +82,10 @@ function wasEdited(c: { createdAt: string; updatedAt: string }): boolean { return c.updatedAt > c.createdAt; } +function isOwn(c: { authorId: string | null }): boolean { + return currentUserId !== null && c.authorId === currentUserId; +} + function getInitials(name: string): string { return name .split(/\s+/) @@ -126,6 +134,60 @@ async function postComment() { } } +function startEdit(msg: FlatMessage) { + editingId = msg.id; + editText = msg.content; +} + +async function saveEdit(commentId: string) { + const text = editText.trim(); + if (!text || posting) return; + posting = true; + try { + const res = await fetch(`/api/documents/${documentId}/comments/${commentId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: text }) + }); + if (res.ok) { + editingId = null; + editText = ''; + await reload(); + } + } finally { + posting = false; + } +} + +function cancelEdit() { + editingId = null; + editText = ''; +} + +function handleEditKeydown(e: KeyboardEvent, commentId: string) { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + saveEdit(commentId); + } else if (e.key === 'Escape') { + cancelEdit(); + } +} + +async function deleteComment(commentId: string) { + if (posting) return; + posting = true; + try { + const res = await fetch(`/api/documents/${documentId}/comments/${commentId}`, { + method: 'DELETE' + }); + if (res.ok) { + await reload(); + } + } finally { + posting = false; + } +} + onMount(() => { if (loadOnMount) { reload(); @@ -181,10 +243,49 @@ onMount(() => { “{parsed.quote}” {/if} -

- - {@html renderBody(parsed.body, msg.mentionDTOs ?? [])} -

+ + {#if editingId === msg.id} + +
Enter speichern · Esc abbrechen
+ {:else} + + +
{ if (isOwn(msg)) startEdit(msg); }}> +

+ + {@html renderBody(parsed.body, msg.mentionDTOs ?? [])} +

+ {#if isOwn(msg)} + + {/if} +
+ {/if} {/each} diff --git a/frontend/src/lib/components/TranscriptionBlock.svelte b/frontend/src/lib/components/TranscriptionBlock.svelte index 55b068ad..582486ef 100644 --- a/frontend/src/lib/components/TranscriptionBlock.svelte +++ b/frontend/src/lib/components/TranscriptionBlock.svelte @@ -13,6 +13,7 @@ type Props = { active: boolean; saveState: SaveState; canComment: boolean; + currentUserId: string | null; onTextChange: (text: string) => void; onFocus: () => void; onDeleteClick: () => void; @@ -28,6 +29,7 @@ let { active, saveState, canComment, + currentUserId, onTextChange, onFocus, onDeleteClick, @@ -214,6 +216,7 @@ function handleTextareaMouseUp() { blockId={blockId} loadOnMount={true} canComment={canComment} + currentUserId={currentUserId} quotedText={selectedQuote} showCompose={commentOpen} onCountChange={(count) => (commentCount = count)} diff --git a/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts b/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts index ddae063f..5e2af323 100644 --- a/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts +++ b/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts @@ -15,6 +15,7 @@ function renderBlock(overrides: Record = {}) { active: false, saveState: 'idle' as const, canComment: true, + currentUserId: 'user-1', onTextChange: vi.fn(), onFocus: vi.fn(), onDeleteClick: vi.fn(), diff --git a/frontend/src/lib/components/TranscriptionEditView.svelte b/frontend/src/lib/components/TranscriptionEditView.svelte index cdcc4b67..4bb02688 100644 --- a/frontend/src/lib/components/TranscriptionEditView.svelte +++ b/frontend/src/lib/components/TranscriptionEditView.svelte @@ -10,12 +10,21 @@ type Props = { documentId: string; blocks: TranscriptionBlockData[]; canComment: boolean; + currentUserId: string | null; onBlockFocus: (blockId: string) => void; onSaveBlock: (blockId: string, text: string) => Promise; onDeleteBlock: (blockId: string) => Promise; }; -let { documentId, blocks, canComment, onBlockFocus, onSaveBlock, onDeleteBlock }: Props = $props(); +let { + documentId, + blocks, + canComment, + currentUserId, + onBlockFocus, + onSaveBlock, + onDeleteBlock +}: Props = $props(); let activeBlockId: string | null = $state(null); let saveStates = new SvelteMap(); @@ -156,6 +165,7 @@ $effect(() => { active={activeBlockId === block.id} saveState={getSaveState(block.id)} canComment={canComment} + currentUserId={currentUserId} onTextChange={(text) => handleTextChange(block.id, text)} onFocus={() => handleFocus(block.id)} onDeleteClick={() => handleDelete(block.id)} diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index 27dd403c..7864ce00 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -9,6 +9,7 @@ let { data } = $props(); const doc = $derived(data.document); const canWrite = $derived(data.canWrite ?? false); +const currentUserId = $derived((data.user?.id as string | undefined) ?? null); // ── File loading ────────────────────────────────────────────────────────────── @@ -217,6 +218,7 @@ onMount(() => { documentId={doc.id} blocks={transcriptionBlocks} canComment={canWrite} + currentUserId={currentUserId} onBlockFocus={handleBlockFocus} onSaveBlock={saveBlock} onDeleteBlock={deleteBlock}