fix: close mentions dropdown on mobile
This commit is contained in:
@@ -1,17 +1,10 @@
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||||
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 { Check } from 'lucide-react'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { HTMLAttributes, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { SimpleUserAvatar } from '../UserAvatar'
|
import { SimpleUserAvatar } from '../UserAvatar'
|
||||||
import { SimpleUsername } from '../Username'
|
import { SimpleUsername } from '../Username'
|
||||||
@@ -70,8 +63,8 @@ export default function Mentions({
|
|||||||
}, [pubkeys, relatedPubkeys, parentEventPubkey, addedPubkeys, removedPubkeys])
|
}, [pubkeys, relatedPubkeys, parentEventPubkey, addedPubkeys, removedPubkeys])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<Popover>
|
||||||
<DropdownMenuTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="px-3"
|
className="px-3"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -80,25 +73,22 @@ export default function Mentions({
|
|||||||
>
|
>
|
||||||
{t('Mentions')} {mentions.length > 0 && `(${mentions.length})`}
|
{t('Mentions')} {mentions.length > 0 && `(${mentions.length})`}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</PopoverTrigger>
|
||||||
<DropdownMenuContent className="w-48">
|
<PopoverContent className="w-52 p-0 py-1">
|
||||||
<div className="space-y-2">
|
<div className="space-y-1">
|
||||||
<DropdownMenuLabel>{t('Mentions')}:</DropdownMenuLabel>
|
|
||||||
{parentEventPubkey && (
|
{parentEventPubkey && (
|
||||||
<DropdownMenuCheckboxItem className="flex gap-1 items-center" checked disabled>
|
<PopoverCheckboxItem checked disabled>
|
||||||
<SimpleUserAvatar userId={parentEventPubkey} size="small" />
|
<SimpleUserAvatar userId={parentEventPubkey} size="small" />
|
||||||
<SimpleUsername
|
<SimpleUsername
|
||||||
userId={parentEventPubkey}
|
userId={parentEventPubkey}
|
||||||
className="font-semibold text-sm truncate"
|
className="font-semibold text-sm truncate"
|
||||||
skeletonClassName="h-3"
|
skeletonClassName="h-3"
|
||||||
/>
|
/>
|
||||||
</DropdownMenuCheckboxItem>
|
</PopoverCheckboxItem>
|
||||||
)}
|
)}
|
||||||
{(pubkeys.length > 0 || relatedPubkeys.length > 0) && <DropdownMenuSeparator />}
|
|
||||||
{pubkeys.concat(relatedPubkeys).map((pubkey, index) => (
|
{pubkeys.concat(relatedPubkeys).map((pubkey, index) => (
|
||||||
<DropdownMenuCheckboxItem
|
<PopoverCheckboxItem
|
||||||
key={`${pubkey}-${index}`}
|
key={`${pubkey}-${index}`}
|
||||||
className="flex gap-1 items-center cursor-pointer"
|
|
||||||
checked={mentions.includes(pubkey)}
|
checked={mentions.includes(pubkey)}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
@@ -116,23 +106,37 @@ export default function Mentions({
|
|||||||
className="font-semibold text-sm truncate"
|
className="font-semibold text-sm truncate"
|
||||||
skeletonClassName="h-3"
|
skeletonClassName="h-3"
|
||||||
/>
|
/>
|
||||||
</DropdownMenuCheckboxItem>
|
</PopoverCheckboxItem>
|
||||||
))}
|
))}
|
||||||
{(relatedPubkeys.length > 0 || pubkeys.length > 0) && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
setAddedPubkeys([...relatedPubkeys])
|
|
||||||
setRemovedPubkeys([])
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Select all')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuContent>
|
</PopoverContent>
|
||||||
</DropdownMenu>
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverCheckboxItem({
|
||||||
|
children,
|
||||||
|
checked,
|
||||||
|
onCheckedChange,
|
||||||
|
disabled,
|
||||||
|
...props
|
||||||
|
}: HTMLAttributes<HTMLButtonElement> & {
|
||||||
|
disabled?: boolean
|
||||||
|
checked: boolean
|
||||||
|
onCheckedChange?: (checked: boolean) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="px-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full rounded-md justify-start px-2"
|
||||||
|
onClick={() => onCheckedChange?.(!checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{checked ? <Check className="shrink-0" /> : <div className="w-4 shrink-0" />}
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,25 +171,30 @@ 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 parentEventPubkey = parentEvent ? parentEvent.pubkey : undefined
|
||||||
const pubkeySet = new Set<string>()
|
const pubkeys: string[] = []
|
||||||
const relatedPubkeySet = new Set<string>()
|
const relatedPubkeys: string[] = []
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const addToSet = (arr: string[], pubkey: string) => {
|
||||||
|
if (pubkey === parentEventPubkey) return
|
||||||
|
if (!arr.includes(pubkey)) arr.push(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
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 === 'nprofile') {
|
||||||
pubkeySet.add(data.pubkey)
|
addToSet(pubkeys, data.pubkey)
|
||||||
} else if (type === 'npub') {
|
} else if (type === 'npub') {
|
||||||
pubkeySet.add(data)
|
addToSet(pubkeys, data)
|
||||||
} else if (['nevent', 'note', 'naddr'].includes(type)) {
|
} else if (['nevent', 'note'].includes(type)) {
|
||||||
const event = await client.fetchEvent(id)
|
const event = await client.fetchEvent(id)
|
||||||
if (event) {
|
if (event) {
|
||||||
pubkeySet.add(event.pubkey)
|
addToSet(pubkeys, event.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -198,41 +203,44 @@ export async function extractMentions(content: string, parentEvent?: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parentEvent) {
|
if (parentEvent) {
|
||||||
parentEventPubkey = parentEvent.pubkey
|
|
||||||
parentEvent.tags.forEach(([tagName, tagValue]) => {
|
parentEvent.tags.forEach(([tagName, tagValue]) => {
|
||||||
if (['p', 'P'].includes(tagName) && !!tagValue) {
|
if (['p', 'P'].includes(tagName) && !!tagValue) {
|
||||||
relatedPubkeySet.add(tagValue)
|
addToSet(relatedPubkeys, tagValue)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentEventPubkey) {
|
|
||||||
pubkeySet.delete(parentEventPubkey)
|
|
||||||
relatedPubkeySet.delete(parentEventPubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pubkeys: Array.from(pubkeySet),
|
pubkeys,
|
||||||
relatedPubkeys: Array.from(relatedPubkeySet).filter((p) => !pubkeySet.has(p)),
|
relatedPubkeys: relatedPubkeys.filter((p) => !pubkeys.includes(p)),
|
||||||
parentEventPubkey
|
parentEventPubkey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractRelatedEventIds(content: string, parentEvent?: Event) {
|
export async function extractRelatedEventIds(content: string, parentEvent?: Event) {
|
||||||
const relatedEventIdSet = new Set<string>()
|
const relatedEventIds: string[] = []
|
||||||
const quoteEventIdSet = new Set<string>()
|
const quoteEventIds: string[] = []
|
||||||
let rootEventId: string | undefined
|
let rootEventId: string | undefined
|
||||||
let parentEventId: string | undefined
|
let parentEventId: string | undefined
|
||||||
const matches = content.match(/nostr:(note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g)
|
const matches = content.match(/nostr:(note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g)
|
||||||
|
|
||||||
|
const addToSet = (arr: string[], item: string) => {
|
||||||
|
if (!arr.includes(item)) arr.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeFromSet = (arr: string[], item: string) => {
|
||||||
|
const index = arr.indexOf(item)
|
||||||
|
if (index !== -1) arr.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
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 === 'nevent') {
|
if (type === 'nevent') {
|
||||||
quoteEventIdSet.add(data.id)
|
addToSet(quoteEventIds, data.id)
|
||||||
} else if (type === 'note') {
|
} else if (type === 'note') {
|
||||||
quoteEventIdSet.add(data)
|
addToSet(quoteEventIds, data)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -240,12 +248,12 @@ export async function extractRelatedEventIds(content: string, parentEvent?: Even
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parentEvent) {
|
if (parentEvent) {
|
||||||
relatedEventIdSet.add(parentEvent.id)
|
addToSet(relatedEventIds, parentEvent.id)
|
||||||
parentEvent.tags.forEach((tag) => {
|
parentEvent.tags.forEach((tag) => {
|
||||||
if (isRootETag(tag)) {
|
if (isRootETag(tag)) {
|
||||||
rootEventId = tag[1]
|
rootEventId = tag[1]
|
||||||
} else if (tagNameEquals('e')(tag)) {
|
} else if (tagNameEquals('e')(tag)) {
|
||||||
relatedEventIdSet.add(tag[1])
|
addToSet(relatedEventIds, tag[1])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (rootEventId || isReplyNoteEvent(parentEvent)) {
|
if (rootEventId || isReplyNoteEvent(parentEvent)) {
|
||||||
@@ -255,19 +263,22 @@ export async function extractRelatedEventIds(content: string, parentEvent?: Even
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rootEventId) relatedEventIdSet.delete(rootEventId)
|
if (rootEventId) {
|
||||||
if (parentEventId) relatedEventIdSet.delete(parentEventId)
|
removeFromSet(relatedEventIds, rootEventId)
|
||||||
|
}
|
||||||
|
if (parentEventId) {
|
||||||
|
removeFromSet(relatedEventIds, parentEventId)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
otherRelatedEventIds: Array.from(relatedEventIdSet),
|
otherRelatedEventIds: relatedEventIds,
|
||||||
quoteEventIds: Array.from(quoteEventIdSet),
|
quoteEventIds,
|
||||||
rootEventId,
|
rootEventId,
|
||||||
parentEventId
|
parentEventId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractCommentMentions(content: string, parentEvent: Event) {
|
export async function extractCommentMentions(content: string, parentEvent: Event) {
|
||||||
const quoteEventIdSet = new Set<string>()
|
const quoteEventIds: 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
|
||||||
const rootEventPubkey = parentEvent.tags.find(tagNameEquals('P'))?.[1] ?? parentEvent.pubkey
|
const rootEventPubkey = parentEvent.tags.find(tagNameEquals('P'))?.[1] ?? parentEvent.pubkey
|
||||||
@@ -275,16 +286,19 @@ 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(/nostr:(note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g)
|
const addToSet = (arr: string[], item: string) => {
|
||||||
|
if (!arr.includes(item)) arr.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = content.match(/nostr:(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 === 'nevent') {
|
if (type === 'nevent') {
|
||||||
quoteEventIdSet.add(data.id)
|
addToSet(quoteEventIds, data.id)
|
||||||
} else if (type === 'note') {
|
} else if (type === 'note') {
|
||||||
quoteEventIdSet.add(data)
|
addToSet(quoteEventIds, data)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -292,7 +306,7 @@ export async function extractCommentMentions(content: string, parentEvent: Event
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
quoteEventIds: Array.from(quoteEventIdSet),
|
quoteEventIds,
|
||||||
rootEventId,
|
rootEventId,
|
||||||
rootEventKind,
|
rootEventKind,
|
||||||
rootEventPubkey,
|
rootEventPubkey,
|
||||||
|
|||||||
Reference in New Issue
Block a user