feat: add support for commenting and reacting on external content

This commit is contained in:
codytseng
2025-11-15 16:26:19 +08:00
parent 5ba5c26fcd
commit 0bb62dd3fb
76 changed files with 1635 additions and 639 deletions

View File

@@ -1,6 +1,6 @@
import SearchInput from '@/components/SearchInput'
import { useSearchProfiles } from '@/hooks'
import { toNote } from '@/lib/link'
import { toExternalContent, toNote } from '@/lib/link'
import { randomString } from '@/lib/random'
import { normalizeUrl } from '@/lib/url'
import { cn } from '@/lib/utils'
@@ -8,7 +8,7 @@ import { useSecondaryPage } from '@/PageManager'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import modalManager from '@/services/modal-manager.service'
import { TSearchParams } from '@/types'
import { Hash, Notebook, Search, Server } from 'lucide-react'
import { Hash, MessageSquare, Notebook, Search, Server } from 'lucide-react'
import { nip19 } from 'nostr-tools'
import {
forwardRef,
@@ -45,6 +45,9 @@ const SearchBar = forwardRef<
if (['w', 'ws', 'ws:', 'ws:/', 'wss', 'wss:', 'wss:/'].includes(input)) {
return undefined
}
if (!input.includes('.')) {
return undefined
}
try {
return normalizeUrl(input)
} catch {
@@ -89,6 +92,8 @@ const SearchBar = forwardRef<
if (params.type === 'note') {
push(toNote(params.search))
} else if (params.type === 'externalContent') {
push(toExternalContent(params.search))
} else {
onSearch(params)
}
@@ -128,8 +133,9 @@ const SearchBar = forwardRef<
setSelectableOptions([
{ type: 'notes', search },
{ type: 'hashtag', search: hashtag, input: `#${hashtag}` },
...(normalizedUrl ? [{ type: 'relay', search: normalizedUrl, input: normalizedUrl }] : []),
{ type: 'externalContent', search, input },
{ type: 'hashtag', search: hashtag, input: `#${hashtag}` },
...profiles.map((profile) => ({
type: 'profile',
search: profile.npub,
@@ -197,6 +203,16 @@ const SearchBar = forwardRef<
/>
)
}
if (option.type === 'externalContent') {
return (
<ExternalContentItem
key={index}
selected={selectedIndex === index}
search={option.search}
onClick={() => updateSearch(option)}
/>
)
}
if (option.type === 'profiles') {
return (
<Item
@@ -322,10 +338,16 @@ function NormalItem({
onClick?: () => void
selected?: boolean
}) {
const { t } = useTranslation()
return (
<Item onClick={onClick} selected={selected}>
<Search className="text-muted-foreground" />
<div className="font-semibold truncate">{search}</div>
<div className="size-10 flex justify-center items-center">
<Search className="text-muted-foreground flex-shrink-0" />
</div>
<div className="flex flex-col min-w-0 flex-1">
<div className="font-semibold truncate">{search}</div>
<div className="text-sm text-muted-foreground">{t('Search for notes')}</div>
</div>
</Item>
)
}
@@ -339,10 +361,16 @@ function HashtagItem({
onClick?: () => void
selected?: boolean
}) {
const { t } = useTranslation()
return (
<Item onClick={onClick} selected={selected}>
<Hash className="text-muted-foreground" />
<div className="font-semibold truncate">{hashtag}</div>
<div className="size-10 flex justify-center items-center">
<Hash className="text-muted-foreground flex-shrink-0" />
</div>
<div className="flex flex-col min-w-0 flex-1">
<div className="font-semibold truncate">#{hashtag}</div>
<div className="text-sm text-muted-foreground">{t('Search for hashtag')}</div>
</div>
</Item>
)
}
@@ -356,10 +384,16 @@ function NoteItem({
onClick?: () => void
selected?: boolean
}) {
const { t } = useTranslation()
return (
<Item onClick={onClick} selected={selected}>
<Notebook className="text-muted-foreground" />
<div className="font-semibold truncate">{id}</div>
<div className="size-10 flex justify-center items-center">
<Notebook className="text-muted-foreground flex-shrink-0" />
</div>
<div className="flex flex-col min-w-0 flex-1">
<div className="font-semibold truncate font-mono text-sm">{id}</div>
<div className="text-sm text-muted-foreground">{t('Go to note')}</div>
</div>
</Item>
)
}
@@ -397,10 +431,39 @@ function RelayItem({
onClick?: () => void
selected?: boolean
}) {
const { t } = useTranslation()
return (
<Item onClick={onClick} selected={selected}>
<Server className="text-muted-foreground" />
<div className="font-semibold truncate">{url}</div>
<div className="size-10 flex justify-center items-center">
<Server className="text-muted-foreground flex-shrink-0" />
</div>
<div className="flex flex-col min-w-0 flex-1">
<div className="font-semibold truncate">{url}</div>
<div className="text-sm text-muted-foreground">{t('Go to relay')}</div>
</div>
</Item>
)
}
function ExternalContentItem({
search,
onClick,
selected
}: {
search: string
onClick?: () => void
selected?: boolean
}) {
const { t } = useTranslation()
return (
<Item onClick={onClick} selected={selected}>
<div className="size-10 flex justify-center items-center">
<MessageSquare className="text-muted-foreground flex-shrink-0" />
</div>
<div className="flex flex-col min-w-0 flex-1">
<div className="font-semibold truncate">{search}</div>
<div className="text-sm text-muted-foreground">{t('View discussions about this')}</div>
</div>
</Item>
)
}