diff --git a/src/components/NoteCard.tsx b/src/components/NoteCard.tsx
index 40dcceb5..23eed397 100644
--- a/src/components/NoteCard.tsx
+++ b/src/components/NoteCard.tsx
@@ -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;
}
diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx
index e7da32a8..5ba7a56a 100644
--- a/src/pages/SearchPage.tsx
+++ b/src/pages/SearchPage.tsx
@@ -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}
/>
)}
+ }
+ label="Sidebar"
+ description="Pin to your sidebar"
+ onClick={() => handleAddToSidebar()}
+ disabled={!saveFeedLabel.trim() || isSavingFeed || isPublishingTabs || isAddingToSidebar}
+ loading={isAddingToSidebar}
+ />
}
label="Share"
diff --git a/src/pages/SpellRunPage.tsx b/src/pages/SpellRunPage.tsx
index ac60a2db..01c95e42 100644
--- a/src/pages/SpellRunPage.tsx
+++ b/src/pages/SpellRunPage.tsx
@@ -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, []);
@@ -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}
/>
+ }
+ label="Sidebar"
+ description="Pin to your sidebar"
+ onClick={handleAddToSidebar}
+ disabled={!sidebarId || alreadyInSidebar}
+ loading={false}
+ />
}
label="Share"