feat(comments): add CommentThread, annotation panel, Diskussion section, and i18n keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-24 11:02:38 +01:00
parent 3e5d296b09
commit 1070e6e9ec
10 changed files with 643 additions and 14 deletions

View File

@@ -1,16 +1,24 @@
<script lang="ts">
import { onMount } from 'svelte';
import { SvelteMap } from 'svelte/reactivity';
import type { PDFDocumentProxy, PDFPageProxy, RenderTask } from 'pdfjs-dist';
import AnnotationLayer from './AnnotationLayer.svelte';
import AnnotationCommentPanel from './AnnotationCommentPanel.svelte';
let {
url,
documentId = '',
canAnnotate = false
canAnnotate = false,
canComment,
currentUserId,
canAdmin
}: {
url: string;
documentId?: string;
canAnnotate?: boolean;
canComment?: boolean;
currentUserId?: string | null;
canAdmin?: boolean;
} = $props();
let pdfDoc = $state<PDFDocumentProxy | null>(null);
@@ -48,6 +56,8 @@ type Annotation = {
let annotations = $state<Annotation[]>([]);
let annotateMode = $state(false);
let annotateColor = $state('#ffff00');
let commentCounts = new SvelteMap<string, number>();
let activeAnnotationId = $state<string | null>(null);
onMount(async () => {
// Dynamic import keeps pdfjs out of the SSR bundle entirely
@@ -159,11 +169,31 @@ async function prerender(doc: PDFDocumentProxy, pageNum: number) {
}
}
async function loadCommentCounts(docId: string, anns: Annotation[]) {
await Promise.all(
anns.map(async (a) => {
try {
const res = await fetch(`/api/documents/${docId}/annotations/${a.id}/comments`);
if (res.ok) {
const threads = (await res.json()) as Array<{ replies: unknown[] }>;
const total = threads.reduce((sum, t) => sum + 1 + t.replies.length, 0);
commentCounts.set(a.id, total);
}
} catch {
// ignore
}
})
);
}
async function loadAnnotations(docId: string) {
if (!docId) return;
try {
const res = await fetch(`/api/documents/${docId}/annotations`);
if (res.ok) annotations = await res.json();
if (res.ok) {
annotations = await res.json();
await loadCommentCounts(docId, annotations);
}
} catch {
// ignore
}
@@ -413,10 +443,26 @@ function zoomOut() {
color={annotateColor}
onDraw={handleAnnotationDraw}
onDelete={handleAnnotationDelete}
commentCounts={Object.fromEntries(commentCounts)}
onAnnotationClick={(id) => (activeAnnotationId = id)}
/>
</div>
</div>
{/if}
</div>
{#if activeAnnotationId}
<AnnotationCommentPanel
documentId={documentId}
annotationId={activeAnnotationId}
canComment={canComment ?? false}
currentUserId={currentUserId ?? null}
canAdmin={canAdmin ?? false}
onClose={() => (activeAnnotationId = null)}
onCountChange={(count) => {
if (activeAnnotationId) commentCounts.set(activeAnnotationId, count);
}}
/>
{/if}
</div>
{/if}