feat: support customizing mentioned users
This commit is contained in:
@@ -1,57 +1,138 @@
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { extractMentions } from '@/lib/event'
|
import { extractMentions } from '@/lib/event'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import UserAvatar from '../UserAvatar'
|
|
||||||
import Username from '../Username'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { SimpleUserAvatar } from '../UserAvatar'
|
||||||
|
import { SimpleUsername } from '../Username'
|
||||||
|
|
||||||
export default function Mentions({
|
export default function Mentions({
|
||||||
content,
|
content,
|
||||||
|
mentions,
|
||||||
|
setMentions,
|
||||||
parentEvent
|
parentEvent
|
||||||
}: {
|
}: {
|
||||||
content: string
|
content: string
|
||||||
|
mentions: string[]
|
||||||
|
setMentions: (mentions: string[]) => void
|
||||||
parentEvent?: Event
|
parentEvent?: Event
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
const [pubkeys, setPubkeys] = useState<string[]>([])
|
const [pubkeys, setPubkeys] = useState<string[]>([])
|
||||||
|
const [relatedPubkeys, setRelatedPubkeys] = useState<string[]>([])
|
||||||
|
const [parentEventPubkey, setParentEventPubkey] = useState<string | undefined>()
|
||||||
|
const [addedPubkeys, setAddedPubkeys] = useState<string[]>([])
|
||||||
|
const [removedPubkeys, setRemovedPubkeys] = useState<string[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
extractMentions(content, parentEvent).then(({ pubkeys }) =>
|
extractMentions(content, parentEvent).then(({ pubkeys, relatedPubkeys, parentEventPubkey }) => {
|
||||||
setPubkeys(pubkeys.filter((p) => p !== pubkey))
|
setPubkeys(pubkeys.filter((p) => p !== pubkey))
|
||||||
)
|
setRelatedPubkeys(relatedPubkeys.filter((p) => p !== pubkey))
|
||||||
|
setParentEventPubkey(parentEventPubkey !== pubkey ? parentEventPubkey : undefined)
|
||||||
|
const potentialMentions = [...pubkeys, ...relatedPubkeys]
|
||||||
|
setAddedPubkeys((pubkeys) => {
|
||||||
|
return pubkeys.filter((p) => potentialMentions.includes(p))
|
||||||
|
})
|
||||||
|
setRemovedPubkeys((pubkeys) => {
|
||||||
|
return pubkeys.filter((p) => potentialMentions.includes(p))
|
||||||
|
})
|
||||||
|
})
|
||||||
}, [content, parentEvent, pubkey])
|
}, [content, parentEvent, pubkey])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newMentions = [...pubkeys]
|
||||||
|
addedPubkeys.forEach((pubkey) => {
|
||||||
|
if (!newMentions.includes(pubkey) && pubkey !== parentEventPubkey) {
|
||||||
|
newMentions.push(pubkey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
removedPubkeys.forEach((pubkey) => {
|
||||||
|
const index = newMentions.indexOf(pubkey)
|
||||||
|
if (index !== -1) {
|
||||||
|
newMentions.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (parentEventPubkey) {
|
||||||
|
newMentions.push(parentEventPubkey)
|
||||||
|
}
|
||||||
|
setMentions(newMentions)
|
||||||
|
}, [pubkeys, relatedPubkeys, parentEventPubkey, addedPubkeys, removedPubkeys])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<DropdownMenu>
|
||||||
<PopoverTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="px-3"
|
className="px-3"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={pubkeys.length === 0}
|
disabled={pubkeys.length === 0 && relatedPubkeys.length === 0 && !parentEventPubkey}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{t('Mentions')} {pubkeys.length > 0 && `(${pubkeys.length})`}
|
{t('Mentions')} {mentions.length > 0 && `(${mentions.length})`}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</DropdownMenuTrigger>
|
||||||
<PopoverContent className="w-48">
|
<DropdownMenuContent className="w-48">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-sm font-semibold">{t('Mentions')}:</div>
|
<DropdownMenuLabel>{t('Mentions')}:</DropdownMenuLabel>
|
||||||
{pubkeys.map((pubkey, index) => (
|
{parentEventPubkey && (
|
||||||
<div key={`${pubkey}-${index}`} className="flex gap-1 items-center">
|
<DropdownMenuCheckboxItem className="flex gap-1 items-center" checked disabled>
|
||||||
<UserAvatar userId={pubkey} size="small" />
|
<SimpleUserAvatar userId={parentEventPubkey} size="small" />
|
||||||
<Username
|
<SimpleUsername
|
||||||
|
userId={parentEventPubkey}
|
||||||
|
className="font-semibold text-sm truncate"
|
||||||
|
skeletonClassName="h-3"
|
||||||
|
/>
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
)}
|
||||||
|
{(pubkeys.length > 0 || relatedPubkeys.length > 0) && <DropdownMenuSeparator />}
|
||||||
|
{pubkeys.concat(relatedPubkeys).map((pubkey, index) => (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={`${pubkey}-${index}`}
|
||||||
|
className="flex gap-1 items-center cursor-pointer"
|
||||||
|
checked={mentions.includes(pubkey)}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (checked) {
|
||||||
|
setAddedPubkeys((pubkeys) => [...pubkeys, pubkey])
|
||||||
|
setRemovedPubkeys((pubkeys) => pubkeys.filter((p) => p !== pubkey))
|
||||||
|
} else {
|
||||||
|
setRemovedPubkeys((pubkeys) => [...pubkeys, pubkey])
|
||||||
|
setAddedPubkeys((pubkeys) => pubkeys.filter((p) => p !== pubkey))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SimpleUserAvatar userId={pubkey} size="small" />
|
||||||
|
<SimpleUsername
|
||||||
userId={pubkey}
|
userId={pubkey}
|
||||||
className="font-semibold text-sm truncate"
|
className="font-semibold text-sm truncate"
|
||||||
skeletonClassName="h-3"
|
skeletonClassName="h-3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DropdownMenuCheckboxItem>
|
||||||
))}
|
))}
|
||||||
|
{(relatedPubkeys.length > 0 || pubkeys.length > 0) && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setAddedPubkeys([...relatedPubkeys])
|
||||||
|
setRemovedPubkeys([])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Select all')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</DropdownMenuContent>
|
||||||
</Popover>
|
</DropdownMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default function NormalPostContent({
|
|||||||
const [addClientTag, setAddClientTag] = useState(false)
|
const [addClientTag, setAddClientTag] = useState(false)
|
||||||
const [specifiedRelayUrls, setSpecifiedRelayUrls] = useState<string[] | undefined>(undefined)
|
const [specifiedRelayUrls, setSpecifiedRelayUrls] = useState<string[] | undefined>(undefined)
|
||||||
const [uploadingPicture, setUploadingPicture] = useState(false)
|
const [uploadingPicture, setUploadingPicture] = useState(false)
|
||||||
|
const [mentions, setMentions] = useState<string[]>([])
|
||||||
const canPost = !!content && !posting
|
const canPost = !!content && !posting
|
||||||
|
|
||||||
const post = async (e: React.MouseEvent) => {
|
const post = async (e: React.MouseEvent) => {
|
||||||
@@ -79,11 +80,11 @@ export default function NormalPostContent({
|
|||||||
}
|
}
|
||||||
const draftEvent =
|
const draftEvent =
|
||||||
parentEvent && parentEvent.kind !== kinds.ShortTextNote
|
parentEvent && parentEvent.kind !== kinds.ShortTextNote
|
||||||
? await createCommentDraftEvent(content, parentEvent, pictureInfos, {
|
? await createCommentDraftEvent(content, parentEvent, pictureInfos, mentions, {
|
||||||
addClientTag,
|
addClientTag,
|
||||||
protectedEvent: !!specifiedRelayUrls
|
protectedEvent: !!specifiedRelayUrls
|
||||||
})
|
})
|
||||||
: await createShortTextNoteDraftEvent(content, pictureInfos, {
|
: await createShortTextNoteDraftEvent(content, pictureInfos, mentions, {
|
||||||
parentEvent,
|
parentEvent,
|
||||||
addClientTag,
|
addClientTag,
|
||||||
protectedEvent: !!specifiedRelayUrls
|
protectedEvent: !!specifiedRelayUrls
|
||||||
@@ -162,7 +163,12 @@ export default function NormalPostContent({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Mentions content={content} parentEvent={parentEvent} />
|
<Mentions
|
||||||
|
content={content}
|
||||||
|
parentEvent={parentEvent}
|
||||||
|
mentions={mentions}
|
||||||
|
setMentions={setMentions}
|
||||||
|
/>
|
||||||
<div className="flex gap-2 items-center max-sm:hidden">
|
<div className="flex gap-2 items-center max-sm:hidden">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default function PicturePostContent({ close }: { close: () => void }) {
|
|||||||
const [posting, setPosting] = useState(false)
|
const [posting, setPosting] = useState(false)
|
||||||
const [showMoreOptions, setShowMoreOptions] = useState(false)
|
const [showMoreOptions, setShowMoreOptions] = useState(false)
|
||||||
const [addClientTag, setAddClientTag] = useState(false)
|
const [addClientTag, setAddClientTag] = useState(false)
|
||||||
|
const [mentions, setMentions] = useState<string[]>([])
|
||||||
const [specifiedRelayUrls, setSpecifiedRelayUrls] = useState<string[] | undefined>(undefined)
|
const [specifiedRelayUrls, setSpecifiedRelayUrls] = useState<string[] | undefined>(undefined)
|
||||||
const canPost = !!content && !posting && pictureInfos.length > 0
|
const canPost = !!content && !posting && pictureInfos.length > 0
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ export default function PicturePostContent({ close }: { close: () => void }) {
|
|||||||
if (!pictureInfos.length) {
|
if (!pictureInfos.length) {
|
||||||
throw new Error(t('Picture note requires images'))
|
throw new Error(t('Picture note requires images'))
|
||||||
}
|
}
|
||||||
const draftEvent = await createPictureNoteDraftEvent(content, pictureInfos, {
|
const draftEvent = await createPictureNoteDraftEvent(content, pictureInfos, mentions, {
|
||||||
addClientTag,
|
addClientTag,
|
||||||
protectedEvent: !!specifiedRelayUrls
|
protectedEvent: !!specifiedRelayUrls
|
||||||
})
|
})
|
||||||
@@ -99,7 +100,7 @@ export default function PicturePostContent({ close }: { close: () => void }) {
|
|||||||
<ChevronDown className={`transition-transform ${showMoreOptions ? 'rotate-180' : ''}`} />
|
<ChevronDown className={`transition-transform ${showMoreOptions ? 'rotate-180' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Mentions content={content} />
|
<Mentions content={content} mentions={mentions} setMentions={setMentions} />
|
||||||
<div className="flex gap-2 items-center max-sm:hidden">
|
<div className="flex gap-2 items-center max-sm:hidden">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
extractCommentMentions,
|
extractCommentMentions,
|
||||||
extractHashtags,
|
extractHashtags,
|
||||||
extractImagesFromContent,
|
extractImagesFromContent,
|
||||||
extractMentions,
|
extractRelatedEventIds,
|
||||||
getEventCoordinate,
|
getEventCoordinate,
|
||||||
isProtectedEvent,
|
isProtectedEvent,
|
||||||
isReplaceable
|
isReplaceable
|
||||||
@@ -54,21 +54,29 @@ export function createRepostDraftEvent(event: Event): TDraftEvent {
|
|||||||
export async function createShortTextNoteDraftEvent(
|
export async function createShortTextNoteDraftEvent(
|
||||||
content: string,
|
content: string,
|
||||||
pictureInfos: { url: string; tags: string[][] }[],
|
pictureInfos: { url: string; tags: string[][] }[],
|
||||||
|
mentions: string[],
|
||||||
options: {
|
options: {
|
||||||
parentEvent?: Event
|
parentEvent?: Event
|
||||||
addClientTag?: boolean
|
addClientTag?: boolean
|
||||||
protectedEvent?: boolean
|
protectedEvent?: boolean
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<TDraftEvent> {
|
): Promise<TDraftEvent> {
|
||||||
const { pubkeys, otherRelatedEventIds, quoteEventIds, rootEventId, parentEventId } =
|
const { otherRelatedEventIds, quoteEventIds, rootEventId, parentEventId } =
|
||||||
await extractMentions(content, options.parentEvent)
|
await extractRelatedEventIds(content, options.parentEvent)
|
||||||
const hashtags = extractHashtags(content)
|
const hashtags = extractHashtags(content)
|
||||||
|
|
||||||
const tags = pubkeys
|
const tags = hashtags.map((hashtag) => ['t', hashtag])
|
||||||
.map((pubkey) => ['p', pubkey])
|
|
||||||
.concat(quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
|
||||||
.concat(hashtags.map((hashtag) => ['t', hashtag]))
|
|
||||||
|
|
||||||
|
// imeta tags
|
||||||
|
const { images } = extractImagesFromContent(content)
|
||||||
|
if (images && images.length) {
|
||||||
|
tags.push(...generateImetaTags(images, pictureInfos))
|
||||||
|
}
|
||||||
|
|
||||||
|
// q tags
|
||||||
|
tags.push(...quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
||||||
|
|
||||||
|
// e tags
|
||||||
if (rootEventId) {
|
if (rootEventId) {
|
||||||
tags.push(['e', rootEventId, client.getEventHint(rootEventId), 'root'])
|
tags.push(['e', rootEventId, client.getEventHint(rootEventId), 'root'])
|
||||||
}
|
}
|
||||||
@@ -79,10 +87,8 @@ export async function createShortTextNoteDraftEvent(
|
|||||||
tags.push(['e', parentEventId, client.getEventHint(parentEventId), 'reply'])
|
tags.push(['e', parentEventId, client.getEventHint(parentEventId), 'reply'])
|
||||||
}
|
}
|
||||||
|
|
||||||
const { images } = extractImagesFromContent(content)
|
// p tags
|
||||||
if (images && images.length) {
|
tags.push(...mentions.map((pubkey) => ['p', pubkey]))
|
||||||
tags.push(...generateImetaTags(images, pictureInfos))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.addClientTag) {
|
if (options.addClientTag) {
|
||||||
tags.push(['client', 'jumble'])
|
tags.push(['client', 'jumble'])
|
||||||
@@ -117,12 +123,13 @@ export function createRelaySetDraftEvent(relaySet: TRelaySet): TDraftEvent {
|
|||||||
export async function createPictureNoteDraftEvent(
|
export async function createPictureNoteDraftEvent(
|
||||||
content: string,
|
content: string,
|
||||||
pictureInfos: { url: string; tags: string[][] }[],
|
pictureInfos: { url: string; tags: string[][] }[],
|
||||||
|
mentions: string[],
|
||||||
options: {
|
options: {
|
||||||
addClientTag?: boolean
|
addClientTag?: boolean
|
||||||
protectedEvent?: boolean
|
protectedEvent?: boolean
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<TDraftEvent> {
|
): Promise<TDraftEvent> {
|
||||||
const { pubkeys, quoteEventIds } = await extractMentions(content)
|
const { quoteEventIds } = await extractRelatedEventIds(content)
|
||||||
const hashtags = extractHashtags(content)
|
const hashtags = extractHashtags(content)
|
||||||
if (!pictureInfos.length) {
|
if (!pictureInfos.length) {
|
||||||
throw new Error('No images found in content')
|
throw new Error('No images found in content')
|
||||||
@@ -130,9 +137,9 @@ export async function createPictureNoteDraftEvent(
|
|||||||
|
|
||||||
const tags = pictureInfos
|
const tags = pictureInfos
|
||||||
.map((info) => ['imeta', ...info.tags.map(([n, v]) => `${n} ${v}`)])
|
.map((info) => ['imeta', ...info.tags.map(([n, v]) => `${n} ${v}`)])
|
||||||
.concat(pubkeys.map((pubkey) => ['p', pubkey]))
|
|
||||||
.concat(quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
|
||||||
.concat(hashtags.map((hashtag) => ['t', hashtag]))
|
.concat(hashtags.map((hashtag) => ['t', hashtag]))
|
||||||
|
.concat(quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
||||||
|
.concat(mentions.map((pubkey) => ['p', pubkey]))
|
||||||
|
|
||||||
if (options.addClientTag) {
|
if (options.addClientTag) {
|
||||||
tags.push(['client', 'jumble'])
|
tags.push(['client', 'jumble'])
|
||||||
@@ -154,13 +161,13 @@ export async function createCommentDraftEvent(
|
|||||||
content: string,
|
content: string,
|
||||||
parentEvent: Event,
|
parentEvent: Event,
|
||||||
pictureInfos: { url: string; tags: string[][] }[],
|
pictureInfos: { url: string; tags: string[][] }[],
|
||||||
|
mentions: string[],
|
||||||
options: {
|
options: {
|
||||||
addClientTag?: boolean
|
addClientTag?: boolean
|
||||||
protectedEvent?: boolean
|
protectedEvent?: boolean
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<TDraftEvent> {
|
): Promise<TDraftEvent> {
|
||||||
const {
|
const {
|
||||||
pubkeys,
|
|
||||||
quoteEventIds,
|
quoteEventIds,
|
||||||
rootEventId,
|
rootEventId,
|
||||||
rootEventKind,
|
rootEventKind,
|
||||||
@@ -171,7 +178,20 @@ export async function createCommentDraftEvent(
|
|||||||
} = await extractCommentMentions(content, parentEvent)
|
} = await extractCommentMentions(content, parentEvent)
|
||||||
const hashtags = extractHashtags(content)
|
const hashtags = extractHashtags(content)
|
||||||
|
|
||||||
const tags = [
|
const tags = hashtags
|
||||||
|
.map((hashtag) => ['t', hashtag])
|
||||||
|
.concat(quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
||||||
|
|
||||||
|
const { images } = extractImagesFromContent(content)
|
||||||
|
if (images && images.length) {
|
||||||
|
tags.push(...generateImetaTags(images, pictureInfos))
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.push(
|
||||||
|
...mentions.filter((pubkey) => pubkey !== parentEventPubkey).map((pubkey) => ['p', pubkey])
|
||||||
|
)
|
||||||
|
tags.push(
|
||||||
|
...[
|
||||||
['E', rootEventId, client.getEventHint(rootEventId), rootEventPubkey],
|
['E', rootEventId, client.getEventHint(rootEventId), rootEventPubkey],
|
||||||
['K', rootEventKind.toString()],
|
['K', rootEventKind.toString()],
|
||||||
['P', rootEventPubkey],
|
['P', rootEventPubkey],
|
||||||
@@ -179,14 +199,7 @@ export async function createCommentDraftEvent(
|
|||||||
['k', parentEventKind.toString()],
|
['k', parentEventKind.toString()],
|
||||||
['p', parentEventPubkey]
|
['p', parentEventPubkey]
|
||||||
]
|
]
|
||||||
.concat(pubkeys.map((pubkey) => ['p', pubkey]))
|
)
|
||||||
.concat(quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
|
||||||
.concat(hashtags.map((hashtag) => ['t', hashtag]))
|
|
||||||
|
|
||||||
const { images } = extractImagesFromContent(content)
|
|
||||||
if (images && images.length) {
|
|
||||||
tags.push(...generateImetaTags(images, pictureInfos))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.addClientTag) {
|
if (options.addClientTag) {
|
||||||
tags.push(['client', 'jumble'])
|
tags.push(['client', 'jumble'])
|
||||||
|
|||||||
@@ -171,11 +171,9 @@ export function getProfileFromProfileEvent(event: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function extractMentions(content: string, parentEvent?: Event) {
|
export async function extractMentions(content: string, parentEvent?: Event) {
|
||||||
|
let parentEventPubkey: string | undefined
|
||||||
const pubkeySet = new Set<string>()
|
const pubkeySet = new Set<string>()
|
||||||
const relatedEventIdSet = new Set<string>()
|
const relatedPubkeySet = new Set<string>()
|
||||||
const quoteEventIdSet = new Set<string>()
|
|
||||||
let rootEventId: string | undefined
|
|
||||||
let parentEventId: string | undefined
|
|
||||||
const matches = content.match(
|
const matches = content.match(
|
||||||
/nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+|note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g
|
/nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+|note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g
|
||||||
)
|
)
|
||||||
@@ -192,7 +190,6 @@ export async function extractMentions(content: string, parentEvent?: Event) {
|
|||||||
const event = await client.fetchEvent(id)
|
const event = await client.fetchEvent(id)
|
||||||
if (event) {
|
if (event) {
|
||||||
pubkeySet.add(event.pubkey)
|
pubkeySet.add(event.pubkey)
|
||||||
quoteEventIdSet.add(event.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -200,13 +197,52 @@ export async function extractMentions(content: string, parentEvent?: Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parentEvent) {
|
||||||
|
parentEventPubkey = parentEvent.pubkey
|
||||||
|
parentEvent.tags.forEach(([tagName, tagValue]) => {
|
||||||
|
if (['p', 'P'].includes(tagName) && !!tagValue) {
|
||||||
|
relatedPubkeySet.add(tagValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentEventPubkey) {
|
||||||
|
pubkeySet.delete(parentEventPubkey)
|
||||||
|
relatedPubkeySet.delete(parentEventPubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pubkeys: Array.from(pubkeySet),
|
||||||
|
relatedPubkeys: Array.from(relatedPubkeySet).filter((p) => !pubkeySet.has(p)),
|
||||||
|
parentEventPubkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractRelatedEventIds(content: string, parentEvent?: Event) {
|
||||||
|
const relatedEventIdSet = new Set<string>()
|
||||||
|
const quoteEventIdSet = new Set<string>()
|
||||||
|
let rootEventId: string | undefined
|
||||||
|
let parentEventId: string | undefined
|
||||||
|
const matches = content.match(/nostr:(note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g)
|
||||||
|
|
||||||
|
for (const m of matches || []) {
|
||||||
|
try {
|
||||||
|
const id = m.split(':')[1]
|
||||||
|
const { type, data } = nip19.decode(id)
|
||||||
|
if (type === 'nevent') {
|
||||||
|
quoteEventIdSet.add(data.id)
|
||||||
|
} else if (type === 'note') {
|
||||||
|
quoteEventIdSet.add(data)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (parentEvent) {
|
if (parentEvent) {
|
||||||
relatedEventIdSet.add(parentEvent.id)
|
relatedEventIdSet.add(parentEvent.id)
|
||||||
pubkeySet.add(parentEvent.pubkey)
|
|
||||||
parentEvent.tags.forEach((tag) => {
|
parentEvent.tags.forEach((tag) => {
|
||||||
if (tagNameEquals('p')(tag)) {
|
if (isRootETag(tag)) {
|
||||||
pubkeySet.add(tag[1])
|
|
||||||
} else if (isRootETag(tag)) {
|
|
||||||
rootEventId = tag[1]
|
rootEventId = tag[1]
|
||||||
} else if (tagNameEquals('e')(tag)) {
|
} else if (tagNameEquals('e')(tag)) {
|
||||||
relatedEventIdSet.add(tag[1])
|
relatedEventIdSet.add(tag[1])
|
||||||
@@ -223,7 +259,6 @@ export async function extractMentions(content: string, parentEvent?: Event) {
|
|||||||
if (parentEventId) relatedEventIdSet.delete(parentEventId)
|
if (parentEventId) relatedEventIdSet.delete(parentEventId)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pubkeys: Array.from(pubkeySet),
|
|
||||||
otherRelatedEventIds: Array.from(relatedEventIdSet),
|
otherRelatedEventIds: Array.from(relatedEventIdSet),
|
||||||
quoteEventIds: Array.from(quoteEventIdSet),
|
quoteEventIds: Array.from(quoteEventIdSet),
|
||||||
rootEventId,
|
rootEventId,
|
||||||
@@ -232,7 +267,6 @@ export async function extractMentions(content: string, parentEvent?: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function extractCommentMentions(content: string, parentEvent: Event) {
|
export async function extractCommentMentions(content: string, parentEvent: Event) {
|
||||||
const pubkeySet = new Set<string>()
|
|
||||||
const quoteEventIdSet = new Set<string>()
|
const quoteEventIdSet = new Set<string>()
|
||||||
const rootEventId = parentEvent.tags.find(tagNameEquals('E'))?.[1] ?? parentEvent.id
|
const rootEventId = parentEvent.tags.find(tagNameEquals('E'))?.[1] ?? parentEvent.id
|
||||||
const rootEventKind = parentEvent.tags.find(tagNameEquals('K'))?.[1] ?? parentEvent.kind
|
const rootEventKind = parentEvent.tags.find(tagNameEquals('K'))?.[1] ?? parentEvent.kind
|
||||||
@@ -241,34 +275,23 @@ export async function extractCommentMentions(content: string, parentEvent: Event
|
|||||||
const parentEventKind = parentEvent.kind
|
const parentEventKind = parentEvent.kind
|
||||||
const parentEventPubkey = parentEvent.pubkey
|
const parentEventPubkey = parentEvent.pubkey
|
||||||
|
|
||||||
const matches = content.match(
|
const matches = content.match(/nostr:(note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g)
|
||||||
/nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+|note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const m of matches || []) {
|
for (const m of matches || []) {
|
||||||
try {
|
try {
|
||||||
const id = m.split(':')[1]
|
const id = m.split(':')[1]
|
||||||
const { type, data } = nip19.decode(id)
|
const { type, data } = nip19.decode(id)
|
||||||
if (type === 'nprofile') {
|
if (type === 'nevent') {
|
||||||
pubkeySet.add(data.pubkey)
|
quoteEventIdSet.add(data.id)
|
||||||
} else if (type === 'npub') {
|
} else if (type === 'note') {
|
||||||
pubkeySet.add(data)
|
quoteEventIdSet.add(data)
|
||||||
} else if (['nevent', 'note', 'naddr'].includes(type)) {
|
|
||||||
const event = await client.fetchEvent(id)
|
|
||||||
if (event) {
|
|
||||||
pubkeySet.add(event.pubkey)
|
|
||||||
quoteEventIdSet.add(event.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pubkeySet.add(parentEvent.pubkey)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pubkeys: Array.from(pubkeySet),
|
|
||||||
quoteEventIds: Array.from(quoteEventIdSet),
|
quoteEventIds: Array.from(quoteEventIdSet),
|
||||||
rootEventId,
|
rootEventId,
|
||||||
rootEventKind,
|
rootEventKind,
|
||||||
|
|||||||
Reference in New Issue
Block a user