128 lines
3.9 KiB
Svelte
128 lines
3.9 KiB
Svelte
<script lang="ts">
|
||
import { goto } from '$app/navigation';
|
||
import { m } from '$lib/paraglide/messages.js';
|
||
import PersonTypeahead from '$lib/person/PersonTypeahead.svelte';
|
||
import GeschichteListRow from '$lib/geschichte/GeschichteListRow.svelte';
|
||
import type { PageData } from './$types';
|
||
|
||
let { data }: { data: PageData } = $props();
|
||
|
||
let showPersonPicker = $state(false);
|
||
|
||
const selectedPersonIds = $derived(data.personFilters.map((p) => p.id!));
|
||
const hasFilters = $derived(data.personFilters.length > 0);
|
||
|
||
function rebuildUrl(personIds: string[]) {
|
||
const url = new URL(window.location.href);
|
||
url.searchParams.delete('personId');
|
||
for (const id of personIds) url.searchParams.append('personId', id);
|
||
return url.pathname + url.search;
|
||
}
|
||
|
||
function clearAll() {
|
||
goto(rebuildUrl([]), { replaceState: true });
|
||
}
|
||
|
||
function addPerson(personId: string) {
|
||
if (!personId || selectedPersonIds.includes(personId)) {
|
||
showPersonPicker = false;
|
||
return;
|
||
}
|
||
showPersonPicker = false;
|
||
goto(rebuildUrl([...selectedPersonIds, personId]));
|
||
}
|
||
|
||
function removePerson(personId: string) {
|
||
goto(rebuildUrl(selectedPersonIds.filter((id) => id !== personId)));
|
||
}
|
||
</script>
|
||
|
||
<div class="mx-auto max-w-4xl px-4 py-8">
|
||
<header class="mb-6 flex flex-wrap items-center justify-between gap-3">
|
||
<h1 class="font-serif text-3xl font-bold text-ink">{m.geschichten_index_title()}</h1>
|
||
{#if data.canBlogWrite}
|
||
<a
|
||
href="/geschichten/new"
|
||
class="inline-flex h-11 items-center rounded bg-primary px-4 font-sans text-sm font-medium text-primary-fg hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||
>
|
||
{m.geschichten_new_button()}
|
||
</a>
|
||
{/if}
|
||
</header>
|
||
|
||
<!-- Filter pills -->
|
||
<div class="mb-6 flex flex-wrap items-center gap-2">
|
||
<button
|
||
type="button"
|
||
aria-pressed={!hasFilters}
|
||
onclick={clearAll}
|
||
class="inline-flex h-11 items-center rounded-full border border-line px-3 font-sans text-xs font-bold tracking-wider text-ink-2 uppercase hover:bg-muted aria-pressed:bg-ink aria-pressed:text-primary-fg"
|
||
>
|
||
{m.geschichten_filter_all_pill()}
|
||
</button>
|
||
|
||
{#each data.personFilters as p (p.id)}
|
||
<button
|
||
type="button"
|
||
aria-pressed="true"
|
||
aria-label={m.geschichten_filter_remove_chip({ name: p.displayName })}
|
||
onclick={() => removePerson(p.id!)}
|
||
class="inline-flex h-11 items-center gap-2 rounded-full bg-ink px-3 font-sans text-xs font-bold tracking-wider text-primary-fg uppercase"
|
||
>
|
||
{p.displayName}
|
||
<span aria-hidden="true">×</span>
|
||
</button>
|
||
{/each}
|
||
|
||
<button
|
||
type="button"
|
||
aria-expanded={showPersonPicker}
|
||
onclick={() => (showPersonPicker = !showPersonPicker)}
|
||
class="inline-flex h-11 items-center rounded-full border border-line px-3 font-sans text-xs font-bold tracking-wider text-ink-2 uppercase hover:bg-muted"
|
||
>
|
||
+ {m.geschichten_filter_choose_person()}
|
||
</button>
|
||
</div>
|
||
|
||
{#if showPersonPicker}
|
||
<div class="mb-4">
|
||
<PersonTypeahead
|
||
name="filter-person"
|
||
label={m.geschichten_filter_choose_person()}
|
||
compact
|
||
autofocus
|
||
onchange={addPerson}
|
||
/>
|
||
{#if selectedPersonIds.length > 1}
|
||
<p class="mt-1 font-sans text-xs text-ink-3">
|
||
{m.geschichten_filter_and_hint()}
|
||
</p>
|
||
{/if}
|
||
</div>
|
||
{/if}
|
||
|
||
<!-- Card list -->
|
||
{#if data.geschichten.length === 0}
|
||
<div class="rounded border border-line bg-surface p-6 text-center font-sans text-sm text-ink-3">
|
||
{#if data.personFilters.length > 0}
|
||
{m.geschichten_empty_for_persons({
|
||
names: data.personFilters.map((p) => p.displayName).join(' & ')
|
||
})}
|
||
{:else}
|
||
{m.geschichten_empty_no_filter()}
|
||
{/if}
|
||
</div>
|
||
{:else}
|
||
<ul class="flex flex-col gap-4">
|
||
{#each data.geschichten as g (g.id)}
|
||
<li
|
||
class="rounded border border-line bg-surface p-5 shadow-sm transition-shadow hover:shadow-md"
|
||
>
|
||
<!-- plaintext for JOURNEY, sanitised-HTML→text for STORY; never {@html} -->
|
||
<GeschichteListRow geschichte={g} />
|
||
</li>
|
||
{/each}
|
||
</ul>
|
||
{/if}
|
||
</div>
|