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:
- title:"text" or title:text - Search in title tag
- subject:"text" or subject:text - Search in subject tag
- description:"text" - Search in description tag
- author:"name" - Search by author tag (not pubkey)
+ 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)
- type:value - Filter by type tag
kind:30023 - Filter by event kind (e.g., 1=notes, 30023=articles, 30817/30818=wiki)
- - Multiple values supported:
author:Aristotle,Plato or kind:30023,2018
+ - Multiple values supported:
t:bitcoin,nostr, pubkey:npub1...,npub2... or kind:30023,2018
@@ -55,8 +51,9 @@ export default function SearchInfo() {
jumble search → searches d-tag
- title:"My Article" from:2024-01-01
- author:"John Doe" type:wiki
+ 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
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)