From 1357c1b2bd134689af2da734de82275b47b44f94 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 6 Feb 2023 16:35:03 +0800 Subject: [PATCH] Fix more edge cases after breaking changes --- src/app.jsx | 18 ++++++++-------- src/components/account.jsx | 4 ++-- src/components/status.jsx | 39 +++++++++++++++++----------------- src/pages/account-statuses.jsx | 10 ++++++--- src/pages/hashtags.jsx | 10 +++++---- src/pages/home.jsx | 4 ++-- src/pages/lists.jsx | 2 +- src/pages/public.jsx | 6 +++--- src/pages/status.jsx | 31 +++++++++++++++++---------- src/utils/states.js | 34 ++++++++++++++++++++++------- 10 files changed, 96 insertions(+), 62 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 8a7ec4d0..9255ee0c 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -37,7 +37,7 @@ import Status from './pages/status'; import Welcome from './pages/welcome'; import { api, initAccount, initClient, initInstance } from './utils/api'; import { getAccessToken } from './utils/auth'; -import states, { saveStatus } from './utils/states'; +import states, { getStatus, saveStatus } from './utils/states'; import store from './utils/store'; import { getCurrentAccount } from './utils/store-utils'; @@ -330,7 +330,7 @@ function App() { let ws; async function startStream() { - const { masto } = api(); + const { masto, instance } = api(); if ( ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN) @@ -361,17 +361,17 @@ async function startStream() { } } - saveStatus(status); + saveStatus(status, instance); }, 5000); stream.on('update', handleNewStatus); stream.on('status.update', (status) => { console.log('STATUS.UPDATE', status); - saveStatus(status); + saveStatus(status, instance); }); stream.on('delete', (statusID) => { console.log('DELETE', statusID); // delete states.statuses[statusID]; - const s = states.statuses[statusID]; + const s = getStatus(statusID); if (s) s._deleted = true; }); stream.on('notification', (notification) => { @@ -385,7 +385,7 @@ async function startStream() { states.notificationsNew.unshift(notification); } - saveStatus(notification.status, { override: false }); + saveStatus(notification.status, instance, { override: false }); }); stream.ws.onclose = () => { @@ -405,7 +405,7 @@ async function startStream() { let lastHidden; function startVisibility() { - const { masto } = api(); + const { masto, instance } = api(); const handleVisible = (visible) => { if (!visible) { const timestamp = Date.now(); @@ -438,7 +438,7 @@ function startVisibility() { // do nothing } else { states.homeNew = newStatuses.map((status) => { - saveStatus(status); + saveStatus(status, instance); return { id: status.id, reblog: status.reblog?.id, @@ -461,7 +461,7 @@ function startVisibility() { states.notificationsNew.unshift(notification); } - saveStatus(notification.status, { override: false }); + saveStatus(notification.status, instance, { override: false }); } } catch (e) { // Silently fail diff --git a/src/components/account.jsx b/src/components/account.jsx index 0ade7b4d..6d244064 100644 --- a/src/components/account.jsx +++ b/src/components/account.jsx @@ -15,8 +15,8 @@ import Icon from './icon'; import Link from './link'; import NameText from './name-text'; -function Account({ account, instance, onClose }) { - const { masto, authenticated } = api({ instance }); +function Account({ account, instance: propInstance, onClose }) { + const { masto, instance, authenticated } = api({ instance: propInstance }); const [uiState, setUIState] = useState('default'); const isString = typeof account === 'string'; const [info, setInfo] = useState(isString ? null : account); diff --git a/src/components/status.jsx b/src/components/status.jsx index 9eaa7fd3..0b0e196e 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -16,7 +16,7 @@ import enhanceContent from '../utils/enhance-content'; import handleContentLinks from '../utils/handle-content-links'; import htmlContentLength from '../utils/html-content-length'; import shortenNumber from '../utils/shorten-number'; -import states, { saveStatus } from '../utils/states'; +import states, { saveStatus, statusKey } from '../utils/states'; import store from '../utils/store'; import visibilityIconsMap from '../utils/visibility-icons-map'; @@ -38,7 +38,7 @@ const memFetchAccount = mem(fetchAccount); function Status({ statusID, status, - instance, + instance: propInstance, withinContext, size = 'm', skeleton, @@ -59,11 +59,12 @@ function Status({ ); } - const { masto, authenticated } = api({ instance }); + const { masto, instance, authenticated } = api({ instance: propInstance }); + const sKey = statusKey(statusID, instance); const snapStates = useSnapshot(states); if (!status) { - status = snapStates.statuses[statusID]; + status = snapStates.statuses[sKey]; } if (!status) { return null; @@ -384,13 +385,13 @@ function Status({ poll={poll} readOnly={readOnly || !authenticated} onUpdate={(newPoll) => { - states.statuses[id].poll = newPoll; + states.statuses[sKey].poll = newPoll; }} refresh={() => { return masto.v1.polls .fetch(poll.id) .then((pollResponse) => { - states.statuses[id].poll = pollResponse; + states.statuses[sKey].poll = pollResponse; }) .catch((e) => {}); // Silently fail }} @@ -400,7 +401,7 @@ function Status({ choices, }) .then((pollResponse) => { - states.statuses[id].poll = pollResponse; + states.statuses[sKey].poll = pollResponse; }) .catch((e) => {}); // Silently fail }} @@ -544,7 +545,7 @@ function Status({ } } // Optimistic - states.statuses[id] = { + states.statuses[sKey] = { ...status, reblogged: !reblogged, reblogsCount: reblogsCount + (reblogged ? -1 : 1), @@ -553,15 +554,15 @@ function Status({ const newStatus = await masto.v1.statuses.unreblog( id, ); - saveStatus(newStatus); + saveStatus(newStatus, instance); } else { const newStatus = await masto.v1.statuses.reblog(id); - saveStatus(newStatus); + saveStatus(newStatus, instance); } } catch (e) { console.error(e); // Revert optimistism - states.statuses[id] = status; + states.statuses[sKey] = status; } }} /> @@ -581,7 +582,7 @@ function Status({ } try { // Optimistic - states.statuses[statusID] = { + states.statuses[sKey] = { ...status, favourited: !favourited, favouritesCount: @@ -591,15 +592,15 @@ function Status({ const newStatus = await masto.v1.statuses.unfavourite( id, ); - saveStatus(newStatus); + saveStatus(newStatus, instance); } else { const newStatus = await masto.v1.statuses.favourite(id); - saveStatus(newStatus); + saveStatus(newStatus, instance); } } catch (e) { console.error(e); // Revert optimistism - states.statuses[statusID] = status; + states.statuses[sKey] = status; } }} /> @@ -617,7 +618,7 @@ function Status({ } try { // Optimistic - states.statuses[statusID] = { + states.statuses[sKey] = { ...status, bookmarked: !bookmarked, }; @@ -625,15 +626,15 @@ function Status({ const newStatus = await masto.v1.statuses.unbookmark( id, ); - saveStatus(newStatus); + saveStatus(newStatus, instance); } else { const newStatus = await masto.v1.statuses.bookmark(id); - saveStatus(newStatus); + saveStatus(newStatus, instance); } } catch (e) { console.error(e); // Revert optimistism - states.statuses[statusID] = status; + states.statuses[sKey] = status; } }} /> diff --git a/src/pages/account-statuses.jsx b/src/pages/account-statuses.jsx index f1ab28cf..2238250c 100644 --- a/src/pages/account-statuses.jsx +++ b/src/pages/account-statuses.jsx @@ -12,8 +12,8 @@ const LIMIT = 20; function AccountStatuses() { const snapStates = useSnapshot(states); - const { id, instance } = useParams(); - const { masto } = api({ instance }); + const { id, ...params } = useParams(); + const { masto, instance } = api({ instance: params.instance }); const accountStatusesIterator = useRef(); async function fetchAccountStatuses(firstLoad) { if (firstLoad || !accountStatusesIterator.current) { @@ -25,7 +25,10 @@ function AccountStatuses() { } const [account, setAccount] = useState({}); - useTitle(`${account?.acct ? '@' + account.acct : 'Posts'}`, '/a/:id'); + useTitle( + `${account?.acct ? '@' + account.acct : 'Posts'}`, + '/a/:instance?/:id', + ); useEffect(() => { (async () => { try { @@ -65,6 +68,7 @@ function AccountStatuses() { } id="account_statuses" + instance={instance} emptyText="Nothing to see here yet." errorText="Unable to load statuses" fetchItems={fetchAccountStatuses} diff --git a/src/pages/hashtags.jsx b/src/pages/hashtags.jsx index 075efb26..31d48800 100644 --- a/src/pages/hashtags.jsx +++ b/src/pages/hashtags.jsx @@ -8,9 +8,10 @@ import useTitle from '../utils/useTitle'; const LIMIT = 20; function Hashtags() { - const { hashtag, instance } = useParams(); - useTitle(`#${hashtag}`, `/t/${hashtag}`); - const { masto } = api({ instance }); + let { hashtag, ...params } = useParams(); + const { masto, instance } = api({ instance: params.instance }); + const title = instance ? `#${hashtag} on ${instance}` : `#${hashtag}`; + useTitle(title, `/t/:instance?/:hashtag`); const hashtagsIterator = useRef(); async function fetchHashtags(firstLoad) { if (firstLoad || !hashtagsIterator.current) { @@ -24,7 +25,7 @@ function Hashtags() { return ( @@ -34,6 +35,7 @@ function Hashtags() { ) } id="hashtags" + instance={instance} emptyText="No one has posted anything with this tag yet." errorText="Unable to load posts with this tag" fetchItems={fetchHashtags} diff --git a/src/pages/home.jsx b/src/pages/home.jsx index e2b6f835..4f332bcc 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -19,7 +19,7 @@ const LIMIT = 20; function Home({ hidden }) { useTitle('Home', '/'); - const { masto } = api(); + const { masto, instance } = api(); const snapStates = useSnapshot(states); const isHomeLocation = snapStates.currentLocation === '/'; const [uiState, setUIState] = useState('default'); @@ -45,7 +45,7 @@ function Home({ hidden }) { return bDate - aDate; }); const homeValues = allStatuses.value.map((status) => { - saveStatus(status); + saveStatus(status, instance); return { id: status.id, reblog: status.reblog?.id, diff --git a/src/pages/lists.jsx b/src/pages/lists.jsx index 81b641ff..3bfcb2b3 100644 --- a/src/pages/lists.jsx +++ b/src/pages/lists.jsx @@ -21,7 +21,7 @@ function Lists() { } const [title, setTitle] = useState(`List ${id}`); - useTitle(title, `/l/${id}`); + useTitle(title, `/l/:id`); useEffect(() => { (async () => { try { diff --git a/src/pages/public.jsx b/src/pages/public.jsx index 0534ff43..a1e03a38 100644 --- a/src/pages/public.jsx +++ b/src/pages/public.jsx @@ -10,10 +10,10 @@ const LIMIT = 20; function Public() { const isLocal = !!useMatch('/p/l/:instance'); - const { instance } = useParams(); - const { masto } = api({ instance }); + const params = useParams(); + const { masto, instance } = api({ instance: params.instance }); const title = `${instance} (${isLocal ? 'local' : 'federated'})`; - useTitle(title, `/p/${instance}`); + useTitle(title, `/p/l?/:instance`); const publicIterator = useRef(); async function fetchPublic(firstLoad) { diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 11d32051..d8f750e1 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -20,7 +20,11 @@ import Status from '../components/status'; import { api } from '../utils/api'; import htmlContentLength from '../utils/html-content-length'; import shortenNumber from '../utils/shorten-number'; -import states, { saveStatus, threadifyStatus } from '../utils/states'; +import states, { + saveStatus, + statusKey, + threadifyStatus, +} from '../utils/states'; import { getCurrentAccount } from '../utils/store-utils'; import useScroll from '../utils/useScroll'; import useTitle from '../utils/useTitle'; @@ -35,13 +39,14 @@ function resetScrollPosition(id) { } function StatusPage() { - const { id, instance } = useParams(); - const { masto } = api({ instance }); + const { id, ...params } = useParams(); + const { masto, instance } = api({ instance: params.instance }); const navigate = useNavigate(); const snapStates = useSnapshot(states); const [statuses, setStatuses] = useState([]); const [uiState, setUIState] = useState('default'); const heroStatusRef = useRef(); + const sKey = statusKey(id, instance); const scrollableRef = useRef(); useEffect(() => { @@ -76,7 +81,7 @@ function StatusPage() { if (cachedStatuses) { // Case 1: It's cached, let's restore them to make it snappy const reallyCachedStatuses = cachedStatuses.filter( - (s) => states.statuses[s.id], + (s) => states.statuses[sKey], // Some are not cached in the global state, so we need to filter them out ); setStatuses(reallyCachedStatuses); @@ -102,14 +107,14 @@ function StatusPage() { retries: 8, }); - const hasStatus = !!snapStates.statuses[id]; - let heroStatus = snapStates.statuses[id]; + const hasStatus = !!snapStates.statuses[sKey]; + let heroStatus = snapStates.statuses[sKey]; if (hasStatus) { console.debug('Hero status is cached'); } else { try { heroStatus = await heroFetch(); - saveStatus(heroStatus); + saveStatus(heroStatus, instance); // Give time for context to appear await new Promise((resolve) => { setTimeout(resolve, 100); @@ -126,11 +131,15 @@ function StatusPage() { const { ancestors, descendants } = context; ancestors.forEach((status) => { - states.statuses[status.id] = status; + saveStatus(status, instance, { + skipThreading: true, + }); }); const nestedDescendants = []; descendants.forEach((status) => { - states.statuses[status.id] = status; + saveStatus(status, instance, { + skipThreading: true, + }); if (status.inReplyToAccountId === status.account.id) { // If replying to self, it's part of the thread, level 1 nestedDescendants.push(status); @@ -201,7 +210,7 @@ function StatusPage() { // Let's threadify this one // Note that all non-hero statuses will trigger saveStatus which will threadify them too // By right, at this point, all descendant statuses should be cached - threadifyStatus(heroStatus); + threadifyStatus(heroStatus, instance); } catch (e) { console.error(e); setUIState('error'); @@ -279,7 +288,7 @@ function StatusPage() { }; }, []); - const heroStatus = snapStates.statuses[id]; + const heroStatus = snapStates.statuses[sKey]; const heroDisplayName = useMemo(() => { // Remove shortcodes from display name if (!heroStatus) return ''; diff --git a/src/utils/states.js b/src/utils/states.js index 0b7eb983..d25056a1 100644 --- a/src/utils/states.js +++ b/src/utils/states.js @@ -53,22 +53,40 @@ export function hideAllModals() { states.showMediaModal = false; } -export function saveStatus(status, opts) { +export function statusKey(id, instance) { + return instance ? `${instance}/${id}` : id; +} + +export function getStatus(statusID, instance) { + if (instance) { + const key = statusKey(statusID, instance); + return states.statuses[key]; + } + return states.statuses[statusID]; +} + +export function saveStatus(status, instance, opts) { + if (typeof instance === 'object') { + opts = instance; + instance = null; + } const { override, skipThreading } = Object.assign( { override: true, skipThreading: false }, opts, ); if (!status) return; - if (!override && states.statuses[status.id]) return; - states.statuses[status.id] = status; + if (!override && getStatus(status.id)) return; + const key = statusKey(status.id, instance); + states.statuses[key] = status; if (status.reblog) { - states.statuses[status.reblog.id] = status.reblog; + const key = statusKey(status.reblog.id, instance); + states.statuses[key] = status.reblog; } // THREAD TRAVERSER if (!skipThreading) { requestAnimationFrame(() => { - threadifyStatus(status); + threadifyStatus(status, instance); if (status.reblog) { threadifyStatus(status.reblog); } @@ -76,8 +94,8 @@ export function saveStatus(status, opts) { } } -export function threadifyStatus(status) { - const { masto } = api(); +export function threadifyStatus(status, propInstance) { + const { masto, instance } = api({ instance: propInstance }); // Return all statuses in the thread, via inReplyToId, if inReplyToAccountId === account.id let fetchIndex = 0; async function traverse(status, index = 0) { @@ -94,7 +112,7 @@ export function threadifyStatus(status) { if (fetchIndex++ > 3) throw 'Too many fetches for thread'; // Some people revive old threads await new Promise((r) => setTimeout(r, 500 * fetchIndex)); // Be nice to rate limits prevStatus = await masto.v1.statuses.fetch(inReplyToId); - saveStatus(prevStatus, { skipThreading: true }); + saveStatus(prevStatus, instance, { skipThreading: true }); } // Prepend so that first status in thread will be index 0 return [...(await traverse(prevStatus, ++index)), status];