diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index e7b57b7f..0be79074 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -109,6 +109,8 @@ type TFeedClientTimeUnit = 'minute' | 'day' | 'week' | 'month' | 'year' /** Client-side “who wrote this” filter on already-loaded posts. */ type TFeedClientAuthorMode = 'everyone' | 'me' | 'npub' +const FEED_FILTER_KIND_MIN = 0 +const FEED_FILTER_KIND_MAX = 40_000 /** Short debounce: batch rapid timeline updates without delaying first paint on feeds like notifications. */ const FEED_PROFILE_BATCH_DEBOUNCE_MS = 50 @@ -420,6 +422,7 @@ const NoteList = forwardRef( const [feedClientSearch, setFeedClientSearch] = useState('') const [feedClientAuthorMode, setFeedClientAuthorMode] = useState('everyone') const [feedClientAuthorNpubInput, setFeedClientAuthorNpubInput] = useState('') + const [feedClientKindInput, setFeedClientKindInput] = useState('') const [feedClientTimeAmount, setFeedClientTimeAmount] = useState('') const [feedClientTimeUnit, setFeedClientTimeUnit] = useState('day') const supportTouch = useMemo(() => isTouchDevice(), []) @@ -518,6 +521,7 @@ const NoteList = forwardRef( setFeedClientSearch('') setFeedClientAuthorMode('everyone') setFeedClientAuthorNpubInput('') + setFeedClientKindInput('') setFeedClientTimeAmount('') setFeedClientTimeUnit('day') setFeedFullSearchEvents(null) @@ -869,6 +873,19 @@ const NoteList = forwardRef( return null }, [feedClientAuthorMode, feedClientAuthorNpubInput, pubkey]) + /** + * `null` => no kind constraint, `number` => valid kind, `undefined` => invalid non-empty input. + */ + const feedClientKindFilter = useMemo(() => { + const raw = feedClientKindInput.trim() + if (raw.length === 0) return null + if (!/^\d+$/.test(raw)) return undefined + const parsed = Number(raw) + if (!Number.isInteger(parsed)) return undefined + if (parsed < FEED_FILTER_KIND_MIN || parsed > FEED_FILTER_KIND_MAX) return undefined + return parsed + }, [feedClientKindInput]) + const applyClientFeedFilter = useCallback( (evts: Event[]) => { let rows = evts @@ -890,6 +907,11 @@ const NoteList = forwardRef( if (feedClientMinCreatedAt !== null) { rows = rows.filter((e) => e.created_at >= feedClientMinCreatedAt) } + if (typeof feedClientKindFilter === 'number') { + rows = rows.filter((e) => e.kind === feedClientKindFilter) + } else if (feedClientKindFilter === undefined) { + rows = [] + } const q = feedClientSearch.trim().toLowerCase() if (q) { rows = rows.filter((e) => { @@ -909,6 +931,7 @@ const NoteList = forwardRef( feedClientAuthorNpubInput, pubkey, feedClientMinCreatedAt, + feedClientKindFilter, feedClientSearch ] ) @@ -932,6 +955,7 @@ const NoteList = forwardRef( (feedClientSearch.trim() || (feedClientAuthorMode === 'me' && !!pubkey) || (feedClientAuthorMode === 'npub' && feedClientAuthorNpubInput.trim() !== '') || + feedClientKindInput.trim() !== '' || feedClientMinCreatedAt !== null) ), [ @@ -939,6 +963,7 @@ const NoteList = forwardRef( feedClientSearch, feedClientAuthorMode, feedClientAuthorNpubInput, + feedClientKindInput, pubkey, feedClientMinCreatedAt ] @@ -1076,12 +1101,21 @@ const NoteList = forwardRef( } const hasSearch = feedClientSearch.trim().length > 0 const hasTime = feedClientMinCreatedAt !== null + const hasKind = typeof feedClientKindFilter === 'number' let hasAuthor = false if (feedClientAuthorMode === 'me' && pubkey) hasAuthor = true if (feedClientAuthorMode === 'npub' && inviteInputToHexPubkey(feedClientAuthorNpubInput)) { hasAuthor = true } - if (!hasSearch && !hasTime && !hasAuthor) { + if (feedClientKindFilter === undefined) { + toast.error( + t('Feed filter kind invalid', { + defaultValue: `Kind must be an integer between ${FEED_FILTER_KIND_MIN} and ${FEED_FILTER_KIND_MAX}.` + }) + ) + return + } + if (!hasSearch && !hasTime && !hasAuthor && !hasKind) { toast.error(t('Feed full search need constraint')) return } @@ -1115,6 +1149,9 @@ const NoteList = forwardRef( typeof finalFilter.since === 'number' ? finalFilter.since : 0 ) } + if (hasKind) { + finalFilter.kinds = [feedClientKindFilter] + } const hasRelayScope = timelineFilterHasNonKindScope(finalFilter) || @@ -1157,6 +1194,7 @@ const NoteList = forwardRef( showFeedClientFilter, feedClientSearch, feedClientMinCreatedAt, + feedClientKindFilter, feedClientAuthorMode, feedClientAuthorNpubInput, pubkey, @@ -2296,8 +2334,9 @@ const NoteList = forwardRef( const feedClientFilterPanelSurfaceClass = useFeedFilterTabRowPortal && feedClientFilterTabRowHost - ? 'mt-1 space-y-3 w-full min-w-[min(100vw-2rem,22rem)] max-w-md rounded-md border border-border bg-background px-3 py-3 shadow-md' - : 'space-y-3 border-t border-border/60 py-3' + ? 'mt-1 w-[min(100vw-1rem,28rem)] max-w-[calc(100vw-1rem)] space-y-3 rounded-lg border border-border bg-background p-3 shadow-lg' + : 'space-y-3 border-t border-border/60 px-2 py-3' + const feedClientFilterSectionClass = 'space-y-2 rounded-md border border-border/60 bg-muted/25 p-2.5' const feedClientFilterChrome = ( <> @@ -2318,7 +2357,7 @@ const NoteList = forwardRef( {feedClientFilterOpen ? (
-
+
@@ -2331,7 +2370,31 @@ const NoteList = forwardRef( className="w-full" />
-
+
+ + { + const v = e.target.value.trim() + if (v === '' || /^\d+$/.test(v)) setFeedClientKindInput(v) + }} + placeholder={t('Feed filter kind placeholder', { defaultValue: 'e.g. 30023' })} + className="w-full sm:max-w-[11rem]" + aria-invalid={feedClientKindFilter === undefined ? true : undefined} + /> +

+ {t('Feed filter kind hint', { + defaultValue: `Integer ${FEED_FILTER_KIND_MIN}-${FEED_FILTER_KIND_MAX}.` + })} +

+
+
{t('Feed filter author npub')} {feedClientAuthorMode === 'npub' ? ( -
+
{t('Feed filter author npub from prefix')} @@ -2365,7 +2428,7 @@ const NoteList = forwardRef( onChange={(e) => setFeedClientAuthorNpubInput(e.target.value)} placeholder={t('Feed filter author npub placeholder')} autoComplete="off" - className="min-w-[12rem] flex-1" + className="w-full" aria-invalid={ feedClientAuthorNpubInput.trim() !== '' && !inviteInputToHexPubkey(feedClientAuthorNpubInput) @@ -2378,8 +2441,9 @@ const NoteList = forwardRef(
-
-
+
+
+
@@ -2396,7 +2460,7 @@ const NoteList = forwardRef( className="w-full" />
-
+
@@ -2416,20 +2480,24 @@ const NoteList = forwardRef(
+
-

{t('Feed filter client-side hint')}

-
+

+ {t('Feed filter client-side hint')} +

+
{feedFullSearchEvents !== null ? ( - ) : null}