diff --git a/src/components/ContentPanel.tsx b/src/components/ContentPanel.tsx index a547a2f4..f311339c 100644 --- a/src/components/ContentPanel.tsx +++ b/src/components/ContentPanel.tsx @@ -98,8 +98,10 @@ const ContentPanel: React.FC = ({ const [showCheckAnimation, setShowCheckAnimation] = useState(false) const [showArticleMenu, setShowArticleMenu] = useState(false) const [showVideoMenu, setShowVideoMenu] = useState(false) + const [showExternalMenu, setShowExternalMenu] = useState(false) const articleMenuRef = useRef(null) const videoMenuRef = useRef(null) + const externalMenuRef = useRef(null) const [ytMeta, setYtMeta] = useState<{ title?: string; description?: string; transcript?: string } | null>(null) const { renderedHtml: renderedMarkdownHtml, previewRef: markdownPreviewRef, processedMarkdown } = useMarkdownToHTML(markdown, relayPool) @@ -145,15 +147,18 @@ const ContentPanel: React.FC = ({ if (videoMenuRef.current && !videoMenuRef.current.contains(target)) { setShowVideoMenu(false) } + if (externalMenuRef.current && !externalMenuRef.current.contains(target)) { + setShowExternalMenu(false) + } } - if (showArticleMenu || showVideoMenu) { + if (showArticleMenu || showVideoMenu || showExternalMenu) { document.addEventListener('mousedown', handleClickOutside) return () => { document.removeEventListener('mousedown', handleClickOutside) } } - }, [showArticleMenu, showVideoMenu]) + }, [showArticleMenu, showVideoMenu, showExternalMenu]) const readingStats = useMemo(() => { const content = markdown || html || '' @@ -280,6 +285,38 @@ const ContentPanel: React.FC = ({ setShowVideoMenu(false) } } + + // External article actions + const toggleExternalMenu = () => setShowExternalMenu(v => !v) + + const handleOpenExternalUrl = () => { + if (selectedUrl) window.open(selectedUrl, '_blank', 'noopener,noreferrer') + setShowExternalMenu(false) + } + + const handleCopyExternalUrl = async () => { + try { + if (selectedUrl) await navigator.clipboard.writeText(selectedUrl) + } catch (e) { + console.warn('Clipboard copy failed', e) + } finally { + setShowExternalMenu(false) + } + } + + const handleShareExternalUrl = async () => { + try { + if (selectedUrl && (navigator as { share?: (d: { title?: string; url?: string }) => Promise }).share) { + await (navigator as { share: (d: { title?: string; url?: string }) => Promise }).share({ title: title || 'Article', url: selectedUrl }) + } else if (selectedUrl) { + await navigator.clipboard.writeText(selectedUrl) + } + } catch (e) { + console.warn('Share failed', e) + } finally { + setShowExternalMenu(false) + } + } // Check if article is already marked as read when URL/article changes useEffect(() => { @@ -533,6 +570,47 @@ const ContentPanel: React.FC = ({ /> )} + {/* Article menu for external URLs */} + {!isNostrArticle && !isExternalVideo && selectedUrl && ( +
+
+ + + {showExternalMenu && ( +
+ + + +
+ )} +
+
+ )} + {/* Article menu for nostr-native articles */} {isNostrArticle && currentArticle && articleLinks && (