feat(browse): sticky tabs + per-tab search bar (server-side, scope-aware)
The Browse tab nav scrolled away (operator didn't know it existed) and Posts had no search. Roll the tab strip + a shared search field into one sticky block pinned under the 64px TopNav. - Posts gains server-side text search: PostFeedService.scroll()/around() + /api/posts accept q (ILIKE over post_title OR description), applied INSIDE the artist/platform WHERE so search stays scoped to the active filter. Scope shown as clearable chips next to the search field. - Artists/Tags search consolidates into the sticky bar: their inner search boxes are removed; they react to route.query.q (q is deep- linkable, e.g. /browse?tab=posts&q=foo). Platform/kind filters stay. - Posts empty state now distinguishes 'no matches' from 'no posts yet'. Tests: posts q-search matches title|description and stays artist-scoped (service); q passthrough (api). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,7 +59,8 @@
|
||||
</div>
|
||||
|
||||
<div v-else-if="store.items.length === 0 && store.done" class="fc-posts__empty">
|
||||
<p>No posts yet. Subscribe to a source on the
|
||||
<p v-if="hasActiveFilter">No posts match your search or filters.</p>
|
||||
<p v-else>No posts yet. Subscribe to a source on the
|
||||
<RouterLink to="/subscriptions">Subscriptions</RouterLink>
|
||||
tab to start capturing posts.
|
||||
</p>
|
||||
@@ -93,10 +94,14 @@ const artistFilter = computed(() => {
|
||||
return raw == null ? null : Number(raw)
|
||||
})
|
||||
const platformFilter = computed(() => route.query.platform || null)
|
||||
const searchFilter = computed(() => route.query.q || null)
|
||||
const postIdFilter = computed(() => {
|
||||
const raw = route.query.post_id
|
||||
return raw == null ? null : Number(raw)
|
||||
})
|
||||
const hasActiveFilter = computed(() =>
|
||||
artistFilter.value != null || platformFilter.value != null || searchFilter.value != null
|
||||
)
|
||||
|
||||
// --- normal feed (downward infinite scroll) ---
|
||||
const sentinel = ref(null)
|
||||
@@ -106,6 +111,7 @@ async function reload() {
|
||||
await store.loadInitial({
|
||||
artist_id: artistFilter.value,
|
||||
platform: platformFilter.value,
|
||||
q: searchFilter.value,
|
||||
})
|
||||
}
|
||||
function onFilters({ artist_id, platform }) {
|
||||
@@ -169,6 +175,7 @@ async function loadAroundAndAnchor() {
|
||||
await store.loadAround(postIdFilter.value, {
|
||||
artist_id: artistFilter.value,
|
||||
platform: platformFilter.value,
|
||||
q: searchFilter.value,
|
||||
})
|
||||
await nextTick()
|
||||
const el = document.getElementById(`fc-post-${store.anchorId}`)
|
||||
@@ -188,7 +195,7 @@ async function activate() {
|
||||
}
|
||||
|
||||
watch(postIdFilter, activate)
|
||||
watch(() => [artistFilter.value, platformFilter.value], async () => {
|
||||
watch(() => [artistFilter.value, platformFilter.value, searchFilter.value], async () => {
|
||||
if (postIdFilter.value != null) return
|
||||
await reload()
|
||||
await nextTick()
|
||||
|
||||
Reference in New Issue
Block a user