import { useEffect, useMemo } from 'react'; import { Link, Navigate, useParams } from 'react-router-dom'; import { Trans, useTranslation } from 'react-i18next'; import { useCommunity } from '@bitsocial/bitsocial-react-hooks'; import { shouldShowSnow } from '../../lib/snow'; import { BottomButton, BracketedCatalogButton, CatalogButton, ReturnButton, TopButton } from '../../components/board-buttons/board-buttons'; import { PageFooterDesktop, PageFooterMobile, ThreadFooterStyleRow } from '../../components/footer'; import LoadingEllipsis from '../../components/loading-ellipsis'; import Tooltip from '../../components/tooltip'; import { useDirectories } from '../../hooks/use-directories'; import { useCommunityIdentifier } from '../../hooks/use-community-identifiers'; import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address'; import { isDirectoryRoute } from '../../lib/utils/route-utils'; import { DirectoryListBoard, sortDirectoryBoardsByRank, useDirectoryList } from '../../hooks/use-directory-list'; import { type CommunityFreshnessState, isCommunityKnownOffline } from '../../lib/utils/community-freshness-utils'; import getShortAddress from '../../lib/get-short-address'; import { get5chanDeveloperBadge } from '../../lib/utils/author-display-utils'; import useCommunityOfflineStore from '../../stores/use-community-offline-store'; import useIsCommunityOffline from '../../hooks/use-is-community-offline'; import { useNowSeconds } from '../../hooks/use-now-seconds'; import postStyles from '../post/post.module.css'; import styles from './directory.module.css'; const DIRECTORY_STATUS_CHECK_LIMIT = 5; const DIRECTORY_STATUS_UNAVAILABLE_MARKER = '\u2014'; const computeBoardStatus = ( communityState: CommunityFreshnessState | undefined, offlineState: CommunityFreshnessState | undefined, nowSeconds: number, isOffline: boolean, isOnlineStatusLoading: boolean, ): 'online' | 'offline' | 'loading' | 'unknown' => { const freshnessState = { state: communityState?.state ?? offlineState?.state, updatedAt: communityState?.updatedAt ?? offlineState?.updatedAt, }; if (isOffline || isCommunityKnownOffline(freshnessState, nowSeconds)) return 'offline'; if (isOnlineStatusLoading) return 'loading'; if (!freshnessState.updatedAt) return 'unknown'; return 'online'; }; const PASS_LINK = '/pass'; const DirectoryDesktopTopControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
[] []
); const DirectoryDesktopFooterControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
[] []
); const DirectoryMobileTopControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
); const DirectoryMobileFooterControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
); interface DirectoryRowProps { board: DirectoryListBoard; nowSeconds: number; rank: number; onVote: () => void; } const DirectoryRow = ({ board, nowSeconds, rank, onVote }: DirectoryRowProps) => { const { t } = useTranslation(); const statusUnavailableReason = t('directory_status_unavailable_reason'); const ownerAddress = board.owner; const ownerDisplay = ownerAddress ? getShortAddress(ownerAddress) || ownerAddress : undefined; const developerBadge = get5chanDeveloperBadge(ownerAddress); const shouldCheckStatus = rank <= DIRECTORY_STATUS_CHECK_LIMIT; const communityIdentifier = useCommunityIdentifier(shouldCheckStatus ? board.address : undefined); const community = useCommunity(shouldCheckStatus && communityIdentifier ? { community: communityIdentifier } : undefined); const { isOffline, isOnlineStatusLoading } = useIsCommunityOffline(community, shouldCheckStatus ? board.address : undefined); const offlineState = useCommunityOfflineStore((state) => (shouldCheckStatus ? state.communityOfflineState[board.address] : undefined)); const status = shouldCheckStatus ? computeBoardStatus(community, offlineState, nowSeconds, isOffline, isOnlineStatusLoading) : 'unavailable'; const boardLink = `/${board.address}`; return ( {rank} {board.address} {ownerDisplay ?? t('directory_owner_anonymous')} {developerBadge && ( <> {' ## '} {developerBadge.label} )} {status === 'unavailable' ? ( {DIRECTORY_STATUS_UNAVAILABLE_MARKER} ) : status === 'loading' ? ( ) : status === 'unknown' ? ( {DIRECTORY_STATUS_UNAVAILABLE_MARKER} ) : ( {t(status === 'offline' ? 'directory_status_offline' : 'directory_status_online')} )} {board.score ?? DIRECTORY_STATUS_UNAVAILABLE_MARKER} [ ] [ ] [ {t('view')} ] ); }; const getRepoEditUrl = (directoryCode: string) => `https://github.com/bitsocialnet/lists/edit/master/5chan-directories/5chan-${directoryCode}-directory.json`; const Directory = () => { const { t } = useTranslation(); const params = useParams(); const boardIdentifier = params.boardIdentifier; const directories = useDirectories(); const isValidDirectoryCode = !!boardIdentifier && isDirectoryRoute(boardIdentifier, directories); const { list, loading } = useDirectoryList(isValidDirectoryCode ? boardIdentifier : undefined); const communityAddress = useResolvedCommunityAddress(); const nowSeconds = useNowSeconds(); const ranked = useMemo(() => (list ? sortDirectoryBoardsByRank(list.boards) : []), [list]); const directoryTitle = list?.title || (boardIdentifier ? `/${boardIdentifier}/ - ${t('directory')}` : t('directory')); useEffect(() => { if (!isValidDirectoryCode) return; document.title = `${directoryTitle} - 5chan`; }, [directoryTitle, isValidDirectoryCode]); if (!isValidDirectoryCode) { return ; } const handleVoteUnavailable = () => { const values = { boardIdentifier }; window.alert(`${t('directory_voting_unavailable_intro', values)}\n\n${t('directory_voting_unavailable_outro', values)}`); }; const isLoadingShell = loading && ranked.length === 0; const boardCount = ranked.length; const repoEditUrl = getRepoEditUrl(boardIdentifier!); return (


{isLoadingShell ? (

) : ranked.length === 0 ? (

{t('directory_empty')}

) : (

{t('directory_heading', { boardIdentifier, count: boardCount })}

)} {!isLoadingShell && ranked.length > 0 && ( <> {ranked.map((board, index) => ( ))}
No. {t('directory_board')} {t('directory_owner')} {t('directory_status')} {t('directory_score')} {t('directory_vote')}
, repoLink: , }} />
)} } styleRow={} />
); }; export default Directory;