mirror of
https://github.com/hoornet/vega.git
synced 2026-05-13 12:28:36 -07:00
Add follows view with followers/following tabs and new follower badges
Followers tab fetches kind 3 events referencing the user, following tab shows the contact list. Each row has avatar, NIP-05 badge, follow/unfollow button, and "follows you" indicator. New follower notifications from the background poller increment a sidebar badge that clears on view open.
This commit is contained in:
@@ -19,6 +19,7 @@ import { NotificationsView } from "./components/notifications/NotificationsView"
|
|||||||
import { BookmarkView } from "./components/bookmark/BookmarkView";
|
import { BookmarkView } from "./components/bookmark/BookmarkView";
|
||||||
import { HashtagFeed } from "./components/feed/HashtagFeed";
|
import { HashtagFeed } from "./components/feed/HashtagFeed";
|
||||||
import { PodcastsView } from "./components/podcast/PodcastsView";
|
import { PodcastsView } from "./components/podcast/PodcastsView";
|
||||||
|
import { FollowsView } from "./components/follows/FollowsView";
|
||||||
import { PodcastPlayerBar } from "./components/podcast/PodcastPlayerBar";
|
import { PodcastPlayerBar } from "./components/podcast/PodcastPlayerBar";
|
||||||
import { ToastContainer } from "./components/shared/ToastContainer";
|
import { ToastContainer } from "./components/shared/ToastContainer";
|
||||||
import { DebugPanel } from "./components/shared/DebugPanel";
|
import { DebugPanel } from "./components/shared/DebugPanel";
|
||||||
@@ -123,6 +124,7 @@ function App() {
|
|||||||
{currentView === "bookmarks" && <BookmarkView />}
|
{currentView === "bookmarks" && <BookmarkView />}
|
||||||
{currentView === "hashtag" && <HashtagFeed />}
|
{currentView === "hashtag" && <HashtagFeed />}
|
||||||
{currentView === "podcasts" && <PodcastsView />}
|
{currentView === "podcasts" && <PodcastsView />}
|
||||||
|
{currentView === "follows" && <FollowsView />}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<PodcastPlayerBar />
|
<PodcastPlayerBar />
|
||||||
|
|||||||
191
src/components/follows/FollowsView.tsx
Normal file
191
src/components/follows/FollowsView.tsx
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useUIStore } from "../../stores/ui";
|
||||||
|
import { useUserStore } from "../../stores/user";
|
||||||
|
import { useNotificationsStore } from "../../stores/notifications";
|
||||||
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
|
import { useNip05Verified } from "../../hooks/useNip05Verified";
|
||||||
|
import { fetchFollowers, ensureConnected } from "../../lib/nostr";
|
||||||
|
import { shortenPubkey } from "../../lib/utils";
|
||||||
|
|
||||||
|
function FollowRow({
|
||||||
|
pubkey,
|
||||||
|
followsYou,
|
||||||
|
}: {
|
||||||
|
pubkey: string;
|
||||||
|
followsYou?: boolean;
|
||||||
|
}) {
|
||||||
|
const profile = useProfile(pubkey);
|
||||||
|
const name = profile?.displayName || profile?.name || shortenPubkey(pubkey);
|
||||||
|
const avatar = profile?.picture;
|
||||||
|
const nip05 = profile?.nip05;
|
||||||
|
const verified = useNip05Verified(pubkey, nip05);
|
||||||
|
|
||||||
|
const { follows, follow, unfollow, pubkey: ownPubkey } = useUserStore();
|
||||||
|
const { openProfile } = useUIStore();
|
||||||
|
const isFollowing = follows.includes(pubkey);
|
||||||
|
const isSelf = pubkey === ownPubkey;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3 px-4 py-2.5 hover:bg-bg-hover transition-colors">
|
||||||
|
<button className="shrink-0" onClick={() => openProfile(pubkey)}>
|
||||||
|
{avatar ? (
|
||||||
|
<img
|
||||||
|
src={avatar}
|
||||||
|
alt=""
|
||||||
|
className="w-9 h-9 rounded-sm object-cover bg-bg-raised hover:opacity-80 transition-opacity"
|
||||||
|
loading="lazy"
|
||||||
|
onError={(e) => { (e.target as HTMLImageElement).style.display = "none"; }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-9 h-9 rounded-sm bg-bg-raised border border-border flex items-center justify-center text-text-dim text-xs hover:border-accent/40 transition-colors">
|
||||||
|
{name.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-baseline gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => openProfile(pubkey)}
|
||||||
|
className="text-text font-medium truncate text-[13px] hover:text-accent transition-colors text-left"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
{nip05 && (
|
||||||
|
<span className={`text-[10px] truncate max-w-40 ${verified === "valid" ? "text-success" : "text-text-dim"}`}>
|
||||||
|
{verified === "valid" ? "✓ " : ""}{nip05}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{followsYou && (
|
||||||
|
<span className="text-[9px] text-text-dim bg-bg-raised px-1.5 py-0.5 rounded-sm">follows you</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isSelf && (
|
||||||
|
<button
|
||||||
|
onClick={() => isFollowing ? unfollow(pubkey) : follow(pubkey)}
|
||||||
|
className={`shrink-0 px-3 py-1 text-[11px] transition-colors ${
|
||||||
|
isFollowing
|
||||||
|
? "text-text-dim border border-border hover:text-danger hover:border-danger"
|
||||||
|
: "bg-accent hover:bg-accent-hover text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isFollowing ? "unfollow" : "follow"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FollowsView() {
|
||||||
|
const { followsTab, setFollowsTab } = useUIStore();
|
||||||
|
const { pubkey, follows } = useUserStore();
|
||||||
|
const { clearNewFollowers } = useNotificationsStore();
|
||||||
|
|
||||||
|
const [followers, setFollowers] = useState<string[]>([]);
|
||||||
|
const [followersLoading, setFollowersLoading] = useState(false);
|
||||||
|
const [followersError, setFollowersError] = useState<string | null>(null);
|
||||||
|
const [followersFetched, setFollowersFetched] = useState(false);
|
||||||
|
|
||||||
|
// Clear badge when view opens
|
||||||
|
useEffect(() => {
|
||||||
|
clearNewFollowers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Fetch followers when tab is selected
|
||||||
|
useEffect(() => {
|
||||||
|
if (followsTab !== "followers" || !pubkey || followersFetched) return;
|
||||||
|
let cancelled = false;
|
||||||
|
setFollowersLoading(true);
|
||||||
|
setFollowersError(null);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
await ensureConnected();
|
||||||
|
const result = await fetchFollowers(pubkey);
|
||||||
|
if (!cancelled) {
|
||||||
|
setFollowers(result);
|
||||||
|
setFollowersFetched(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!cancelled) setFollowersError(`Failed to load followers: ${err}`);
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setFollowersLoading(false);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}, [followsTab, pubkey]);
|
||||||
|
|
||||||
|
// Build followers set for "follows you" badge on Following tab
|
||||||
|
const followersSet = new Set(followers);
|
||||||
|
|
||||||
|
const tabs: Array<{ id: "followers" | "following"; label: string; count?: number }> = [
|
||||||
|
{ id: "followers", label: "followers", count: followersFetched ? followers.length : undefined },
|
||||||
|
{ id: "following", label: "following", count: follows.length },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="border-b border-border px-4 py-2.5 flex items-center gap-4 shrink-0">
|
||||||
|
<span className="text-text font-medium text-[13px]">follows</span>
|
||||||
|
<div className="flex gap-3 ml-auto">
|
||||||
|
{tabs.map((t) => (
|
||||||
|
<button
|
||||||
|
key={t.id}
|
||||||
|
onClick={() => setFollowsTab(t.id)}
|
||||||
|
className={`text-[12px] transition-colors flex items-center gap-1 ${
|
||||||
|
followsTab === t.id
|
||||||
|
? "text-accent border-b border-accent pb-0.5"
|
||||||
|
: "text-text-dim hover:text-text"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t.label}
|
||||||
|
{t.count !== undefined && (
|
||||||
|
<span className="text-[10px] text-text-dim">({t.count})</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
{followsTab === "followers" && (
|
||||||
|
<>
|
||||||
|
{followersLoading && (
|
||||||
|
<div className="px-4 py-8 text-center text-text-dim text-[12px]">
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<span className="w-4 h-4 border-2 border-accent border-t-transparent rounded-full animate-spin" />
|
||||||
|
Loading followers…
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{followersError && (
|
||||||
|
<p className="px-4 py-4 text-danger text-[12px]">{followersError}</p>
|
||||||
|
)}
|
||||||
|
{!followersLoading && !followersError && followers.length === 0 && followersFetched && (
|
||||||
|
<p className="px-4 py-8 text-text-dim text-[12px] text-center">No followers found yet.</p>
|
||||||
|
)}
|
||||||
|
{followers.map((pk) => (
|
||||||
|
<FollowRow key={pk} pubkey={pk} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{followsTab === "following" && (
|
||||||
|
<>
|
||||||
|
{follows.length === 0 && (
|
||||||
|
<p className="px-4 py-8 text-text-dim text-[12px] text-center">Not following anyone yet.</p>
|
||||||
|
)}
|
||||||
|
{follows.map((pk) => (
|
||||||
|
<FollowRow key={pk} pubkey={pk} followsYou={followersSet.has(pk)} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ const NAV_ITEMS = [
|
|||||||
{ id: "bookmarks" as const, label: "bookmarks", icon: "▪" },
|
{ id: "bookmarks" as const, label: "bookmarks", icon: "▪" },
|
||||||
{ id: "dm" as const, label: "messages", icon: "✉" },
|
{ id: "dm" as const, label: "messages", icon: "✉" },
|
||||||
{ id: "notifications" as const, label: "notifications", icon: "🔔" },
|
{ id: "notifications" as const, label: "notifications", icon: "🔔" },
|
||||||
|
{ id: "follows" as const, label: "follows", icon: "♺" },
|
||||||
{ id: "zaps" as const, label: "zaps", icon: "⚡" },
|
{ id: "zaps" as const, label: "zaps", icon: "⚡" },
|
||||||
{ id: "relays" as const, label: "relays", icon: "⟐" },
|
{ id: "relays" as const, label: "relays", icon: "⟐" },
|
||||||
{ id: "settings" as const, label: "settings", icon: "⚙" },
|
{ id: "settings" as const, label: "settings", icon: "⚙" },
|
||||||
@@ -25,7 +26,7 @@ const NAV_ITEMS = [
|
|||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const { currentView, setView, sidebarCollapsed, toggleSidebar } = useUIStore();
|
const { currentView, setView, sidebarCollapsed, toggleSidebar } = useUIStore();
|
||||||
const { loggedIn } = useUserStore();
|
const { loggedIn } = useUserStore();
|
||||||
const { unreadCount: notifUnread, dmUnreadCount } = useNotificationsStore();
|
const { unreadCount: notifUnread, dmUnreadCount, newFollowersCount } = useNotificationsStore();
|
||||||
const draftCount = useDraftStore((s) => s.drafts.length);
|
const draftCount = useDraftStore((s) => s.drafts.length);
|
||||||
const bookmarkUnread = useBookmarkStore((s) => s.unreadArticleCount());
|
const bookmarkUnread = useBookmarkStore((s) => s.unreadArticleCount());
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ export function Sidebar() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{NAV_ITEMS.map((item) => {
|
{NAV_ITEMS.map((item) => {
|
||||||
const badge = item.id === "dm" ? dmUnreadCount : item.id === "notifications" ? notifUnread : item.id === "bookmarks" ? bookmarkUnread : 0;
|
const badge = item.id === "dm" ? dmUnreadCount : item.id === "notifications" ? notifUnread : item.id === "bookmarks" ? bookmarkUnread : item.id === "follows" ? newFollowersCount : 0;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export { getNDK, getNDKUptimeMs, connectToRelays, ensureConnected, resetNDK, getStoredRelayUrls, addRelay, removeRelay, fetchWithTimeout, withTimeout, FEED_TIMEOUT, THREAD_TIMEOUT, SINGLE_TIMEOUT } from "./core";
|
export { getNDK, getNDKUptimeMs, connectToRelays, ensureConnected, resetNDK, getStoredRelayUrls, addRelay, removeRelay, fetchWithTimeout, withTimeout, FEED_TIMEOUT, THREAD_TIMEOUT, SINGLE_TIMEOUT } from "./core";
|
||||||
export { fetchGlobalFeed, fetchFollowFeed, fetchUserNotes, fetchUserNotesNIP65, fetchNoteById, fetchReplies, publishNote, publishReply, publishRepost, publishQuote, fetchHashtagFeed, fetchThreadEvents, fetchAncestors } from "./notes";
|
export { fetchGlobalFeed, fetchFollowFeed, fetchUserNotes, fetchUserNotesNIP65, fetchNoteById, fetchReplies, publishNote, publishReply, publishRepost, publishQuote, fetchHashtagFeed, fetchThreadEvents, fetchAncestors } from "./notes";
|
||||||
export { publishProfile, publishContactList, fetchProfile, fetchFollowSuggestions, fetchMentions, fetchNewFollowers } from "./social";
|
export { publishProfile, publishContactList, fetchProfile, fetchFollowSuggestions, fetchMentions, fetchFollowers, fetchNewFollowers } from "./social";
|
||||||
export { publishArticle, fetchArticle, fetchAuthorArticles, fetchArticleFeed, searchArticles, fetchByAddr } from "./articles";
|
export { publishArticle, fetchArticle, fetchAuthorArticles, fetchArticleFeed, searchArticles, fetchByAddr } from "./articles";
|
||||||
export { publishReaction, fetchReactionCount, fetchReplyCount, fetchZapCount, fetchBatchEngagement, fetchZapsReceived, fetchZapsSent } from "./engagement";
|
export { publishReaction, fetchReactionCount, fetchReplyCount, fetchZapCount, fetchBatchEngagement, fetchZapsReceived, fetchZapsSent } from "./engagement";
|
||||||
export { fetchDMConversations, fetchDMThread, sendDM, decryptDM } from "./dms";
|
export { fetchDMConversations, fetchDMThread, sendDM, decryptDM } from "./dms";
|
||||||
|
|||||||
@@ -82,6 +82,20 @@ export async function fetchMentions(pubkey: string, since: number, limit = 50):
|
|||||||
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
|
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchFollowers(pubkey: string, limit = 200): Promise<string[]> {
|
||||||
|
const instance = getNDK();
|
||||||
|
const events = await fetchWithTimeout(
|
||||||
|
instance,
|
||||||
|
{ kinds: [3 as NDKKind], "#p": [pubkey], limit },
|
||||||
|
FEED_TIMEOUT,
|
||||||
|
);
|
||||||
|
const followerPubkeys = new Set<string>();
|
||||||
|
for (const e of events) {
|
||||||
|
if (e.pubkey !== pubkey) followerPubkeys.add(e.pubkey);
|
||||||
|
}
|
||||||
|
return Array.from(followerPubkeys);
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchNewFollowers(pubkey: string, since: number, limit = 20): Promise<NDKEvent[]> {
|
export async function fetchNewFollowers(pubkey: string, since: number, limit = 20): Promise<NDKEvent[]> {
|
||||||
const instance = getNDK();
|
const instance = getNDK();
|
||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ async function pollOnce(pubkey: string) {
|
|||||||
recentlySeen.add(e.id!);
|
recentlySeen.add(e.id!);
|
||||||
const name = await getProfileName(e.pubkey);
|
const name = await getProfileName(e.pubkey);
|
||||||
notifyFollower(name).catch(() => {});
|
notifyFollower(name).catch(() => {});
|
||||||
|
useNotificationsStore.getState().incrementNewFollowers();
|
||||||
}
|
}
|
||||||
if (followers.length > 0) ts.followers = now;
|
if (followers.length > 0) ts.followers = now;
|
||||||
} catch { /* non-critical */ }
|
} catch { /* non-critical */ }
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface NotificationsState {
|
|||||||
currentPubkey: string | null;
|
currentPubkey: string | null;
|
||||||
dmLastSeen: Record<string, number>;
|
dmLastSeen: Record<string, number>;
|
||||||
dmUnreadCount: number;
|
dmUnreadCount: number;
|
||||||
|
newFollowersCount: number;
|
||||||
|
|
||||||
fetchNotifications: (pubkey: string) => Promise<void>;
|
fetchNotifications: (pubkey: string) => Promise<void>;
|
||||||
markRead: (eventId: string) => void;
|
markRead: (eventId: string) => void;
|
||||||
@@ -21,6 +22,8 @@ interface NotificationsState {
|
|||||||
isRead: (eventId: string) => boolean;
|
isRead: (eventId: string) => boolean;
|
||||||
markDMRead: (partnerPubkey: string) => void;
|
markDMRead: (partnerPubkey: string) => void;
|
||||||
computeDMUnread: (conversations: Array<{ partnerPubkey: string; lastAt: number }>) => void;
|
computeDMUnread: (conversations: Array<{ partnerPubkey: string; lastAt: number }>) => void;
|
||||||
|
incrementNewFollowers: () => void;
|
||||||
|
clearNewFollowers: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadReadIds(): Set<string> {
|
function loadReadIds(): Set<string> {
|
||||||
@@ -54,6 +57,7 @@ export const useNotificationsStore = create<NotificationsState>((set, get) => ({
|
|||||||
currentPubkey: null,
|
currentPubkey: null,
|
||||||
dmLastSeen: loadDMLastSeen(),
|
dmLastSeen: loadDMLastSeen(),
|
||||||
dmUnreadCount: 0,
|
dmUnreadCount: 0,
|
||||||
|
newFollowersCount: 0,
|
||||||
|
|
||||||
fetchNotifications: async (pubkey: string) => {
|
fetchNotifications: async (pubkey: string) => {
|
||||||
const state = get();
|
const state = get();
|
||||||
@@ -120,4 +124,7 @@ export const useNotificationsStore = create<NotificationsState>((set, get) => ({
|
|||||||
const dmUnreadCount = unreadConvos.length;
|
const dmUnreadCount = unreadConvos.length;
|
||||||
set({ dmUnreadCount });
|
set({ dmUnreadCount });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
incrementNewFollowers: () => set((s) => ({ newFollowersCount: s.newFollowersCount + 1 })),
|
||||||
|
clearNewFollowers: () => set({ newFollowersCount: 0 }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { create } from "zustand";
|
|||||||
|
|
||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
|
||||||
type View = "feed" | "search" | "relays" | "settings" | "profile" | "thread" | "article-editor" | "article" | "articles" | "media" | "podcasts" | "about" | "zaps" | "dm" | "notifications" | "bookmarks" | "hashtag";
|
type View = "feed" | "search" | "relays" | "settings" | "profile" | "thread" | "article-editor" | "article" | "articles" | "media" | "podcasts" | "about" | "zaps" | "dm" | "notifications" | "bookmarks" | "hashtag" | "follows";
|
||||||
type FeedTab = "global" | "following" | "trending";
|
type FeedTab = "global" | "following" | "trending";
|
||||||
|
|
||||||
interface ViewStackEntry {
|
interface ViewStackEntry {
|
||||||
@@ -29,9 +29,11 @@ interface UIState {
|
|||||||
showHelp: boolean;
|
showHelp: boolean;
|
||||||
showDebugPanel: boolean;
|
showDebugPanel: boolean;
|
||||||
feedLanguageFilter: string | null;
|
feedLanguageFilter: string | null;
|
||||||
|
followsTab: "followers" | "following";
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
themeId: string;
|
themeId: string;
|
||||||
setView: (view: View) => void;
|
setView: (view: View) => void;
|
||||||
|
setFollowsTab: (tab: "followers" | "following") => void;
|
||||||
setFeedTab: (tab: FeedTab) => void;
|
setFeedTab: (tab: FeedTab) => void;
|
||||||
openProfile: (pubkey: string) => void;
|
openProfile: (pubkey: string) => void;
|
||||||
openThread: (note: NDKEvent, from?: View) => void;
|
openThread: (note: NDKEvent, from?: View) => void;
|
||||||
@@ -68,10 +70,12 @@ export const useUIStore = create<UIState>((set, _get) => ({
|
|||||||
showHelp: false,
|
showHelp: false,
|
||||||
showDebugPanel: false,
|
showDebugPanel: false,
|
||||||
feedLanguageFilter: null,
|
feedLanguageFilter: null,
|
||||||
|
followsTab: "followers",
|
||||||
fontSize: parseInt(localStorage.getItem(FONT_SIZE_KEY) || "14", 10),
|
fontSize: parseInt(localStorage.getItem(FONT_SIZE_KEY) || "14", 10),
|
||||||
themeId: localStorage.getItem(THEME_KEY) || "midnight",
|
themeId: localStorage.getItem(THEME_KEY) || "midnight",
|
||||||
setView: (currentView) => set({ currentView }),
|
setView: (currentView) => set({ currentView }),
|
||||||
setFeedTab: (feedTab) => set({ feedTab }),
|
setFeedTab: (feedTab) => set({ feedTab }),
|
||||||
|
setFollowsTab: (followsTab) => set({ followsTab }),
|
||||||
openProfile: (pubkey) => set((s) => {
|
openProfile: (pubkey) => set((s) => {
|
||||||
const stack = [...s.viewStack, { view: s.currentView, selectedNote: s.selectedNote, selectedPubkey: s.selectedPubkey }].slice(-MAX_STACK);
|
const stack = [...s.viewStack, { view: s.currentView, selectedNote: s.selectedNote, selectedPubkey: s.selectedPubkey }].slice(-MAX_STACK);
|
||||||
return { currentView: "profile", selectedPubkey: pubkey, previousView: s.currentView as View, viewStack: stack };
|
return { currentView: "profile", selectedPubkey: pubkey, previousView: s.currentView as View, viewStack: stack };
|
||||||
|
|||||||
Reference in New Issue
Block a user