From 48ae98f0037a20e783bdd79d86920b1553d3ec88 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 11 Nov 2025 14:28:03 +0100 Subject: [PATCH] updated search, to be more precise --- src/components/SearchBar/index.tsx | 25 +++++++-- src/components/SearchInfo.tsx | 15 +++--- src/lib/search-parser.ts | 31 +++++++---- src/pages/secondary/NoteListPage/index.tsx | 24 +++------ src/pages/secondary/SearchPage/index.tsx | 60 +++++++++------------- 5 files changed, 79 insertions(+), 76 deletions(-) diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index ed1f662..06d0396 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -343,7 +343,10 @@ function NormalItem({ }) { return ( - +
+ + FULL TEXT +
{search}
) @@ -360,7 +363,10 @@ function HashtagItem({ }) { return ( - +
+ + HASHTAG +
{hashtag}
) @@ -377,7 +383,10 @@ function NoteItem({ }) { return ( - +
+ + NOTE +
{id}
) @@ -413,7 +422,10 @@ function DTagItem({ }) { return ( - +
+ + D-TAG +
{dtag}
) @@ -430,7 +442,10 @@ function RelayItem({ }) { return ( - +
+ + RELAY +
{url}
) diff --git a/src/components/SearchInfo.tsx b/src/components/SearchInfo.tsx index af010a1..df27a44 100644 --- a/src/components/SearchInfo.tsx +++ b/src/components/SearchInfo.tsx @@ -36,17 +36,13 @@ export default function SearchInfo() {
- Metadata fields: + Filters:
@@ -55,8 +51,9 @@ export default function SearchInfo() {

diff --git a/src/lib/search-parser.ts b/src/lib/search-parser.ts index d1478b0..8724fe2 100644 --- a/src/lib/search-parser.ts +++ b/src/lib/search-parser.ts @@ -2,18 +2,20 @@ * Advanced search parser for Nostr events * Supports multiple search parameters: * - Date ranges: YYYY-MM-DD to YYYY-MM-DD, from:YYYY-MM-DD, to:YYYY-MM-DD, before:YYYY-MM-DD, after:YYYY-MM-DD - * - Title: title:"text" or title:text - * - Subject: subject:"text" or subject:text - * - Description: description:"text" or description:text - * - Author: author:"name" (author tag, not pubkey) - * - Pubkey: pubkey:npub... or pubkey:hex... - * - Type: type:value + * - Hashtag: t:hashtag or hashtag:hashtag (filters by #t tag) + * - Pubkey: pubkey:npub... or pubkey:hex... (filters by authors field) + * - Events: events:hex, events:note1..., events:nevent1..., events:naddr1... (filters by ids field) * - Kind: kind:30023 (filter by event kind) - * - Plain text: becomes d-tag search for replaceable events + * - Plain text: becomes d-tag search for replaceable events (uses #d tag) + * + * Note: Nostr only supports single-letter tag indexes (#d, #t, #p, #e, #a, etc.) + * Multi-letter tags like title, subject, description, author, type are parsed but + * not used in filters as relays don't index them. */ export interface AdvancedSearchParams { dtag?: string + hashtag?: string | string[] // t-tag/hashtag (uses #t tag) title?: string | string[] subject?: string | string[] description?: string | string[] @@ -81,8 +83,8 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams { // Support both 4-digit (YYYY) and 2-digit (YY) years, date ranges (DATE to DATE) const dateRangePattern = /(\d{2,4}-\d{2}-\d{2})\s+to\s+(\d{2,4}-\d{2}-\d{2})/gi const datePattern = /(?:from|to|before|after):(\d{2,4}-\d{2}-\d{2})/gi - const quotedPattern = /(title|subject|description|author|type|pubkey|events):"([^"]+)"/gi - const unquotedPattern = /(title|subject|description|author|pubkey|type|kind|events):([^\s]+)/gi + const quotedPattern = /(title|subject|description|author|type|pubkey|events|hashtag|t):"([^"]+)"/gi + const unquotedPattern = /(title|subject|description|author|pubkey|type|kind|events|hashtag|t):([^\s]+)/gi // Pattern to detect bare nip19 IDs (nevent, note, naddr) or hex event IDs // These start with the prefix and are base32 encoded (use word boundary to avoid partial matches) @@ -167,6 +169,10 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams { const values = parseValues(value) switch (param) { + case 'hashtag': + case 't': + params.hashtag = values.length === 1 ? values[0] : values + break case 'title': params.title = values.length === 1 ? values[0] : values break @@ -209,6 +215,13 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams { lastIndex = Math.max(lastIndex, end) switch (param) { + case 'hashtag': + case 't': + if (!params.hashtag) { + const values = parseValues(value) + params.hashtag = values.length === 1 ? values[0] : values + } + break case 'title': if (!params.title) { const values = parseValues(value) diff --git a/src/pages/secondary/NoteListPage/index.tsx b/src/pages/secondary/NoteListPage/index.tsx index 6e26040..09953c9 100644 --- a/src/pages/secondary/NoteListPage/index.tsx +++ b/src/pages/secondary/NoteListPage/index.tsx @@ -185,29 +185,19 @@ const NoteListPage = forwardRef(({ index, hid } // Advanced search parameters (support multiple values) - const title = searchParams.getAll('title') - const subject = searchParams.getAll('subject') - const description = searchParams.getAll('description') - const author = searchParams.getAll('author') + // Note: Nostr only supports single-letter tag indexes, so we can't filter by + // multi-letter tags like title, subject, description, author, type const searchPubkey = searchParams.getAll('pubkey') const searchEvents = searchParams.getAll('events') - const type = searchParams.getAll('type') const from = searchParams.get('from') const to = searchParams.get('to') const before = searchParams.get('before') const after = searchParams.get('after') // Check if we have any advanced search parameters - if (title.length > 0 || subject.length > 0 || description.length > 0 || author.length > 0 || searchPubkey.length > 0 || searchEvents.length > 0 || type.length > 0 || from || to || before || after) { + if (searchPubkey.length > 0 || searchEvents.length > 0 || from || to || before || after) { const filter: any = {} - // Tag-based filters (support multiple values - use OR logic) - if (title.length > 0) filter['#title'] = title - if (subject.length > 0) filter['#subject'] = subject - if (description.length > 0) filter['#description'] = description - if (author.length > 0) filter['#author'] = author - if (type.length > 0) filter['#type'] = type - // Pubkey filter (support multiple pubkeys: hex, npub, nprofile, NIP-05) if (searchPubkey.length > 0) { const decodedPubkeys: string[] = [] @@ -315,16 +305,16 @@ const NoteListPage = forwardRef(({ index, hid // Build title from search params const titleParts: string[] = [] - if (title.length > 0) titleParts.push(`title:${title.join(',')}`) - if (subject.length > 0) titleParts.push(`subject:${subject.join(',')}`) - if (author.length > 0) titleParts.push(`author:${author.join(',')}`) + // Note: hashtag is handled separately via 't' parameter earlier in the function if (searchPubkey.length > 0) { const pubkeyDisplay = searchPubkey.length === 1 ? `${searchPubkey[0].substring(0, 16)}...` : `${searchPubkey.length} pubkeys` titleParts.push(`pubkey:${pubkeyDisplay}`) } - if (type.length > 0) titleParts.push(`type:${type.join(',')}`) + if (searchEvents.length > 0) { + titleParts.push(`${searchEvents.length} event${searchEvents.length > 1 ? 's' : ''}`) + } if (from || to || before || after) { const dateParts: string[] = [] if (from) dateParts.push(`from:${from}`) diff --git a/src/pages/secondary/SearchPage/index.tsx b/src/pages/secondary/SearchPage/index.tsx index 0e51387..2248988 100644 --- a/src/pages/secondary/SearchPage/index.tsx +++ b/src/pages/secondary/SearchPage/index.tsx @@ -46,44 +46,39 @@ const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number const searchParams = parseAdvancedSearch(params.search) // Check if we have advanced search parameters (not just plain text) + // Exclude unsupported multi-letter tag params (title, subject, description, author, type) const hasAdvancedParams = Object.keys(searchParams).some(key => - key !== 'dtag' && searchParams[key as keyof typeof searchParams] + key !== 'dtag' && + key !== 'title' && + key !== 'subject' && + key !== 'description' && + key !== 'author' && + key !== 'type' && + searchParams[key as keyof typeof searchParams] ) + // Handle hashtag search - route to hashtag page + if (searchParams.hashtag) { + const hashtag = Array.isArray(searchParams.hashtag) ? searchParams.hashtag[0] : searchParams.hashtag + const urlParams = new URLSearchParams() + urlParams.set('t', hashtag) + if (searchParams.kinds) { + searchParams.kinds.forEach(k => urlParams.append('k', k.toString())) + } + push(`/notes?${urlParams.toString()}`) + return + } + if (hasAdvancedParams || searchParams.dtag) { // Route to NoteListPage with advanced search + // Note: Only include parameters that Nostr relays actually support + // (single-letter tag indexes: #d, #t, #p, #e, #a, etc.) const urlParams = new URLSearchParams() if (searchParams.dtag) { urlParams.set('d', searchParams.dtag) } - if (searchParams.title) { - if (Array.isArray(searchParams.title)) { - searchParams.title.forEach(t => urlParams.append('title', t)) - } else { - urlParams.set('title', searchParams.title) - } - } - if (searchParams.subject) { - if (Array.isArray(searchParams.subject)) { - searchParams.subject.forEach(s => urlParams.append('subject', s)) - } else { - urlParams.set('subject', searchParams.subject) - } - } - if (searchParams.description) { - if (Array.isArray(searchParams.description)) { - searchParams.description.forEach(d => urlParams.append('description', d)) - } else { - urlParams.set('description', searchParams.description) - } - } - if (searchParams.author) { - if (Array.isArray(searchParams.author)) { - searchParams.author.forEach(a => urlParams.append('author', a)) - } else { - urlParams.set('author', searchParams.author) - } - } + // Skip title, subject, description, author, type - these use multi-letter tags + // that Nostr relays don't index if (searchParams.pubkey) { if (Array.isArray(searchParams.pubkey)) { searchParams.pubkey.forEach(p => urlParams.append('pubkey', p)) @@ -98,13 +93,6 @@ const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number urlParams.set('events', searchParams.events) } } - if (searchParams.type) { - if (Array.isArray(searchParams.type)) { - searchParams.type.forEach(t => urlParams.append('type', t)) - } else { - urlParams.set('type', searchParams.type) - } - } if (searchParams.from) urlParams.set('from', searchParams.from) if (searchParams.to) urlParams.set('to', searchParams.to) if (searchParams.before) urlParams.set('before', searchParams.before)