feat(geschichte-detail): avatar metabar + doc reference cards per spec R-2/LR-2
Detail header gains the author avatar with a two-line author block; journeys say 'zusammengestellt am' instead of 'veröffentlicht am'. Bearbeiten/Löschen move into the metabar for stories too (were at the article bottom). StoryReader renders real document reference cards (icon, title, date · von X an Y) instead of a placeholder link, person chips get avatar initials, and journey items lose the doubled spacing. Shared formatDocumentMetaLine() in geschichte/utils feeds both readers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import { safeHtml } from '$lib/shared/utils/sanitize';
|
||||
import { getInitials, personAvatarColor } from '$lib/person/personFormat';
|
||||
import { formatDocumentMetaLine } from './utils';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type GeschichteView = components['schemas']['GeschichteView'];
|
||||
|
||||
interface Props {
|
||||
geschichte: GeschichteView;
|
||||
canBlogWrite: boolean;
|
||||
ondelete?: () => Promise<void>;
|
||||
}
|
||||
|
||||
let { geschichte: g, canBlogWrite, ondelete }: Props = $props();
|
||||
let { geschichte: g }: Props = $props();
|
||||
|
||||
const sanitized = $derived(safeHtml(g.body));
|
||||
|
||||
const documentItems = $derived(g.items.filter((i) => i.document));
|
||||
|
||||
function personName(p: { firstName?: string; lastName?: string }): string {
|
||||
return [p.firstName, p.lastName].filter(Boolean).join(' ').trim();
|
||||
}
|
||||
@@ -47,8 +49,15 @@ function personName(p: { firstName?: string; lastName?: string }): string {
|
||||
<a
|
||||
href="/persons/{p.id}"
|
||||
style="display: inline-flex; min-height: 44px"
|
||||
class="inline-flex min-h-[44px] items-center rounded-full bg-muted px-3 py-2.5 font-sans text-sm text-ink hover:bg-accent-bg focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
class="inline-flex min-h-[44px] items-center gap-2 rounded-full border border-line bg-surface px-3 py-1.5 font-sans text-sm font-medium text-ink hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full font-sans text-[8px] font-bold text-white"
|
||||
style="background-color: {personAvatarColor(p.id)}"
|
||||
>
|
||||
{getInitials(personName(p))}
|
||||
</span>
|
||||
{personName(p)}
|
||||
</a>
|
||||
</li>
|
||||
@@ -58,19 +67,50 @@ function personName(p: { firstName?: string; lastName?: string }): string {
|
||||
{/if}
|
||||
|
||||
<!-- Dokumente (JourneyItems) -->
|
||||
{#if g.items && g.items.some((i) => i.document)}
|
||||
{#if documentItems.length > 0}
|
||||
<section class="mt-8 border-t border-line pt-6">
|
||||
<h2 class="mb-3 font-sans text-xs font-bold tracking-widest text-ink-2 uppercase">
|
||||
{m.geschichten_documents_section()}
|
||||
</h2>
|
||||
<ul class="flex flex-col gap-2">
|
||||
{#each g.items.filter((i) => i.document) as item (item.id)}
|
||||
{#each documentItems as item (item.id)}
|
||||
<li>
|
||||
<a
|
||||
href="/documents/{item.document!.id}"
|
||||
class="block rounded border border-line bg-surface px-4 py-3 font-serif text-base text-ink hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
class="flex items-start gap-3 rounded-sm border border-line bg-surface p-3 transition-shadow hover:shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
>
|
||||
{m.geschichten_document_link_placeholder()}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="flex h-9 w-9 shrink-0 items-center justify-center rounded bg-muted"
|
||||
>
|
||||
<svg class="h-4 w-4 text-ink-3" viewBox="0 0 10 12" fill="none">
|
||||
<rect
|
||||
x="1"
|
||||
y="1"
|
||||
width="8"
|
||||
height="10"
|
||||
rx="1"
|
||||
stroke="currentColor"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<path
|
||||
d="M3 4h4M3 6.5h4M3 9h2"
|
||||
stroke="currentColor"
|
||||
stroke-width=".8"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block font-sans text-sm leading-snug font-semibold text-ink">
|
||||
{item.document!.title}
|
||||
</span>
|
||||
{#if formatDocumentMetaLine(item.document!)}
|
||||
<span class="block font-sans text-xs text-ink-3">
|
||||
{formatDocumentMetaLine(item.document!)}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
</a>
|
||||
{#if item.note}
|
||||
<!-- plaintext — do NOT use {@html} here -->
|
||||
@@ -81,22 +121,3 @@ function personName(p: { firstName?: string; lastName?: string }): string {
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Author actions -->
|
||||
{#if canBlogWrite}
|
||||
<div class="mt-10 flex items-center gap-3 border-t border-line pt-6">
|
||||
<a
|
||||
href="/geschichten/{g.id}/edit"
|
||||
class="inline-flex h-11 items-center rounded border border-line bg-surface px-4 font-sans text-sm font-medium text-ink hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
>
|
||||
{m.btn_edit()}
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ondelete?.()}
|
||||
class="inline-flex h-11 items-center rounded font-sans text-sm font-medium text-danger hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
>
|
||||
{m.btn_delete()}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user