feat: long form article
This commit is contained in:
@@ -47,7 +47,7 @@ export default function HighlightButton({ onHighlight, containerRef }: Highlight
|
||||
|
||||
// Position button above the selection area, centered horizontally
|
||||
setPosition({
|
||||
top: rect.top + scrollTop - 36, // 36px above the selection
|
||||
top: rect.top + scrollTop - 48, // 48px above the selection
|
||||
left: rect.left + scrollLeft + rect.width / 2 // Center of the selection
|
||||
})
|
||||
setSelectedText(text)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { SecondaryPageLink, useSecondaryPage } from '@/PageManager'
|
||||
import ImageWithLightbox from '@/components/ImageWithLightbox'
|
||||
import HighlightButton from '@/components/HighlightButton'
|
||||
import PostEditor from '@/components/PostEditor'
|
||||
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata'
|
||||
import { toNote, toNoteList, toProfile } from '@/lib/link'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import Markdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import NostrNode from './NostrNode'
|
||||
@@ -20,6 +22,14 @@ export default function LongFormArticle({
|
||||
}) {
|
||||
const { push } = useSecondaryPage()
|
||||
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(
|
||||
() =>
|
||||
@@ -74,54 +84,64 @@ export default function LongFormArticle({
|
||||
/>
|
||||
)
|
||||
}) as Components,
|
||||
[]
|
||||
[event.pubkey]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`prose prose-zinc max-w-none dark:prose-invert break-words overflow-wrap-anywhere ${className || ''}`}
|
||||
>
|
||||
<h1 className="break-words">{metadata.title}</h1>
|
||||
{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}
|
||||
<>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`prose prose-zinc max-w-none dark:prose-invert break-words overflow-wrap-anywhere ${className || ''}`}
|
||||
>
|
||||
{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>
|
||||
<h1 className="break-words">{metadata.title}</h1>
|
||||
{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}
|
||||
</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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user