feat: quote
This commit is contained in:
@@ -2,7 +2,7 @@ import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
|
||||
import { MessageCircle } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import PostDialog from '../PostDialog'
|
||||
import { formatCount } from './utils'
|
||||
|
||||
@@ -10,17 +10,22 @@ export default function ReplyButton({ event }: { event: Event }) {
|
||||
const { noteStatsMap } = useNoteStats()
|
||||
const { pubkey } = useNostr()
|
||||
const { replyCount } = useMemo(() => noteStatsMap.get(event.id) ?? {}, [noteStatsMap, event.id])
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PostDialog parentEvent={event}>
|
||||
<>
|
||||
<button
|
||||
className="flex gap-1 items-center text-muted-foreground enabled:hover:text-blue-400"
|
||||
disabled={!pubkey}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
<MessageCircle size={16} />
|
||||
<div className="text-sm">{formatCount(replyCount)}</div>
|
||||
</button>
|
||||
</PostDialog>
|
||||
<PostDialog parentEvent={event} open={open} setOpen={setOpen} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger
|
||||
} from '@renderer/components/ui/alert-dialog'
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@renderer/components/ui/dropdown-menu'
|
||||
import { createRepostDraftEvent } from '@renderer/lib/draft-event'
|
||||
import { getSharableEventId } from '@renderer/lib/event'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { Loader, Repeat } from 'lucide-react'
|
||||
import { Loader, PencilLine, Repeat } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import PostDialog from '../PostDialog'
|
||||
import { formatCount } from './utils'
|
||||
|
||||
export default function RepostButton({
|
||||
@@ -30,6 +27,7 @@ export default function RepostButton({
|
||||
const { noteStatsMap, fetchNoteRepostCount, fetchNoteRepostedStatus, markNoteAsReposted } =
|
||||
useNoteStats()
|
||||
const [reposting, setReposting] = useState(false)
|
||||
const [isPostDialogOpen, setIsPostDialogOpen] = useState(false)
|
||||
const { repostCount, hasReposted } = useMemo(
|
||||
() => noteStatsMap.get(event.id) ?? {},
|
||||
[noteStatsMap, event.id]
|
||||
@@ -64,7 +62,7 @@ export default function RepostButton({
|
||||
|
||||
const targetRelayList = await client.fetchRelayList(event.pubkey)
|
||||
const repost = createRepostDraftEvent(event)
|
||||
await publish(repost, targetRelayList.read.slice(0, 3))
|
||||
await publish(repost, targetRelayList.read.slice(0, 5))
|
||||
markNoteAsReposted(event.id)
|
||||
} catch (error) {
|
||||
console.error('repost failed', error)
|
||||
@@ -76,33 +74,46 @@ export default function RepostButton({
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'flex gap-1 items-center enabled:hover:text-lime-500',
|
||||
hasReposted ? 'text-lime-500' : 'text-muted-foreground'
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
disabled={!canRepost}
|
||||
title="repost"
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'flex gap-1 items-center enabled:hover:text-lime-500',
|
||||
hasReposted ? 'text-lime-500' : 'text-muted-foreground'
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
disabled={!canRepost}
|
||||
title="repost"
|
||||
>
|
||||
{reposting ? <Loader className="animate-spin" size={16} /> : <Repeat size={16} />}
|
||||
<div className="text-sm">{formatCount(repostCount)}</div>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
{reposting ? <Loader className="animate-spin" size={16} /> : <Repeat size={16} />}
|
||||
<div className="text-sm">{formatCount(repostCount)}</div>
|
||||
</button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Repost Note</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to repost this note?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={(e) => e.stopPropagation()}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={repost}>Repost</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<DropdownMenuItem onClick={repost}>
|
||||
<Repeat /> Repost
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsPostDialogOpen(true)
|
||||
}}
|
||||
>
|
||||
<PencilLine /> Quote
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<PostDialog
|
||||
open={isPostDialogOpen}
|
||||
setOpen={setIsPostDialogOpen}
|
||||
defaultContent={'\nnostr:' + getSharableEventId(event)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
import PostDialog from '@renderer/components/PostDialog'
|
||||
import { Button } from '@renderer/components/ui/button'
|
||||
import { PencilLine } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function PostButton({ variant = 'titlebar' }: { variant?: 'titlebar' | 'sidebar' }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PostDialog>
|
||||
<Button variant={variant} size={variant} title="new post">
|
||||
<>
|
||||
<Button
|
||||
variant={variant}
|
||||
size={variant}
|
||||
title="new post"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
<PencilLine />
|
||||
{variant === 'sidebar' && <div>Post</div>}
|
||||
</Button>
|
||||
</PostDialog>
|
||||
<PostDialog open={open} setOpen={setOpen} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ import {
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
DialogTitle
|
||||
} from '@renderer/components/ui/dialog'
|
||||
import { ScrollArea } from '@renderer/components/ui/scroll-area'
|
||||
import { Textarea } from '@renderer/components/ui/textarea'
|
||||
@@ -15,23 +14,26 @@ import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { LoaderCircle } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useState } from 'react'
|
||||
import { Dispatch, useState } from 'react'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Mentions from './Metions'
|
||||
import Preview from './Preview'
|
||||
import Uploader from './Uploader'
|
||||
|
||||
export default function PostDialog({
|
||||
children,
|
||||
parentEvent
|
||||
defaultContent = '',
|
||||
parentEvent,
|
||||
open,
|
||||
setOpen
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
defaultContent?: string
|
||||
parentEvent?: Event
|
||||
open: boolean
|
||||
setOpen: Dispatch<boolean>
|
||||
}) {
|
||||
const { toast } = useToast()
|
||||
const { publish, checkLogin } = useNostr()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [content, setContent] = useState('')
|
||||
const [content, setContent] = useState(defaultContent)
|
||||
const [posting, setPosting] = useState(false)
|
||||
const canPost = !!content && !posting
|
||||
|
||||
@@ -88,7 +90,6 @@ export default function PostDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent className="p-0" withoutClose>
|
||||
<ScrollArea className="px-4 h-full max-h-screen">
|
||||
<div className="space-y-4 px-2 py-6">
|
||||
@@ -107,6 +108,7 @@ export default function PostDialog({
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Textarea
|
||||
className="h-32"
|
||||
onChange={handleTextareaChange}
|
||||
value={content}
|
||||
placeholder="Write something..."
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Event } from 'nostr-tools'
|
||||
import { formatTimestamp } from '@renderer/lib/timestamp'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useState } from 'react'
|
||||
import Content from '../Content'
|
||||
import LikeButton from '../NoteStats/LikeButton'
|
||||
import ParentNotePreview from '../ParentNotePreview'
|
||||
import PostDialog from '../PostDialog'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import LikeButton from '../NoteStats/LikeButton'
|
||||
import PostDialog from '../PostDialog'
|
||||
import ParentNotePreview from '../ParentNotePreview'
|
||||
|
||||
export default function ReplyNote({
|
||||
event,
|
||||
@@ -18,6 +19,8 @@ export default function ReplyNote({
|
||||
onClickParent?: (eventId: string) => void
|
||||
highlight?: boolean
|
||||
}) {
|
||||
const [isPostDialogOpen, setIsPostDialogOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex space-x-2 items-start rounded-lg p-2 transition-colors duration-500 ${highlight ? 'bg-highlight/50' : ''}`}
|
||||
@@ -35,12 +38,11 @@ export default function ReplyNote({
|
||||
<Content event={event} size="small" />
|
||||
<div className="flex gap-2 text-xs">
|
||||
<div className="text-muted-foreground/60">{formatTimestamp(event.created_at)}</div>
|
||||
<PostDialog parentEvent={event}>
|
||||
<div className="text-muted-foreground hover:text-primary cursor-pointer">reply</div>
|
||||
</PostDialog>
|
||||
<div className="text-muted-foreground hover:text-primary cursor-pointer">reply</div>
|
||||
</div>
|
||||
</div>
|
||||
<LikeButton event={event} variant="reply" />
|
||||
<PostDialog parentEvent={event} open={isPostDialogOpen} setOpen={setIsPostDialogOpen} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ class ClientService {
|
||||
this.eventBatchLoadFn.bind(this),
|
||||
{ cache: false }
|
||||
)
|
||||
private profileCache = new LRUCache<string, Promise<TProfile | undefined>>({ max: 10000 })
|
||||
private profileDataloader = new DataLoader<string, TProfile | undefined>(
|
||||
private profileCache = new LRUCache<string, Promise<TProfile>>({ max: 10000 })
|
||||
private profileDataloader = new DataLoader<string, TProfile>(
|
||||
(ids) => Promise.all(ids.map((id) => this._fetchProfileByBench32Id(id))),
|
||||
{ cacheMap: this.profileCache }
|
||||
)
|
||||
@@ -237,7 +237,15 @@ class ClientService {
|
||||
|
||||
let event: NEvent | undefined
|
||||
if (filter.ids) {
|
||||
event = await this.fetchEventById(relays, filter.ids[0])
|
||||
const eventId = filter.ids[0]
|
||||
if (eventId !== id) {
|
||||
const cache = this.eventCache.get(eventId)
|
||||
if (cache) {
|
||||
this.eventDataLoader.prime(id, cache)
|
||||
return cache
|
||||
}
|
||||
}
|
||||
event = await this.fetchEventById(relays, eventId)
|
||||
} else {
|
||||
event = await this.tryHarderToFetchEvent(relays, filter)
|
||||
}
|
||||
@@ -271,6 +279,14 @@ class ClientService {
|
||||
throw new Error('Invalid id')
|
||||
}
|
||||
|
||||
if (pubkey !== id) {
|
||||
const cache = this.profileCache.get(pubkey)
|
||||
if (cache) {
|
||||
this.profileDataloader.prime(id, cache)
|
||||
return cache
|
||||
}
|
||||
}
|
||||
|
||||
const profileFromBigRelays = this.fetchProfileFromBigRelaysDataloader.load(pubkey)
|
||||
if (profileFromBigRelays) {
|
||||
return profileFromBigRelays
|
||||
|
||||
Reference in New Issue
Block a user