"use client" import Link from "next/link" import { usePathname, useSearchParams } from "next/navigation" import { useSubscriptions } from "@/lib/queries/use-subscriptions" import { useUnreadCounts } from "@/lib/queries/use-unread-counts" import { useCustomFeeds } from "@/lib/queries/use-custom-feeds" import { useUserInterfaceStore } from "@/lib/stores/user-interface-store" import { classNames } from "@/lib/utilities" const NAVIGATION_LINK_CLASS = "block whitespace-nowrap px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary" const ACTIVE_LINK_CLASS = "bg-background-tertiary text-text-primary" function getFaviconUrl(feedUrl: string): string | null { try { const hostname = new URL(feedUrl).hostname return `https://www.google.com/s2/favicons?domain=${hostname}&sz=16` } catch { return null } } function FeedFavicon({ feedUrl }: { feedUrl: string }) { const faviconUrl = getFaviconUrl(feedUrl) if (!faviconUrl) return null return ( ) } function displayNameForSubscription(subscription: { customTitle: string | null feedTitle: string feedUrl: string }): string { if (subscription.customTitle) return subscription.customTitle if (subscription.feedTitle) return subscription.feedTitle try { return new URL(subscription.feedUrl).hostname } catch { return subscription.feedUrl || "untitled feed" } } function UnreadBadge({ count }: { count: number }) { if (count === 0) return null return ( {count > 999 ? "999+" : count} ) } function sidebarFocusClass( focusedPanel: string, focusedSidebarIndex: number, navIndex: number ): string { return focusedPanel === "sidebar" && focusedSidebarIndex === navIndex ? "bg-background-tertiary text-text-primary" : "" } export function SidebarContent() { const pathname = usePathname() const searchParameters = useSearchParams() const { data } = useSubscriptions() const { data: unreadCounts } = useUnreadCounts() const { data: customFeedsData } = useCustomFeeds() const setAddFeedDialogOpen = useUserInterfaceStore( (state) => state.setAddFeedDialogOpen ) const toggleSidebar = useUserInterfaceStore((state) => state.toggleSidebar) const showFeedFavicons = useUserInterfaceStore( (state) => state.showFeedFavicons ) const expandedFolderIdentifiers = useUserInterfaceStore( (state) => state.expandedFolderIdentifiers ) const toggleFolderExpansion = useUserInterfaceStore( (state) => state.toggleFolderExpansion ) const focusedPanel = useUserInterfaceStore((state) => state.focusedPanel) const focusedSidebarIndex = useUserInterfaceStore( (state) => state.focusedSidebarIndex ) function closeSidebarOnMobile() { if (typeof window !== "undefined" && window.innerWidth < 768) { toggleSidebar() } } const folders = data?.folders ?? [] const subscriptions = data?.subscriptions ?? [] const ungroupedSubscriptions = subscriptions.filter( (subscription) => !subscription.folderIdentifier ) const totalUnreadCount = Object.values(unreadCounts ?? {}).reduce( (sum, count) => sum + count, 0 ) function getFolderUnreadCount(folderIdentifier: string): number { return subscriptions .filter( (subscription) => subscription.folderIdentifier === folderIdentifier ) .reduce( (sum, subscription) => sum + (unreadCounts?.[subscription.feedIdentifier] ?? 0), 0 ) } const activeFeedIdentifier = searchParameters.get("feed") const activeFolderIdentifier = searchParameters.get("folder") const activeCustomFeedIdentifier = searchParameters.get("custom_feed") let navIndex = 0 return ( 0 ? { "data-has-unreads": "" } : {})} onClick={closeSidebarOnMobile} className={classNames( NAVIGATION_LINK_CLASS, "flex items-center", pathname === "/reader" && !activeFeedIdentifier && !activeFolderIdentifier && !activeCustomFeedIdentifier && ACTIVE_LINK_CLASS, sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) )} > all entries saved highlights shares {customFeedsData && customFeedsData.length > 0 && ( {customFeedsData.map((customFeed) => ( {customFeed.iconUrl && ( )} {customFeed.name} ))} )} {ungroupedSubscriptions.length > 0 && ( {ungroupedSubscriptions.map((subscription) => ( 0 ? { "data-has-unreads": "" } : {})} onClick={closeSidebarOnMobile} className={classNames( NAVIGATION_LINK_CLASS, "flex items-center truncate pl-4 text-[0.85em]", activeFeedIdentifier === subscription.feedIdentifier && ACTIVE_LINK_CLASS, sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) )} > {showFeedFavicons && ( )} {displayNameForSubscription(subscription)} {subscription.feedType === "podcast" && ( ♫ )} {subscription.consecutiveFailures > 0 && ( [!] )} ))} )} {folders.map((folder) => { const isExpanded = expandedFolderIdentifiers.includes( folder.folderIdentifier ) const folderSubscriptions = subscriptions.filter( (subscription) => subscription.folderIdentifier === folder.folderIdentifier ) const folderUnreadCount = getFolderUnreadCount( folder.folderIdentifier ) const folderNavIndex = navIndex++ return ( 0 ? { "data-has-unreads": "" } : {})} className={classNames( "flex w-full items-center gap-1 px-2 py-1", sidebarFocusClass(focusedPanel, focusedSidebarIndex, folderNavIndex) )} > toggleFolderExpansion(folder.folderIdentifier) } className="shrink-0 px-0.5 text-text-secondary transition-colors hover:text-text-primary" > {isExpanded ? "\u25BE" : "\u25B8"} {folder.iconUrl && ( )} {folder.name} {isExpanded && ( {folderSubscriptions.map((subscription) => ( 0 ? { "data-has-unreads": "" } : {})} onClick={closeSidebarOnMobile} className={classNames( NAVIGATION_LINK_CLASS, "flex items-center truncate pl-6 text-[0.85em]", activeFeedIdentifier === subscription.feedIdentifier && ACTIVE_LINK_CLASS, sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) )} > {showFeedFavicons && ( )} {displayNameForSubscription(subscription)} {subscription.feedType === "podcast" && ( ♫ )} {subscription.consecutiveFailures > 0 && ( [!] )} ))} )} ) })} setAddFeedDialogOpen(true)} className={classNames( "w-full px-2 py-1 text-left text-text-dim transition-colors hover:bg-background-tertiary hover:text-text-secondary", sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) )} > + add feed ) }