Default the Search Kind filter to Agora content only
Search previously defaulted to 'all kinds' — every kind in the picker (50+), which means a user typing 'bitcoin' into the search bar got a firehose of music tracks, podcasts, bird detections, geocaches, and every other supported NIP. The Posts tab is meant to surface Agora content; the long-tail kinds belong behind an explicit opt-in. Introduce 'agora' as a new sentinel value for the Kind filter that expands to AGORA_PRESET_KIND_VALUES (Campaigns, Pledges, Communities, Posts, Articles, Events, Polls, Photos, Videos). Make it the default in DEFAULT_FILTERS, teach parseKindFilter to resolve it, and render both 'Agora content' and 'All kinds' as top-level selectable rows in the KindPicker (with the existing Agora-curated quick-list still appearing as individual selectable items below). The active-filter chip summary suppresses a chip when the default 'agora' is in effect, surfaces 'All kinds' as a chip when the user explicitly broadens the search, and continues to show individual kind labels for everything else.
This commit is contained in:
@@ -171,7 +171,7 @@ export function KindPicker({ value, options, onChange }: {
|
||||
return { presetOptions: preset, otherOptions: other };
|
||||
}, [filtered, presetSet, search]);
|
||||
|
||||
const selected = value === 'all' || value === 'custom' ? null : options.find((o) => o.value === value);
|
||||
const selected = value === 'all' || value === 'agora' || value === 'custom' ? null : options.find((o) => o.value === value);
|
||||
const SelectedIcon = selected?.icon;
|
||||
|
||||
const handleSelect = (v: string) => { onChange(v); setOpen(false); setSearch(''); };
|
||||
@@ -189,7 +189,13 @@ export function KindPicker({ value, options, onChange }: {
|
||||
? <SelectedIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
: <Hash className="size-3.5 shrink-0 text-muted-foreground" />}
|
||||
<span className="flex-1 truncate">
|
||||
{value === 'all' ? 'All' : value === 'custom' ? 'Custom...' : (selected?.label ?? value)}
|
||||
{value === 'all'
|
||||
? 'All kinds'
|
||||
: value === 'agora'
|
||||
? 'Agora content'
|
||||
: value === 'custom'
|
||||
? 'Custom...'
|
||||
: (selected?.label ?? value)}
|
||||
</span>
|
||||
<ChevronDown className="size-3 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
@@ -217,7 +223,12 @@ export function KindPicker({ value, options, onChange }: {
|
||||
</div>
|
||||
{canScrollUp && <KindScrollCaret direction="up" onMouseEnter={() => startScroll('up')} onMouseLeave={stopScroll} />}
|
||||
<div ref={refCallback} className="overflow-y-auto flex-1 min-h-0" onScroll={onScroll}>
|
||||
{!search && <KindPickerItem icon={null} label="All kinds" active={value === 'all'} onClick={() => handleSelect('all')} />}
|
||||
{!search && (
|
||||
<>
|
||||
<KindPickerItem icon={null} label="Agora content" active={value === 'agora'} onClick={() => handleSelect('agora')} />
|
||||
<KindPickerItem icon={null} label="All kinds" active={value === 'all'} onClick={() => handleSelect('all')} />
|
||||
</>
|
||||
)}
|
||||
{!search && presetOptions.length > 0 && (
|
||||
<>
|
||||
<div className="px-2.5 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { AGORA_PRESET_KIND_VALUES } from "./feedFilterUtils"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
@@ -9,12 +10,15 @@ export function cn(...inputs: ClassValue[]) {
|
||||
* Parse a kindFilter string into an array of kind numbers.
|
||||
* Supports:
|
||||
* - 'all' → undefined (no override)
|
||||
* - 'agora' → the AGORA_PRESET_KIND_VALUES set (Campaigns, Pledges,
|
||||
* Communities, Posts, Articles, Events, Polls, Photos, Videos)
|
||||
* - 'custom' → parse customKindText as comma/space-separated numbers
|
||||
* - Single kind number (e.g. '1') → [1]
|
||||
* - Comma-separated kind numbers (e.g. '1,30023,20') → [1, 30023, 20]
|
||||
*/
|
||||
export function parseKindFilter(kindFilter: string, customKindText?: string): number[] | undefined {
|
||||
if (kindFilter === 'all' || kindFilter === '') return undefined;
|
||||
if (kindFilter === 'agora') return AGORA_PRESET_KIND_VALUES.map(Number);
|
||||
if (kindFilter === 'custom') {
|
||||
if (!customKindText) return undefined;
|
||||
const parsed = customKindText.trim().split(/[\s,]+/).map(Number).filter((n) => Number.isInteger(n) && n > 0);
|
||||
|
||||
@@ -65,7 +65,7 @@ const DEFAULT_FILTERS = {
|
||||
mediaType: 'all' as const,
|
||||
language: 'global',
|
||||
platform: 'nostr' as const,
|
||||
kindFilter: 'all',
|
||||
kindFilter: 'agora',
|
||||
customKindText: '',
|
||||
authorScope: 'anyone' as AuthorScope,
|
||||
sort: 'recent' as SortPref,
|
||||
@@ -252,15 +252,18 @@ export function SearchPage() {
|
||||
const allKindNumbers = useMemo(() => kindOptions.map((o) => Number(o.value)), [kindOptions]);
|
||||
|
||||
// Resolve kindsOverride from the current kind filter state.
|
||||
// "all" means every kind in the picker list, not undefined (which would let
|
||||
// useStreamPosts fall back to only the user's enabled feed-settings kinds).
|
||||
// "all" means every kind in the picker list, not undefined (which would
|
||||
// let useStreamPosts fall back to only the user's enabled feed-settings
|
||||
// kinds). "agora" expands to the curated Agora preset set.
|
||||
const kindsOverride = useMemo<number[]>(
|
||||
() => kindFilter === 'all' ? allKindNumbers : (parseKindFilter(kindFilter, customKindText) ?? allKindNumbers),
|
||||
[kindFilter, customKindText, allKindNumbers],
|
||||
);
|
||||
|
||||
// Detect kind + media type conflict: a specific kind is selected AND a media type is set
|
||||
const hasKindMediaConflict = kindFilter !== 'all' && kindsOverride.length > 0 && mediaType !== 'all';
|
||||
// Detect kind + media type conflict: a non-broad kind is selected AND a
|
||||
// media type is set. "all" and "agora" are both broad selections that
|
||||
// don't conflict with media filters.
|
||||
const hasKindMediaConflict = kindFilter !== 'all' && kindFilter !== 'agora' && kindsOverride.length > 0 && mediaType !== 'all';
|
||||
|
||||
// Determine if any filter differs from the default
|
||||
const hasActiveFilters = !includeReplies || mediaType !== DEFAULT_FILTERS.mediaType ||
|
||||
@@ -292,7 +295,7 @@ export function SearchPage() {
|
||||
: ['protocol:nostr'];
|
||||
if (debouncedSearchQuery.trim()) parts.push(debouncedSearchQuery.trim());
|
||||
if (language !== 'global') parts.push(`language:${language}`);
|
||||
const isDedicatedKindQuery = kindFilter === 'all' && (mediaType === 'vines' || mediaType === 'images' || mediaType === 'videos');
|
||||
const isDedicatedKindQuery = (kindFilter === 'all' || kindFilter === 'agora') && (mediaType === 'vines' || mediaType === 'images' || mediaType === 'videos');
|
||||
if (!isDedicatedKindQuery && !hasKindMediaConflict) {
|
||||
if (mediaType === 'images') { parts.push('media:true'); parts.push('video:false'); }
|
||||
else if (mediaType === 'videos') parts.push('video:true');
|
||||
@@ -311,7 +314,13 @@ export function SearchPage() {
|
||||
if (language !== 'global') labels.push(language.toUpperCase());
|
||||
if (platform !== 'nostr') labels.push({ activitypub: 'Mastodon', atproto: 'Bluesky' }[platform] ?? platform);
|
||||
if (sort !== 'recent') labels.push(sort === 'hot' ? 'Hot' : 'Trending');
|
||||
if (kindFilter !== 'all' && kindFilter !== 'custom') {
|
||||
if (kindFilter === 'agora') {
|
||||
// 'agora' is the default — no chip needed.
|
||||
} else if (kindFilter === 'all') {
|
||||
labels.push('All kinds');
|
||||
} else if (kindFilter === 'custom') {
|
||||
if (customKindText) labels.push(`Kind: ${customKindText}`);
|
||||
} else {
|
||||
const kindValues = kindFilter.split(',').filter(Boolean);
|
||||
if (kindValues.length === 1) {
|
||||
const opt = kindOptions.find(o => o.value === kindValues[0]);
|
||||
@@ -320,8 +329,6 @@ export function SearchPage() {
|
||||
} else if (kindValues.length > 1) {
|
||||
labels.push(`${kindValues.length} kinds`);
|
||||
}
|
||||
} else if (kindFilter === 'custom' && customKindText) {
|
||||
labels.push(`Kind: ${customKindText}`);
|
||||
}
|
||||
if (authorScope === 'follows') labels.push('My follows');
|
||||
if (authorScope === 'people' && authorPubkeys.length > 0) labels.push(`${authorPubkeys.length} author${authorPubkeys.length > 1 ? 's' : ''}`);
|
||||
|
||||
Reference in New Issue
Block a user