{Array.from(pubkeySet).map((pubkey, index) => (
-
+
{
+ client.fetchProfileEvent(pubkey).catch(() => {})
+ push(toProfile(pubkey))
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ client.fetchProfileEvent(pubkey).catch(() => {})
+ push(toProfile(pubkey))
+ }
+ }}
+ >
+
+
))}
{hasMore &&
}
{hasMore &&
}
diff --git a/src/components/RelayInfo/RelayReviewCard.tsx b/src/components/RelayInfo/RelayReviewCard.tsx
index 7441bcfa..4d414713 100644
--- a/src/components/RelayInfo/RelayReviewCard.tsx
+++ b/src/components/RelayInfo/RelayReviewCard.tsx
@@ -2,6 +2,7 @@ import { useSmartNoteNavigation } from '@/PageManager'
import { getStarsFromRelayReviewEvent } from '@/lib/event-metadata'
import { toNote } from '@/lib/link'
import { cn } from '@/lib/utils'
+import client from '@/services/client.service'
import { NostrEvent } from 'nostr-tools'
import { useMemo } from 'react'
import ClientTag from '../ClientTag'
@@ -31,6 +32,7 @@ export default function RelayReviewCard({
if (target.closest('button') || target.closest('[role="button"]') || target.closest('a') || target.closest('[data-embedded-note]') || target.closest('[data-parent-note-preview]')) {
return
}
+ client.addEventToCache(event)
navigateToNote(toNote(event))
}}
>
diff --git a/src/components/ReplyNote/index.tsx b/src/components/ReplyNote/index.tsx
index c0be38ef..f364e4c8 100644
--- a/src/components/ReplyNote/index.tsx
+++ b/src/components/ReplyNote/index.tsx
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { isMentioningMutedUsers } from '@/lib/event'
import { toNote } from '@/lib/link'
+import client from '@/services/client.service'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
@@ -65,6 +66,7 @@ export default function ReplyNote({
if (onClickReply) {
onClickReply(event)
} else {
+ client.addEventToCache(event)
navigateToNote(toNote(event))
}
}}
diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx
index 06d0396c..eb1d07a2 100644
--- a/src/components/SearchBar/index.tsx
+++ b/src/components/SearchBar/index.tsx
@@ -1,6 +1,7 @@
import SearchInput from '@/components/SearchInput'
import { useSearchProfiles } from '@/hooks'
import { toNote, toNoteList } from '@/lib/link'
+import client from '@/services/client.service'
import { randomString } from '@/lib/random'
import { normalizeUrl } from '@/lib/url'
import { normalizeToDTag } from '@/lib/search-parser'
@@ -90,12 +91,18 @@ const SearchBar = forwardRef<
blur()
if (params.type === 'note') {
+ // Prime event cache so note page finds it without re-fetch
+ client.fetchEvent(params.search).then((ev) => { if (ev) client.addEventToCache(ev) }).catch(() => {})
navigateToNote(toNote(params.search))
} else if (params.type === 'hashtag') {
navigateToHashtag(toNoteList({ hashtag: params.search }))
} else if (params.type === 'dtag') {
// Navigate to d-tag search using same pattern as hashtag
navigateToHashtag(toNoteList({ domain: params.search }))
+ } else if (params.type === 'profile') {
+ // Prime profile cache so profile page finds it without re-fetch
+ client.fetchProfileEvent(params.search).catch(() => {})
+ onSearch(params)
} else {
onSearch(params)
}
diff --git a/src/components/TextareaWithMentionAutocomplete/index.tsx b/src/components/TextareaWithMentionAutocomplete/index.tsx
index 191ac929..ce677e35 100644
--- a/src/components/TextareaWithMentionAutocomplete/index.tsx
+++ b/src/components/TextareaWithMentionAutocomplete/index.tsx
@@ -1,5 +1,7 @@
import { Textarea } from '@/components/ui/textarea'
import MentionList from '@/components/PostEditor/PostTextarea/Mention/MentionList'
+import { NEVENT_NADDR_PICKER_ID } from '@/components/PostEditor/PostTextarea/Mention/constants'
+import { useNeventPicker } from '@/components/PostEditor/PostTextarea/Mention/NeventNaddrPickerDialog'
import client from '@/services/client.service'
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
@@ -31,6 +33,7 @@ const TextareaWithMentionAutocomplete = forwardRef
(null)
const searchTimeoutRef = useRef | null>(null)
+ const neventPicker = useNeventPicker()
const closeMention = useCallback(() => {
setMentionOpen(false)
@@ -39,14 +42,29 @@ const TextareaWithMentionAutocomplete = forwardRef {
+ (id: string) => {
const ta = textareaRef.current
if (!ta) return
const start = mentionStart
const end = start + 1 + mentionQuery.length
const before = value.slice(0, start)
const after = value.slice(end)
- const insert = MENTION_INSERT_PREFIX + npub
+
+ if (id === NEVENT_NADDR_PICKER_ID && neventPicker) {
+ closeMention()
+ neventPicker.openNeventPicker((link: string) => {
+ const insert = link + ' '
+ onChange(before + insert + after)
+ setTimeout(() => {
+ ta.focus()
+ const newPos = start + insert.length
+ ta.setSelectionRange(newPos, newPos)
+ }, 0)
+ })
+ return
+ }
+
+ const insert = MENTION_INSERT_PREFIX + id
onChange(before + insert + after)
closeMention()
setTimeout(() => {
@@ -55,7 +73,7 @@ const TextareaWithMentionAutocomplete = forwardRef {
@@ -66,14 +84,15 @@ const TextareaWithMentionAutocomplete = forwardRef {
client
- .searchNpubsFromLocal(mentionQuery.trim(), MENTION_LIMIT)
+ .searchNpubsForMention(mentionQuery.trim(), MENTION_LIMIT)
.then((npubs) => {
const list = npubs ?? []
setMentionItems(list)
@@ -158,7 +177,7 @@ const TextareaWithMentionAutocomplete = forwardRef
insertMention(id)}
+ command={({ id }) => insertMention(id as string)}
selectedIndex={selectedIndex}
onSelectIndex={setSelectedIndex}
/>
diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts
index a18e7c6d..ca1b2d5e 100644
--- a/src/i18n/locales/de.ts
+++ b/src/i18n/locales/de.ts
@@ -42,6 +42,9 @@ export default {
'Write something...': 'Schreibe etwas...',
Cancel: 'Abbrechen',
Mentions: '@',
+ 'Search for event or address…': 'Nach Event oder Adresse suchen…',
+ 'Search notes…': 'Notizen suchen…',
+ 'No notes found': 'Keine Notizen gefunden',
'Failed to post': 'Posten fehlgeschlagen',
'Post successful': 'Beitrag erfolgreich',
'Your post has been published': 'Dein Beitrag wurde veröffentlicht',
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index fe0cad2f..fd98e52f 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -42,6 +42,9 @@ export default {
'Write something...': 'Write something...',
Cancel: 'Cancel',
Mentions: 'Mentions',
+ 'Search for event or address…': 'Search for event or address…',
+ 'Search notes…': 'Search notes…',
+ 'No notes found': 'No notes found',
'Failed to post': 'Failed to post',
'Post successful': 'Post successful',
'Your post has been published': 'Your post has been published',
diff --git a/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx b/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
index 1446585b..3b51b62f 100644
--- a/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
+++ b/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
@@ -29,6 +29,7 @@ import RelayIcon from '@/components/RelayIcon'
import GifPicker from '@/components/GifPicker'
import EmojiPickerDialog from '@/components/EmojiPickerDialog'
import Uploader from '@/components/PostEditor/Uploader'
+import { NeventPickerProvider } from '@/components/PostEditor/PostTextarea/Mention/NeventNaddrPickerDialog'
import logger from '@/lib/logger'
// Utility functions for thread creation
@@ -516,6 +517,7 @@ export default function CreateThreadDialog({
className="absolute inset-0 pointer-events-none"
aria-hidden
/>
+
{t('Create New Thread')}
@@ -1035,6 +1037,7 @@ export default function CreateThreadDialog({
+
)
}
diff --git a/src/pages/secondary/NotePage/index.tsx b/src/pages/secondary/NotePage/index.tsx
index c8f556be..0e3860a5 100644
--- a/src/pages/secondary/NotePage/index.tsx
+++ b/src/pages/secondary/NotePage/index.tsx
@@ -1,6 +1,7 @@
import { useSecondaryPage, useSmartNoteNavigation } from '@/PageManager'
import { ExtendedKind } from '@/constants'
import ContentPreview from '@/components/ContentPreview'
+import client from '@/services/client.service'
import Note from '@/components/Note'
import NoteInteractions from '@/components/NoteInteractions'
import NoteStats from '@/components/NoteStats'
@@ -559,6 +560,7 @@ function ParentNote({
)}
onClick={(e) => {
e.stopPropagation()
+ if (event) client.addEventToCache(event)
navigateToNote(toNote(event ?? eventBech32Id))
}}
>
@@ -567,6 +569,7 @@ function ParentNote({
className="truncate flex-1"
onClick={(e) => {
e.stopPropagation()
+ if (event) client.addEventToCache(event)
navigateToNote(toNote(event ?? eventBech32Id))
}}
>
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index fc359546..5c3514e7 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -1808,6 +1808,75 @@ class ClientService extends EventTarget {
return result.map((pubkey) => pubkeyToNpub(pubkey as string)).filter(Boolean) as string[]
}
+ /**
+ * Npubs for @-mention dropdown: (1) follow-list profiles matching the query,
+ * (2) local index, (3) relay search on SEARCHABLE_RELAY_URLS (same as search page).
+ */
+ async searchNpubsForMention(query: string, limit: number = 100): Promise