feat(geschichten): wire discovery integrations on Person and Document pages
Person detail (/persons/[id]):
- Server load fetches GET /api/geschichten?status=PUBLISHED&personId={id}
in parallel with the existing person/document queries.
- Renders <GeschichtenCard> below the received-documents list when the
person has at least one published story.
Document detail (/documents/[id]):
- Server load adds the same parallel call with documentId={id}.
- DocumentTopBar gains geschichten + canBlogWrite props that flow through
to DocumentMetadataDrawer.
- DocumentMetadataDrawer's grid expands to lg:grid-cols-4 when the
Geschichten column should appear (stories exist OR user can author),
and shows "+ Geschichte anhängen" / "Alle anzeigen" links following the
>= 3-story threshold from issue comment #5758.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,12 @@ import RelationshipPill from '$lib/components/RelationshipPill.svelte';
|
||||
|
||||
type Person = { id: string; firstName?: string | null; lastName: string; displayName: string };
|
||||
type Tag = { id: string; name: string };
|
||||
type GeschichteSummary = {
|
||||
id: string;
|
||||
title: string;
|
||||
publishedAt?: string;
|
||||
author?: { firstName?: string; lastName?: string; email: string };
|
||||
};
|
||||
|
||||
type Props = {
|
||||
documentDate: string | null;
|
||||
@@ -16,6 +22,9 @@ type Props = {
|
||||
receivers: Person[];
|
||||
tags: Tag[];
|
||||
inferredRelationship?: { labelFromA: string; labelFromB: string } | null;
|
||||
geschichten?: GeschichteSummary[];
|
||||
documentId?: string;
|
||||
canBlogWrite?: boolean;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -25,10 +34,30 @@ let {
|
||||
sender,
|
||||
receivers,
|
||||
tags,
|
||||
inferredRelationship = null
|
||||
inferredRelationship = null,
|
||||
geschichten = [],
|
||||
documentId,
|
||||
canBlogWrite = false
|
||||
}: Props = $props();
|
||||
|
||||
const VISIBLE_RECEIVER_LIMIT = 5;
|
||||
const VISIBLE_GESCHICHTEN_LIMIT = 3;
|
||||
const showGeschichtenColumn = $derived(geschichten.length > 0 || canBlogWrite);
|
||||
const visibleGeschichten = $derived(geschichten.slice(0, VISIBLE_GESCHICHTEN_LIMIT));
|
||||
const hasGeschichtenOverflow = $derived(geschichten.length >= VISIBLE_GESCHICHTEN_LIMIT);
|
||||
const gridClass = $derived(showGeschichtenColumn ? 'lg:grid-cols-4' : 'lg:grid-cols-3');
|
||||
|
||||
function formatGeschichteAuthor(g: GeschichteSummary): string {
|
||||
const a = g.author;
|
||||
if (!a) return '';
|
||||
const full = [a.firstName, a.lastName].filter(Boolean).join(' ').trim();
|
||||
return full || a.email || '';
|
||||
}
|
||||
|
||||
function formatGeschichteDate(g: GeschichteSummary): string {
|
||||
if (!g.publishedAt) return '';
|
||||
return formatDate(g.publishedAt.slice(0, 10), 'short');
|
||||
}
|
||||
|
||||
const formattedDate = $derived(documentDate ? formatDate(documentDate) : '—');
|
||||
const displayLocation = $derived(location ?? '—');
|
||||
@@ -67,7 +96,7 @@ function getFullName(person: Person): string {
|
||||
{/snippet}
|
||||
|
||||
<div class="border-b border-line p-6">
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<div class="grid grid-cols-1 gap-6 {gridClass}">
|
||||
<!-- Column 1: Details -->
|
||||
<div>
|
||||
<h2 class="mb-4 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
@@ -159,5 +188,51 @@ function getFullName(person: Person): string {
|
||||
<p class="font-serif text-sm text-ink-3">{m.doc_details_no_tags()}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Column 4: Geschichten (visible when stories exist or user can author) -->
|
||||
{#if showGeschichtenColumn}
|
||||
<div>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h2 class="font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
{m.geschichten_card_heading()}
|
||||
</h2>
|
||||
{#if canBlogWrite && documentId}
|
||||
<a
|
||||
href="/geschichten/new?documentId={documentId}"
|
||||
class="font-sans text-xs font-medium text-ink/60 hover:text-ink"
|
||||
>
|
||||
{m.geschichten_card_attach_action()}
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if geschichten.length === 0}
|
||||
<p class="font-serif text-sm text-ink-3">—</p>
|
||||
{:else}
|
||||
<ul class="space-y-2 font-serif text-sm">
|
||||
{#each visibleGeschichten as g (g.id)}
|
||||
<li>
|
||||
<a href="/geschichten/{g.id}" class="block text-ink hover:underline">
|
||||
{g.title}
|
||||
</a>
|
||||
<p class="font-sans text-xs text-ink-3">
|
||||
{formatGeschichteAuthor(g)}
|
||||
{#if formatGeschichteDate(g)}· {formatGeschichteDate(g)}{/if}
|
||||
</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if hasGeschichtenOverflow && documentId}
|
||||
<a
|
||||
href="/geschichten?documentId={documentId}"
|
||||
class="mt-3 inline-flex font-sans text-xs font-medium text-ink hover:underline"
|
||||
>
|
||||
{m.geschichten_card_show_all()} →
|
||||
</a>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,12 +25,21 @@ type Doc = {
|
||||
tags?: Tag[] | null;
|
||||
};
|
||||
|
||||
type GeschichteSummary = {
|
||||
id: string;
|
||||
title: string;
|
||||
publishedAt?: string;
|
||||
author?: { firstName?: string; lastName?: string; email: string };
|
||||
};
|
||||
|
||||
type Props = {
|
||||
doc: Doc;
|
||||
canWrite: boolean;
|
||||
fileUrl: string;
|
||||
transcribeMode: boolean;
|
||||
inferredRelationship?: { labelFromA: string; labelFromB: string } | null;
|
||||
geschichten?: GeschichteSummary[];
|
||||
canBlogWrite?: boolean;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -38,7 +47,9 @@ let {
|
||||
canWrite,
|
||||
fileUrl,
|
||||
transcribeMode = $bindable(),
|
||||
inferredRelationship = null
|
||||
inferredRelationship = null,
|
||||
geschichten = [],
|
||||
canBlogWrite = false
|
||||
}: Props = $props();
|
||||
|
||||
let detailsOpen = $state(false);
|
||||
@@ -283,6 +294,9 @@ let mobileMenuOpen = $state(false);
|
||||
receivers={doc.receivers ? [...doc.receivers] : []}
|
||||
tags={doc.tags ? [...doc.tags] : []}
|
||||
inferredRelationship={inferredRelationship}
|
||||
geschichten={geschichten}
|
||||
documentId={doc.id}
|
||||
canBlogWrite={canBlogWrite}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user