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:
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user