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,19 +1,26 @@
import { error, redirect } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import { createApiClient } from '$lib/api.server';
import { getErrorMessage } from '$lib/errors';
export async function load({ params, fetch }) {
const { id } = params;
const api = createApiClient(fetch);
const base = env.API_INTERNAL_URL || 'http://localhost:8080';
const result = await api.GET('/api/documents/{id}', { params: { path: { id } } });
const [docResult, commentsRes] = await Promise.all([
api.GET('/api/documents/{id}', { params: { path: { id } } }),
fetch(`${base}/api/documents/${id}/comments`)
]);
if (result.response.status === 401) throw redirect(302, '/login');
if (docResult.response.status === 401) throw redirect(302, '/login');
if (!result.response.ok) {
const code = (result.error as unknown as { code?: string })?.code;
throw error(result.response.status, getErrorMessage(code));
if (!docResult.response.ok) {
const code = (docResult.error as unknown as { code?: string })?.code;
throw error(docResult.response.status, getErrorMessage(code));
}
return { document: result.data! };
const comments = commentsRes.ok ? await commentsRes.json() : [];
return { document: docResult.data!, comments };
}

View File

@@ -4,10 +4,18 @@ import { formatDate } from '$lib/utils/date';
import { diffWords } from 'diff';
import ExpandableText from '$lib/components/ExpandableText.svelte';
import PdfViewer from '$lib/components/PdfViewer.svelte';
import CommentThread from '$lib/components/CommentThread.svelte';
let { data } = $props();
const doc = $derived(data.document);
const canComment = $derived((data.canAnnotate || data.canWrite) ?? false);
const canAdmin = $derived(
(data.user?.groups as Array<{ permissions: string[] }> | undefined)?.some((g) =>
g.permissions.includes('ADMIN')
) ?? false
);
const currentUserId = $derived((data.user?.id as string | undefined) ?? null);
let fileUrl = $state('');
let isLoading = $state(false);
@@ -821,6 +829,24 @@ function versionLabel(v: VersionSummary, index: number): string {
{/if}
</div>
<!-- 5. DISKUSSION -->
<div>
<div class="border-b border-brand-sand pb-2">
<h3 class="font-sans text-xs font-bold tracking-widest text-brand-navy uppercase">
{m.comment_section_title()}
</h3>
</div>
<div class="mt-4">
<CommentThread
documentId={doc.id}
initialComments={data.comments ?? []}
canComment={canComment}
currentUserId={currentUserId}
canAdmin={canAdmin}
/>
</div>
</div>
<!-- Footer -->
<div class="border-t border-brand-sand pt-4 font-sans text-[10px] text-gray-400">
<p class="truncate">ID: {doc.id}</p>
@@ -875,7 +901,14 @@ function versionLabel(v: VersionSummary, index: number): string {
<p class="font-sans text-sm tracking-wide uppercase">{m.doc_no_scan()}</p>
</div>
{:else if fileUrl && doc.contentType?.startsWith('application/pdf')}
<PdfViewer url={fileUrl} documentId={doc.id} canAnnotate={data.canAnnotate} />
<PdfViewer
url={fileUrl}
documentId={doc.id}
canAnnotate={data.canAnnotate}
canComment={canComment}
currentUserId={currentUserId}
canAdmin={canAdmin}
/>
{:else if fileUrl}
<div class="flex h-full w-full items-center justify-center overflow-auto p-8">
<img