diff --git a/frontend/messages/de.json b/frontend/messages/de.json
index 78d24422..8d8a86c9 100644
--- a/frontend/messages/de.json
+++ b/frontend/messages/de.json
@@ -242,5 +242,13 @@
"admin_system_backfill_btn": "Jetzt auffüllen",
"admin_system_backfill_success": "{count} Dokumente wurden aufgefüllt.",
"comp_expandable_show_more": "Mehr anzeigen",
- "comp_expandable_show_less": "Weniger anzeigen"
+ "comp_expandable_show_less": "Weniger anzeigen",
+ "error_comment_not_found": "Der Kommentar wurde nicht gefunden.",
+ "comment_section_title": "Diskussion",
+ "comment_placeholder": "Kommentar schreiben…",
+ "comment_btn_post": "Senden",
+ "comment_btn_reply": "Antworten",
+ "comment_edited_label": "· bearbeitet",
+ "comment_panel_title": "Kommentare",
+ "comment_panel_close": "Schließen"
}
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index cb9f5abf..8b9fbdf5 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -242,5 +242,13 @@
"admin_system_backfill_btn": "Backfill now",
"admin_system_backfill_success": "{count} documents were backfilled.",
"comp_expandable_show_more": "Show more",
- "comp_expandable_show_less": "Show less"
+ "comp_expandable_show_less": "Show less",
+ "error_comment_not_found": "The comment could not be found.",
+ "comment_section_title": "Discussion",
+ "comment_placeholder": "Write a comment…",
+ "comment_btn_post": "Send",
+ "comment_btn_reply": "Reply",
+ "comment_edited_label": "· edited",
+ "comment_panel_title": "Comments",
+ "comment_panel_close": "Close"
}
diff --git a/frontend/messages/es.json b/frontend/messages/es.json
index 1285aacc..01d37915 100644
--- a/frontend/messages/es.json
+++ b/frontend/messages/es.json
@@ -242,5 +242,13 @@
"admin_system_backfill_btn": "Completar ahora",
"admin_system_backfill_success": "{count} documentos fueron completados.",
"comp_expandable_show_more": "Mostrar más",
- "comp_expandable_show_less": "Mostrar menos"
+ "comp_expandable_show_less": "Mostrar menos",
+ "error_comment_not_found": "El comentario no pudo encontrarse.",
+ "comment_section_title": "Discusión",
+ "comment_placeholder": "Escribe un comentario…",
+ "comment_btn_post": "Enviar",
+ "comment_btn_reply": "Responder",
+ "comment_edited_label": "· editado",
+ "comment_panel_title": "Comentarios",
+ "comment_panel_close": "Cerrar"
}
diff --git a/frontend/src/lib/components/AnnotationCommentPanel.svelte b/frontend/src/lib/components/AnnotationCommentPanel.svelte
new file mode 100644
index 00000000..b1bb59fe
--- /dev/null
+++ b/frontend/src/lib/components/AnnotationCommentPanel.svelte
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+ {m.comment_panel_title()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {m.comment_panel_title()}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/AnnotationLayer.svelte b/frontend/src/lib/components/AnnotationLayer.svelte
index 3a8cfae6..0321514d 100644
--- a/frontend/src/lib/components/AnnotationLayer.svelte
+++ b/frontend/src/lib/components/AnnotationLayer.svelte
@@ -23,13 +23,17 @@ let {
canAnnotate,
color,
onDraw,
- onDelete
+ onDelete,
+ commentCounts,
+ onAnnotationClick
}: {
annotations: Annotation[];
canAnnotate: boolean;
color: string;
onDraw: (rect: { x: number; y: number; width: number; height: number }) => void;
onDelete: (id: string) => void;
+ commentCounts?: Record;
+ onAnnotationClick?: (id: string) => void;
} = $props();
let drawStart = $state<{ x: number; y: number } | null>(null);
@@ -112,6 +116,10 @@ const containerStyle = $derived(
onAnnotationClick?.(annotation.id)}
+ onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') onAnnotationClick?.(annotation.id); }}
style="
position: absolute;
left: {annotation.x * 100}%;
@@ -119,7 +127,8 @@ const containerStyle = $derived(
width: {annotation.width * 100}%;
height: {annotation.height * 100}%;
background-color: {hexToRgba(annotation.color, 0.3)};
- pointer-events: {canAnnotate ? 'auto' : 'none'};
+ pointer-events: auto;
+ {onAnnotationClick && !canAnnotate ? 'cursor: pointer;' : ''}
"
>
{#if canAnnotate}
@@ -150,6 +159,29 @@ const containerStyle = $derived(
">×
{/if}
+ {#if (commentCounts?.[annotation.id] ?? 0) > 0}
+
+ {commentCounts?.[annotation.id]}
+
+ {/if}
{/each}
diff --git a/frontend/src/lib/components/CommentThread.svelte b/frontend/src/lib/components/CommentThread.svelte
new file mode 100644
index 00000000..0e3afd7a
--- /dev/null
+++ b/frontend/src/lib/components/CommentThread.svelte
@@ -0,0 +1,394 @@
+
+
+
+ {#each comments as thread, ti (thread.id)}
+
0 ? 'border-t border-brand-sand pt-4' : ''}>
+
+
+ {#if editingId === thread.id}
+
+
+
+
+
+
+
+ {:else}
+
+
+
+ {thread.authorName}
+ {timeAgo(thread.createdAt)}
+ {#if wasEdited(thread)}
+
+ {m.comment_edited_label()}
+ {timeAgo(thread.updatedAt)}
+
+ {/if}
+
+
{thread.content}
+
+ {#if canModify(thread)}
+
+
+
+
+ {/if}
+
+
+ {#if thread.replies.length === 0 && canComment}
+
+
+
+ {/if}
+ {/if}
+
+
+
+ {#each thread.replies as reply, ri (reply.id)}
+
+ {#if editingId === reply.id}
+
+
+
+
+
+
+
+ {:else}
+
+
+
+ {reply.authorName}
+ {timeAgo(reply.createdAt)}
+ {#if wasEdited(reply)}
+
+ {m.comment_edited_label()}
+ {timeAgo(reply.updatedAt)}
+
+ {/if}
+
+
{reply.content}
+
+ {#if canModify(reply)}
+
+
+
+
+ {/if}
+
+
+ {#if ri === thread.replies.length - 1 && canComment}
+
+
+
+ {/if}
+ {/if}
+
+ {/each}
+
+
+ {#if replyingTo === thread.id}
+
+
+
+
+
+
+
+ {/if}
+
+ {/each}
+
+
+ {#if canComment}
+
+ {/if}
+
diff --git a/frontend/src/lib/components/PdfViewer.svelte b/frontend/src/lib/components/PdfViewer.svelte
index 7863143c..fc27684d 100644
--- a/frontend/src/lib/components/PdfViewer.svelte
+++ b/frontend/src/lib/components/PdfViewer.svelte
@@ -1,16 +1,24 @@