import { sanitizeTerminalText } from '../../utils/terminal'; import type { CategoryState } from '../state/types'; import { terminalCellWidth, truncateTerminalText } from '../text'; export interface FeedListItem { type: 'virtual' | 'separator' | 'categoryHeader' | 'feed'; id: string; label: string; unreadCount?: number; feedId?: number; feedType?: 'all' | 'starred' | 'feed'; categoryName?: string; } export function buildFeedListItems(categories: CategoryState[], totalUnread: number, totalStarred: number) { const list: FeedListItem[] = [ { type: 'virtual', id: 'all', label: 'All Articles', unreadCount: totalUnread, feedType: 'all', }, { type: 'virtual', id: 'starred', label: 'Starred', unreadCount: totalStarred, feedType: 'starred', }, { type: 'separator', id: 'sep1', label: '' }, ]; for (const category of categories) { if (!category.name) continue; list.push({ type: 'categoryHeader', id: `cat-${category.name}`, label: sanitizeTerminalText(category.name), unreadCount: category.totalUnread, categoryName: category.name, }); if (!category.collapsed) { for (const feed of category.feeds) { list.push({ type: 'feed', id: `feed-${feed.id}`, label: sanitizeTerminalText(feed.title), unreadCount: feed.unreadCount, feedId: feed.id, feedType: 'feed', }); } } } const uncategorized = categories.find((category) => category.name === ''); if (uncategorized) { if (categories.length > 1) { list.push({ type: 'separator', id: 'sep2', label: '' }); } for (const feed of uncategorized.feeds) { list.push({ type: 'feed', id: `feed-${feed.id}`, label: sanitizeTerminalText(feed.title), unreadCount: feed.unreadCount, feedId: feed.id, feedType: 'feed', }); } } return list; } export function getNavigableFeedIndices(items: FeedListItem[]): number[] { return items.map((item, index) => (item.type !== 'separator' ? index : -1)).filter((index) => index >= 0); } export function getFeedScrollOffset(selectedIndex: number, visibleRows: number, itemCount: number): number { const margin = 2; if (selectedIndex < visibleRows - margin) return 0; return Math.min(Math.max(0, selectedIndex - margin), Math.max(0, itemCount - visibleRows)); } export function getVisibleFeedItems(items: FeedListItem[], scrollOffset: number, visibleRows: number) { return items.map((item, index) => ({ item, index })).slice(scrollOffset, scrollOffset + visibleRows); } export function getFeedVisibleRows(termHeight: number): number { return Math.max(0, Math.floor(termHeight) - 6); } export function truncateFeedLabel(label: string, countText: string, indent: number, contentWidth: number): string { const available = Math.max(0, contentWidth - indent); const labelBudget = Math.max(0, available - terminalCellWidth(countText)); return truncateTerminalText(label, labelBudget); }