diff --git a/frontend/src/lib/components/CommentThread.svelte b/frontend/src/lib/components/CommentThread.svelte
index b741c632..65eff1c4 100644
--- a/frontend/src/lib/components/CommentThread.svelte
+++ b/frontend/src/lib/components/CommentThread.svelte
@@ -9,24 +9,28 @@ import type { MentionDTO } from '$lib/types';
type Props = {
documentId: string;
annotationId?: string | null;
+ blockId?: string | null;
initialComments?: Comment[];
loadOnMount?: boolean;
canComment: boolean;
currentUserId: string | null;
canAdmin: boolean;
targetCommentId?: string | null;
+ quotedText?: string | null;
onCountChange?: (count: number) => void;
};
let {
documentId,
annotationId = null,
+ blockId = null,
initialComments = [],
loadOnMount = false,
canComment,
currentUserId,
canAdmin,
targetCommentId = null,
+ quotedText = null,
onCountChange
}: Props = $props();
@@ -43,11 +47,20 @@ let replyMentionCandidates: MentionDTO[] = $state([]);
let editMentionCandidates: MentionDTO[] = $state([]);
const commentsBase = $derived(
- annotationId
- ? `/api/documents/${documentId}/annotations/${annotationId}/comments`
- : `/api/documents/${documentId}/comments`
+ blockId
+ ? `/api/documents/${documentId}/transcription-blocks/${blockId}/comments`
+ : annotationId
+ ? `/api/documents/${documentId}/annotations/${annotationId}/comments`
+ : `/api/documents/${documentId}/comments`
);
+// Pre-fill comment box with quoted text when selection changes
+$effect(() => {
+ if (quotedText && quotedText.trim()) {
+ newText = `> "${quotedText}"\n\n`;
+ }
+});
+
function timeAgo(iso: string): string {
const diff = Date.now() - new Date(iso).getTime();
const minutes = Math.floor(diff / 60000);
diff --git a/frontend/src/lib/components/TranscriptionBlock.svelte b/frontend/src/lib/components/TranscriptionBlock.svelte
index 0b07068f..0b370561 100644
--- a/frontend/src/lib/components/TranscriptionBlock.svelte
+++ b/frontend/src/lib/components/TranscriptionBlock.svelte
@@ -1,15 +1,19 @@
-
+
+
+
+ {#if active}
+
+ {m.transcription_block_quote_hint()}
+
+ {/if}
+
+
{#if saveState === 'saving'}
@@ -143,5 +185,35 @@ function handleDelete() {
+
+
+ {#if commentOpen}
+
+
+
+ {m.comment_section_title()}
+
+
+
+
+
+ {/if}
diff --git a/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts b/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts
index e3158023..7489ed52 100644
--- a/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts
+++ b/frontend/src/lib/components/TranscriptionBlock.svelte.spec.ts
@@ -8,11 +8,14 @@ afterEach(cleanup);
function renderBlock(overrides: Record = {}) {
return render(TranscriptionBlock, {
blockId: 'block-1',
+ documentId: 'doc-1',
blockNumber: 3,
text: 'Liebe Mutter,',
label: null,
active: false,
saveState: 'idle' as const,
+ canComment: true,
+ currentUserId: 'user-1',
onTextChange: vi.fn(),
onFocus: vi.fn(),
onDeleteClick: vi.fn(),
@@ -111,4 +114,21 @@ describe('TranscriptionBlock — interactions', () => {
await textarea.click();
expect(onFocus).toHaveBeenCalled();
});
+
+ it('shows Kommentieren button that opens comment thread', async () => {
+ renderBlock();
+ const btn = page.getByText('Kommentieren');
+ await expect.element(btn).toBeInTheDocument();
+ });
+
+ it('shows quote hint when block is active', async () => {
+ renderBlock({ active: true });
+ await expect.element(page.getByText('Text markieren für Zitat')).toBeInTheDocument();
+ });
+
+ it('hides quote hint when block is not active', async () => {
+ renderBlock({ active: false });
+ const hint = page.getByText('Text markieren für Zitat');
+ await expect.element(hint).not.toBeInTheDocument();
+ });
});
diff --git a/frontend/src/lib/components/TranscriptionEditView.svelte b/frontend/src/lib/components/TranscriptionEditView.svelte
index e30d10f5..65fba8f0 100644
--- a/frontend/src/lib/components/TranscriptionEditView.svelte
+++ b/frontend/src/lib/components/TranscriptionEditView.svelte
@@ -9,12 +9,22 @@ type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error';
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, 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();
@@ -149,11 +159,14 @@ $effect(() => {
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 ff05d7f4..2b8d4aaf 100644
--- a/frontend/src/routes/documents/[id]/+page.svelte
+++ b/frontend/src/routes/documents/[id]/+page.svelte
@@ -234,6 +234,8 @@ onMount(() => {