diff --git a/src/components/SearchInfo.tsx b/src/components/SearchInfo.tsx
index df27a44..0b95ceb 100644
--- a/src/components/SearchInfo.tsx
+++ b/src/components/SearchInfo.tsx
@@ -1,4 +1,4 @@
-import { Info } from 'lucide-react'
+import { Info, BookOpen } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import {
@@ -19,46 +19,47 @@ export default function SearchInfo() {
const searchInfoContent = (
-
Advanced Search Parameters
+
Search Parameters
Plain text: Searches by d-tag for replaceable events (normalized, hyphenated)
-
Date ranges:
-
- YYYY-MM-DD to YYYY-MM-DD - Date range (e.g., 2025-10-23 to 2025-10-30)
- from:YYYY-MM-DD - Events from this date
- to:YYYY-MM-DD - Events until this date
- before:YYYY-MM-DD - Events before this date
- after:YYYY-MM-DD - Events after this date
- - Supports 2-digit years (e.g.,
25-10-23 = 2025-10-23)
-
+
Event IDs: Bare event IDs work as standard search (hex, note1, nevent1, naddr1)
Filters:
t:hashtag or hashtag:hashtag - Filter by hashtag (t-tag)
- pubkey:npub..., pubkey:hex, pubkey:nprofile..., or pubkey:user@domain.com - Filter by pubkey (accepts npub, nprofile, hex, or NIP-05)
- events:hex, events:note1..., events:nevent1..., or events:naddr1... - Filter by specific events (accepts hex, note, nevent, or naddr)
- kind:30023 - Filter by event kind (e.g., 1=notes, 30023=articles, 30817/30818=wiki)
- - Multiple values supported:
t:bitcoin,nostr, pubkey:npub1...,npub2... or kind:30023,2018
+ - Multiple values supported:
t:bitcoin,nostr
+
+ Kind filter: Use URL parameter k= with other filters (e.g., ?t=bitcoin&k=1 or ?t=testfile&k=30023). Cannot be used alone.
+
Examples:
jumble search → searches d-tag
- t:bitcoin from:2024-01-01
- pubkey:npub1abc... from:2024-01-01
- kind:30023 from:2024-01-01
- 2025-10-23 to 2025-10-30 → date range
+ t:bitcoin → hashtag search
+ note1abc... → searches for event ID
+
)
@@ -85,6 +86,17 @@ export default function SearchInfo() {
{searchInfoContent}
+
diff --git a/src/lib/search-parser.ts b/src/lib/search-parser.ts
index 8724fe2..b2603c8 100644
--- a/src/lib/search-parser.ts
+++ b/src/lib/search-parser.ts
@@ -1,16 +1,14 @@
/**
* 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
* - 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)
+ * - Event IDs: Bare event IDs (hex, note1, nevent1, naddr1) work as standard search
* - 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.
+ * Note:
+ * - Nostr only supports single-letter tag indexes (#d, #t, #p, #e, #a, etc.)
+ * - Kind filter is only available as URL parameter k= (e.g., ?t=bitcoin&k=1)
+ * - Date searches and pubkey filters are not supported
*/
export interface AdvancedSearchParams {
@@ -21,13 +19,10 @@ export interface AdvancedSearchParams {
description?: string | string[]
author?: string | string[]
pubkey?: string | string[] // Accepts: hex, npub, nprofile, or NIP-05
- events?: string | string[] // Accepts: hex event ID, note, nevent, naddr
+ events?: string | string[] // Accepts: hex event ID, note, nevent, naddr (bare IDs work as standard search)
type?: string | string[]
- from?: string // YYYY-MM-DD
- to?: string // YYYY-MM-DD
- before?: string // YYYY-MM-DD
- after?: string // YYYY-MM-DD
- kinds?: number[]
+ // Date searches removed - not supported
+ // Kind filter only available as URL parameter k=
}
/**
@@ -43,28 +38,6 @@ export function normalizeToDTag(term: string): string {
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
}
-/**
- * Normalize date to YYYY-MM-DD format
- * Supports both 2-digit (YY) and 4-digit (YYYY) years
- */
-function normalizeDate(dateStr: string): string {
- const parts = dateStr.split('-')
- if (parts.length !== 3) return dateStr
-
- let year = parts[0]
- const month = parts[1]
- const day = parts[2]
-
- // Convert 2-digit year to 4-digit
- if (year.length === 2) {
- const yearNum = parseInt(year)
- // Assume years 00-30 are 2000-2030, years 31-99 are 1931-1999
- year = yearNum <= 30 ? `20${year.padStart(2, '0')}` : `19${year}`
- }
-
- return `${year}-${month}-${day}`
-}
-
/**
* Parse advanced search query
*/
@@ -75,16 +48,14 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams {
.replace(/\s+/g, ' ') // Replace multiple whitespace with single space
.replace(/\s*,\s*/g, ',') // Normalize spaces around commas
.replace(/\s*:\s*/g, ':') // Normalize spaces around colons
- .replace(/\s+to\s+/gi, ' to ') // Normalize "to" in date ranges
const params: AdvancedSearchParams = {}
// Regular expressions for different parameter types
- // 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|hashtag|t):"([^"]+)"/gi
- const unquotedPattern = /(title|subject|description|author|pubkey|type|kind|events|hashtag|t):([^\s]+)/gi
+ // Note: Date searches, kind: prefix, and pubkey: prefix removed
+ // Kind only available as URL parameter k=
+ const quotedPattern = /(title|subject|description|author|type|hashtag|t):"([^"]+)"/gi
+ const unquotedPattern = /(title|subject|description|author|type|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)
@@ -188,14 +159,6 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams {
case 'type':
params.type = values.length === 1 ? values[0] : values
break
- case 'pubkey':
- const pubkeyValues = parseValues(value)
- params.pubkey = pubkeyValues.length === 1 ? pubkeyValues[0] : pubkeyValues
- break
- case 'events':
- const eventValues = parseValues(value)
- params.events = eventValues.length === 1 ? eventValues[0] : eventValues
- break
}
}
@@ -246,38 +209,17 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams {
params.author = values.length === 1 ? values[0] : values
}
break
- case 'pubkey':
- if (!params.pubkey) {
- const pubkeyValues = parseValues(value)
- params.pubkey = pubkeyValues.length === 1 ? pubkeyValues[0] : pubkeyValues
- }
- break
- case 'events':
- if (!params.events) {
- const eventValues = parseValues(value)
- params.events = eventValues.length === 1 ? eventValues[0] : eventValues
- }
- break
case 'type':
if (!params.type) {
const values = parseValues(value)
params.type = values.length === 1 ? values[0] : values
}
break
- case 'kind':
- const kindValues = parseValues(value)
- params.kinds = params.kinds || []
- for (const kindVal of kindValues) {
- const kindNum = parseInt(kindVal)
- if (!isNaN(kindNum)) {
- params.kinds.push(kindNum)
- }
- }
- break
}
}
// Process detected bare event IDs (those not used as parameters)
+ // Note: Bare event IDs are left as plain text for standard search, not stored as filter params
for (const detectedId of detectedEventIds) {
const start = detectedId.start
// Skip if already used by a parameter pattern
@@ -285,20 +227,12 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams {
continue
}
- // Mark as used
+ // Mark as used - but don't store in params.events, leave as plain text for standard search
usedIndices.push(start, detectedId.end)
-
- // Store the event ID in params.events
- if (!params.events) {
- params.events = detectedId.id
- } else if (Array.isArray(params.events)) {
- params.events.push(detectedId.id)
- } else {
- params.events = [params.events, detectedId.id]
- }
}
// Process detected bare pubkey IDs (those not used as parameters)
+ // Note: Pubkey filters removed - not supported
for (const detectedId of detectedPubkeyIds) {
const start = detectedId.start
// Skip if already used by a parameter pattern
@@ -306,66 +240,11 @@ export function parseAdvancedSearch(query: string): AdvancedSearchParams {
continue
}
- // Mark as used
+ // Mark as used - but don't store in params.pubkey, leave as plain text
usedIndices.push(start, detectedId.end)
-
- // Store the pubkey ID in params.pubkey
- if (!params.pubkey) {
- params.pubkey = detectedId.id
- } else if (Array.isArray(params.pubkey)) {
- params.pubkey.push(detectedId.id)
- } else {
- params.pubkey = [params.pubkey, detectedId.id]
- }
}
- // Process date range patterns first (DATE to DATE)
- dateRangePattern.lastIndex = 0
- while ((match = dateRangePattern.exec(normalizedQuery)) !== null) {
- const startDate = normalizeDate(match[1])
- const endDate = normalizeDate(match[2])
- const start = match.index
- const end = start + match[0].length
-
- usedIndices.push(start, end)
- lastIndex = Math.max(lastIndex, end)
-
- // Use from/to for date ranges
- params.from = startDate
- params.to = endDate
- }
-
- // Process date parameters (from:, to:, before:, after:)
- datePattern.lastIndex = 0
- while ((match = datePattern.exec(normalizedQuery)) !== null) {
- const start = match.index
- // Skip if already used by date range pattern
- if (usedIndices.some((idx, i) => i % 2 === 0 && start >= idx && start <= usedIndices[i + 1])) {
- continue
- }
-
- const param = match[0].split(':')[0].toLowerCase()
- const value = normalizeDate(match[1])
- const end = start + match[0].length
-
- usedIndices.push(start, end)
- lastIndex = Math.max(lastIndex, end)
-
- switch (param) {
- case 'from':
- params.from = value
- break
- case 'to':
- params.to = value
- break
- case 'before':
- params.before = value
- break
- case 'after':
- params.after = value
- break
- }
- }
+ // Date searches removed - not supported
// Extract plain text (everything not matched by patterns)
usedIndices.sort((a, b) => a - b)
diff --git a/src/pages/primary/SearchPage/index.tsx b/src/pages/primary/SearchPage/index.tsx
index f8c353a..e538617 100644
--- a/src/pages/primary/SearchPage/index.tsx
+++ b/src/pages/primary/SearchPage/index.tsx
@@ -3,7 +3,8 @@ import SearchResult from '@/components/SearchResult'
import PrimaryPageLayout, { TPrimaryPageLayoutRef } from '@/layouts/PrimaryPageLayout'
import { usePrimaryPage } from '@/PageManager'
import { TSearchParams } from '@/types'
-import SearchInfo from '@/components/SearchInfo'
+import { BookOpen } from 'lucide-react'
+import { Button } from '@/components/ui/button'
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
const SearchPage = forwardRef((_, ref) => {
@@ -50,7 +51,20 @@ const SearchPage = forwardRef((_, ref) => {
diff --git a/src/pages/secondary/NoteListPage/index.tsx b/src/pages/secondary/NoteListPage/index.tsx
index 09953c9..45c7972 100644
--- a/src/pages/secondary/NoteListPage/index.tsx
+++ b/src/pages/secondary/NoteListPage/index.tsx
@@ -184,159 +184,9 @@ const NoteListPage = forwardRef(({ index, hid
return
}
- // Advanced search parameters (support multiple values)
- // 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 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 (searchPubkey.length > 0 || searchEvents.length > 0 || from || to || before || after) {
- const filter: any = {}
-
- // Pubkey filter (support multiple pubkeys: hex, npub, nprofile, NIP-05)
- if (searchPubkey.length > 0) {
- const decodedPubkeys: string[] = []
- for (const pubkeyInput of searchPubkey) {
- try {
- // Check if it's a NIP-05 identifier
- if (pubkeyInput.includes('@')) {
- // Will need to resolve NIP-05, but for now we'll handle it separately
- // For now, try to fetch and decode
- const pubkeys = await fetchPubkeysFromDomain(pubkeyInput.split('@')[1])
- decodedPubkeys.push(...pubkeys)
- } else if (pubkeyInput.startsWith('npub') || pubkeyInput.startsWith('nprofile')) {
- const decoded = nip19.decode(pubkeyInput)
- if (decoded.type === 'npub') {
- decodedPubkeys.push(decoded.data)
- } else if (decoded.type === 'nprofile') {
- decodedPubkeys.push(decoded.data.pubkey)
- }
- } else {
- // Assume hex pubkey
- decodedPubkeys.push(pubkeyInput)
- }
- } catch (e) {
- // If decoding fails, try as hex or skip
- if (/^[a-f0-9]{64}$/i.test(pubkeyInput)) {
- decodedPubkeys.push(pubkeyInput)
- }
- }
- }
- if (decodedPubkeys.length > 0) {
- filter.authors = decodedPubkeys
- }
- }
-
- // Events filter (support multiple events: hex, note, nevent, naddr)
- if (searchEvents.length > 0) {
- const eventIds: string[] = []
- for (const eventInput of searchEvents) {
- try {
- if (/^[a-f0-9]{64}$/i.test(eventInput)) {
- // Hex event ID
- eventIds.push(eventInput)
- } else if (eventInput.startsWith('note1') || eventInput.startsWith('nevent1') || eventInput.startsWith('naddr1')) {
- const decoded = nip19.decode(eventInput)
- if (decoded.type === 'note') {
- eventIds.push(decoded.data)
- } else if (decoded.type === 'nevent') {
- eventIds.push(decoded.data.id)
- } else if (decoded.type === 'naddr') {
- // For naddr, we need to filter by kind, pubkey, and d-tag
- if (!filter.kinds) filter.kinds = []
- if (!filter.kinds.includes(decoded.data.kind)) {
- filter.kinds.push(decoded.data.kind)
- }
- if (!filter.authors) filter.authors = []
- if (!filter.authors.includes(decoded.data.pubkey)) {
- filter.authors.push(decoded.data.pubkey)
- }
- if (decoded.data.identifier) {
- if (!filter['#d']) filter['#d'] = []
- if (!filter['#d'].includes(decoded.data.identifier)) {
- filter['#d'].push(decoded.data.identifier)
- }
- }
- continue // Skip adding to eventIds for naddr
- }
- }
- } catch (e) {
- // Skip invalid event IDs
- }
- }
- if (eventIds.length > 0) {
- filter.ids = eventIds
- }
- }
-
- // Date filters - convert to unix timestamps
- let since: number | undefined
- let until: number | undefined
-
- if (from) {
- const date = new Date(from + 'T00:00:00Z')
- since = Math.floor(date.getTime() / 1000)
- }
- if (to) {
- const date = new Date(to + 'T23:59:59Z')
- until = Math.floor(date.getTime() / 1000)
- }
- if (before) {
- const date = new Date(before + 'T00:00:00Z')
- until = Math.min(until || Infinity, Math.floor(date.getTime() / 1000) - 1)
- }
- if (after) {
- const date = new Date(after + 'T23:59:59Z')
- since = Math.max(since || 0, Math.floor(date.getTime() / 1000) + 1)
- }
-
- if (since) filter.since = since
- if (until) filter.until = until
-
- // Kinds filter
- if (kinds.length > 0) {
- filter.kinds = kinds
- }
-
- // Build title from search params
- const titleParts: string[] = []
- // 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 (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}`)
- if (to) dateParts.push(`to:${to}`)
- if (before) dateParts.push(`before:${before}`)
- if (after) dateParts.push(`after:${after}`)
- titleParts.push(dateParts.join(', '))
- }
-
- setTitle(`Search: ${titleParts.join(' ')}`)
- setData({
- type: 'search',
- kinds: kinds.length > 0 ? kinds : undefined
- })
- setSubRequests([
- {
- filter,
- urls: BIG_RELAY_URLS
- }
- ])
- return
- }
+ // Advanced search parameters removed
+ // Note: Only hashtag (t=) and kind (k=) URL parameters are supported
+ // Date searches, pubkey filters, and event filters removed - not supported
}, [pubkey, relayList, handleSubscribeHashtag, push, t, isSubscribed, subscribe, client])
// Initialize on mount
diff --git a/src/pages/secondary/SearchPage/index.tsx b/src/pages/secondary/SearchPage/index.tsx
index 2248988..7774fae 100644
--- a/src/pages/secondary/SearchPage/index.tsx
+++ b/src/pages/secondary/SearchPage/index.tsx
@@ -5,7 +5,8 @@ import { toSearch } from '@/lib/link'
import { parseAdvancedSearch } from '@/lib/search-parser'
import { useSecondaryPage } from '@/PageManager'
import { TSearchParams } from '@/types'
-import SearchInfo from '@/components/SearchInfo'
+import { BookOpen } from 'lucide-react'
+import { Button } from '@/components/ui/button'
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react'
const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => {
@@ -79,27 +80,9 @@ const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number
}
// 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))
- } else {
- urlParams.set('pubkey', searchParams.pubkey)
- }
- }
- if (searchParams.events) {
- if (Array.isArray(searchParams.events)) {
- searchParams.events.forEach(e => urlParams.append('events', e))
- } else {
- urlParams.set('events', searchParams.events)
- }
- }
- if (searchParams.from) urlParams.set('from', searchParams.from)
- if (searchParams.to) urlParams.set('to', searchParams.to)
- if (searchParams.before) urlParams.set('before', searchParams.before)
- if (searchParams.after) urlParams.set('after', searchParams.after)
- if (searchParams.kinds) {
- searchParams.kinds.forEach(k => urlParams.append('k', k.toString()))
- }
+ // Note: Bare event IDs are handled as standard search, not as filter params
+ // Date searches and pubkey filters removed - not supported
+ // Kind filter only available as URL parameter k=, not from search parser
push(`/notes?${urlParams.toString()}`)
return
@@ -126,7 +109,20 @@ const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number