From 1070e6e9ec2915f7aa463692b97ebd1cef42384a Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 24 Mar 2026 11:02:38 +0100 Subject: [PATCH] feat(comments): add CommentThread, annotation panel, Diskussion section, and i18n keys Co-Authored-By: Claude Sonnet 4.6 --- frontend/messages/de.json | 10 +- frontend/messages/en.json | 10 +- frontend/messages/es.json | 10 +- .../components/AnnotationCommentPanel.svelte | 90 ++++ .../src/lib/components/AnnotationLayer.svelte | 36 +- .../src/lib/components/CommentThread.svelte | 394 ++++++++++++++++++ frontend/src/lib/components/PdfViewer.svelte | 50 ++- frontend/src/lib/errors.ts | 3 + .../src/routes/documents/[id]/+page.server.ts | 19 +- .../src/routes/documents/[id]/+page.svelte | 35 +- 10 files changed, 643 insertions(+), 14 deletions(-) create mode 100644 frontend/src/lib/components/AnnotationCommentPanel.svelte create mode 100644 frontend/src/lib/components/CommentThread.svelte 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()} +

+ +
+
+ +
+
+
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} +
0 ? 'border-t border-brand-sand pt-4' : ''}> +
+ +
+ +
+
+
+ {/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 @@