2022-12-10 12:14:48 +03:00
|
|
|
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';
|
|
|
|
|
2022-12-16 08:27:04 +03:00
|
|
|
function StatusPage({ id }) {
|
2022-12-10 12:14:48 +03:00
|
|
|
const snapStates = useSnapshot(states);
|
|
|
|
const [statuses, setStatuses] = useState([{ id }]);
|
|
|
|
const [uiState, setUIState] = useState('default');
|
|
|
|
const heroStatusRef = useRef();
|
|
|
|
|
|
|
|
useEffect(async () => {
|
|
|
|
// If id is completely new, reset the whole list
|
|
|
|
if (!statuses.find((s) => s.id === id)) {
|
|
|
|
setStatuses([{ id }]);
|
|
|
|
}
|
|
|
|
|
|
|
|
setUIState('loading');
|
|
|
|
|
|
|
|
if (!states.statuses.has(id)) {
|
|
|
|
try {
|
|
|
|
const status = await masto.statuses.fetch(id);
|
|
|
|
states.statuses.set(id, status);
|
|
|
|
} catch (e) {
|
|
|
|
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 directReplies = [];
|
|
|
|
descendants.forEach((status) => {
|
|
|
|
states.statuses.set(status.id, status);
|
|
|
|
if (status.inReplyToId === id) {
|
|
|
|
directReplies.push(status);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
console.log({ ancestors, descendants, directReplies });
|
|
|
|
|
|
|
|
if (directReplies.length) {
|
|
|
|
const heroStatus = states.statuses.get(id);
|
|
|
|
const heroStatusRepliesCount = heroStatus.repliesCount;
|
|
|
|
if (heroStatusRepliesCount != directReplies.length) {
|
|
|
|
// If replies count doesn't match, refetch the status
|
|
|
|
const status = await masto.statuses.fetch(id);
|
|
|
|
states.statuses.set(id, status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const allStatuses = [
|
|
|
|
...ancestors.map((s) => ({ id: s.id, ancestor: true })),
|
|
|
|
{ id },
|
|
|
|
...descendants.map((s) => ({
|
|
|
|
id: s.id,
|
|
|
|
descendant: true,
|
|
|
|
directReply:
|
|
|
|
s.inReplyToId === id || s.inReplyToAccountId === s.account.id,
|
|
|
|
// I can assume if the reply is to the same account, it's a direct reply. In other words, it's a thread?!?
|
|
|
|
})),
|
|
|
|
];
|
|
|
|
setStatuses(allStatuses);
|
|
|
|
} catch (e) {
|
|
|
|
setUIState('error');
|
|
|
|
}
|
|
|
|
|
|
|
|
setUIState('default');
|
|
|
|
}, [id, snapStates.reloadStatusPage]);
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
if (heroStatusRef.current && statuses.length > 1) {
|
|
|
|
heroStatusRef.current.scrollIntoView({
|
|
|
|
behavior: 'smooth',
|
|
|
|
block: 'start',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, [id]);
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
heroStatusRef.current?.scrollIntoView({
|
|
|
|
// behavior: 'smooth',
|
|
|
|
block: 'start',
|
|
|
|
});
|
|
|
|
}, [statuses]);
|
|
|
|
|
|
|
|
const heroStatus = states.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 '';
|
2022-12-15 12:14:33 +03:00
|
|
|
const { spoilerText, content } = heroStatus;
|
|
|
|
let text;
|
|
|
|
if (spoilerText) {
|
|
|
|
text = spoilerText;
|
|
|
|
} else {
|
|
|
|
const div = document.createElement('div');
|
|
|
|
div.innerHTML = content;
|
|
|
|
text = div.innerText.trim();
|
|
|
|
}
|
2022-12-10 12:14:48 +03:00
|
|
|
if (text.length > 64) {
|
2022-12-15 12:14:33 +03:00
|
|
|
// "The title should ideally be less than 64 characters in length"
|
|
|
|
// https://www.w3.org/Provider/Style/TITLE.html
|
2022-12-10 12:14:48 +03:00
|
|
|
text = text.slice(0, 64) + '…';
|
|
|
|
}
|
|
|
|
return text;
|
|
|
|
}, [heroStatus]);
|
|
|
|
useTitle(
|
|
|
|
heroDisplayName && heroContentText
|
2022-12-15 12:14:33 +03:00
|
|
|
? `${heroDisplayName}: "${heroContentText}"`
|
2022-12-10 12:14:48 +03:00
|
|
|
: 'Status',
|
|
|
|
);
|
|
|
|
|
|
|
|
const comments = statuses.filter((s) => s.descendant);
|
|
|
|
const replies = comments.filter((s) => s.directReply);
|
|
|
|
|
|
|
|
const prevRoute = states.history.findLast((h) => {
|
|
|
|
return h === '/' || /notifications/i.test(h);
|
|
|
|
});
|
|
|
|
const closeLink = `#${prevRoute || '/'}`;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div class="deck-backdrop">
|
|
|
|
<Link href={closeLink}></Link>
|
|
|
|
<div
|
|
|
|
class={`status-deck deck contained ${
|
|
|
|
statuses.length > 1 ? 'padded-bottom' : ''
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<header>
|
|
|
|
<h1>Status</h1>
|
|
|
|
<div class="header-side">
|
|
|
|
<Loader hidden={uiState !== 'loading'} />
|
|
|
|
<Link class="button plain deck-close" href={closeLink}>
|
|
|
|
<Icon icon="x" size="xl" />
|
|
|
|
</Link>
|
|
|
|
</div>
|
|
|
|
</header>
|
|
|
|
<ul class="timeline flat contextual">
|
|
|
|
{statuses.map((status) => {
|
|
|
|
const { id: statusID, ancestor, descendant, directReply } = status;
|
|
|
|
const isHero = statusID === id;
|
|
|
|
return (
|
|
|
|
<li
|
|
|
|
key={statusID}
|
|
|
|
ref={isHero ? heroStatusRef : null}
|
|
|
|
class={`${isHero ? '' : 'insignificant'} ${
|
|
|
|
ancestor ? 'ancestor' : ''
|
|
|
|
} ${descendant ? 'descendant' : ''} ${
|
|
|
|
descendant && !directReply ? 'indirect' : ''
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
{isHero ? (
|
|
|
|
<Status statusID={statusID} withinContext size="l" />
|
|
|
|
) : (
|
|
|
|
<Link
|
|
|
|
class="
|
|
|
|
status-link
|
|
|
|
"
|
|
|
|
href={`#/s/${statusID}`}
|
|
|
|
>
|
|
|
|
<Status statusID={statusID} withinContext />
|
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
{uiState === 'loading' &&
|
|
|
|
isHero &&
|
2022-12-10 16:19:38 +03:00
|
|
|
!!heroStatus?.repliesCount &&
|
|
|
|
statuses.length === 1 && (
|
2022-12-10 12:14:48 +03:00
|
|
|
<div class="status-loading">
|
2022-12-10 16:19:38 +03:00
|
|
|
<Loader />
|
|
|
|
{/* {' '}<span>
|
2022-12-10 12:14:48 +03:00
|
|
|
{!!replies.length &&
|
|
|
|
replies.length !== comments.length && (
|
|
|
|
<>
|
|
|
|
{replies.length} repl
|
|
|
|
{replies.length > 1 ? 'ies' : 'y'}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{!!comments.length && (
|
|
|
|
<>
|
|
|
|
{' '}
|
|
|
|
• {comments.length} comment
|
|
|
|
{comments.length > 1 ? 's' : ''}
|
|
|
|
</>
|
|
|
|
)}
|
2022-12-10 16:19:38 +03:00
|
|
|
</span> */}
|
2022-12-10 12:14:48 +03:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
2022-12-16 08:27:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
export default StatusPage;
|