14 changed files with 209 additions and 93 deletions
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
import { |
||||
EmbeddedEmojiParser, |
||||
EmbeddedEventParser, |
||||
EmbeddedImageParser, |
||||
EmbeddedMentionParser, |
||||
EmbeddedVideoParser, |
||||
parseContent |
||||
} from '@/lib/content-parser' |
||||
import { cn } from '@/lib/utils' |
||||
import { TEmoji } from '@/types' |
||||
import { useMemo } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
import { EmbeddedMentionText } from '../Embedded' |
||||
import Emoji from '../Emoji' |
||||
|
||||
export default function Content({ |
||||
content, |
||||
className, |
||||
emojiInfos |
||||
}: { |
||||
content: string |
||||
className?: string |
||||
emojiInfos?: TEmoji[] |
||||
}) { |
||||
const { t } = useTranslation() |
||||
const nodes = useMemo(() => { |
||||
return parseContent(content, [ |
||||
EmbeddedImageParser, |
||||
EmbeddedVideoParser, |
||||
EmbeddedEventParser, |
||||
EmbeddedMentionParser, |
||||
EmbeddedEmojiParser |
||||
]) |
||||
}, [content]) |
||||
|
||||
return ( |
||||
<span className={cn('pointer-events-none', className)}> |
||||
{nodes.map((node, index) => { |
||||
if (node.type === 'text') { |
||||
return node.data |
||||
} |
||||
if (node.type === 'image' || node.type === 'images') { |
||||
return index > 0 ? ` [${t('image')}]` : `[${t('image')}]` |
||||
} |
||||
if (node.type === 'video') { |
||||
return index > 0 ? ` [${t('video')}]` : `[${t('video')}]` |
||||
} |
||||
if (node.type === 'event') { |
||||
return index > 0 ? ` [${t('note')}]` : `[${t('note')}]` |
||||
} |
||||
if (node.type === 'mention') { |
||||
return <EmbeddedMentionText key={index} userId={node.data.split(':')[1]} /> |
||||
} |
||||
if (node.type === 'emoji') { |
||||
const shortcode = node.data.split(':')[1] |
||||
const emoji = emojiInfos?.find((e) => e.shortcode === shortcode) |
||||
if (!emoji) return node.data |
||||
return <Emoji key={index} emoji={emoji} /> |
||||
} |
||||
})} |
||||
</span> |
||||
) |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
import { useTranslatedEvent } from '@/hooks' |
||||
import { getEmojiInfosFromEmojiTags } from '@/lib/tag' |
||||
import { cn } from '@/lib/utils' |
||||
import { Event } from 'nostr-tools' |
||||
import { useMemo } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
import Content from './Content' |
||||
|
||||
export default function HighlightPreview({ |
||||
event, |
||||
className |
||||
}: { |
||||
event: Event |
||||
className?: string |
||||
}) { |
||||
const { t } = useTranslation() |
||||
const translatedEvent = useTranslatedEvent(event.id) |
||||
const emojiInfos = useMemo(() => getEmojiInfosFromEmojiTags(event.tags), [event]) |
||||
|
||||
return ( |
||||
<div className={cn('pointer-events-none', className)}> |
||||
[{t('Highlight')}]{' '} |
||||
<Content |
||||
content={translatedEvent?.content ?? event.content} |
||||
emojiInfos={emojiInfos} |
||||
className="italic pr-0.5" |
||||
/> |
||||
</div> |
||||
) |
||||
} |
||||
@ -1,68 +1,24 @@
@@ -1,68 +1,24 @@
|
||||
import { useTranslatedEvent } from '@/hooks' |
||||
import { |
||||
EmbeddedEmojiParser, |
||||
EmbeddedEventParser, |
||||
EmbeddedImageParser, |
||||
EmbeddedMentionParser, |
||||
EmbeddedVideoParser, |
||||
parseContent |
||||
} from '@/lib/content-parser' |
||||
import { getEmojiInfosFromEmojiTags } from '@/lib/tag' |
||||
import { cn } from '@/lib/utils' |
||||
import { Event } from 'nostr-tools' |
||||
import { useMemo } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
import { EmbeddedMentionText } from '../Embedded' |
||||
import Emoji from '../Emoji' |
||||
import Content from './Content' |
||||
|
||||
export default function NormalContentPreview({ |
||||
event, |
||||
className, |
||||
onClick |
||||
className |
||||
}: { |
||||
event: Event |
||||
className?: string |
||||
onClick?: React.MouseEventHandler<HTMLDivElement> | undefined |
||||
}) { |
||||
const { t } = useTranslation() |
||||
const translatedEvent = useTranslatedEvent(event?.id) |
||||
const nodes = useMemo(() => { |
||||
return parseContent(event.content, [ |
||||
EmbeddedImageParser, |
||||
EmbeddedVideoParser, |
||||
EmbeddedEventParser, |
||||
EmbeddedMentionParser, |
||||
EmbeddedEmojiParser |
||||
]) |
||||
}, [event, translatedEvent]) |
||||
|
||||
const emojiInfos = getEmojiInfosFromEmojiTags(event?.tags) |
||||
const emojiInfos = useMemo(() => getEmojiInfosFromEmojiTags(event?.tags), [event]) |
||||
|
||||
return ( |
||||
<div className={cn('pointer-events-none', className)} onClick={onClick}> |
||||
{nodes.map((node, index) => { |
||||
if (node.type === 'text') { |
||||
return node.data |
||||
} |
||||
if (node.type === 'image' || node.type === 'images') { |
||||
return index > 0 ? ` [${t('image')}]` : `[${t('image')}]` |
||||
} |
||||
if (node.type === 'video') { |
||||
return index > 0 ? ` [${t('video')}]` : `[${t('video')}]` |
||||
} |
||||
if (node.type === 'event') { |
||||
return index > 0 ? ` [${t('note')}]` : `[${t('note')}]` |
||||
} |
||||
if (node.type === 'mention') { |
||||
return <EmbeddedMentionText key={index} userId={node.data.split(':')[1]} /> |
||||
} |
||||
if (node.type === 'emoji') { |
||||
const shortcode = node.data.split(':')[1] |
||||
const emoji = emojiInfos.find((e) => e.shortcode === shortcode) |
||||
if (!emoji) return node.data |
||||
return <Emoji key={index} emoji={emoji} /> |
||||
} |
||||
})} |
||||
</div> |
||||
<Content |
||||
content={translatedEvent?.content ?? event.content} |
||||
className={className} |
||||
emojiInfos={emojiInfos} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
import { useTranslatedEvent } from '@/hooks' |
||||
import { getEmojiInfosFromEmojiTags } from '@/lib/tag' |
||||
import { cn } from '@/lib/utils' |
||||
import { Event } from 'nostr-tools' |
||||
import { useMemo } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
import Content from './Content' |
||||
|
||||
export default function PollPreview({ event, className }: { event: Event; className?: string }) { |
||||
const { t } = useTranslation() |
||||
const translatedEvent = useTranslatedEvent(event.id) |
||||
const emojiInfos = useMemo(() => getEmojiInfosFromEmojiTags(event.tags), [event]) |
||||
|
||||
return ( |
||||
<div className={cn('pointer-events-none', className)}> |
||||
[{t('Poll')}]{' '} |
||||
<Content |
||||
content={translatedEvent?.content ?? event.content} |
||||
emojiInfos={emojiInfos} |
||||
className="italic pr-0.5" |
||||
/> |
||||
</div> |
||||
) |
||||
} |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
import { useFetchEvent } from '@/hooks' |
||||
import { toNote } from '@/lib/link' |
||||
import { generateBech32IdFromETag, tagNameEquals } from '@/lib/tag' |
||||
import { cn } from '@/lib/utils' |
||||
import { useSecondaryPage } from '@/PageManager' |
||||
import { Vote } from 'lucide-react' |
||||
import { Event } from 'nostr-tools' |
||||
import { useMemo } from 'react' |
||||
import ContentPreview from '../../ContentPreview' |
||||
import { FormattedTimestamp } from '../../FormattedTimestamp' |
||||
import UserAvatar from '../../UserAvatar' |
||||
|
||||
export function PollResponseNotification({ |
||||
notification, |
||||
isNew = false |
||||
}: { |
||||
notification: Event |
||||
isNew?: boolean |
||||
}) { |
||||
const { push } = useSecondaryPage() |
||||
const eventId = useMemo(() => { |
||||
const eTag = notification.tags.find(tagNameEquals('e')) |
||||
return eTag ? generateBech32IdFromETag(eTag) : undefined |
||||
}, [notification]) |
||||
const { event: pollEvent } = useFetchEvent(eventId) |
||||
|
||||
if (!pollEvent) { |
||||
return null |
||||
} |
||||
|
||||
return ( |
||||
<div |
||||
className="flex gap-2 items-center cursor-pointer py-2" |
||||
onClick={() => push(toNote(pollEvent))} |
||||
> |
||||
<UserAvatar userId={notification.pubkey} size="small" /> |
||||
<Vote size={24} className="text-violet-400" /> |
||||
<ContentPreview |
||||
className={cn('truncate flex-1 w-0', isNew ? 'font-semibold' : 'text-muted-foreground')} |
||||
event={pollEvent} |
||||
/> |
||||
<div className="text-muted-foreground"> |
||||
<FormattedTimestamp timestamp={notification.created_at} short /> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
Loading…
Reference in new issue