feat(nostr): NIP-50 search via relay.nostr.band and nostr.wine (M11.7)

Search input sends REQ with search field to NIP-50 supporting relays.
Results shown as note cards with author, content, timestamp. Filter
by content type. NIP-50 button triggers relay search on Enter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 00:25:24 +00:00
co-authored by Claude Opus 4.6
parent 49c9f71aa2
commit ffc6524286
2 changed files with 122 additions and 7 deletions
@@ -93,12 +93,23 @@
</div>
</div>
<input
v-model="search"
type="text"
placeholder="Search notes, npubs..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10"
/>
<div class="flex gap-2">
<input
v-model="search"
type="text"
placeholder="Search notes, npubs..."
class="flex-1 px-3 py-2 rounded-lg text-xs outline-none transition-colors bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10"
@keydown.enter="triggerNostrSearch"
/>
<button
v-if="search.trim()"
class="px-2.5 py-2 rounded-lg text-[10px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30 shrink-0"
:disabled="isSearching"
@click="triggerNostrSearch"
>
{{ isSearching ? '...' : 'NIP-50' }}
</button>
</div>
<div class="flex gap-1.5">
<button
@@ -229,7 +240,7 @@ import { useNostrIdentity } from '@/composables/useNostrIdentity'
defineEmits<{ selectNote: [note: NostrNote] }>()
const { events: notes, isConnected, relayStates, connect, publishEvent } = useNostr()
const { events: notes, isConnected, relayStates, connect, publishEvent, searchResults, isSearching, searchNostr } = useNostr()
const { isLoggedIn, signEvent } = useNostrIdentity()
const { verifyNip05 } = useNip05Verification()
@@ -277,7 +288,18 @@ function formatTime(ts: number): string {
return `${Math.floor(diff / 86400)}d`
}
const usingNostrSearch = ref(false)
const filteredNotes = computed(() => {
// If NIP-50 search active, show those results
if (usingNostrSearch.value && searchResults.value.length > 0) {
let result = searchResults.value
if (activeKind.value !== null) {
result = result.filter(n => n.kind === activeKind.value)
}
return result
}
let result = notes.value
if (activeKind.value !== null) {
result = result.filter(n => n.kind === activeKind.value)
@@ -293,6 +315,17 @@ const filteredNotes = computed(() => {
return result
})
function triggerNostrSearch() {
if (!search.value.trim()) return
usingNostrSearch.value = true
searchNostr(search.value.trim(), activeKind.value ? [activeKind.value] : undefined)
}
// Clear NIP-50 mode when search is cleared
watch(search, (val) => {
if (!val.trim()) usingNostrSearch.value = false
})
// Verify NIP-05 for notes that have it
watch(filteredNotes, (visibleNotes) => {
for (const note of visibleNotes) {