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:
@@ -3,6 +3,7 @@ import { goto } from '$app/navigation';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import { formatDate } from '$lib/shared/utils/date';
|
||||
import { formatAuthorDisplayName } from '$lib/geschichte/utils';
|
||||
import { getInitials, personAvatarColor } from '$lib/person/personFormat';
|
||||
import { getConfirmService } from '$lib/shared/services/confirm.svelte';
|
||||
import { csrfFetch } from '$lib/shared/cookies';
|
||||
import { parseBackendError, getErrorMessage } from '$lib/shared/errors';
|
||||
@@ -61,15 +62,34 @@ async function handleDelete() {
|
||||
{m.journey_badge_detail()}
|
||||
</span>
|
||||
{/if}
|
||||
<h1 id="geschichte-title" class="mb-4 font-serif text-3xl leading-tight font-bold text-ink">
|
||||
<h1 id="geschichte-title" class="mb-4 font-serif text-3xl leading-tight text-ink">
|
||||
{g.title}
|
||||
</h1>
|
||||
<div class="border-subtle mb-4 flex items-center gap-3 border-b pb-4">
|
||||
<p class="font-sans text-sm text-ink-3">
|
||||
{authorName}
|
||||
{#if publishedAt}· {m.geschichten_published_on({ date: publishedAt })}{/if}
|
||||
</p>
|
||||
{#if isJourney && data.canBlogWrite}
|
||||
<div class="mb-4 flex items-center gap-3 border-b border-line-2 pb-4">
|
||||
{#if authorName}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full font-sans text-xs font-bold text-white"
|
||||
style="background-color: {personAvatarColor(g.author?.id ?? authorName)}"
|
||||
>
|
||||
{getInitials(authorName)}
|
||||
</span>
|
||||
{/if}
|
||||
<div>
|
||||
{#if authorName}
|
||||
<p class="font-sans text-sm leading-tight font-semibold text-ink">{authorName}</p>
|
||||
{/if}
|
||||
{#if publishedAt}
|
||||
<p class="font-sans text-xs text-ink-3">
|
||||
{#if isJourney}
|
||||
{m.journey_compiled_on({ date: publishedAt })}
|
||||
{:else}
|
||||
{m.geschichten_published_on({ date: publishedAt })}
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if data.canBlogWrite}
|
||||
<div class="ml-auto flex items-center gap-3">
|
||||
<a
|
||||
href="/geschichten/{g.id}/edit"
|
||||
@@ -101,7 +121,7 @@ async function handleDelete() {
|
||||
{#if isJourney}
|
||||
<JourneyReader geschichte={g} />
|
||||
{:else}
|
||||
<StoryReader geschichte={g} canBlogWrite={data.canBlogWrite} ondelete={handleDelete} />
|
||||
<StoryReader geschichte={g} />
|
||||
{/if}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,9 @@ const baseGeschichte = (overrides: Partial<GeschichteView> = {}): GeschichteView
|
||||
});
|
||||
|
||||
const baseData = (overrides: Record<string, unknown> = {}) => ({
|
||||
user: undefined,
|
||||
canWrite: false,
|
||||
canAnnotate: false,
|
||||
geschichte: baseGeschichte(),
|
||||
canBlogWrite: false,
|
||||
...overrides
|
||||
@@ -176,8 +179,28 @@ describe('geschichten/[id] page', () => {
|
||||
});
|
||||
|
||||
await expect.element(page.getByText('Erwähnte Dokumente')).toBeVisible();
|
||||
await expect.element(page.getByText('Dokument öffnen')).toBeVisible();
|
||||
await expect.element(page.getByText('Brief 1923')).toBeVisible();
|
||||
await expect.element(page.getByText('Brief aus 1923')).toBeVisible();
|
||||
expect(document.querySelector('a[href="/documents/d1"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('JOURNEY shows "zusammengestellt am" instead of "veröffentlicht am"', async () => {
|
||||
render(GeschichtePage, {
|
||||
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||
props: { data: baseData({ geschichte: baseGeschichte({ type: 'JOURNEY' }) }) }
|
||||
});
|
||||
|
||||
await expect.element(page.getByText(/zusammengestellt am/i)).toBeVisible();
|
||||
await expect.element(page.getByText(/veröffentlicht am/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the author avatar initials in the meta bar', async () => {
|
||||
render(GeschichtePage, {
|
||||
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||
props: { data: baseData() }
|
||||
});
|
||||
|
||||
await expect.element(page.getByText('AS', { exact: true })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders edit and delete actions when canBlogWrite is true', async () => {
|
||||
|
||||
Reference in New Issue
Block a user