Fix more edge cases after breaking changes

This commit is contained in:
Lim Chee Aun 2023-02-06 16:35:03 +08:00
parent 1debfc2c12
commit 1357c1b2bd
10 changed files with 96 additions and 62 deletions

View file

@ -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

View file

@ -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);

View file

@ -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({
</div>
);
}
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;
}
}}
/>

View file

@ -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() {
</h1>
}
id="account_statuses"
instance={instance}
emptyText="Nothing to see here yet."
errorText="Unable to load statuses"
fetchItems={fetchAccountStatuses}

View file

@ -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 (
<Timeline
key={hashtag}
title={instance ? `#${hashtag} on ${instance}` : `#${hashtag}`}
title={title}
titleComponent={
!!instance && (
<h1 class="header-account">
@ -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}

View file

@ -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,

View file

@ -21,7 +21,7 @@ function Lists() {
}
const [title, setTitle] = useState(`List ${id}`);
useTitle(title, `/l/${id}`);
useTitle(title, `/l/:id`);
useEffect(() => {
(async () => {
try {

View file

@ -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) {

View file

@ -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 '';

View file

@ -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];