Add sidebar destination to save popovers and restore NoteCard click guards
- Add 'Sidebar' option (PanelLeft icon) to save popover on SpellRunPage and SearchPage
- SearchPage publishes a kind:777 spell before adding to sidebar (same as Share flow)
- SpellRunPage adds directly since nevent is already available
- Restore target.closest('button') and target.closest('a') guards in NoteCard
click handlers to prevent double-navigation when clicking interactive elements
This commit is contained in:
@@ -255,7 +255,9 @@ export const NoteCard = memo(function NoteCard({
|
||||
target.closest("[data-radix-dialog-content]") ||
|
||||
target.closest("[data-vaul-drawer]") ||
|
||||
target.closest("[data-vaul-drawer-overlay]") ||
|
||||
target.closest('[data-testid="zap-modal"]')
|
||||
target.closest('[data-testid="zap-modal"]') ||
|
||||
target.closest("button") ||
|
||||
target.closest("a")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -270,7 +272,9 @@ export const NoteCard = memo(function NoteCard({
|
||||
target.closest("[data-radix-dialog-content]") ||
|
||||
target.closest("[data-vaul-drawer]") ||
|
||||
target.closest("[data-vaul-drawer-overlay]") ||
|
||||
target.closest('[data-testid="zap-modal"]')
|
||||
target.closest('[data-testid="zap-modal"]') ||
|
||||
target.closest("button") ||
|
||||
target.closest("a")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
BookmarkPlus,
|
||||
Check,
|
||||
Loader2,
|
||||
PanelLeft,
|
||||
Globe, Users, UserSearch,
|
||||
Clock, Flame, TrendingUp,
|
||||
Share2,
|
||||
@@ -38,6 +39,7 @@ import { useSearchProfiles } from '@/hooks/useSearchProfiles';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { useStreamPosts } from '@/hooks/useStreamPosts';
|
||||
import { useFeed, type FeedItem } from '@/hooks/useFeed';
|
||||
import { useFeedSettings } from '@/hooks/useFeedSettings';
|
||||
import { useSavedFeeds } from '@/hooks/useSavedFeeds';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useProfileTabs } from '@/hooks/useProfileTabs';
|
||||
@@ -355,6 +357,7 @@ export function SearchPage() {
|
||||
const { lists } = useUserLists();
|
||||
const { data: followPacks = [] } = useFollowPacks();
|
||||
const { savedFeeds, addSavedFeed, isPending: isSavingFeed } = useSavedFeeds();
|
||||
const { addToSidebar } = useFeedSettings();
|
||||
const profileTabsQuery = useProfileTabs(user?.pubkey);
|
||||
const { publishProfileTabs, isPending: isPublishingTabs } = usePublishProfileTabs();
|
||||
const { mutateAsync: publishEvent } = useNostrPublish();
|
||||
@@ -363,6 +366,7 @@ export function SearchPage() {
|
||||
const [saveFeedLabel, setSaveFeedLabel] = useState('');
|
||||
const [savedJustNow, setSavedJustNow] = useState(false);
|
||||
const [isSharing, setIsSharing] = useState(false);
|
||||
const [isAddingToSidebar, setIsAddingToSidebar] = useState(false);
|
||||
|
||||
const listPickerValue = useMatchedListId(authorPubkeys);
|
||||
|
||||
@@ -467,6 +471,30 @@ export function SearchPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddToSidebar = async () => {
|
||||
if (!saveFeedLabel.trim() || isAddingToSidebar || !user) return;
|
||||
setIsAddingToSidebar(true);
|
||||
try {
|
||||
const tags = currentSpellTags.map(([t, ...rest]) =>
|
||||
t === 'name' ? ['name', saveFeedLabel.trim()] :
|
||||
t === 'alt' ? ['alt', `Spell: ${saveFeedLabel.trim()}`] :
|
||||
[t, ...rest]
|
||||
);
|
||||
const event = await publishEvent({ kind: 777, content: '', tags, created_at: Math.floor(Date.now() / 1000) });
|
||||
const neventId = nip19.neventEncode({ id: event.id, author: event.pubkey, kind: event.kind });
|
||||
addToSidebar(`nostr:${neventId}`);
|
||||
setSavePopoverOpen(false);
|
||||
setSaveFeedLabel('');
|
||||
setSavedJustNow(true);
|
||||
setTimeout(() => setSavedJustNow(false), 2000);
|
||||
toast({ title: 'Added to sidebar' });
|
||||
} catch (err) {
|
||||
toast({ title: 'Failed to add to sidebar', description: err instanceof Error ? err.message : undefined, variant: 'destructive' });
|
||||
} finally {
|
||||
setIsAddingToSidebar(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve author pubkeys for the stream
|
||||
const streamAuthorPubkeys = authorScope === 'follows'
|
||||
? followPubkeys
|
||||
@@ -598,6 +626,14 @@ export function SearchPage() {
|
||||
loading={isPublishingTabs}
|
||||
/>
|
||||
)}
|
||||
<SaveDestinationRow
|
||||
icon={<PanelLeft className="size-4 text-muted-foreground" />}
|
||||
label="Sidebar"
|
||||
description="Pin to your sidebar"
|
||||
onClick={() => handleAddToSidebar()}
|
||||
disabled={!saveFeedLabel.trim() || isSavingFeed || isPublishingTabs || isAddingToSidebar}
|
||||
loading={isAddingToSidebar}
|
||||
/>
|
||||
<SaveDestinationRow
|
||||
icon={<Share2 className="size-4 text-muted-foreground" />}
|
||||
label="Share"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { nip19 } from 'nostr-tools';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { AlertCircle, BookmarkPlus, Check, Loader2, Share2, User, WandSparkles } from 'lucide-react';
|
||||
import { AlertCircle, BookmarkPlus, Check, Loader2, PanelLeft, Share2, User, WandSparkles } from 'lucide-react';
|
||||
|
||||
import { NoteCard } from '@/components/NoteCard';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
@@ -21,6 +21,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useFollowList } from '@/hooks/useFollowActions';
|
||||
import { useProfileTabs } from '@/hooks/useProfileTabs';
|
||||
import { usePublishProfileTabs } from '@/hooks/usePublishProfileTabs';
|
||||
import { useFeedSettings } from '@/hooks/useFeedSettings';
|
||||
import { useSavedFeeds } from '@/hooks/useSavedFeeds';
|
||||
import { useStreamPosts } from '@/hooks/useStreamPosts';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
@@ -99,6 +100,7 @@ export function SpellRunPage() {
|
||||
|
||||
// ── Save popover state ───────────────────────────────────────────────
|
||||
const { savedFeeds, addSavedFeed, removeSavedFeed } = useSavedFeeds();
|
||||
const { addToSidebar, orderedItems } = useFeedSettings();
|
||||
const profileTabsQuery = useProfileTabs(user?.pubkey);
|
||||
const { publishProfileTabs, isPending: isPublishingTabs } = usePublishProfileTabs();
|
||||
const { toast } = useToast();
|
||||
@@ -127,6 +129,10 @@ export function SpellRunPage() {
|
||||
|
||||
const alreadySaved = !!matchingSavedFeed;
|
||||
|
||||
/** The nostr: URI for this spell (used for sidebar). */
|
||||
const sidebarId = nevent ? `nostr:${nevent}` : undefined;
|
||||
const alreadyInSidebar = sidebarId ? orderedItems.includes(sidebarId) : false;
|
||||
|
||||
const handleSaveHomeFeed = useCallback(async () => {
|
||||
if (!spellAsFilter || !saveFeedLabel.trim()) return;
|
||||
await addSavedFeed(saveFeedLabel.trim(), spellAsFilter as Record<string, unknown>, []);
|
||||
@@ -168,6 +174,16 @@ export function SpellRunPage() {
|
||||
}
|
||||
}, [spellEvent, nevent, saveFeedLabel, toast]);
|
||||
|
||||
const handleAddToSidebar = useCallback(() => {
|
||||
if (!sidebarId) return;
|
||||
addToSidebar(sidebarId);
|
||||
setSavePopoverOpen(false);
|
||||
setSaveFeedLabel('');
|
||||
setSavedJustNow(true);
|
||||
setTimeout(() => setSavedJustNow(false), 2000);
|
||||
toast({ title: 'Added to sidebar' });
|
||||
}, [sidebarId, addToSidebar, toast]);
|
||||
|
||||
const handleRemoveSaved = useCallback(async () => {
|
||||
if (!matchingSavedFeed) return;
|
||||
await removeSavedFeed(matchingSavedFeed.id);
|
||||
@@ -264,6 +280,14 @@ export function SpellRunPage() {
|
||||
disabled={!saveFeedLabel.trim() || isPublishingTabs}
|
||||
loading={isPublishingTabs}
|
||||
/>
|
||||
<SaveDestinationRow
|
||||
icon={<PanelLeft className="size-4 text-muted-foreground" />}
|
||||
label="Sidebar"
|
||||
description="Pin to your sidebar"
|
||||
onClick={handleAddToSidebar}
|
||||
disabled={!sidebarId || alreadyInSidebar}
|
||||
loading={false}
|
||||
/>
|
||||
<SaveDestinationRow
|
||||
icon={<Share2 className="size-4 text-muted-foreground" />}
|
||||
label="Share"
|
||||
|
||||
Reference in New Issue
Block a user