Merge pull request #70 from cheeaun/main

Update from main
This commit is contained in:
Chee Aun 2023-02-23 23:31:09 +08:00 committed by GitHub
commit 0b1974e94b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 246 additions and 89 deletions

17
package-lock.json generated
View file

@ -20,6 +20,7 @@
"masto": "~5.10.0", "masto": "~5.10.0",
"mem": "~9.0.2", "mem": "~9.0.2",
"p-retry": "~5.1.2", "p-retry": "~5.1.2",
"p-throttle": "~5.0.0",
"preact": "~10.12.1", "preact": "~10.12.1",
"react-hotkeys-hook": "~4.3.7", "react-hotkeys-hook": "~4.3.7",
"react-intersection-observer": "~9.4.2", "react-intersection-observer": "~9.4.2",
@ -4985,6 +4986,17 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/p-throttle": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.0.0.tgz",
"integrity": "sha512-iXBFjW4kP/5Ivw7uC9EDnj+/xo3pNn4Rws3zgMGPwXnWTv1M3P0LVdZxLrqRUI5JK0Fp3Du0bt6lCaVrI3WF7g==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/param-case": { "node_modules/param-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
@ -10482,6 +10494,11 @@
"retry": "^0.13.1" "retry": "^0.13.1"
} }
}, },
"p-throttle": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.0.0.tgz",
"integrity": "sha512-iXBFjW4kP/5Ivw7uC9EDnj+/xo3pNn4Rws3zgMGPwXnWTv1M3P0LVdZxLrqRUI5JK0Fp3Du0bt6lCaVrI3WF7g=="
},
"param-case": { "param-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",

View file

@ -22,6 +22,7 @@
"masto": "~5.10.0", "masto": "~5.10.0",
"mem": "~9.0.2", "mem": "~9.0.2",
"p-retry": "~5.1.2", "p-retry": "~5.1.2",
"p-throttle": "~5.0.0",
"preact": "~10.12.1", "preact": "~10.12.1",
"react-hotkeys-hook": "~4.3.7", "react-hotkeys-hook": "~4.3.7",
"react-intersection-observer": "~9.4.2", "react-intersection-observer": "~9.4.2",

View file

@ -33,9 +33,13 @@ const imageRoute = new Route(
); );
registerRoute(imageRoute); registerRoute(imageRoute);
// 1-day cache for /api/v1/instance and /api/v1/custom_emojis // 1-day cache for
// - /api/v1/instance
// - /api/v1/custom_emojis
// - /api/v1/preferences
// - /api/v1/lists/:id
const apiExtendedRoute = new RegExpRoute( const apiExtendedRoute = new RegExpRoute(
/^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis)/, /^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+)/,
new StaleWhileRevalidate({ new StaleWhileRevalidate({
cacheName: 'api-extended', cacheName: 'api-extended',
plugins: [ plugins: [

View file

@ -745,14 +745,6 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
margin-top: 16px; margin-top: 16px;
transform: translate(-50%, 0); transform: translate(-50%, 0);
font-size: 90%; font-size: 90%;
background-color: var(--button-bg-color);
background-image: linear-gradient(
160deg,
rgba(255, 255, 255, 0.5),
rgba(255, 255, 255, 0) 50%
);
box-shadow: 0 3px 8px -1px var(--drop-shadow-color),
0 10px 36px -4px var(--button-bg-blur-color);
} }
.updates-button .icon { .updates-button .icon {
vertical-align: top; vertical-align: top;
@ -905,12 +897,16 @@ body:has(.status-deck) .media-post-link {
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
/* Don't do this if there's a modal sheet (.sheet) */ /* Don't do this if there's a modal sheet (.sheet) */
:has(#modal-container .carousel):has(.status-deck):not(:has(.sheet)) :has(#modal-container .carousel):has(.status-deck):not(
:has(.sheet, #compose-container)
)
.status-deck { .status-deck {
width: 350px; width: 350px;
min-width: 0; min-width: 0;
} }
:has(#modal-container .carousel):has(.status-deck):not(:has(.sheet)) :has(#modal-container .carousel):has(.status-deck):not(
:has(.sheet, #compose-container)
)
#modal-container #modal-container
> div { > div {
left: 0; left: 0;
@ -1099,9 +1095,11 @@ body:has(.status-deck) .media-post-link {
backdrop-filter: blur(8px) saturate(3); backdrop-filter: blur(8px) saturate(3);
border: var(--hairline-width) solid var(--bg-color); border: var(--hairline-width) solid var(--bg-color);
box-shadow: 0 3px 8px -1px var(--drop-shadow-color); box-shadow: 0 3px 8px -1px var(--drop-shadow-color);
text-shadow: 0 var(--hairline-width) var(--bg-color), 0 0 8px var(--bg-color);
} }
.glass-menu .szh-menu__item--hover { .glass-menu .szh-menu__item--hover {
background-color: var(--button-bg-blur-color); background-color: var(--button-bg-blur-color);
text-shadow: none;
} }
/* DONUT METER */ /* DONUT METER */
@ -1159,20 +1157,26 @@ meter.donut:is(.danger, .explode):after {
color: var(--red-color); color: var(--red-color);
} }
/* TOAST */ /* SHINY PILL */
:root .toastify { .shiny-pill {
color: var(--button-text-color);
text-shadow: 0 -1px var(--drop-shadow-color);
background-color: var(--button-bg-color); background-color: var(--button-bg-color);
background-image: linear-gradient( background-image: linear-gradient(
160deg, 160deg,
rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5),
rgba(255, 255, 255, 0) 50% rgba(255, 255, 255, 0) 50%
); );
color: var(--button-text-color);
border-radius: 10em;
padding: 8px 16px;
box-shadow: 0 3px 8px -1px var(--drop-shadow-color), box-shadow: 0 3px 8px -1px var(--drop-shadow-color),
0 10px 36px -4px var(--button-bg-blur-color); 0 10px 36px -4px var(--button-bg-blur-color),
inset var(--hairline-width) var(--hairline-width) rgba(255, 255, 255, 0.5);
}
/* TOAST */
:root .toastify {
padding: 8px 16px;
} }
.toastify-bottom { .toastify-bottom {
margin-bottom: env(safe-area-inset-bottom); margin-bottom: env(safe-area-inset-bottom);
@ -1487,6 +1491,9 @@ ul.link-list li a .icon {
transition: transform 0.4s var(--timing-function); transition: transform 0.4s var(--timing-function);
--back-transition: transform 0.4s ease-out; --back-transition: transform 0.4s ease-out;
} }
.timeline:not(.flat) > li:not(:has(.status-carousel)) {
transform: translate3d(0, 0, 0);
}
.timeline:not(.flat) > li:has(.status-link.is-active) { .timeline:not(.flat) > li:has(.status-link.is-active) {
transition: var(--back-transition); transition: var(--back-transition);
transform: translate3d(-2.5vw, 0, 0); transform: translate3d(-2.5vw, 0, 0);

View file

@ -318,13 +318,14 @@ function App() {
null null
} }
onClose={(results) => { onClose={(results) => {
const { newStatus } = results || {}; const { newStatus, instance } = results || {};
states.showCompose = false; states.showCompose = false;
window.__COMPOSE__ = null; window.__COMPOSE__ = null;
if (newStatus) { if (newStatus) {
states.reloadStatusPage++; states.reloadStatusPage++;
setTimeout(() => { setTimeout(() => {
const toast = Toastify({ const toast = Toastify({
className: 'shiny-pill',
text: 'Status posted. Check it out.', text: 'Status posted. Check it out.',
duration: 10_000, // 10 seconds duration: 10_000, // 10 seconds
gravity: 'bottom', gravity: 'bottom',
@ -333,7 +334,11 @@ function App() {
onClick: () => { onClick: () => {
toast.hideToast(); toast.hideToast();
states.prevLocation = location; states.prevLocation = location;
navigate(`/s/${newStatus.id}`); navigate(
instance
? `/${instance}/s/${newStatus.id}`
: `/s/${newStatus.id}`,
);
}, },
}); });
toast.showToast(); toast.showToast();

View file

@ -1,6 +1,7 @@
import './account.css'; import './account.css';
import { useEffect, useRef, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { api } from '../utils/api'; import { api } from '../utils/api';
import emojifyText from '../utils/emojify-text'; import emojifyText from '../utils/emojify-text';
@ -82,8 +83,11 @@ function Account({ account, instance: propInstance, onClose }) {
username, username,
} = info || {}; } = info || {};
const escRef = useHotkeys('esc', onClose, [onClose]);
return ( return (
<div <div
ref={escRef}
id="account-container" id="account-container"
class={`sheet ${uiState === 'loading' ? 'skeleton' : ''}`} class={`sheet ${uiState === 'loading' ? 'skeleton' : ''}`}
> >

View file

@ -113,7 +113,8 @@ function Compose({
const currentAccount = getCurrentAccount(); const currentAccount = getCurrentAccount();
const currentAccountInfo = currentAccount.info; const currentAccountInfo = currentAccount.info;
const { configuration } = getCurrentInstance(); const instance = getCurrentInstance();
const { configuration } = instance;
console.log('⚙️ Configuration', configuration); console.log('⚙️ Configuration', configuration);
const { const {
@ -141,20 +142,6 @@ function Compose({
const prefs = store.account.get('preferences') || {}; const prefs = store.account.get('preferences') || {};
const customEmojis = useRef();
useEffect(() => {
(async () => {
try {
const emojis = await masto.v1.customEmojis.list();
console.log({ emojis });
customEmojis.current = emojis;
} catch (e) {
// silent fail
console.error(e);
}
})();
}, []);
const oninputTextarea = () => { const oninputTextarea = () => {
if (!textareaRef.current) return; if (!textareaRef.current) return;
textareaRef.current.dispatchEvent(new Event('input')); textareaRef.current.dispatchEvent(new Event('input'));
@ -799,6 +786,7 @@ function Compose({
// Close // Close
onClose({ onClose({
newStatus, newStatus,
instance,
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -1057,6 +1045,20 @@ const Textarea = forwardRef((props, ref) => {
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
const charCount = snapStates.composerCharacterCount; const charCount = snapStates.composerCharacterCount;
const customEmojis = useRef();
useEffect(() => {
(async () => {
try {
const emojis = await masto.v1.customEmojis.list();
console.log({ emojis });
customEmojis.current = emojis;
} catch (e) {
// silent fail
console.error(e);
}
})();
}, []);
const textExpanderRef = useRef(); const textExpanderRef = useRef();
const textExpanderTextRef = useRef(''); const textExpanderTextRef = useRef('');
useEffect(() => { useEffect(() => {

View file

@ -16,7 +16,7 @@ audio = Audio track
function Media({ media, showOriginal, autoAnimate, onClick = () => {} }) { function Media({ media, showOriginal, autoAnimate, onClick = () => {} }) {
const { blurhash, description, meta, previewUrl, remoteUrl, url, type } = const { blurhash, description, meta, previewUrl, remoteUrl, url, type } =
media; media;
const { original, small, focus } = meta || {}; const { original = {}, small, focus } = meta || {};
const width = showOriginal ? original?.width : small?.width; const width = showOriginal ? original?.width : small?.width;
const height = showOriginal ? original?.height : small?.height; const height = showOriginal ? original?.height : small?.height;

View file

@ -1,5 +1,6 @@
import './shortcuts-settings.css'; import './shortcuts-settings.css';
import mem from 'mem';
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useState } from 'preact/hooks';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
@ -90,10 +91,15 @@ export const SHORTCUTS_META = {
icon: 'notification', icon: 'notification',
}, },
list: { list: {
title: async ({ id }) => { title: mem(
const list = await api().masto.v1.lists.fetch(id); async ({ id }) => {
return list.title; const list = await api().masto.v1.lists.fetch(id);
}, return list.title;
},
{
cacheKey: ([{ id }]) => id,
},
),
path: ({ id }) => `/l/${id}`, path: ({ id }) => `/l/${id}`,
icon: 'list', icon: 'list',
}, },
@ -109,10 +115,15 @@ export const SHORTCUTS_META = {
icon: 'search', icon: 'search',
}, },
'account-statuses': { 'account-statuses': {
title: async ({ id }) => { title: mem(
const account = await api().masto.v1.accounts.fetch(id); async ({ id }) => {
return account.username || account.acct || account.displayName; const account = await api().masto.v1.accounts.fetch(id);
}, return account.username || account.acct || account.displayName;
},
{
cacheKey: ([{ id }]) => id,
},
),
path: ({ id }) => `/a/${id}`, path: ({ id }) => `/a/${id}`,
icon: 'user', icon: 'user',
}, },

View file

@ -1,7 +1,7 @@
import './shortcuts.css'; import './shortcuts.css';
import { Menu, MenuItem } from '@szhsin/react-menu'; import { Menu, MenuItem } from '@szhsin/react-menu';
import { useRef } from 'preact/hooks'; import { useMemo, useRef } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
@ -23,29 +23,33 @@ function Shortcuts() {
const menuRef = useRef(); const menuRef = useRef();
const formattedShortcuts = shortcuts const formattedShortcuts = useMemo(
.map((pin, i) => { () =>
const { type, ...data } = pin; shortcuts
if (!SHORTCUTS_META[type]) return null; .map((pin, i) => {
let { path, title, icon } = SHORTCUTS_META[type]; const { type, ...data } = pin;
if (!SHORTCUTS_META[type]) return null;
let { path, title, icon } = SHORTCUTS_META[type];
if (typeof path === 'function') { if (typeof path === 'function') {
path = path(data, i); path = path(data, i);
} }
if (typeof title === 'function') { if (typeof title === 'function') {
title = title(data); title = title(data);
} }
if (typeof icon === 'function') { if (typeof icon === 'function') {
icon = icon(data); icon = icon(data);
} }
return { return {
path, path,
title, title,
icon, icon,
}; };
}) })
.filter(Boolean); .filter(Boolean),
[shortcuts],
);
const navigate = useNavigate(); const navigate = useNavigate();
useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => { useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => {

View file

@ -2,6 +2,7 @@ import './status.css';
import { Menu, MenuItem } from '@szhsin/react-menu'; import { Menu, MenuItem } from '@szhsin/react-menu';
import mem from 'mem'; import mem from 'mem';
import pThrottle from 'p-throttle';
import { memo } from 'preact/compat'; import { memo } from 'preact/compat';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import 'swiped-events'; import 'swiped-events';
@ -26,6 +27,11 @@ import Link from './link';
import Media from './media'; import Media from './media';
import RelativeTime from './relative-time'; import RelativeTime from './relative-time';
const throttle = pThrottle({
limit: 1,
interval: 1000,
});
function fetchAccount(id, masto) { function fetchAccount(id, masto) {
try { try {
return masto.v1.accounts.fetch(id); return masto.v1.accounts.fetch(id);
@ -374,14 +380,26 @@ function Status({
__html: enhanceContent(content, { __html: enhanceContent(content, {
emojis, emojis,
postEnhanceDOM: (dom) => { postEnhanceDOM: (dom) => {
// Remove target="_blank" from links
dom dom
.querySelectorAll('a.u-url[target="_blank"]') .querySelectorAll('a.u-url[target="_blank"]')
.forEach((a) => { .forEach((a) => {
// Remove target="_blank" from links
if (!/http/i.test(a.innerText.trim())) { if (!/http/i.test(a.innerText.trim())) {
a.removeAttribute('target'); a.removeAttribute('target');
} }
}); });
// Unfurl Mastodon links
dom
.querySelectorAll(
'a[href]:not(.u-url):not(.mention):not(.hashtag)',
)
.forEach((a) => {
if (isMastodonLinkMaybe(a.href)) {
unfurlMastodonLink(currentInstance, a.href).then(() => {
a.removeAttribute('target');
});
}
});
}, },
}), }),
}} }}
@ -463,7 +481,9 @@ function Status({
!sensitive && !sensitive &&
!spoilerText && !spoilerText &&
!poll && !poll &&
!mediaAttachments.length && <Card card={card} />} !mediaAttachments.length && (
<Card card={card} instance={currentInstance} />
)}
</div> </div>
{size === 'l' && ( {size === 'l' && (
<> <>
@ -702,7 +722,7 @@ function Status({
); );
} }
function Card({ card }) { function Card({ card, instance }) {
const { const {
blurhash, blurhash,
title, title,
@ -729,12 +749,38 @@ function Card({ card }) {
const isLandscape = width / height >= 1.2; const isLandscape = width / height >= 1.2;
const size = isLandscape ? 'large' : ''; const size = isLandscape ? 'large' : '';
const [cardStatusURL, setCardStatusURL] = useState(null);
// const [cardStatusID, setCardStatusID] = useState(null);
useEffect(() => {
if (hasText && image && isMastodonLinkMaybe(url)) {
unfurlMastodonLink(instance, url).then((result) => {
if (!result) return;
const { id, url } = result;
setCardStatusURL('#' + url);
// NOTE: This is for quote post
// (async () => {
// const { masto } = api({ instance });
// const status = await masto.v1.statuses.fetch(id);
// saveStatus(status, instance);
// setCardStatusID(id);
// })();
});
}
}, [hasText, image]);
// if (cardStatusID) {
// return (
// <Status statusID={cardStatusID} instance={instance} size="s" readOnly />
// );
// }
if (hasText && image) { if (hasText && image) {
const domain = new URL(url).hostname.replace(/^www\./, ''); const domain = new URL(url).hostname.replace(/^www\./, '');
return ( return (
<a <a
href={url} href={cardStatusURL || url}
target="_blank" target={cardStatusURL ? null : '_blank'}
rel="nofollow noopener noreferrer" rel="nofollow noopener noreferrer"
class={`card link ${size}`} class={`card link ${size}`}
> >
@ -1129,4 +1175,57 @@ export function formatDuration(time) {
} }
} }
function isMastodonLinkMaybe(url) {
return /^https:\/\/.*\/\d+$/i.test(url);
}
const denylistDomains = /(twitter|github)\.com/i;
const failedUnfurls = {};
function _unfurlMastodonLink(instance, url) {
if (denylistDomains.test(url)) {
return;
}
if (failedUnfurls[url]) {
return;
}
const instanceRegex = new RegExp(instance + '/');
if (instanceRegex.test(states.unfurledLinks[url]?.url)) {
return Promise.resolve(states.unfurledLinks[url]);
}
console.debug('🦦 Unfurling URL', url);
const { masto } = api({ instance });
return masto.v2
.search({
q: url,
type: 'statuses',
resolve: true,
limit: 1,
})
.then((results) => {
if (results.statuses.length > 0) {
const status = results.statuses[0];
const { id } = status;
const statusURL = `/${instance}/s/${id}`;
const result = {
id,
url: statusURL,
};
console.debug('🦦 Unfurled URL', url, id, statusURL);
states.unfurledLinks[url] = result;
return result;
} else {
failedUnfurls[url] = true;
throw new Error('No results');
}
})
.catch((e) => {
failedUnfurls[url] = true;
console.warn(e);
// Silently fail
});
}
const unfurlMastodonLink = throttle(_unfurlMastodonLink);
export default memo(Status); export default memo(Status);

View file

@ -275,7 +275,7 @@ function Timeline({
!hiddenUI && !hiddenUI &&
showNew && ( showNew && (
<button <button
class="updates-button" class="updates-button shiny-pill"
type="button" type="button"
onClick={() => { onClick={() => {
loadItems(true); loadItems(true);

View file

@ -85,8 +85,7 @@
} }
body { body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular', font-family: ui-rounded, system-ui;
sans-serif;
font-size: 16px; font-size: 16px;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -267,8 +266,8 @@ pre {
pre code, pre code,
code { code {
font-size: 90%; font-size: 90%;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono',
monospace; Menlo, Courier, monospace;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {

View file

@ -165,7 +165,7 @@ function Notifications() {
</div> </div>
{snapStates.notificationsShowNew && uiState !== 'loading' && ( {snapStates.notificationsShowNew && uiState !== 'loading' && (
<button <button
class="updates-button" class="updates-button shiny-pill"
type="button" type="button"
onClick={() => { onClick={() => {
loadNotifications(true); loadNotifications(true);

View file

@ -4,13 +4,9 @@ function handleContentLinks(opts) {
const { mentions = [], instance } = opts || {}; const { mentions = [], instance } = opts || {};
return (e) => { return (e) => {
let { target } = e; let { target } = e;
if (target.parentNode.tagName.toLowerCase() === 'a') { target = target.closest('a');
target = target.parentNode; if (!target) return;
} if (target.classList.contains('u-url')) {
if (
target.tagName.toLowerCase() === 'a' &&
target.classList.contains('u-url')
) {
const targetText = ( const targetText = (
target.querySelector('span') || target target.querySelector('span') || target
).innerText.trim(); ).innerText.trim();
@ -39,16 +35,17 @@ function handleContentLinks(opts) {
instance, instance,
}; };
} }
} else if ( } else if (target.classList.contains('hashtag')) {
target.tagName.toLowerCase() === 'a' &&
target.classList.contains('hashtag')
) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const tag = target.innerText.replace(/^#/, '').trim(); const tag = target.innerText.replace(/^#/, '').trim();
const hashURL = instance ? `#/${instance}/t/${tag}` : `#/t/${tag}`; const hashURL = instance ? `#/${instance}/t/${tag}` : `#/t/${tag}`;
console.log({ hashURL }); console.log({ hashURL });
location.hash = hashURL; location.hash = hashURL;
} else if (states.unfurledLinks[target.href]?.url) {
e.preventDefault();
e.stopPropagation();
location.hash = `#${states.unfurledLinks[target.href].url}`;
} }
}; };
} }

View file

@ -1,3 +1,4 @@
import mem from 'mem';
import { proxy, subscribe } from 'valtio'; import { proxy, subscribe } from 'valtio';
import { subscribeKey } from 'valtio/utils'; import { subscribeKey } from 'valtio/utils';
@ -24,6 +25,7 @@ const states = proxy({
reloadStatusPage: 0, reloadStatusPage: 0,
spoilers: {}, spoilers: {},
scrollPositions: {}, scrollPositions: {},
unfurledLinks: {},
// Modals // Modals
showCompose: false, showCompose: false,
showSettings: false, showSettings: false,
@ -129,7 +131,8 @@ export function threadifyStatus(status, propInstance) {
if (!prevStatus) { if (!prevStatus) {
if (fetchIndex++ > 3) throw 'Too many fetches for thread'; // Some people revive old threads 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 await new Promise((r) => setTimeout(r, 500 * fetchIndex)); // Be nice to rate limits
prevStatus = await masto.v1.statuses.fetch(inReplyToId); // prevStatus = await masto.v1.statuses.fetch(inReplyToId);
prevStatus = await fetchStatus(inReplyToId, masto);
saveStatus(prevStatus, instance, { skipThreading: true }); saveStatus(prevStatus, instance, { skipThreading: true });
} }
// Prepend so that first status in thread will be index 0 // Prepend so that first status in thread will be index 0
@ -149,3 +152,7 @@ export function threadifyStatus(status, propInstance) {
console.error(e, status); console.error(e, status);
}); });
} }
const fetchStatus = mem((statusID, masto) => {
return masto.v1.statuses.fetch(statusID);
});