ActivityFeed: add title search bar with URL sync
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
let datePre = 'all';
|
let datePre = 'all';
|
||||||
let dateFrom = '';
|
let dateFrom = '';
|
||||||
let dateTo = '';
|
let dateTo = '';
|
||||||
|
let query = '';
|
||||||
let shown = PAGE_SIZE;
|
let shown = PAGE_SIZE;
|
||||||
let loading = true;
|
let loading = true;
|
||||||
let loadingMore = false;
|
let loadingMore = false;
|
||||||
@@ -87,8 +88,11 @@
|
|||||||
(!dateFrom || a.started_at >= dateFrom) && (!dateTo || a.started_at < dateTo)
|
(!dateFrom || a.started_at >= dateFrom) && (!dateTo || a.started_at < dateTo)
|
||||||
);
|
);
|
||||||
$: filtered = sport === 'all' ? withDate : withDate.filter(a => a.sport === sport);
|
$: filtered = sport === 'all' ? withDate : withDate.filter(a => a.sport === sport);
|
||||||
$: visible = filtered.slice(0, shown);
|
$: withSearch = query.trim()
|
||||||
$: canShowMore = shown < filtered.length;
|
? filtered.filter(a => a.title?.toLowerCase().includes(query.trim().toLowerCase()))
|
||||||
|
: filtered;
|
||||||
|
$: visible = withSearch.slice(0, shown);
|
||||||
|
$: canShowMore = shown < withSearch.length;
|
||||||
$: hasMore = canShowMore || pendingShards.length > 0 || feedNextPage > 0;
|
$: hasMore = canShowMore || pendingShards.length > 0 || feedNextPage > 0;
|
||||||
|
|
||||||
async function loadMore() {
|
async function loadMore() {
|
||||||
@@ -122,12 +126,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (sport || datePre) shown = PAGE_SIZE; // reset pagination on filter change
|
$: if (sport || datePre || query) shown = PAGE_SIZE; // reset pagination on filter change
|
||||||
|
|
||||||
$: if (mounted) {
|
$: if (mounted) {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
if (sport === 'all') params.delete('sport'); else params.set('sport', sport);
|
if (sport === 'all') params.delete('sport'); else params.set('sport', sport);
|
||||||
if (datePre === 'all') params.delete('date'); else params.set('date', datePre);
|
if (datePre === 'all') params.delete('date'); else params.set('date', datePre);
|
||||||
|
if (!query.trim()) params.delete('q'); else params.set('q', query.trim());
|
||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
history.replaceState(null, '', qs ? `?${qs}` : window.location.pathname);
|
history.replaceState(null, '', qs ? `?${qs}` : window.location.pathname);
|
||||||
}
|
}
|
||||||
@@ -136,6 +141,7 @@
|
|||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
sport = (params.get('sport') as Sport | 'all') ?? 'all';
|
sport = (params.get('sport') as Sport | 'all') ?? 'all';
|
||||||
datePre = params.get('date') ?? 'all';
|
datePre = params.get('date') ?? 'all';
|
||||||
|
query = params.get('q') ?? '';
|
||||||
mounted = true;
|
mounted = true;
|
||||||
|
|
||||||
// Resolve the logged-in handle so we can show the owner their private activities.
|
// Resolve the logged-in handle so we can show the owner their private activities.
|
||||||
@@ -204,15 +210,25 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{#if all.length > 0}
|
{#if all.length > 0}
|
||||||
<span class="ml-auto text-sm text-zinc-500 self-center">
|
<span class="ml-auto text-sm text-zinc-500 self-center">
|
||||||
{#if totalActivities > filtered.length}
|
{#if totalActivities > withSearch.length}
|
||||||
{filtered.length} of {totalActivities} activities
|
{withSearch.length} of {totalActivities} activities
|
||||||
{:else}
|
{:else}
|
||||||
{filtered.length} {filtered.length === 1 ? 'activity' : 'activities'}
|
{withSearch.length} {withSearch.length === 1 ? 'activity' : 'activities'}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="relative mb-3">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
placeholder="Search activities…"
|
||||||
|
bind:value={query}
|
||||||
|
class="w-full px-4 py-2 rounded-lg bg-zinc-900 border border-zinc-700 text-white placeholder-zinc-500 text-sm focus:outline-none focus:border-zinc-500 transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Date filter bar -->
|
<!-- Date filter bar -->
|
||||||
<div class="flex gap-2 mb-6 flex-wrap">
|
<div class="flex gap-2 mb-6 flex-wrap">
|
||||||
{#each [{ value: 'all', label: 'All time' }, { value: '7d', label: '7 days' }, { value: '30d', label: '30 days' }, { value: '6mo', label: '6 months' }, ...allYears.map(y => ({ value: y, label: y }))] as d}
|
{#each [{ value: 'all', label: 'All time' }, { value: '7d', label: '7 days' }, { value: '30d', label: '30 days' }, { value: '6mo', label: '6 months' }, ...allYears.map(y => ({ value: y, label: y }))] as d}
|
||||||
@@ -238,8 +254,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<p class="text-red-400 text-center py-12">Could not load activities: {error}</p>
|
<p class="text-red-400 text-center py-12">Could not load activities: {error}</p>
|
||||||
{:else if filtered.length === 0}
|
{:else if withSearch.length === 0}
|
||||||
<p class="text-zinc-500 text-center py-12">No activities found.</p>
|
<p class="text-zinc-500 text-center py-12">
|
||||||
|
{#if query.trim()}No activities match "{query.trim()}".{:else}No activities found.{/if}
|
||||||
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{#each visible as a (a.id)}
|
{#each visible as a (a.id)}
|
||||||
|
|||||||
Reference in New Issue
Block a user