diff options
| author | Fuwn <[email protected]> | 2026-02-08 00:10:09 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 00:10:09 -0800 |
| commit | f2baa5a45fa9e5b8c3d0aa0ecbb0113c5166747b (patch) | |
| tree | b74ff019343b6f571e5b09f826069831e8bf563d /apps/web/app/reader | |
| parent | feat: display folders above ungrouped feeds in sidebar, add delete-all-custom... (diff) | |
| download | asa.news-f2baa5a45fa9e5b8c3d0aa0ecbb0113c5166747b.tar.xz asa.news-f2baa5a45fa9e5b8c3d0aa0ecbb0113c5166747b.zip | |
feat: add appearance option to toggle folders above/below ungrouped feeds in sidebar
Diffstat (limited to 'apps/web/app/reader')
| -rw-r--r-- | apps/web/app/reader/_components/sidebar-content.tsx | 206 | ||||
| -rw-r--r-- | apps/web/app/reader/settings/_components/appearance-settings.tsx | 21 |
2 files changed, 128 insertions, 99 deletions
diff --git a/apps/web/app/reader/_components/sidebar-content.tsx b/apps/web/app/reader/_components/sidebar-content.tsx index 844f8b3..401d203 100644 --- a/apps/web/app/reader/_components/sidebar-content.tsx +++ b/apps/web/app/reader/_components/sidebar-content.tsx @@ -96,6 +96,9 @@ export function SidebarContent() { const focusedSidebarIndex = useUserInterfaceStore( (state) => state.focusedSidebarIndex ) + const showFoldersAboveFeeds = useUserInterfaceStore( + (state) => state.showFoldersAboveFeeds + ) function closeSidebarOnMobile() { if (typeof window !== "undefined" && window.innerWidth < 768) { toggleSidebar() @@ -214,59 +217,107 @@ export function SidebarContent() { </div> )} - {folders.map((folder) => { - const isExpanded = expandedFolderIdentifiers.includes( - folder.folderIdentifier - ) - const folderSubscriptions = subscriptions.filter( - (subscription) => - subscription.folderIdentifier === folder.folderIdentifier - ) - const folderUnreadCount = getFolderUnreadCount( - folder.folderIdentifier - ) + {(showFoldersAboveFeeds + ? ["folders", "ungrouped"] + : ["ungrouped", "folders"] + ).map((section) => + section === "folders" + ? 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++ + const folderNavIndex = navIndex++ - return ( - <div key={folder.folderIdentifier} className="mt-2"> - <div - data-sidebar-nav-item - {...(folderUnreadCount > 0 ? { "data-has-unreads": "" } : {})} - className={classNames( - "flex w-full items-center gap-1 px-2 py-1", - sidebarFocusClass(focusedPanel, focusedSidebarIndex, folderNavIndex) - )} - > - <button - type="button" - aria-expanded={isExpanded} - onClick={() => - toggleFolderExpansion(folder.folderIdentifier) - } - className="shrink-0 px-0.5 text-text-secondary transition-colors hover:text-text-primary" - > - {isExpanded ? "\u25BE" : "\u25B8"} - </button> - <Link - href={`/reader?folder=${folder.folderIdentifier}`} - onClick={closeSidebarOnMobile} - className={classNames( - "flex flex-1 items-center gap-2 truncate text-text-secondary transition-colors hover:text-text-primary", - activeFolderIdentifier === folder.folderIdentifier && - "text-text-primary" - )} - > - {folder.iconUrl && ( - <img src={folder.iconUrl} alt="" width={16} height={16} className="shrink-0" loading="lazy" /> - )} - <span className="truncate">{folder.name}</span> - </Link> - <UnreadBadge count={folderUnreadCount} /> - </div> - {isExpanded && ( - <div className="space-y-0.5"> - {folderSubscriptions.map((subscription) => ( + return ( + <div key={folder.folderIdentifier} className="mt-2"> + <div + data-sidebar-nav-item + {...(folderUnreadCount > 0 ? { "data-has-unreads": "" } : {})} + className={classNames( + "flex w-full items-center gap-1 px-2 py-1", + sidebarFocusClass(focusedPanel, focusedSidebarIndex, folderNavIndex) + )} + > + <button + type="button" + aria-expanded={isExpanded} + onClick={() => + toggleFolderExpansion(folder.folderIdentifier) + } + className="shrink-0 px-0.5 text-text-secondary transition-colors hover:text-text-primary" + > + {isExpanded ? "\u25BE" : "\u25B8"} + </button> + <Link + href={`/reader?folder=${folder.folderIdentifier}`} + onClick={closeSidebarOnMobile} + className={classNames( + "flex flex-1 items-center gap-2 truncate text-text-secondary transition-colors hover:text-text-primary", + activeFolderIdentifier === folder.folderIdentifier && + "text-text-primary" + )} + > + {folder.iconUrl && ( + <img src={folder.iconUrl} alt="" width={16} height={16} className="shrink-0" loading="lazy" /> + )} + <span className="truncate">{folder.name}</span> + </Link> + <UnreadBadge count={folderUnreadCount} /> + </div> + {isExpanded && ( + <div className="space-y-0.5"> + {folderSubscriptions.map((subscription) => ( + <Link + key={subscription.subscriptionIdentifier} + href={`/reader?feed=${subscription.feedIdentifier}`} + data-sidebar-nav-item + {...((unreadCounts?.[subscription.feedIdentifier] ?? 0) > 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 && ( + <FeedFavicon feedUrl={subscription.feedUrl} /> + )} + <span className={classNames("truncate", showFeedFavicons && "ml-2")}> + {displayNameForSubscription(subscription)} + </span> + {subscription.feedType === "podcast" && ( + <span className="ml-1 shrink-0 text-text-dim" title="podcast">♫</span> + )} + {subscription.consecutiveFailures > 0 && ( + <span className="ml-1 shrink-0 text-status-warning" title={subscription.lastFetchError ?? "feed error"}> + [!] + </span> + )} + <UnreadBadge + count={ + unreadCounts?.[subscription.feedIdentifier] ?? 0 + } + /> + </Link> + ))} + </div> + )} + </div> + ) + }) + : ungroupedSubscriptions.length > 0 && ( + <div key="ungrouped" className="mt-3 space-y-0.5"> + {ungroupedSubscriptions.map((subscription) => ( <Link key={subscription.subscriptionIdentifier} href={`/reader?feed=${subscription.feedIdentifier}`} @@ -275,9 +326,9 @@ export function SidebarContent() { onClick={closeSidebarOnMobile} className={classNames( NAVIGATION_LINK_CLASS, - "flex items-center truncate pl-6 text-[0.85em]", - activeFeedIdentifier === - subscription.feedIdentifier && ACTIVE_LINK_CLASS, + "flex items-center truncate pl-4 text-[0.85em]", + activeFeedIdentifier === subscription.feedIdentifier && + ACTIVE_LINK_CLASS, sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) )} > @@ -296,55 +347,12 @@ export function SidebarContent() { </span> )} <UnreadBadge - count={ - unreadCounts?.[subscription.feedIdentifier] ?? 0 - } + count={unreadCounts?.[subscription.feedIdentifier] ?? 0} /> </Link> ))} </div> - )} - </div> - ) - })} - - {ungroupedSubscriptions.length > 0 && ( - <div className="mt-3 space-y-0.5"> - {ungroupedSubscriptions.map((subscription) => ( - <Link - key={subscription.subscriptionIdentifier} - href={`/reader?feed=${subscription.feedIdentifier}`} - data-sidebar-nav-item - {...((unreadCounts?.[subscription.feedIdentifier] ?? 0) > 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 && ( - <FeedFavicon feedUrl={subscription.feedUrl} /> - )} - <span className={classNames("truncate", showFeedFavicons && "ml-2")}> - {displayNameForSubscription(subscription)} - </span> - {subscription.feedType === "podcast" && ( - <span className="ml-1 shrink-0 text-text-dim" title="podcast">♫</span> - )} - {subscription.consecutiveFailures > 0 && ( - <span className="ml-1 shrink-0 text-status-warning" title={subscription.lastFetchError ?? "feed error"}> - [!] - </span> - )} - <UnreadBadge - count={unreadCounts?.[subscription.feedIdentifier] ?? 0} - /> - </Link> - ))} - </div> + ) )} <div className="mt-3 space-y-0.5"> diff --git a/apps/web/app/reader/settings/_components/appearance-settings.tsx b/apps/web/app/reader/settings/_components/appearance-settings.tsx index 6c04f00..aaf499a 100644 --- a/apps/web/app/reader/settings/_components/appearance-settings.tsx +++ b/apps/web/app/reader/settings/_components/appearance-settings.tsx @@ -49,6 +49,12 @@ export function AppearanceSettings() { const setShowReadingTime = useUserInterfaceStore( (state) => state.setShowReadingTime ) + const showFoldersAboveFeeds = useUserInterfaceStore( + (state) => state.showFoldersAboveFeeds + ) + const setShowFoldersAboveFeeds = useUserInterfaceStore( + (state) => state.setShowFoldersAboveFeeds + ) return ( <div className="px-4 py-3"> <div className="mb-6"> @@ -188,6 +194,21 @@ export function AppearanceSettings() { <span>show entry images</span> </label> </div> + <div className="mb-6"> + <h3 className="mb-2 text-text-primary">sidebar ordering</h3> + <p className="mb-3 text-text-dim"> + display folders above ungrouped feeds in the sidebar + </p> + <label className="flex cursor-pointer items-center gap-2 text-text-primary"> + <input + type="checkbox" + checked={showFoldersAboveFeeds} + onChange={(event) => setShowFoldersAboveFeeds(event.target.checked)} + className="accent-text-primary" + /> + <span>show folders above feeds</span> + </label> + </div> <div> <h3 className="mb-2 text-text-primary">reading time</h3> <p className="mb-3 text-text-dim"> |