import { Link } from 'preact-router/match'; import { useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'preact/hooks'; import { useSnapshot } from 'valtio'; import Icon from '../components/icon'; import Loader from '../components/loader'; import Status from '../components/status'; import states from '../utils/states'; import useTitle from '../utils/useTitle'; function StatusPage({ id }) { const snapStates = useSnapshot(states); const [statuses, setStatuses] = useState([{ id }]); const [uiState, setUIState] = useState('default'); const heroStatusRef = useRef(); useEffect(async () => { const containsStatus = statuses.find((s) => s.id === id); const statusesWithSameAccountID = statuses.filter( (s) => s.accountID === containsStatus?.accountID, ); if (statusesWithSameAccountID.length > 1) { setStatuses( statusesWithSameAccountID.map((s) => ({ ...s, thread: true, descendant: undefined, ancestor: undefined, })), ); } else { setStatuses([{ id }]); } setUIState('loading'); const hasStatus = snapStates.statuses.has(id); let heroStatus = snapStates.statuses.get(id); try { heroStatus = await masto.statuses.fetch(id); states.statuses.set(id, heroStatus); } catch (e) { // Silent fail if status is cached if (!hasStatus) { setUIState('error'); alert('Error fetching status'); } return; } try { const context = await masto.statuses.fetchContext(id); const { ancestors, descendants } = context; ancestors.forEach((status) => { states.statuses.set(status.id, status); }); const nestedDescendants = []; descendants.forEach((status) => { states.statuses.set(status.id, status); if (status.inReplyToAccountId === status.account.id) { // If replying to self, it's part of the thread, level 1 nestedDescendants.push(status); } else if (status.inReplyToId === heroStatus.id) { // If replying to the hero status, it's a reply, level 1 nestedDescendants.push(status); } else { // If replying to someone else, it's a reply to a reply, level 2 const parent = descendants.find((s) => s.id === status.inReplyToId); if (parent) { if (!parent.__replies) { parent.__replies = []; } parent.__replies.push(status); } else { // If no parent, it's probably a reply to a reply to a reply, level 3 console.warn('[LEVEL 3] No parent found for', status); } } }); console.log({ ancestors, descendants, nestedDescendants }); const allStatuses = [ ...ancestors.map((s) => ({ id: s.id, ancestor: true, accountID: s.account.id, })), { id, accountID: heroStatus.account.id }, ...nestedDescendants.map((s) => ({ id: s.id, accountID: s.account.id, descendant: true, thread: s.account.id === heroStatus.account.id, replies: s.__replies?.map((r) => r.id), })), ]; console.log({ allStatuses }); setStatuses(allStatuses); } catch (e) { console.error(e); setUIState('error'); } setUIState('default'); }, [id, snapStates.reloadStatusPage]); useLayoutEffect(() => { if (heroStatusRef.current && statuses.length > 1) { heroStatusRef.current.scrollIntoView({ behavior: 'smooth', block: 'start', }); } }, [id]); useLayoutEffect(() => { const hasAncestor = statuses.some((s) => s.ancestor); if (hasAncestor) { heroStatusRef.current?.scrollIntoView({ // behavior: 'smooth', block: 'start', }); } }, [statuses]); const heroStatus = snapStates.statuses.get(id); const heroDisplayName = useMemo(() => { // Remove shortcodes from display name if (!heroStatus) return ''; const { account } = heroStatus; const div = document.createElement('div'); div.innerHTML = account.displayName; return div.innerText.trim(); }, [heroStatus]); const heroContentText = useMemo(() => { if (!heroStatus) return ''; const { spoilerText, content } = heroStatus; let text; if (spoilerText) { text = spoilerText; } else { const div = document.createElement('div'); div.innerHTML = content; text = div.innerText.trim(); } if (text.length > 64) { // "The title should ideally be less than 64 characters in length" // https://www.w3.org/Provider/Style/TITLE.html text = text.slice(0, 64) + '…'; } return text; }, [heroStatus]); useTitle( heroDisplayName && heroContentText ? `${heroDisplayName}: "${heroContentText}"` : 'Status', ); const prevRoute = states.history.findLast((h) => { return h === '/' || /notifications/i.test(h); }); const closeLink = `#${prevRoute || '/'}`; const [limit, setLimit] = useState(40); const showMore = useMemo(() => { // return number of statuses to show return statuses.length - limit; }, [statuses.length, limit]); const hasManyStatuses = statuses.length > 40; return (
1 ? 'padded-bottom' : '' }`} >

Status

); } export default StatusPage;