Replaces the left sidebar layout with: - Full-viewport PDF/image viewer (never resizes, position: absolute) - Fixed floating bottom panel with tabs: Metadaten, Transkription, Diskussion, Verlauf - Compact top bar with title, date · sender → receivers row, and Annotieren / Edit / Download actions - Drag-to-resize panel with localStorage persistence of open/height/tab - Panel opens automatically to Diskussion when an annotation is clicked - Documents without a file default to showing the Metadaten tab New components: DocumentTopBar, DocumentViewer, DocumentBottomPanel, PanelMetadata, PanelTranscription, PanelDiscussion, PanelHistory PdfViewer: annotateMode and activeAnnotationId lifted to bindable props; AnnotationCommentPanel removed (discussion moves to the Diskussion tab). Closes #62 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
143 lines
4.0 KiB
Svelte
143 lines
4.0 KiB
Svelte
<script lang="ts">
|
|
import { m } from '$lib/paraglide/messages.js';
|
|
|
|
type Person = { id: string; firstName: string; lastName: string };
|
|
|
|
type Doc = {
|
|
id: string;
|
|
title?: string | null;
|
|
originalFilename?: string | null;
|
|
documentDate?: string | null;
|
|
sender?: Person | null;
|
|
receivers?: Person[] | null;
|
|
filePath?: string | null;
|
|
contentType?: string | null;
|
|
};
|
|
|
|
type Props = {
|
|
doc: Doc;
|
|
canWrite: boolean;
|
|
canAnnotate: boolean;
|
|
fileUrl: string;
|
|
annotateMode: boolean;
|
|
};
|
|
|
|
let { doc, canWrite, canAnnotate, fileUrl, annotateMode = $bindable() }: Props = $props();
|
|
|
|
const isPdf = $derived(!!doc.filePath && doc.contentType?.startsWith('application/pdf'));
|
|
|
|
const receiverDisplay = $derived.by(() => {
|
|
const receivers = doc.receivers ?? [];
|
|
if (receivers.length === 0) return null;
|
|
const shown = receivers.slice(0, 2);
|
|
const extra = receivers.length - shown.length;
|
|
const names = shown.map((r) => `${r.firstName} ${r.lastName}`).join(', ');
|
|
return extra > 0 ? `${names} +${extra}` : names;
|
|
});
|
|
|
|
const compactMeta = $derived.by(() => {
|
|
const parts: string[] = [];
|
|
if (doc.documentDate) {
|
|
parts.push(
|
|
new Intl.DateTimeFormat('de-DE', {
|
|
day: 'numeric',
|
|
month: 'numeric',
|
|
year: 'numeric'
|
|
}).format(new Date(doc.documentDate + 'T12:00:00'))
|
|
);
|
|
}
|
|
if (doc.sender) {
|
|
const senderName = `${doc.sender.firstName} ${doc.sender.lastName}`;
|
|
const receiver = receiverDisplay;
|
|
parts.push(receiver ? `${senderName} → ${receiver}` : senderName);
|
|
} else if (receiverDisplay) {
|
|
parts.push(`→ ${receiverDisplay}`);
|
|
}
|
|
return parts.join(' · ');
|
|
});
|
|
</script>
|
|
|
|
<div
|
|
class="z-20 flex shrink-0 items-center justify-between border-b border-brand-sand bg-white px-6 py-3 shadow-sm"
|
|
>
|
|
<!-- Left: back + title -->
|
|
<div class="flex min-w-0 items-center gap-4 overflow-hidden">
|
|
<a
|
|
href="/"
|
|
class="group flex shrink-0 items-center gap-2 font-sans text-sm font-medium text-gray-500 transition-colors hover:text-brand-navy"
|
|
>
|
|
<div
|
|
class="flex h-8 w-8 items-center justify-center rounded-full bg-brand-sand transition-colors group-hover:bg-brand-mint"
|
|
>
|
|
<img
|
|
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Left-MD.svg"
|
|
alt=""
|
|
aria-hidden="true"
|
|
class="h-4 w-4"
|
|
/>
|
|
</div>
|
|
<span class="hidden sm:inline">{m.btn_back()}</span>
|
|
</a>
|
|
|
|
<div class="min-w-0 border-l border-gray-200 pl-4">
|
|
<h1
|
|
class="truncate font-serif text-base leading-tight text-brand-navy"
|
|
title={doc.title ?? doc.originalFilename ?? ''}
|
|
>
|
|
{doc.title || doc.originalFilename}
|
|
</h1>
|
|
{#if compactMeta}
|
|
<p class="truncate font-sans text-xs text-gray-500" title={compactMeta}>
|
|
{compactMeta}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right: actions -->
|
|
<div class="ml-4 flex shrink-0 items-center gap-2 font-sans">
|
|
{#if canAnnotate && isPdf}
|
|
<button
|
|
onclick={() => (annotateMode = !annotateMode)}
|
|
aria-label={annotateMode ? m.doc_panel_annotate_stop() : m.doc_panel_annotate()}
|
|
class="rounded px-3 py-1.5 font-sans text-xs font-medium transition {annotateMode
|
|
? 'bg-brand-navy text-white'
|
|
: 'border border-brand-navy text-brand-navy hover:bg-brand-navy hover:text-white'}"
|
|
>
|
|
{annotateMode ? m.doc_panel_annotate_stop() : m.doc_panel_annotate()}
|
|
</button>
|
|
{/if}
|
|
|
|
{#if canWrite}
|
|
<a
|
|
href="/documents/{doc.id}/edit"
|
|
class="flex items-center gap-2 rounded border border-brand-navy bg-transparent px-3 py-1.5 text-xs font-medium text-brand-navy transition hover:bg-brand-navy hover:text-white"
|
|
>
|
|
<img
|
|
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Edit-Content-MD.svg"
|
|
alt=""
|
|
aria-hidden="true"
|
|
class="h-4 w-4"
|
|
/>
|
|
{m.btn_edit()}
|
|
</a>
|
|
{/if}
|
|
|
|
{#if doc.filePath}
|
|
<a
|
|
href={fileUrl}
|
|
download={doc.originalFilename}
|
|
class="rounded border border-transparent bg-brand-sand/50 p-1.5 text-brand-navy transition hover:bg-brand-mint"
|
|
title={m.doc_download_title()}
|
|
>
|
|
<img
|
|
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Download-MD.svg"
|
|
alt=""
|
|
aria-hidden="true"
|
|
class="h-5 w-5"
|
|
/>
|
|
</a>
|
|
{/if}
|
|
</div>
|
|
</div>
|