mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-01-23 09:06:23 +01:00
Fix more edge cases after breaking changes
This commit is contained in:
parent
1debfc2c12
commit
1357c1b2bd
10 changed files with 96 additions and 62 deletions
18
src/app.jsx
18
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -21,7 +21,7 @@ function Lists() {
|
|||
}
|
||||
|
||||
const [title, setTitle] = useState(`List ${id}`);
|
||||
useTitle(title, `/l/${id}`);
|
||||
useTitle(title, `/l/:id`);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 '';
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in a new issue