feat: add support for publishing highlights
This commit is contained in:
@@ -15,7 +15,7 @@ import { cn } from '@/lib/utils'
|
|||||||
import mediaUpload from '@/services/media-upload.service'
|
import mediaUpload from '@/services/media-upload.service'
|
||||||
import { TImetaInfo } from '@/types'
|
import { TImetaInfo } from '@/types'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
EmbeddedHashtag,
|
EmbeddedHashtag,
|
||||||
EmbeddedLNInvoice,
|
EmbeddedLNInvoice,
|
||||||
@@ -25,8 +25,10 @@ import {
|
|||||||
} from '../Embedded'
|
} from '../Embedded'
|
||||||
import Emoji from '../Emoji'
|
import Emoji from '../Emoji'
|
||||||
import ExternalLink from '../ExternalLink'
|
import ExternalLink from '../ExternalLink'
|
||||||
|
import HighlightButton from '../HighlightButton'
|
||||||
import ImageGallery from '../ImageGallery'
|
import ImageGallery from '../ImageGallery'
|
||||||
import MediaPlayer from '../MediaPlayer'
|
import MediaPlayer from '../MediaPlayer'
|
||||||
|
import PostEditor from '../PostEditor'
|
||||||
import WebPreview from '../WebPreview'
|
import WebPreview from '../WebPreview'
|
||||||
import XEmbeddedPost from '../XEmbeddedPost'
|
import XEmbeddedPost from '../XEmbeddedPost'
|
||||||
import YoutubeEmbeddedPlayer from '../YoutubeEmbeddedPlayer'
|
import YoutubeEmbeddedPlayer from '../YoutubeEmbeddedPlayer'
|
||||||
@@ -35,13 +37,18 @@ export default function Content({
|
|||||||
event,
|
event,
|
||||||
content,
|
content,
|
||||||
className,
|
className,
|
||||||
mustLoadMedia
|
mustLoadMedia,
|
||||||
|
enableHighlight = false
|
||||||
}: {
|
}: {
|
||||||
event?: Event
|
event?: Event
|
||||||
content?: string
|
content?: string
|
||||||
className?: string
|
className?: string
|
||||||
mustLoadMedia?: boolean
|
mustLoadMedia?: boolean
|
||||||
|
enableHighlight?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [showHighlightEditor, setShowHighlightEditor] = useState(false)
|
||||||
|
const [selectedText, setSelectedText] = useState('')
|
||||||
const translatedEvent = useTranslatedEvent(event?.id)
|
const translatedEvent = useTranslatedEvent(event?.id)
|
||||||
const { nodes, allImages, lastNormalUrl, emojiInfos } = useMemo(() => {
|
const { nodes, allImages, lastNormalUrl, emojiInfos } = useMemo(() => {
|
||||||
const _content = translatedEvent?.content ?? event?.content ?? content
|
const _content = translatedEvent?.content ?? event?.content ?? content
|
||||||
@@ -95,81 +102,99 @@ export default function Content({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleHighlight = (text: string) => {
|
||||||
|
setSelectedText(text)
|
||||||
|
setShowHighlightEditor(true)
|
||||||
|
}
|
||||||
|
|
||||||
let imageIndex = 0
|
let imageIndex = 0
|
||||||
return (
|
return (
|
||||||
<div className={cn('text-wrap break-words whitespace-pre-wrap', className)}>
|
<>
|
||||||
{nodes.map((node, index) => {
|
<div ref={contentRef} className={cn('text-wrap break-words whitespace-pre-wrap', className)}>
|
||||||
if (node.type === 'text') {
|
{nodes.map((node, index) => {
|
||||||
return node.data
|
if (node.type === 'text') {
|
||||||
}
|
return node.data
|
||||||
if (node.type === 'image' || node.type === 'images') {
|
}
|
||||||
const start = imageIndex
|
if (node.type === 'image' || node.type === 'images') {
|
||||||
const end = imageIndex + (Array.isArray(node.data) ? node.data.length : 1)
|
const start = imageIndex
|
||||||
imageIndex = end
|
const end = imageIndex + (Array.isArray(node.data) ? node.data.length : 1)
|
||||||
return (
|
imageIndex = end
|
||||||
<ImageGallery
|
return (
|
||||||
className="mt-2"
|
<ImageGallery
|
||||||
key={index}
|
className="mt-2"
|
||||||
images={allImages}
|
key={index}
|
||||||
start={start}
|
images={allImages}
|
||||||
end={end}
|
start={start}
|
||||||
mustLoad={mustLoadMedia}
|
end={end}
|
||||||
/>
|
mustLoad={mustLoadMedia}
|
||||||
)
|
/>
|
||||||
}
|
)
|
||||||
if (node.type === 'media') {
|
}
|
||||||
return (
|
if (node.type === 'media') {
|
||||||
<MediaPlayer className="mt-2" key={index} src={node.data} mustLoad={mustLoadMedia} />
|
return (
|
||||||
)
|
<MediaPlayer className="mt-2" key={index} src={node.data} mustLoad={mustLoadMedia} />
|
||||||
}
|
)
|
||||||
if (node.type === 'url') {
|
}
|
||||||
return <ExternalLink url={node.data} key={index} />
|
if (node.type === 'url') {
|
||||||
}
|
return <ExternalLink url={node.data} key={index} />
|
||||||
if (node.type === 'invoice') {
|
}
|
||||||
return <EmbeddedLNInvoice invoice={node.data} key={index} className="mt-2" />
|
if (node.type === 'invoice') {
|
||||||
}
|
return <EmbeddedLNInvoice invoice={node.data} key={index} className="mt-2" />
|
||||||
if (node.type === 'websocket-url') {
|
}
|
||||||
return <EmbeddedWebsocketUrl url={node.data} key={index} />
|
if (node.type === 'websocket-url') {
|
||||||
}
|
return <EmbeddedWebsocketUrl url={node.data} key={index} />
|
||||||
if (node.type === 'event') {
|
}
|
||||||
const id = node.data.split(':')[1]
|
if (node.type === 'event') {
|
||||||
return <EmbeddedNote key={index} noteId={id} className="mt-2" />
|
const id = node.data.split(':')[1]
|
||||||
}
|
return <EmbeddedNote key={index} noteId={id} className="mt-2" />
|
||||||
if (node.type === 'mention') {
|
}
|
||||||
return <EmbeddedMention key={index} userId={node.data.split(':')[1]} />
|
if (node.type === 'mention') {
|
||||||
}
|
return <EmbeddedMention key={index} userId={node.data.split(':')[1]} />
|
||||||
if (node.type === 'hashtag') {
|
}
|
||||||
return <EmbeddedHashtag hashtag={node.data} key={index} />
|
if (node.type === 'hashtag') {
|
||||||
}
|
return <EmbeddedHashtag hashtag={node.data} key={index} />
|
||||||
if (node.type === 'emoji') {
|
}
|
||||||
const shortcode = node.data.split(':')[1]
|
if (node.type === 'emoji') {
|
||||||
const emoji = emojiInfos.find((e) => e.shortcode === shortcode)
|
const shortcode = node.data.split(':')[1]
|
||||||
if (!emoji) return node.data
|
const emoji = emojiInfos.find((e) => e.shortcode === shortcode)
|
||||||
return <Emoji classNames={{ img: 'mb-1' }} emoji={emoji} key={index} />
|
if (!emoji) return node.data
|
||||||
}
|
return <Emoji classNames={{ img: 'mb-1' }} emoji={emoji} key={index} />
|
||||||
if (node.type === 'youtube') {
|
}
|
||||||
return (
|
if (node.type === 'youtube') {
|
||||||
<YoutubeEmbeddedPlayer
|
return (
|
||||||
key={index}
|
<YoutubeEmbeddedPlayer
|
||||||
url={node.data}
|
key={index}
|
||||||
className="mt-2"
|
url={node.data}
|
||||||
mustLoad={mustLoadMedia}
|
className="mt-2"
|
||||||
/>
|
mustLoad={mustLoadMedia}
|
||||||
)
|
/>
|
||||||
}
|
)
|
||||||
if (node.type === 'x-post') {
|
}
|
||||||
return (
|
if (node.type === 'x-post') {
|
||||||
<XEmbeddedPost
|
return (
|
||||||
key={index}
|
<XEmbeddedPost
|
||||||
url={node.data}
|
key={index}
|
||||||
className="mt-2"
|
url={node.data}
|
||||||
mustLoad={mustLoadMedia}
|
className="mt-2"
|
||||||
/>
|
mustLoad={mustLoadMedia}
|
||||||
)
|
/>
|
||||||
}
|
)
|
||||||
return null
|
}
|
||||||
})}
|
return null
|
||||||
{lastNormalUrl && <WebPreview className="mt-2" url={lastNormalUrl} />}
|
})}
|
||||||
</div>
|
{lastNormalUrl && <WebPreview className="mt-2" url={lastNormalUrl} />}
|
||||||
|
</div>
|
||||||
|
{enableHighlight && (
|
||||||
|
<HighlightButton onHighlight={handleHighlight} containerRef={contentRef} />
|
||||||
|
)}
|
||||||
|
{enableHighlight && (
|
||||||
|
<PostEditor
|
||||||
|
highlightedText={selectedText}
|
||||||
|
parentStuff={event}
|
||||||
|
open={showHighlightEditor}
|
||||||
|
setOpen={setShowHighlightEditor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/components/HighlightButton/index.tsx
Normal file
115
src/components/HighlightButton/index.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Highlighter } from 'lucide-react'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface HighlightButtonProps {
|
||||||
|
onHighlight: (selectedText: string) => void
|
||||||
|
containerRef?: React.RefObject<HTMLElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HighlightButton({ onHighlight, containerRef }: HighlightButtonProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [position, setPosition] = useState<{ top: number; left: number } | null>(null)
|
||||||
|
const [selectedText, setSelectedText] = useState('')
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSelectionEnd = () => {
|
||||||
|
// Use a small delay to ensure selection is complete
|
||||||
|
setTimeout(() => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
const text = selection?.toString().trim()
|
||||||
|
|
||||||
|
if (!text || text.length === 0) {
|
||||||
|
setPosition(null)
|
||||||
|
setSelectedText('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if selection is within the container (if provided)
|
||||||
|
if (containerRef?.current) {
|
||||||
|
const range = selection?.getRangeAt(0)
|
||||||
|
if (range && !containerRef.current.contains(range.commonAncestorContainer)) {
|
||||||
|
setPosition(null)
|
||||||
|
setSelectedText('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = selection?.getRangeAt(0)
|
||||||
|
if (!range) return
|
||||||
|
|
||||||
|
// Get the bounding rect of the entire selection
|
||||||
|
const rect = range.getBoundingClientRect()
|
||||||
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
||||||
|
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
|
||||||
|
|
||||||
|
// Position button above the selection area, centered horizontally
|
||||||
|
setPosition({
|
||||||
|
top: rect.top + scrollTop - 48, // 48px above the selection
|
||||||
|
left: rect.left + scrollLeft + rect.width / 2 // Center of the selection
|
||||||
|
})
|
||||||
|
setSelectedText(text)
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only listen to mouseup and touchend (when user finishes selection)
|
||||||
|
document.addEventListener('mouseup', handleSelectionEnd)
|
||||||
|
document.addEventListener('touchend', handleSelectionEnd)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mouseup', handleSelectionEnd)
|
||||||
|
document.removeEventListener('touchend', handleSelectionEnd)
|
||||||
|
}
|
||||||
|
}, [containerRef])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (buttonRef.current && !buttonRef.current.contains(event.target as Node)) {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection?.toString().trim()) {
|
||||||
|
setPosition(null)
|
||||||
|
setSelectedText('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!position || !selectedText) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed z-50 animate-in fade-in-0 slide-in-from-bottom-4 duration-200"
|
||||||
|
style={{
|
||||||
|
top: `${position.top}px`,
|
||||||
|
left: `${position.left}px`
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
ref={buttonRef}
|
||||||
|
size="sm"
|
||||||
|
variant="default"
|
||||||
|
className="shadow-lg gap-2 -translate-x-1/2"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onHighlight(selectedText)
|
||||||
|
// Clear selection after highlighting
|
||||||
|
window.getSelection()?.removeAllRanges()
|
||||||
|
setPosition(null)
|
||||||
|
setSelectedText('')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Highlighter className="h-4 w-4" />
|
||||||
|
{t('Highlight')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ export default function Highlight({ event, className }: { event: Event; classNam
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('text-wrap break-words whitespace-pre-wrap space-y-4', className)}>
|
<div className={cn('text-wrap break-words whitespace-pre-wrap space-y-4', className)}>
|
||||||
{comment && <Content event={createFakeEvent({ content: comment })} />}
|
{comment && <Content event={createFakeEvent({ content: comment, tags: event.tags })} />}
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="w-1 flex-shrink-0 my-1 bg-primary/60 rounded-md" />
|
<div className="w-1 flex-shrink-0 my-1 bg-primary/60 rounded-md" />
|
||||||
<div className="italic whitespace-pre-line">
|
<div className="italic whitespace-pre-line">
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { SecondaryPageLink, useSecondaryPage } from '@/PageManager'
|
import { SecondaryPageLink, useSecondaryPage } from '@/PageManager'
|
||||||
import ImageWithLightbox from '@/components/ImageWithLightbox'
|
import ImageWithLightbox from '@/components/ImageWithLightbox'
|
||||||
|
import HighlightButton from '@/components/HighlightButton'
|
||||||
|
import PostEditor from '@/components/PostEditor'
|
||||||
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata'
|
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata'
|
||||||
import { toNote, toNoteList, toProfile } from '@/lib/link'
|
import { toNote, toNoteList, toProfile } from '@/lib/link'
|
||||||
import { ExternalLink } from 'lucide-react'
|
import { ExternalLink } from 'lucide-react'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, kinds } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo, useRef, useState } from 'react'
|
||||||
import Markdown from 'react-markdown'
|
import Markdown from 'react-markdown'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
import NostrNode from './NostrNode'
|
import NostrNode from './NostrNode'
|
||||||
@@ -20,6 +22,14 @@ export default function LongFormArticle({
|
|||||||
}) {
|
}) {
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const metadata = useMemo(() => getLongFormArticleMetadataFromEvent(event), [event])
|
const metadata = useMemo(() => getLongFormArticleMetadataFromEvent(event), [event])
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [showHighlightEditor, setShowHighlightEditor] = useState(false)
|
||||||
|
const [selectedText, setSelectedText] = useState('')
|
||||||
|
|
||||||
|
const handleHighlight = (text: string) => {
|
||||||
|
setSelectedText(text)
|
||||||
|
setShowHighlightEditor(true)
|
||||||
|
}
|
||||||
|
|
||||||
const components = useMemo(
|
const components = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -74,54 +84,64 @@ export default function LongFormArticle({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}) as Components,
|
}) as Components,
|
||||||
[]
|
[event.pubkey]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`prose prose-zinc max-w-none dark:prose-invert break-words overflow-wrap-anywhere ${className || ''}`}
|
<div
|
||||||
>
|
ref={contentRef}
|
||||||
<h1 className="break-words">{metadata.title}</h1>
|
className={`prose prose-zinc max-w-none dark:prose-invert break-words overflow-wrap-anywhere ${className || ''}`}
|
||||||
{metadata.summary && (
|
|
||||||
<blockquote>
|
|
||||||
<p className="break-words">{metadata.summary}</p>
|
|
||||||
</blockquote>
|
|
||||||
)}
|
|
||||||
{metadata.image && (
|
|
||||||
<ImageWithLightbox
|
|
||||||
image={{ url: metadata.image, pubkey: event.pubkey }}
|
|
||||||
className="w-full aspect-[3/1] object-cover my-0"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Markdown
|
|
||||||
remarkPlugins={[remarkGfm, remarkNostr]}
|
|
||||||
urlTransform={(url) => {
|
|
||||||
if (url.startsWith('nostr:')) {
|
|
||||||
return url.slice(6) // Remove 'nostr:' prefix for rendering
|
|
||||||
}
|
|
||||||
return url
|
|
||||||
}}
|
|
||||||
components={components}
|
|
||||||
>
|
>
|
||||||
{event.content}
|
<h1 className="break-words">{metadata.title}</h1>
|
||||||
</Markdown>
|
{metadata.summary && (
|
||||||
{metadata.tags.length > 0 && (
|
<blockquote>
|
||||||
<div className="flex gap-2 flex-wrap pb-2">
|
<p className="break-words">{metadata.summary}</p>
|
||||||
{metadata.tags.map((tag) => (
|
</blockquote>
|
||||||
<div
|
)}
|
||||||
key={tag}
|
{metadata.image && (
|
||||||
title={tag}
|
<ImageWithLightbox
|
||||||
className="flex items-center rounded-full px-3 bg-muted text-muted-foreground max-w-44 cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
image={{ url: metadata.image, pubkey: event.pubkey }}
|
||||||
onClick={(e) => {
|
className="w-full aspect-[3/1] object-cover my-0"
|
||||||
e.stopPropagation()
|
/>
|
||||||
push(toNoteList({ hashtag: tag, kinds: [kinds.LongFormArticle] }))
|
)}
|
||||||
}}
|
<Markdown
|
||||||
>
|
remarkPlugins={[remarkGfm, remarkNostr]}
|
||||||
#<span className="truncate">{tag}</span>
|
urlTransform={(url) => {
|
||||||
</div>
|
if (url.startsWith('nostr:')) {
|
||||||
))}
|
return url.slice(6) // Remove 'nostr:' prefix for rendering
|
||||||
</div>
|
}
|
||||||
)}
|
return url
|
||||||
</div>
|
}}
|
||||||
|
components={components}
|
||||||
|
>
|
||||||
|
{event.content}
|
||||||
|
</Markdown>
|
||||||
|
{metadata.tags.length > 0 && (
|
||||||
|
<div className="flex gap-2 flex-wrap pb-2">
|
||||||
|
{metadata.tags.map((tag) => (
|
||||||
|
<div
|
||||||
|
key={tag}
|
||||||
|
title={tag}
|
||||||
|
className="flex items-center rounded-full px-3 bg-muted text-muted-foreground max-w-44 cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
push(toNoteList({ hashtag: tag, kinds: [kinds.LongFormArticle] }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
#<span className="truncate">{tag}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<HighlightButton onHighlight={handleHighlight} containerRef={contentRef} />
|
||||||
|
<PostEditor
|
||||||
|
highlightedText={selectedText}
|
||||||
|
parentStuff={event}
|
||||||
|
open={showHighlightEditor}
|
||||||
|
setOpen={setShowHighlightEditor}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export default function Note({
|
|||||||
} else if (event.kind === ExtendedKind.FOLLOW_PACK) {
|
} else if (event.kind === ExtendedKind.FOLLOW_PACK) {
|
||||||
content = <FollowPack className="mt-2" event={event} />
|
content = <FollowPack className="mt-2" event={event} />
|
||||||
} else {
|
} else {
|
||||||
content = <Content className="mt-2" event={event} />
|
content = <Content className="mt-2" event={event} enableHighlight />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Highlighter } from 'lucide-react'
|
||||||
|
import { Event } from 'nostr-tools'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Notification from './Notification'
|
||||||
|
|
||||||
|
export function HighlightNotification({
|
||||||
|
notification,
|
||||||
|
isNew = false
|
||||||
|
}: {
|
||||||
|
notification: Event
|
||||||
|
isNew?: boolean
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
notificationId={notification.id}
|
||||||
|
icon={<Highlighter size={24} className="text-orange-400" />}
|
||||||
|
sender={notification.pubkey}
|
||||||
|
sentAt={notification.created_at}
|
||||||
|
targetEvent={notification}
|
||||||
|
description={t('highlighted your note')}
|
||||||
|
isNew={isNew}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { useNostr } from '@/providers/NostrProvider'
|
|||||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, kinds } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import { HighlightNotification } from './HighlightNotification'
|
||||||
import { MentionNotification } from './MentionNotification'
|
import { MentionNotification } from './MentionNotification'
|
||||||
import { PollResponseNotification } from './PollResponseNotification'
|
import { PollResponseNotification } from './PollResponseNotification'
|
||||||
import { ReactionNotification } from './ReactionNotification'
|
import { ReactionNotification } from './ReactionNotification'
|
||||||
@@ -60,5 +61,8 @@ export function NotificationItem({
|
|||||||
if (notification.kind === ExtendedKind.POLL_RESPONSE) {
|
if (notification.kind === ExtendedKind.POLL_RESPONSE) {
|
||||||
return <PollResponseNotification notification={notification} isNew={isNew} />
|
return <PollResponseNotification notification={notification} isNew={isNew} />
|
||||||
}
|
}
|
||||||
|
if (notification.kind === kinds.Highlights) {
|
||||||
|
return <HighlightNotification notification={notification} isNew={isNew} />
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
case 'mentions':
|
case 'mentions':
|
||||||
return [
|
return [
|
||||||
kinds.ShortTextNote,
|
kinds.ShortTextNote,
|
||||||
|
kinds.Highlights,
|
||||||
ExtendedKind.COMMENT,
|
ExtendedKind.COMMENT,
|
||||||
ExtendedKind.VOICE_COMMENT,
|
ExtendedKind.VOICE_COMMENT,
|
||||||
ExtendedKind.POLL
|
ExtendedKind.POLL
|
||||||
@@ -70,6 +71,7 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
kinds.GenericRepost,
|
kinds.GenericRepost,
|
||||||
kinds.Reaction,
|
kinds.Reaction,
|
||||||
kinds.Zap,
|
kinds.Zap,
|
||||||
|
kinds.Highlights,
|
||||||
ExtendedKind.COMMENT,
|
ExtendedKind.COMMENT,
|
||||||
ExtendedKind.POLL_RESPONSE,
|
ExtendedKind.POLL_RESPONSE,
|
||||||
ExtendedKind.VOICE_COMMENT,
|
ExtendedKind.VOICE_COMMENT,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import Note from '@/components/Note'
|
import Note from '@/components/Note'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
|
import { BIG_RELAY_URLS } from '@/constants'
|
||||||
import {
|
import {
|
||||||
createCommentDraftEvent,
|
createCommentDraftEvent,
|
||||||
|
createHighlightDraftEvent,
|
||||||
createPollDraftEvent,
|
createPollDraftEvent,
|
||||||
createShortTextNoteDraftEvent,
|
createShortTextNoteDraftEvent,
|
||||||
deleteDraftEventCache
|
deleteDraftEventCache
|
||||||
@@ -24,18 +26,19 @@ import PostOptions from './PostOptions'
|
|||||||
import PostRelaySelector from './PostRelaySelector'
|
import PostRelaySelector from './PostRelaySelector'
|
||||||
import PostTextarea, { TPostTextareaHandle } from './PostTextarea'
|
import PostTextarea, { TPostTextareaHandle } from './PostTextarea'
|
||||||
import Uploader from './Uploader'
|
import Uploader from './Uploader'
|
||||||
import { BIG_RELAY_URLS } from '@/constants'
|
|
||||||
|
|
||||||
export default function PostContent({
|
export default function PostContent({
|
||||||
defaultContent = '',
|
defaultContent = '',
|
||||||
parentStuff,
|
parentStuff,
|
||||||
close,
|
close,
|
||||||
openFrom
|
openFrom,
|
||||||
|
highlightedText
|
||||||
}: {
|
}: {
|
||||||
defaultContent?: string
|
defaultContent?: string
|
||||||
parentStuff?: Event | string
|
parentStuff?: Event | string
|
||||||
close: () => void
|
close: () => void
|
||||||
openFrom?: string[]
|
openFrom?: string[]
|
||||||
|
highlightedText?: string
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { pubkey, publish, checkLogin } = useNostr()
|
const { pubkey, publish, checkLogin } = useNostr()
|
||||||
@@ -68,7 +71,7 @@ export default function PostContent({
|
|||||||
const canPost = useMemo(() => {
|
const canPost = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
!!pubkey &&
|
!!pubkey &&
|
||||||
!!text &&
|
(!!text || !!highlightedText) &&
|
||||||
!posting &&
|
!posting &&
|
||||||
!uploadProgresses.length &&
|
!uploadProgresses.length &&
|
||||||
(!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2) &&
|
(!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2) &&
|
||||||
@@ -77,6 +80,7 @@ export default function PostContent({
|
|||||||
}, [
|
}, [
|
||||||
pubkey,
|
pubkey,
|
||||||
text,
|
text,
|
||||||
|
highlightedText,
|
||||||
posting,
|
posting,
|
||||||
uploadProgresses,
|
uploadProgresses,
|
||||||
isPoll,
|
isPoll,
|
||||||
@@ -123,30 +127,23 @@ export default function PostContent({
|
|||||||
const post = async (e?: React.MouseEvent) => {
|
const post = async (e?: React.MouseEvent) => {
|
||||||
e?.stopPropagation()
|
e?.stopPropagation()
|
||||||
checkLogin(async () => {
|
checkLogin(async () => {
|
||||||
if (!canPost || postingRef.current) return
|
if (!canPost || !pubkey || postingRef.current) return
|
||||||
|
|
||||||
postingRef.current = true
|
postingRef.current = true
|
||||||
setPosting(true)
|
setPosting(true)
|
||||||
try {
|
try {
|
||||||
const draftEvent =
|
const draftEvent = await createDraftEvent({
|
||||||
parentStuff &&
|
parentStuff,
|
||||||
(typeof parentStuff === 'string' || parentStuff.kind !== kinds.ShortTextNote)
|
highlightedText,
|
||||||
? await createCommentDraftEvent(text, parentStuff, mentions, {
|
text,
|
||||||
addClientTag,
|
mentions,
|
||||||
protectedEvent: isProtectedEvent,
|
isPoll,
|
||||||
isNsfw
|
pollCreateData,
|
||||||
})
|
pubkey,
|
||||||
: isPoll
|
addClientTag,
|
||||||
? await createPollDraftEvent(pubkey!, text, mentions, pollCreateData, {
|
isProtectedEvent,
|
||||||
addClientTag,
|
isNsfw
|
||||||
isNsfw
|
})
|
||||||
})
|
|
||||||
: await createShortTextNoteDraftEvent(text, mentions, {
|
|
||||||
parentEvent,
|
|
||||||
addClientTag,
|
|
||||||
protectedEvent: isProtectedEvent,
|
|
||||||
isNsfw
|
|
||||||
})
|
|
||||||
|
|
||||||
const _additionalRelayUrls = [...additionalRelayUrls]
|
const _additionalRelayUrls = [...additionalRelayUrls]
|
||||||
if (parentStuff && typeof parentStuff === 'string') {
|
if (parentStuff && typeof parentStuff === 'string') {
|
||||||
@@ -205,7 +202,14 @@ export default function PostContent({
|
|||||||
{parentEvent && (
|
{parentEvent && (
|
||||||
<ScrollArea className="flex max-h-48 flex-col overflow-y-auto rounded-lg border bg-muted/40">
|
<ScrollArea className="flex max-h-48 flex-col overflow-y-auto rounded-lg border bg-muted/40">
|
||||||
<div className="p-2 sm:p-3 pointer-events-none">
|
<div className="p-2 sm:p-3 pointer-events-none">
|
||||||
<Note size="small" event={parentEvent} hideParentNotePreview />
|
{highlightedText ? (
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="w-1 flex-shrink-0 my-1 bg-primary/60 rounded-md" />
|
||||||
|
<div className="italic whitespace-pre-line">{highlightedText}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Note size="small" event={parentEvent} hideParentNotePreview />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
)}
|
)}
|
||||||
@@ -220,6 +224,7 @@ export default function PostContent({
|
|||||||
onUploadStart={handleUploadStart}
|
onUploadStart={handleUploadStart}
|
||||||
onUploadProgress={handleUploadProgress}
|
onUploadProgress={handleUploadProgress}
|
||||||
onUploadEnd={handleUploadEnd}
|
onUploadEnd={handleUploadEnd}
|
||||||
|
placeholder={highlightedText ? t('Write your thoughts about this highlight...') : undefined}
|
||||||
/>
|
/>
|
||||||
{isPoll && (
|
{isPoll && (
|
||||||
<PollEditor
|
<PollEditor
|
||||||
@@ -332,7 +337,7 @@ export default function PostContent({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={!canPost} onClick={post}>
|
<Button type="submit" disabled={!canPost} onClick={post}>
|
||||||
{posting && <LoaderCircle className="animate-spin" />}
|
{posting && <LoaderCircle className="animate-spin" />}
|
||||||
{parentStuff ? t('Reply') : t('Post')}
|
{parentStuff ? (highlightedText ? t('Publish Highlight') : t('Reply')) : t('Post')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -366,3 +371,62 @@ export default function PostContent({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createDraftEvent({
|
||||||
|
parentStuff,
|
||||||
|
text,
|
||||||
|
mentions,
|
||||||
|
isPoll,
|
||||||
|
pollCreateData,
|
||||||
|
pubkey,
|
||||||
|
addClientTag,
|
||||||
|
isProtectedEvent,
|
||||||
|
isNsfw,
|
||||||
|
highlightedText
|
||||||
|
}: {
|
||||||
|
parentStuff: Event | string | undefined
|
||||||
|
text: string
|
||||||
|
mentions: string[]
|
||||||
|
isPoll: boolean
|
||||||
|
pollCreateData: TPollCreateData
|
||||||
|
pubkey: string
|
||||||
|
addClientTag: boolean
|
||||||
|
isProtectedEvent: boolean
|
||||||
|
isNsfw: boolean
|
||||||
|
highlightedText?: string
|
||||||
|
}) {
|
||||||
|
const { parentEvent, externalContent } =
|
||||||
|
typeof parentStuff === 'string'
|
||||||
|
? { parentEvent: undefined, externalContent: parentStuff }
|
||||||
|
: { parentEvent: parentStuff, externalContent: undefined }
|
||||||
|
|
||||||
|
if (highlightedText && parentEvent) {
|
||||||
|
return createHighlightDraftEvent(highlightedText, text, parentEvent, mentions, {
|
||||||
|
addClientTag,
|
||||||
|
protectedEvent: isProtectedEvent,
|
||||||
|
isNsfw
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentStuff && (externalContent || parentEvent?.kind !== kinds.ShortTextNote)) {
|
||||||
|
return await createCommentDraftEvent(text, parentStuff, mentions, {
|
||||||
|
addClientTag,
|
||||||
|
protectedEvent: isProtectedEvent,
|
||||||
|
isNsfw
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPoll) {
|
||||||
|
return await createPollDraftEvent(pubkey, text, mentions, pollCreateData, {
|
||||||
|
addClientTag,
|
||||||
|
isNsfw
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await createShortTextNoteDraftEvent(text, mentions, {
|
||||||
|
parentEvent,
|
||||||
|
addClientTag,
|
||||||
|
protectedEvent: isProtectedEvent,
|
||||||
|
isNsfw
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const PostTextarea = forwardRef<
|
|||||||
onUploadStart?: (file: File, cancel: () => void) => void
|
onUploadStart?: (file: File, cancel: () => void) => void
|
||||||
onUploadProgress?: (file: File, progress: number) => void
|
onUploadProgress?: (file: File, progress: number) => void
|
||||||
onUploadEnd?: (file: File) => void
|
onUploadEnd?: (file: File) => void
|
||||||
|
placeholder?: string
|
||||||
}
|
}
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
@@ -52,7 +53,8 @@ const PostTextarea = forwardRef<
|
|||||||
className,
|
className,
|
||||||
onUploadStart,
|
onUploadStart,
|
||||||
onUploadProgress,
|
onUploadProgress,
|
||||||
onUploadEnd
|
onUploadEnd,
|
||||||
|
placeholder
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
@@ -67,6 +69,7 @@ const PostTextarea = forwardRef<
|
|||||||
HardBreak,
|
HardBreak,
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder:
|
placeholder:
|
||||||
|
placeholder ??
|
||||||
t('Write something...') + ' (' + t('Paste or drop media files to upload') + ')'
|
t('Write something...') + ' (' + t('Paste or drop media files to upload') + ')'
|
||||||
}),
|
}),
|
||||||
Emoji.configure({
|
Emoji.configure({
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
|||||||
import postEditor from '@/services/post-editor.service'
|
import postEditor from '@/services/post-editor.service'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { Dispatch, useMemo } from 'react'
|
import { Dispatch, useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import PostContent from './PostContent'
|
import PostContent from './PostContent'
|
||||||
import Title from './Title'
|
import Title from './Title'
|
||||||
|
|
||||||
@@ -25,14 +26,17 @@ export default function PostEditor({
|
|||||||
parentStuff,
|
parentStuff,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
openFrom
|
openFrom,
|
||||||
|
highlightedText
|
||||||
}: {
|
}: {
|
||||||
defaultContent?: string
|
defaultContent?: string
|
||||||
parentStuff?: Event | string
|
parentStuff?: Event | string
|
||||||
open: boolean
|
open: boolean
|
||||||
setOpen: Dispatch<boolean>
|
setOpen: Dispatch<boolean>
|
||||||
openFrom?: string[]
|
openFrom?: string[]
|
||||||
|
highlightedText?: string
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { isSmallScreen } = useScreenSize()
|
const { isSmallScreen } = useScreenSize()
|
||||||
|
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
@@ -42,9 +46,10 @@ export default function PostEditor({
|
|||||||
parentStuff={parentStuff}
|
parentStuff={parentStuff}
|
||||||
close={() => setOpen(false)}
|
close={() => setOpen(false)}
|
||||||
openFrom={openFrom}
|
openFrom={openFrom}
|
||||||
|
highlightedText={highlightedText}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}, [])
|
}, [highlightedText])
|
||||||
|
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
return (
|
return (
|
||||||
@@ -64,7 +69,7 @@ export default function PostEditor({
|
|||||||
<div className="space-y-4 px-2 py-6">
|
<div className="space-y-4 px-2 py-6">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="text-start">
|
<SheetTitle className="text-start">
|
||||||
<Title parentStuff={parentStuff} />
|
{highlightedText ? t('Create Highlight') : <Title parentStuff={parentStuff} />}
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
<SheetDescription className="hidden" />
|
<SheetDescription className="hidden" />
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
@@ -92,7 +97,7 @@ export default function PostEditor({
|
|||||||
<div className="space-y-4 px-2 py-6">
|
<div className="space-y-4 px-2 py-6">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<Title parentStuff={parentStuff} />
|
{highlightedText ? t('Create Highlight') : <Title parentStuff={parentStuff} />}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="hidden" />
|
<DialogDescription className="hidden" />
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ export default {
|
|||||||
'reacted to your note': 'تفاعل مع ملاحظتك',
|
'reacted to your note': 'تفاعل مع ملاحظتك',
|
||||||
'reposted your note': 'أعاد نشر ملاحظتك',
|
'reposted your note': 'أعاد نشر ملاحظتك',
|
||||||
'zapped your note': 'زاب ملاحظتك',
|
'zapped your note': 'زاب ملاحظتك',
|
||||||
|
'highlighted your note': 'أبرز ملاحظتك',
|
||||||
'zapped you': 'زابك',
|
'zapped you': 'زابك',
|
||||||
'Mark as read': 'تعليم كمقروء',
|
'Mark as read': 'تعليم كمقروء',
|
||||||
Report: 'تبليغ',
|
Report: 'تبليغ',
|
||||||
@@ -583,6 +584,9 @@ export default {
|
|||||||
'Special Follow': 'متابعة خاصة',
|
'Special Follow': 'متابعة خاصة',
|
||||||
'Unfollow Special': 'إلغاء المتابعة الخاصة',
|
'Unfollow Special': 'إلغاء المتابعة الخاصة',
|
||||||
'Personal Feeds': 'التدفقات الشخصية',
|
'Personal Feeds': 'التدفقات الشخصية',
|
||||||
'Relay Feeds': 'تدفقات الترحيل'
|
'Relay Feeds': 'تدفقات الترحيل',
|
||||||
|
'Create Highlight': 'إنشاء تمييز',
|
||||||
|
'Write your thoughts about this highlight...': 'اكتب أفكارك حول هذا التمييز...',
|
||||||
|
'Publish Highlight': 'نشر التمييز'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ export default {
|
|||||||
'reacted to your note': 'hat auf Ihre Notiz reagiert',
|
'reacted to your note': 'hat auf Ihre Notiz reagiert',
|
||||||
'reposted your note': 'hat Ihre Notiz geteilt',
|
'reposted your note': 'hat Ihre Notiz geteilt',
|
||||||
'zapped your note': 'hat Ihre Notiz gezappt',
|
'zapped your note': 'hat Ihre Notiz gezappt',
|
||||||
|
'highlighted your note': 'hat Ihre Notiz hervorgehoben',
|
||||||
'zapped you': 'hat Sie gezappt',
|
'zapped you': 'hat Sie gezappt',
|
||||||
'Mark as read': 'Als gelesen markieren',
|
'Mark as read': 'Als gelesen markieren',
|
||||||
Report: 'Melden',
|
Report: 'Melden',
|
||||||
@@ -599,6 +600,10 @@ export default {
|
|||||||
'Special Follow': 'Besonders Folgen',
|
'Special Follow': 'Besonders Folgen',
|
||||||
'Unfollow Special': 'Besonders Entfolgen',
|
'Unfollow Special': 'Besonders Entfolgen',
|
||||||
'Personal Feeds': 'Persönliche Feeds',
|
'Personal Feeds': 'Persönliche Feeds',
|
||||||
'Relay Feeds': 'Relay-Feeds'
|
'Relay Feeds': 'Relay-Feeds',
|
||||||
|
'Create Highlight': 'Markierung Erstellen',
|
||||||
|
'Write your thoughts about this highlight...':
|
||||||
|
'Schreiben Sie Ihre Gedanken zu dieser Markierung...',
|
||||||
|
'Publish Highlight': 'Markierung Veröffentlichen'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export default {
|
|||||||
'reacted to your note': 'reacted to your note',
|
'reacted to your note': 'reacted to your note',
|
||||||
'reposted your note': 'reposted your note',
|
'reposted your note': 'reposted your note',
|
||||||
'zapped your note': 'zapped your note',
|
'zapped your note': 'zapped your note',
|
||||||
|
'highlighted your note': 'highlighted your note',
|
||||||
'zapped you': 'zapped you',
|
'zapped you': 'zapped you',
|
||||||
'Mark as read': 'Mark as read',
|
'Mark as read': 'Mark as read',
|
||||||
Report: 'Report',
|
Report: 'Report',
|
||||||
@@ -586,6 +587,9 @@ export default {
|
|||||||
'Special Follow': 'Special Follow',
|
'Special Follow': 'Special Follow',
|
||||||
'Unfollow Special': 'Unfollow Special',
|
'Unfollow Special': 'Unfollow Special',
|
||||||
'Personal Feeds': 'Personal Feeds',
|
'Personal Feeds': 'Personal Feeds',
|
||||||
'Relay Feeds': 'Relay Feeds'
|
'Relay Feeds': 'Relay Feeds',
|
||||||
|
'Create Highlight': 'Create Highlight',
|
||||||
|
'Write your thoughts about this highlight...': 'Write your thoughts about this highlight...',
|
||||||
|
'Publish Highlight': 'Publish Highlight'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ export default {
|
|||||||
'reacted to your note': 'reaccionó a tu nota',
|
'reacted to your note': 'reaccionó a tu nota',
|
||||||
'reposted your note': 'reposteó tu nota',
|
'reposted your note': 'reposteó tu nota',
|
||||||
'zapped your note': 'zappeó tu nota',
|
'zapped your note': 'zappeó tu nota',
|
||||||
|
'highlighted your note': 'destacó tu nota',
|
||||||
'zapped you': 'te zappeó',
|
'zapped you': 'te zappeó',
|
||||||
'Mark as read': 'Marcar como leído',
|
'Mark as read': 'Marcar como leído',
|
||||||
Report: 'Reportar',
|
Report: 'Reportar',
|
||||||
@@ -595,6 +596,10 @@ export default {
|
|||||||
'Special Follow': 'Seguir Especial',
|
'Special Follow': 'Seguir Especial',
|
||||||
'Unfollow Special': 'Dejar de Seguir Especial',
|
'Unfollow Special': 'Dejar de Seguir Especial',
|
||||||
'Personal Feeds': 'Feeds Personales',
|
'Personal Feeds': 'Feeds Personales',
|
||||||
'Relay Feeds': 'Feeds de Relays'
|
'Relay Feeds': 'Feeds de Relays',
|
||||||
|
'Create Highlight': 'Crear Resaltado',
|
||||||
|
'Write your thoughts about this highlight...':
|
||||||
|
'Escribe tus pensamientos sobre este resaltado...',
|
||||||
|
'Publish Highlight': 'Publicar Resaltado'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ export default {
|
|||||||
'reacted to your note': 'به یادداشت شما واکنش نشان داد',
|
'reacted to your note': 'به یادداشت شما واکنش نشان داد',
|
||||||
'reposted your note': 'یادداشت شما را بازنشر کرد',
|
'reposted your note': 'یادداشت شما را بازنشر کرد',
|
||||||
'zapped your note': 'یادداشت شما را زپ کرد',
|
'zapped your note': 'یادداشت شما را زپ کرد',
|
||||||
|
'highlighted your note': 'یادداشت شما را برجسته کرد',
|
||||||
'zapped you': 'شما را زپ کرد',
|
'zapped you': 'شما را زپ کرد',
|
||||||
'Mark as read': 'علامتگذاری به عنوان خوانده شده',
|
'Mark as read': 'علامتگذاری به عنوان خوانده شده',
|
||||||
Report: 'گزارش',
|
Report: 'گزارش',
|
||||||
@@ -589,6 +590,9 @@ export default {
|
|||||||
'Special Follow': 'دنبال کردن ویژه',
|
'Special Follow': 'دنبال کردن ویژه',
|
||||||
'Unfollow Special': 'لغو دنبال کردن ویژه',
|
'Unfollow Special': 'لغو دنبال کردن ویژه',
|
||||||
'Personal Feeds': 'فیدهای شخصی',
|
'Personal Feeds': 'فیدهای شخصی',
|
||||||
'Relay Feeds': 'فیدهای رله'
|
'Relay Feeds': 'فیدهای رله',
|
||||||
|
'Create Highlight': 'ایجاد برجستهسازی',
|
||||||
|
'Write your thoughts about this highlight...': 'نظرات خود را درباره این برجستهسازی بنویسید...',
|
||||||
|
'Publish Highlight': 'انتشار برجستهسازی'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ export default {
|
|||||||
'reacted to your note': 'a réagi à votre note',
|
'reacted to your note': 'a réagi à votre note',
|
||||||
'reposted your note': 'a repartagé votre note',
|
'reposted your note': 'a repartagé votre note',
|
||||||
'zapped your note': 'a zappé votre note',
|
'zapped your note': 'a zappé votre note',
|
||||||
|
'highlighted your note': 'a mis en évidence votre note',
|
||||||
'zapped you': 'vous a zappé',
|
'zapped you': 'vous a zappé',
|
||||||
'Mark as read': 'Marquer comme lu',
|
'Mark as read': 'Marquer comme lu',
|
||||||
Report: 'Signaler',
|
Report: 'Signaler',
|
||||||
@@ -598,6 +599,9 @@ export default {
|
|||||||
'Special Follow': 'Suivre Spécial',
|
'Special Follow': 'Suivre Spécial',
|
||||||
'Unfollow Special': 'Ne Plus Suivre Spécial',
|
'Unfollow Special': 'Ne Plus Suivre Spécial',
|
||||||
'Personal Feeds': 'Flux Personnels',
|
'Personal Feeds': 'Flux Personnels',
|
||||||
'Relay Feeds': 'Flux de Relais'
|
'Relay Feeds': 'Flux de Relais',
|
||||||
|
'Create Highlight': 'Créer un Surlignage',
|
||||||
|
'Write your thoughts about this highlight...': 'Écrivez vos pensées sur ce surlignage...',
|
||||||
|
'Publish Highlight': 'Publier le Surlignage'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,6 +388,7 @@ export default {
|
|||||||
'reacted to your note': 'ने आपके नोट पर प्रतिक्रिया दी',
|
'reacted to your note': 'ने आपके नोट पर प्रतिक्रिया दी',
|
||||||
'reposted your note': 'ने आपके नोट को रीपोस्ट किया',
|
'reposted your note': 'ने आपके नोट को रीपोस्ट किया',
|
||||||
'zapped your note': 'ने आपके नोट को जैप किया',
|
'zapped your note': 'ने आपके नोट को जैप किया',
|
||||||
|
'highlighted your note': 'ने आपके नोट को हाइलाइट किया',
|
||||||
'zapped you': 'ने आपको जैप किया',
|
'zapped you': 'ने आपको जैप किया',
|
||||||
'Mark as read': 'पढ़ा हुआ मार्क करें',
|
'Mark as read': 'पढ़ा हुआ मार्क करें',
|
||||||
Report: 'रिपोर्ट करें',
|
Report: 'रिपोर्ट करें',
|
||||||
@@ -590,6 +591,9 @@ export default {
|
|||||||
'Special Follow': 'विशेष फ़ॉलो',
|
'Special Follow': 'विशेष फ़ॉलो',
|
||||||
'Unfollow Special': 'विशेष अनफ़ॉलो',
|
'Unfollow Special': 'विशेष अनफ़ॉलो',
|
||||||
'Personal Feeds': 'व्यक्तिगत फ़ीड',
|
'Personal Feeds': 'व्यक्तिगत फ़ीड',
|
||||||
'Relay Feeds': 'रिले फ़ीड'
|
'Relay Feeds': 'रिले फ़ीड',
|
||||||
|
'Create Highlight': 'हाइलाइट बनाएं',
|
||||||
|
'Write your thoughts about this highlight...': 'इस हाइलाइट के बारे में अपने विचार लिखें...',
|
||||||
|
'Publish Highlight': 'हाइलाइट प्रकाशित करें'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ export default {
|
|||||||
'reacted to your note': 'reagált a posztodra',
|
'reacted to your note': 'reagált a posztodra',
|
||||||
'reposted your note': 'újraposztolta a posztodat',
|
'reposted your note': 'újraposztolta a posztodat',
|
||||||
'zapped your note': 'zappolta a posztodat',
|
'zapped your note': 'zappolta a posztodat',
|
||||||
|
'highlighted your note': 'kiemelte a posztodat',
|
||||||
'zapped you': 'zappolt téged',
|
'zapped you': 'zappolt téged',
|
||||||
'Mark as read': 'Megjelölés olvasottként',
|
'Mark as read': 'Megjelölés olvasottként',
|
||||||
Report: 'Jelentés',
|
Report: 'Jelentés',
|
||||||
@@ -584,6 +585,9 @@ export default {
|
|||||||
'Special Follow': 'Különleges Követés',
|
'Special Follow': 'Különleges Követés',
|
||||||
'Unfollow Special': 'Különleges Követés Megszüntetése',
|
'Unfollow Special': 'Különleges Követés Megszüntetése',
|
||||||
'Personal Feeds': 'Személyes Feedek',
|
'Personal Feeds': 'Személyes Feedek',
|
||||||
'Relay Feeds': 'Relay Feedek'
|
'Relay Feeds': 'Relay Feedek',
|
||||||
|
'Create Highlight': 'Kiemelés Létrehozása',
|
||||||
|
'Write your thoughts about this highlight...': 'Írd le a gondolataidat erről a kiemelésről...',
|
||||||
|
'Publish Highlight': 'Kiemelés Közzététele'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ export default {
|
|||||||
'reacted to your note': 'ha reagito alla tua nota',
|
'reacted to your note': 'ha reagito alla tua nota',
|
||||||
'reposted your note': 'ha ricondiviso la tua nota',
|
'reposted your note': 'ha ricondiviso la tua nota',
|
||||||
'zapped your note': 'ha zappato la tua nota',
|
'zapped your note': 'ha zappato la tua nota',
|
||||||
|
'highlighted your note': 'ha evidenziato la tua nota',
|
||||||
'zapped you': 'ti ha zappato',
|
'zapped you': 'ti ha zappato',
|
||||||
'Mark as read': 'Segna come letto',
|
'Mark as read': 'Segna come letto',
|
||||||
Report: 'Segnala',
|
Report: 'Segnala',
|
||||||
@@ -594,6 +595,10 @@ export default {
|
|||||||
'Special Follow': 'Segui Speciale',
|
'Special Follow': 'Segui Speciale',
|
||||||
'Unfollow Special': 'Smetti di Seguire Speciale',
|
'Unfollow Special': 'Smetti di Seguire Speciale',
|
||||||
'Personal Feeds': 'Feed Personali',
|
'Personal Feeds': 'Feed Personali',
|
||||||
'Relay Feeds': 'Feed di Relay'
|
'Relay Feeds': 'Feed di Relay',
|
||||||
|
'Create Highlight': 'Crea Evidenziazione',
|
||||||
|
'Write your thoughts about this highlight...':
|
||||||
|
'Scrivi i tuoi pensieri su questa evidenziazione...',
|
||||||
|
'Publish Highlight': 'Pubblica Evidenziazione'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ export default {
|
|||||||
'reacted to your note': 'あなたのノートにリアクションしました',
|
'reacted to your note': 'あなたのノートにリアクションしました',
|
||||||
'reposted your note': 'あなたのノートをリポストしました',
|
'reposted your note': 'あなたのノートをリポストしました',
|
||||||
'zapped your note': 'あなたのノートにザップしました',
|
'zapped your note': 'あなたのノートにザップしました',
|
||||||
|
'highlighted your note': 'あなたのノートをハイライトしました',
|
||||||
'zapped you': 'あなたにザップしました',
|
'zapped you': 'あなたにザップしました',
|
||||||
'Mark as read': '既読にする',
|
'Mark as read': '既読にする',
|
||||||
Report: '報告',
|
Report: '報告',
|
||||||
@@ -589,6 +590,10 @@ export default {
|
|||||||
'Special Follow': '特別フォロー',
|
'Special Follow': '特別フォロー',
|
||||||
'Unfollow Special': '特別フォロー解除',
|
'Unfollow Special': '特別フォロー解除',
|
||||||
'Personal Feeds': '個人フィード',
|
'Personal Feeds': '個人フィード',
|
||||||
'Relay Feeds': 'リレーフィード'
|
'Relay Feeds': 'リレーフィード',
|
||||||
|
'Create Highlight': 'ハイライトを作成',
|
||||||
|
'Write your thoughts about this highlight...':
|
||||||
|
'このハイライトについての考えを書いてください...',
|
||||||
|
'Publish Highlight': 'ハイライトを公開'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ export default {
|
|||||||
'reacted to your note': '당신의 노트에 반응했습니다',
|
'reacted to your note': '당신의 노트에 반응했습니다',
|
||||||
'reposted your note': '당신의 노트를 리포스트했습니다',
|
'reposted your note': '당신의 노트를 리포스트했습니다',
|
||||||
'zapped your note': '당신의 노트를 잽했습니다',
|
'zapped your note': '당신의 노트를 잽했습니다',
|
||||||
|
'highlighted your note': '당신의 노트를 하이라이트했습니다',
|
||||||
'zapped you': '당신을 잽했습니다',
|
'zapped you': '당신을 잽했습니다',
|
||||||
'Mark as read': '읽음으로 표시',
|
'Mark as read': '읽음으로 표시',
|
||||||
Report: '신고',
|
Report: '신고',
|
||||||
@@ -588,6 +589,9 @@ export default {
|
|||||||
'Special Follow': '특별 팔로우',
|
'Special Follow': '특별 팔로우',
|
||||||
'Unfollow Special': '특별 팔로우 해제',
|
'Unfollow Special': '특별 팔로우 해제',
|
||||||
'Personal Feeds': '개인 피드',
|
'Personal Feeds': '개인 피드',
|
||||||
'Relay Feeds': '릴레이 피드'
|
'Relay Feeds': '릴레이 피드',
|
||||||
|
'Create Highlight': '하이라이트 만들기',
|
||||||
|
'Write your thoughts about this highlight...': '이 하이라이트에 대한 생각을 작성하세요...',
|
||||||
|
'Publish Highlight': '하이라이트 게시'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,6 +390,7 @@ export default {
|
|||||||
'reacted to your note': 'zareagował na twój wpis',
|
'reacted to your note': 'zareagował na twój wpis',
|
||||||
'reposted your note': 'repostował twój wpis',
|
'reposted your note': 'repostował twój wpis',
|
||||||
'zapped your note': 'zappował twój wpis',
|
'zapped your note': 'zappował twój wpis',
|
||||||
|
'highlighted your note': 'wyróżnił twój wpis',
|
||||||
'zapped you': 'zappował cię',
|
'zapped you': 'zappował cię',
|
||||||
'Mark as read': 'Oznacz jako przeczytane',
|
'Mark as read': 'Oznacz jako przeczytane',
|
||||||
Report: 'Zgłoś',
|
Report: 'Zgłoś',
|
||||||
@@ -595,6 +596,10 @@ export default {
|
|||||||
'Special Follow': 'Specjalne Śledzenie',
|
'Special Follow': 'Specjalne Śledzenie',
|
||||||
'Unfollow Special': 'Cofnij Specjalne Śledzenie',
|
'Unfollow Special': 'Cofnij Specjalne Śledzenie',
|
||||||
'Personal Feeds': 'Osobiste Kanały',
|
'Personal Feeds': 'Osobiste Kanały',
|
||||||
'Relay Feeds': 'Kanały Przekaźników'
|
'Relay Feeds': 'Kanały Przekaźników',
|
||||||
|
'Create Highlight': 'Utwórz Podświetlenie',
|
||||||
|
'Write your thoughts about this highlight...':
|
||||||
|
'Napisz swoje przemyślenia na temat tego podświetlenia...',
|
||||||
|
'Publish Highlight': 'Opublikuj Podświetlenie'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ export default {
|
|||||||
'reacted to your note': 'reagiu à sua nota',
|
'reacted to your note': 'reagiu à sua nota',
|
||||||
'reposted your note': 'republicou sua nota',
|
'reposted your note': 'republicou sua nota',
|
||||||
'zapped your note': 'zappeou sua nota',
|
'zapped your note': 'zappeou sua nota',
|
||||||
|
'highlighted your note': 'destacou sua nota',
|
||||||
'zapped you': 'zappeou você',
|
'zapped you': 'zappeou você',
|
||||||
'Mark as read': 'Marcar como lida',
|
'Mark as read': 'Marcar como lida',
|
||||||
Report: 'Denunciar',
|
Report: 'Denunciar',
|
||||||
@@ -590,6 +591,10 @@ export default {
|
|||||||
'Special Follow': 'Favoritos',
|
'Special Follow': 'Favoritos',
|
||||||
'Unfollow Special': 'Desfavoritar',
|
'Unfollow Special': 'Desfavoritar',
|
||||||
'Personal Feeds': 'Meus feeds',
|
'Personal Feeds': 'Meus feeds',
|
||||||
'Relay Feeds': 'Feeds de relays'
|
'Relay Feeds': 'Feeds de relays',
|
||||||
|
'Create Highlight': 'Criar Destaque',
|
||||||
|
'Write your thoughts about this highlight...':
|
||||||
|
'Escreva seus pensamentos sobre este destaque...',
|
||||||
|
'Publish Highlight': 'Publicar Destaque'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ export default {
|
|||||||
'reacted to your note': 'reagiu à sua nota',
|
'reacted to your note': 'reagiu à sua nota',
|
||||||
'reposted your note': 'republicou a sua nota',
|
'reposted your note': 'republicou a sua nota',
|
||||||
'zapped your note': 'zappeou a sua nota',
|
'zapped your note': 'zappeou a sua nota',
|
||||||
|
'highlighted your note': 'destacou a sua nota',
|
||||||
'zapped you': 'zappeou-o',
|
'zapped you': 'zappeou-o',
|
||||||
'Mark as read': 'Marcar como lida',
|
'Mark as read': 'Marcar como lida',
|
||||||
Report: 'Denunciar',
|
Report: 'Denunciar',
|
||||||
@@ -593,6 +594,10 @@ export default {
|
|||||||
'Special Follow': 'Seguir Especial',
|
'Special Follow': 'Seguir Especial',
|
||||||
'Unfollow Special': 'Deixar de Seguir Especial',
|
'Unfollow Special': 'Deixar de Seguir Especial',
|
||||||
'Personal Feeds': 'Feeds Pessoais',
|
'Personal Feeds': 'Feeds Pessoais',
|
||||||
'Relay Feeds': 'Feeds de Relays'
|
'Relay Feeds': 'Feeds de Relays',
|
||||||
|
'Create Highlight': 'Criar Destaque',
|
||||||
|
'Write your thoughts about this highlight...':
|
||||||
|
'Escreva os seus pensamentos sobre este destaque...',
|
||||||
|
'Publish Highlight': 'Publicar Destaque'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,6 +390,7 @@ export default {
|
|||||||
'reacted to your note': 'отреагировал на вашу заметку',
|
'reacted to your note': 'отреагировал на вашу заметку',
|
||||||
'reposted your note': 'репостнул вашу заметку',
|
'reposted your note': 'репостнул вашу заметку',
|
||||||
'zapped your note': 'заппил вашу заметку',
|
'zapped your note': 'заппил вашу заметку',
|
||||||
|
'highlighted your note': 'выделил вашу заметку',
|
||||||
'zapped you': 'заппил вас',
|
'zapped you': 'заппил вас',
|
||||||
'Mark as read': 'Отметить как прочитанное',
|
'Mark as read': 'Отметить как прочитанное',
|
||||||
Report: 'Пожаловаться',
|
Report: 'Пожаловаться',
|
||||||
@@ -595,6 +596,9 @@ export default {
|
|||||||
'Special Follow': 'Особая Подписка',
|
'Special Follow': 'Особая Подписка',
|
||||||
'Unfollow Special': 'Отменить Особую Подписку',
|
'Unfollow Special': 'Отменить Особую Подписку',
|
||||||
'Personal Feeds': 'Личные Ленты',
|
'Personal Feeds': 'Личные Ленты',
|
||||||
'Relay Feeds': 'Ленты Релеев'
|
'Relay Feeds': 'Ленты Релеев',
|
||||||
|
'Create Highlight': 'Создать Выделение',
|
||||||
|
'Write your thoughts about this highlight...': 'Напишите свои мысли об этом выделении...',
|
||||||
|
'Publish Highlight': 'Опубликовать Выделение'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,6 +382,7 @@ export default {
|
|||||||
'reacted to your note': 'ได้แสดงปฏิกิริยาต่อโน้ตของคุณ',
|
'reacted to your note': 'ได้แสดงปฏิกิริยาต่อโน้ตของคุณ',
|
||||||
'reposted your note': 'ได้รีโพสต์โน้ตของคุณ',
|
'reposted your note': 'ได้รีโพสต์โน้ตของคุณ',
|
||||||
'zapped your note': 'ได้แซปโน้ตของคุณ',
|
'zapped your note': 'ได้แซปโน้ตของคุณ',
|
||||||
|
'highlighted your note': 'ได้ไฮไลต์โน้ตของคุณ',
|
||||||
'zapped you': 'ได้แซปคุณ',
|
'zapped you': 'ได้แซปคุณ',
|
||||||
'Mark as read': 'ทำเครื่องหมายว่าอ่านแล้ว',
|
'Mark as read': 'ทำเครื่องหมายว่าอ่านแล้ว',
|
||||||
Report: 'รายงาน',
|
Report: 'รายงาน',
|
||||||
@@ -582,6 +583,9 @@ export default {
|
|||||||
'Special Follow': 'ติดตามพิเศษ',
|
'Special Follow': 'ติดตามพิเศษ',
|
||||||
'Unfollow Special': 'ยกเลิกติดตามพิเศษ',
|
'Unfollow Special': 'ยกเลิกติดตามพิเศษ',
|
||||||
'Personal Feeds': 'ฟีดส่วนตัว',
|
'Personal Feeds': 'ฟีดส่วนตัว',
|
||||||
'Relay Feeds': 'ฟีดรีเลย์'
|
'Relay Feeds': 'ฟีดรีเลย์',
|
||||||
|
'Create Highlight': 'สร้างไฮไลท์',
|
||||||
|
'Write your thoughts about this highlight...': 'เขียนความคิดของคุณเกี่ยวกับไฮไลท์นี้...',
|
||||||
|
'Publish Highlight': 'เผยแพร่ไฮไลท์'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ export default {
|
|||||||
'reacted to your note': '对您的笔记做出了反应',
|
'reacted to your note': '对您的笔记做出了反应',
|
||||||
'reposted your note': '转发了您的笔记',
|
'reposted your note': '转发了您的笔记',
|
||||||
'zapped your note': '打闪了您的笔记',
|
'zapped your note': '打闪了您的笔记',
|
||||||
|
'highlighted your note': '高亮了您的笔记',
|
||||||
'zapped you': '给您打闪',
|
'zapped you': '给您打闪',
|
||||||
'Mark as read': '标记为已读',
|
'Mark as read': '标记为已读',
|
||||||
Report: '举报',
|
Report: '举报',
|
||||||
@@ -575,6 +576,9 @@ export default {
|
|||||||
'Special Follow': '特别关注',
|
'Special Follow': '特别关注',
|
||||||
'Unfollow Special': '取消特别关注',
|
'Unfollow Special': '取消特别关注',
|
||||||
'Personal Feeds': '个人订阅',
|
'Personal Feeds': '个人订阅',
|
||||||
'Relay Feeds': '中继订阅'
|
'Relay Feeds': '中继订阅',
|
||||||
|
'Create Highlight': '创建高亮',
|
||||||
|
'Write your thoughts about this highlight...': '写下你对这段高亮的想法...',
|
||||||
|
'Publish Highlight': '发布高亮'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,6 +307,74 @@ export async function createCommentDraftEvent(
|
|||||||
return setDraftEventCache(baseDraft)
|
return setDraftEventCache(baseDraft)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/nostr-protocol/nips/blob/master/84.md
|
||||||
|
export function createHighlightDraftEvent(
|
||||||
|
highlightedText: string,
|
||||||
|
comment: string = '',
|
||||||
|
sourceEvent: Event,
|
||||||
|
mentions: string[],
|
||||||
|
options: {
|
||||||
|
addClientTag?: boolean
|
||||||
|
protectedEvent?: boolean
|
||||||
|
isNsfw?: boolean
|
||||||
|
} = {}
|
||||||
|
): TDraftEvent {
|
||||||
|
const { content: transformedEmojisContent, emojiTags } = transformCustomEmojisInContent(comment)
|
||||||
|
const quoteTags = extractQuoteTags(comment)
|
||||||
|
const hashtags = extractHashtags(transformedEmojisContent)
|
||||||
|
|
||||||
|
const tags = emojiTags.concat(hashtags.map((hashtag) => buildTTag(hashtag)))
|
||||||
|
|
||||||
|
// imeta tags
|
||||||
|
const images = extractImagesFromContent(transformedEmojisContent)
|
||||||
|
if (images && images.length) {
|
||||||
|
tags.push(...generateImetaTags(images))
|
||||||
|
}
|
||||||
|
|
||||||
|
// q tags
|
||||||
|
tags.push(...quoteTags)
|
||||||
|
|
||||||
|
// p tags
|
||||||
|
tags.push(
|
||||||
|
...mentions
|
||||||
|
.filter((pubkey) => pubkey !== sourceEvent.pubkey)
|
||||||
|
.map((pubkey) => ['p', pubkey, '', 'mention'])
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add comment tag if comment exists
|
||||||
|
if (transformedEmojisContent) {
|
||||||
|
tags.push(['comment', transformedEmojisContent])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add source reference
|
||||||
|
const hint = client.getEventHint(sourceEvent.id)
|
||||||
|
if (isReplaceableEvent(sourceEvent.kind)) {
|
||||||
|
tags.push(['a', getReplaceableCoordinateFromEvent(sourceEvent), hint, 'source'])
|
||||||
|
} else {
|
||||||
|
tags.push(['e', sourceEvent.id, hint, 'source'])
|
||||||
|
}
|
||||||
|
tags.push(['p', sourceEvent.pubkey, '', 'author'])
|
||||||
|
|
||||||
|
if (options.addClientTag) {
|
||||||
|
tags.push(buildClientTag())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.isNsfw) {
|
||||||
|
tags.push(buildNsfwTag())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.protectedEvent) {
|
||||||
|
tags.push(buildProtectedTag())
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseDraft = {
|
||||||
|
kind: kinds.Highlights,
|
||||||
|
content: highlightedText,
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
return setDraftEventCache(baseDraft)
|
||||||
|
}
|
||||||
|
|
||||||
export function createRelayListDraftEvent(mailboxRelays: TMailboxRelay[]): TDraftEvent {
|
export function createRelayListDraftEvent(mailboxRelays: TMailboxRelay[]): TDraftEvent {
|
||||||
return {
|
return {
|
||||||
kind: kinds.RelayList,
|
kind: kinds.RelayList,
|
||||||
|
|||||||
Reference in New Issue
Block a user