From 201dd60cb744c8a23d069c18cac8d24bac5b2fe7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 23 Mar 2026 00:02:00 -0500 Subject: [PATCH] Add sidebar toggle to external content pages and post 3-dots menu ExternalContentPage (country, URL, ISBN pages) gains a 3-dots menu on the right side of the action bar with Add/Remove from sidebar. NoteMoreMenu gets an Add/Remove from sidebar item after 'Add to list', so any post can be pinned to the sidebar from its options menu. Both locations toggle between add (PanelLeft icon) and remove (Trash2 icon) based on whether the item is already in the sidebar. --- src/components/NoteMoreMenu.tsx | 21 ++++++++++++++ src/pages/ExternalContentPage.tsx | 47 ++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/components/NoteMoreMenu.tsx b/src/components/NoteMoreMenu.tsx index 030c7b8b..3c0a557a 100644 --- a/src/components/NoteMoreMenu.tsx +++ b/src/components/NoteMoreMenu.tsx @@ -14,6 +14,7 @@ import { Trash2, StickyNote, ListPlus, + PanelLeft, Copy, Check, } from 'lucide-react'; @@ -48,6 +49,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useMuteList } from '@/hooks/useMuteList'; import { useDeleteEvent } from '@/hooks/useDeleteEvent'; +import { useFeedSettings } from '@/hooks/useFeedSettings'; import { genUserName } from '@/lib/genUserName'; import { timeAgo } from '@/lib/timeAgo'; import { toast } from '@/hooks/useToast'; @@ -241,9 +243,12 @@ function NoteMoreMenuContent({ event, open, onOpenChange, onReport, onMention, o const { addMute, removeMute, isMuted } = useMuteList(); const userMuted = isMuted('pubkey', event.pubkey); const { mutate: deleteEvent, isPending: isDeleting } = useDeleteEvent(); + const { addToSidebar, removeFromSidebar, orderedItems } = useFeedSettings(); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const nip19Id = encodeEventNip19(event); + const nostrUri = `nostr:${nip19Id}`; + const isInSidebar = orderedItems.includes(nostrUri); const close = () => onOpenChange(false); @@ -264,6 +269,17 @@ function NoteMoreMenuContent({ event, open, onOpenChange, onReport, onMention, o close(); }; + const handleToggleSidebar = () => { + if (isInSidebar) { + removeFromSidebar(nostrUri); + toast({ title: 'Removed from sidebar' }); + } else { + addToSidebar(nostrUri); + toast({ title: 'Added to sidebar' }); + } + close(); + }; + const handleTogglePin = () => { togglePin.mutate(event.id, { onSuccess: () => { @@ -383,6 +399,11 @@ function NoteMoreMenuContent({ event, open, onOpenChange, onReport, onMention, o onClick={() => { onAddToList(); }} /> )} + : } + label={isInSidebar ? 'Remove from sidebar' : 'Add to sidebar'} + onClick={handleToggleSidebar} + /> diff --git a/src/pages/ExternalContentPage.tsx b/src/pages/ExternalContentPage.tsx index 7375379e..da3b835b 100644 --- a/src/pages/ExternalContentPage.tsx +++ b/src/pages/ExternalContentPage.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo, useRef, useState } from 'react'; import { useSeoMeta } from '@unhead/react'; -import { ArrowLeft, Globe, Heart, MessageSquare, Repeat2, Star, AlertTriangle } from 'lucide-react'; +import { ArrowLeft, Globe, Heart, MessageSquare, MoreHorizontal, Repeat2, Star, AlertTriangle, PanelLeft, Trash2 } from 'lucide-react'; import { Link, useLocation, useParams } from 'react-router-dom'; import { Skeleton } from '@/components/ui/skeleton'; import { Badge } from '@/components/ui/badge'; @@ -9,6 +9,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { getAvatarShape } from '@/lib/avatarShape'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import { ThreadedReplyList } from '@/components/ThreadedReplyList'; import { ComposeBox } from '@/components/ComposeBox'; import { ReplyComposeModal } from '@/components/ReplyComposeModal'; @@ -36,6 +39,7 @@ import { useLinkPreview } from '@/hooks/useLinkPreview'; import { useLayoutOptions } from '@/contexts/LayoutContext'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useNostrPublish } from '@/hooks/useNostrPublish'; +import { useFeedSettings } from '@/hooks/useFeedSettings'; import { useQueryClient } from '@tanstack/react-query'; import { useToast } from '@/hooks/useToast'; import { getDisplayName } from '@/lib/getDisplayName'; @@ -73,6 +77,19 @@ function ExternalActionBar({ content }: { content: ExternalContent }) { const queryClient = useQueryClient(); const { toast } = useToast(); const identifier = content.value; + const { addToSidebar, removeFromSidebar, orderedItems } = useFeedSettings(); + + const isInSidebar = orderedItems.includes(identifier); + + const handleAddToSidebar = useCallback(() => { + addToSidebar(identifier); + toast({ title: 'Added to sidebar' }); + }, [identifier, addToSidebar, toast]); + + const handleRemoveFromSidebar = useCallback(() => { + removeFromSidebar(identifier); + toast({ title: 'Removed from sidebar' }); + }, [identifier, removeFromSidebar, toast]); const userReactionData = useExternalUserReaction(content); const reactionCount = useExternalReactionCount(content); @@ -224,6 +241,34 @@ function ExternalActionBar({ content }: { content: ExternalContent }) { )} + {/* Spacer pushes the 3-dots menu to the right */} +
+ + {/* 3-dots menu with sidebar action */} + + + + + + {isInSidebar ? ( + + + Remove from sidebar + + ) : ( + + + Add to sidebar + + )} + + + {shareOpen && (