commit
4ea8e2c145
25
.github/workflows/tagrelease.yml
vendored
Normal file
25
.github/workflows/tagrelease.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: Create Release on every tag push in `production`
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: production
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- run: npm ci && npm run build
|
||||
- run: cd dist && zip -r ../phanpy-dist.zip . && cd ..
|
||||
- uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
generate_release_notes: true
|
||||
files: phanpy-dist.zip
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -26,3 +26,4 @@ dist-ssr
|
|||
# Custom
|
||||
.env.dev
|
||||
src/data/instances-full.json
|
||||
phanpy-dist.zip
|
|
@ -138,10 +138,12 @@ Costs involved in running and developing this web app:
|
|||
|
||||
[Phanpy](https://bulbapedia.bulbagarden.net/wiki/Phanpy_(Pok%C3%A9mon)) is a Ground-type Pokémon.
|
||||
|
||||
## Maintainers
|
||||
## Maintainers + contributors
|
||||
|
||||
- [Chee Aun](https://github.com/cheeaun) ([Mastodon](https://mastodon.social/@cheeaun)) ([Twitter](https://twitter.com/cheeaun))
|
||||
|
||||
[![Contributors](https://contrib.rocks/image?repo=cheeaun/phanpy)](https://github.com/cheeaun/phanpy/graphs/contributors)
|
||||
|
||||
## Backstory
|
||||
|
||||
I am one of the earliest users of Twitter. Twitter was launched on [15 July 2006](https://en.wikipedia.org/wiki/Twitter). I joined on December 2006 and my [first tweet](https://twitter.com/cheeaun/status/1298723) was posted on 18 December 2006.
|
||||
|
|
1939
package-lock.json
generated
1939
package-lock.json
generated
File diff suppressed because it is too large
Load diff
12
package.json
12
package.json
|
@ -14,8 +14,8 @@
|
|||
"@github/text-expander-element": "~2.5.0",
|
||||
"@iconify-icons/mingcute": "~1.2.8",
|
||||
"@justinribeiro/lite-youtube": "~1.5.0",
|
||||
"@szhsin/react-menu": "~4.0.3",
|
||||
"@uidotdev/usehooks": "~2.3.1",
|
||||
"@szhsin/react-menu": "~4.1.0",
|
||||
"@uidotdev/usehooks": "~2.4.0",
|
||||
"dayjs": "~1.11.10",
|
||||
"dayjs-twitter": "~0.5.0",
|
||||
"fast-blurhash": "~1.1.2",
|
||||
|
@ -23,8 +23,8 @@
|
|||
"idb-keyval": "~6.2.1",
|
||||
"just-debounce-it": "~3.2.0",
|
||||
"lz-string": "~1.5.0",
|
||||
"masto": "~5.11.4",
|
||||
"mem": "~9.0.2",
|
||||
"masto": "~6.3.1",
|
||||
"moize": "~6.1.6",
|
||||
"p-retry": "~6.1.0",
|
||||
"p-throttle": "~5.1.0",
|
||||
"preact": "~10.18.1",
|
||||
|
@ -42,11 +42,11 @@
|
|||
"valtio": "1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "~2.5.0",
|
||||
"@preact/preset-vite": "~2.6.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "~4.2.0",
|
||||
"postcss": "~8.4.31",
|
||||
"postcss-dark-theme-class": "~1.0.0",
|
||||
"postcss-preset-env": "~9.1.4",
|
||||
"postcss-preset-env": "~9.2.0",
|
||||
"twitter-text": "~3.1.0",
|
||||
"vite": "~4.4.11",
|
||||
"vite-plugin-generate-file": "~0.0.4",
|
||||
|
|
|
@ -165,6 +165,7 @@ self.addEventListener('notificationclick', (event) => {
|
|||
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
await event.notification.close();
|
||||
const clients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true,
|
||||
|
@ -194,7 +195,6 @@ self.addEventListener('notificationclick', (event) => {
|
|||
console.log('NOTIFICATION CLICK openWindow', url);
|
||||
await self.clients.openWindow(url);
|
||||
}
|
||||
await event.notification.close();
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
|
12
src/app.css
12
src/app.css
|
@ -1418,6 +1418,13 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
|||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
line-height: 0;
|
||||
|
||||
svg {
|
||||
contain: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* TAG */
|
||||
|
@ -1754,7 +1761,7 @@ meter.donut[hidden] {
|
|||
font-weight: 500;
|
||||
text-shadow: 0 1px var(--bg-color);
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--outline-color);
|
||||
border: 2px solid var(--link-faded-color);
|
||||
box-shadow: 0 3px 16px var(--drop-shadow-color),
|
||||
0 6px 16px -3px var(--drop-shadow-color);
|
||||
}
|
||||
|
@ -1762,8 +1769,7 @@ meter.donut[hidden] {
|
|||
color: var(--text-color);
|
||||
border-color: var(--link-color);
|
||||
filter: none !important;
|
||||
box-shadow: 0 0 0 1px var(--link-text-color),
|
||||
0 3px 16px var(--drop-shadow-color),
|
||||
box-shadow: 0 3px 16px var(--drop-shadow-color),
|
||||
0 6px 16px -3px var(--drop-shadow-color),
|
||||
0 6px 16px var(--drop-shadow-color);
|
||||
}
|
||||
|
|
21
src/app.jsx
21
src/app.jsx
|
@ -114,13 +114,13 @@ function App() {
|
|||
code,
|
||||
});
|
||||
|
||||
const masto = initClient({ instance: instanceURL, accessToken });
|
||||
const client = initClient({ instance: instanceURL, accessToken });
|
||||
await Promise.allSettled([
|
||||
initInstance(masto, instanceURL),
|
||||
initAccount(masto, instanceURL, accessToken, vapidKey),
|
||||
initInstance(client, instanceURL),
|
||||
initAccount(client, instanceURL, accessToken, vapidKey),
|
||||
]);
|
||||
initStates();
|
||||
initPreferences(masto);
|
||||
initPreferences(client);
|
||||
|
||||
setIsLoggedIn(true);
|
||||
setUIState('default');
|
||||
|
@ -130,14 +130,15 @@ function App() {
|
|||
const account = getCurrentAccount();
|
||||
if (account) {
|
||||
store.session.set('currentAccount', account.info.id);
|
||||
const { masto, instance } = api({ account });
|
||||
console.log('masto', masto);
|
||||
const { client } = api({ account });
|
||||
const { instance } = client;
|
||||
// console.log('masto', masto);
|
||||
initStates();
|
||||
initPreferences(masto);
|
||||
initPreferences(client);
|
||||
setUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
await initInstance(masto, instance);
|
||||
await initInstance(client, instance);
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setIsLoggedIn(true);
|
||||
|
@ -253,9 +254,9 @@ function App() {
|
|||
<Shortcuts />
|
||||
)}
|
||||
<Modals />
|
||||
<NotificationService />
|
||||
{isLoggedIn && <NotificationService />}
|
||||
<BackgroundService isLoggedIn={isLoggedIn} />
|
||||
<SearchCommand onClose={focusDeck} />
|
||||
{uiState !== 'loading' && <SearchCommand onClose={focusDeck} />}
|
||||
<KeyboardShortcutsHelp />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -220,6 +220,7 @@
|
|||
}
|
||||
|
||||
.account-container .actions {
|
||||
margin-block: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
|
@ -342,23 +343,82 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.account-container .posting-stats {
|
||||
.account-container .posting-stats-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
color: inherit;
|
||||
background-color: var(--bg-faded-color);
|
||||
padding: 8px 12px;
|
||||
font-size: 90%;
|
||||
color: var(--text-insignificant-color);
|
||||
line-height: 1;
|
||||
vertical-align: text-top;
|
||||
border-radius: 4px;
|
||||
|
||||
&:is(:hover, :focus-within) {
|
||||
color: var(--text-color);
|
||||
background-color: var(--link-bg-hover-color);
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
margin: 0;
|
||||
opacity: 0.5;
|
||||
transform: scale(0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wobble {
|
||||
0% {
|
||||
transform: rotate(-4deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(4deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading-spin {
|
||||
0% {
|
||||
transform: rotate(0deg) scale(0.75);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg) scale(0.75);
|
||||
}
|
||||
}
|
||||
.posting-stats-icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 8px;
|
||||
filter: opacity(0.75);
|
||||
animation: wobble 2s linear both infinite alternate !important;
|
||||
|
||||
&.loading {
|
||||
animation: loading-spin 0.35s linear both infinite !important;
|
||||
}
|
||||
}
|
||||
|
||||
.account-container {
|
||||
--posting-stats-size: 8px;
|
||||
--original-color: var(--link-color);
|
||||
|
||||
.posting-stats {
|
||||
font-size: 90%;
|
||||
color: var(--text-insignificant-color);
|
||||
background-color: var(--bg-faded-color);
|
||||
padding: 8px 12px;
|
||||
--size: 8px;
|
||||
--original-color: var(--link-color);
|
||||
|
||||
&:is(:hover, :focus-within) {
|
||||
background-color: var(--link-bg-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
.posting-stats-bar {
|
||||
--gap: 0.5px;
|
||||
--gap-color: var(--outline-color);
|
||||
height: var(--size);
|
||||
border-radius: var(--size);
|
||||
height: var(--posting-stats-size);
|
||||
border-radius: var(--posting-stats-size);
|
||||
overflow: hidden;
|
||||
margin: 8px 0;
|
||||
box-shadow: inset 0 0 0 1px var(--outline-color),
|
||||
|
@ -388,9 +448,9 @@
|
|||
|
||||
.posting-stats-legend-item {
|
||||
display: inline-block;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: var(--size);
|
||||
width: var(--posting-stats-size);
|
||||
height: var(--posting-stats-size);
|
||||
border-radius: var(--posting-stats-size);
|
||||
background-color: var(--text-insignificant-color);
|
||||
vertical-align: middle;
|
||||
margin: 0 4px 2px;
|
||||
|
|
|
@ -15,6 +15,7 @@ import enhanceContent from '../utils/enhance-content';
|
|||
import getHTMLText from '../utils/getHTMLText';
|
||||
import handleContentLinks from '../utils/handle-content-links';
|
||||
import niceDateTime from '../utils/nice-date-time';
|
||||
import pmem from '../utils/pmem';
|
||||
import shortenNumber from '../utils/shorten-number';
|
||||
import showToast from '../utils/show-toast';
|
||||
import states, { hideAllModals } from '../utils/states';
|
||||
|
@ -55,6 +56,66 @@ const MUTE_DURATIONS_LABELS = {
|
|||
|
||||
const LIMIT = 80;
|
||||
|
||||
const ACCOUNT_INFO_MAX_AGE = 1000 * 60 * 10; // 10 mins
|
||||
|
||||
function fetchFamiliarFollowers(currentID, masto) {
|
||||
return masto.v1.accounts.familiarFollowers.fetch({
|
||||
id: [currentID],
|
||||
});
|
||||
}
|
||||
const memFetchFamiliarFollowers = pmem(fetchFamiliarFollowers, {
|
||||
maxAge: ACCOUNT_INFO_MAX_AGE,
|
||||
});
|
||||
|
||||
async function fetchPostingStats(accountID, masto) {
|
||||
const fetchStatuses = masto.v1.accounts
|
||||
.$select(accountID)
|
||||
.statuses.list({
|
||||
limit: 20,
|
||||
})
|
||||
.next();
|
||||
|
||||
const { value: statuses } = await fetchStatuses;
|
||||
console.log('fetched statuses', statuses);
|
||||
const stats = {
|
||||
total: statuses.length,
|
||||
originals: 0,
|
||||
replies: 0,
|
||||
boosts: 0,
|
||||
};
|
||||
// Categories statuses by type
|
||||
// - Original posts (not replies to others)
|
||||
// - Threads (self-replies + 1st original post)
|
||||
// - Boosts (reblogs)
|
||||
// - Replies (not-self replies)
|
||||
statuses.forEach((status) => {
|
||||
if (status.reblog) {
|
||||
stats.boosts++;
|
||||
} else if (
|
||||
!!status.inReplyToId &&
|
||||
status.inReplyToAccountId !== status.account.id // Not self-reply
|
||||
) {
|
||||
stats.replies++;
|
||||
} else {
|
||||
stats.originals++;
|
||||
}
|
||||
});
|
||||
|
||||
// Count days since last post
|
||||
if (statuses.length) {
|
||||
stats.daysSinceLastPost = Math.ceil(
|
||||
(Date.now() - new Date(statuses[statuses.length - 1].createdAt)) /
|
||||
86400000,
|
||||
);
|
||||
}
|
||||
|
||||
console.log('posting stats', stats);
|
||||
return stats;
|
||||
}
|
||||
const memFetchPostingStats = pmem(fetchPostingStats, {
|
||||
maxAge: ACCOUNT_INFO_MAX_AGE,
|
||||
});
|
||||
|
||||
function AccountInfo({
|
||||
account,
|
||||
fetchAccount = () => {},
|
||||
|
@ -149,7 +210,7 @@ function AccountInfo({
|
|||
const familiarFollowersCache = useRef([]);
|
||||
async function fetchFollowers(firstLoad) {
|
||||
if (firstLoad || !followersIterator.current) {
|
||||
followersIterator.current = masto.v1.accounts.listFollowers(id, {
|
||||
followersIterator.current = masto.v1.accounts.$select(id).followers.list({
|
||||
limit: LIMIT,
|
||||
});
|
||||
}
|
||||
|
@ -162,9 +223,9 @@ function AccountInfo({
|
|||
// On first load, fetch familiar followers, merge to top of results' `value`
|
||||
// Remove dups on every fetch
|
||||
if (firstLoad) {
|
||||
const familiarFollowers = await masto.v1.accounts.fetchFamiliarFollowers(
|
||||
id,
|
||||
);
|
||||
const familiarFollowers = await masto.v1.accounts
|
||||
.familiarFollowers(id)
|
||||
.fetch();
|
||||
familiarFollowersCache.current = familiarFollowers[0].accounts;
|
||||
newValue = [
|
||||
...familiarFollowersCache.current,
|
||||
|
@ -193,7 +254,7 @@ function AccountInfo({
|
|||
const followingIterator = useRef();
|
||||
async function fetchFollowing(firstLoad) {
|
||||
if (firstLoad || !followingIterator.current) {
|
||||
followingIterator.current = masto.v1.accounts.listFollowing(id, {
|
||||
followingIterator.current = masto.v1.accounts.$select(id).following.list({
|
||||
limit: LIMIT,
|
||||
});
|
||||
}
|
||||
|
@ -206,71 +267,47 @@ function AccountInfo({
|
|||
|
||||
const [familiarFollowers, setFamiliarFollowers] = useState([]);
|
||||
const [postingStats, setPostingStats] = useState();
|
||||
const hasPostingStats = postingStats?.total >= 3;
|
||||
const [postingStatsUIState, setPostingStatsUIState] = useState('default');
|
||||
const hasPostingStats = !!postingStats?.total;
|
||||
|
||||
const onRelationshipChange = useCallback(
|
||||
({ relationship, currentID }) => {
|
||||
if (!relationship.following) {
|
||||
(async () => {
|
||||
const renderFamiliarFollowers = async (currentID) => {
|
||||
try {
|
||||
const fetchFamiliarFollowers =
|
||||
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
|
||||
const fetchStatuses = currentMasto.v1.accounts
|
||||
.listStatuses(currentID, {
|
||||
limit: 20,
|
||||
})
|
||||
.next();
|
||||
|
||||
const followers = await fetchFamiliarFollowers;
|
||||
const followers = await memFetchFamiliarFollowers(
|
||||
currentID,
|
||||
currentMasto,
|
||||
);
|
||||
console.log('fetched familiar followers', followers);
|
||||
setFamiliarFollowers(
|
||||
followers[0].accounts.slice(0, FAMILIAR_FOLLOWERS_LIMIT),
|
||||
);
|
||||
|
||||
if (!standalone) {
|
||||
const { value: statuses } = await fetchStatuses;
|
||||
console.log('fetched statuses', statuses);
|
||||
const stats = {
|
||||
total: statuses.length,
|
||||
originals: 0,
|
||||
replies: 0,
|
||||
boosts: 0,
|
||||
};
|
||||
// Categories statuses by type
|
||||
// - Original posts (not replies to others)
|
||||
// - Threads (self-replies + 1st original post)
|
||||
// - Boosts (reblogs)
|
||||
// - Replies (not-self replies)
|
||||
statuses.forEach((status) => {
|
||||
if (status.reblog) {
|
||||
stats.boosts++;
|
||||
} else if (
|
||||
status.inReplyToAccountId !== currentID &&
|
||||
!!status.inReplyToId
|
||||
) {
|
||||
stats.replies++;
|
||||
} else {
|
||||
stats.originals++;
|
||||
}
|
||||
});
|
||||
|
||||
// Count days since last post
|
||||
stats.daysSinceLastPost = Math.ceil(
|
||||
(Date.now() -
|
||||
new Date(statuses[statuses.length - 1].createdAt)) /
|
||||
86400000,
|
||||
);
|
||||
|
||||
console.log('posting stats', stats);
|
||||
setPostingStats(stats);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const renderPostingStats = async () => {
|
||||
if (!id) return;
|
||||
setPostingStatsUIState('loading');
|
||||
try {
|
||||
const stats = await memFetchPostingStats(id, masto);
|
||||
setPostingStats(stats);
|
||||
setPostingStatsUIState('default');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setPostingStatsUIState('error');
|
||||
}
|
||||
};
|
||||
|
||||
const onRelationshipChange = useCallback(
|
||||
({ relationship, currentID }) => {
|
||||
if (!relationship.following) {
|
||||
renderFamiliarFollowers(currentID);
|
||||
if (!standalone) {
|
||||
renderPostingStats();
|
||||
}
|
||||
}
|
||||
},
|
||||
[standalone],
|
||||
[standalone, id],
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -307,7 +344,7 @@ function AccountInfo({
|
|||
<p>████████ ███████</p>
|
||||
<p>███████████████ ███████████████</p>
|
||||
</div>
|
||||
<p class="stats">
|
||||
<div class="stats">
|
||||
<div>
|
||||
<span>██</span> Followers
|
||||
</div>
|
||||
|
@ -318,7 +355,7 @@ function AccountInfo({
|
|||
<span>██</span> Posts
|
||||
</div>
|
||||
<div>Joined ██</div>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
) : (
|
||||
|
@ -583,8 +620,8 @@ function AccountInfo({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
{hasPostingStats && (
|
||||
<Link
|
||||
{!!postingStats && (
|
||||
<LinkOrDiv
|
||||
to={accountLink}
|
||||
class="account-metadata-box"
|
||||
onClick={() => {
|
||||
|
@ -593,6 +630,7 @@ function AccountInfo({
|
|||
>
|
||||
<div class="shazam-container">
|
||||
<div class="shazam-container-inner">
|
||||
{hasPostingStats ? (
|
||||
<div
|
||||
class="posting-stats"
|
||||
title={`${Math.round(
|
||||
|
@ -622,7 +660,8 @@ function AccountInfo({
|
|||
100
|
||||
}%`,
|
||||
'--replies-percentage': `${
|
||||
((postingStats.originals + postingStats.replies) /
|
||||
((postingStats.originals +
|
||||
postingStats.replies) /
|
||||
postingStats.total) *
|
||||
100
|
||||
}%`,
|
||||
|
@ -643,10 +682,45 @@ function AccountInfo({
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div class="posting-stats">Post stats unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</LinkOrDiv>
|
||||
)}
|
||||
<div class="account-metadata-box">
|
||||
<div
|
||||
class="shazam-container no-animation"
|
||||
hidden={!!postingStats}
|
||||
>
|
||||
<div class="shazam-container-inner">
|
||||
<button
|
||||
type="button"
|
||||
class="posting-stats-button"
|
||||
disabled={postingStatsUIState === 'loading'}
|
||||
onClick={() => {
|
||||
renderPostingStats();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={`posting-stats-bar posting-stats-icon ${
|
||||
postingStatsUIState === 'loading' ? 'loading' : ''
|
||||
}`}
|
||||
style={{
|
||||
'--originals-percentage': '33%',
|
||||
'--replies-percentage': '66%',
|
||||
}}
|
||||
/>
|
||||
View post stats{' '}
|
||||
{/* <Loader
|
||||
abrupt
|
||||
hidden={postingStatsUIState !== 'loading'}
|
||||
/> */}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RelatedActions
|
||||
info={info}
|
||||
instance={instance}
|
||||
|
@ -712,7 +786,7 @@ function RelatedActions({
|
|||
// Grab this account from my logged-in instance
|
||||
const acctHasInstance = info.acct.includes('@');
|
||||
try {
|
||||
const results = await currentMasto.v2.search({
|
||||
const results = await currentMasto.v2.search.fetch({
|
||||
q: acctHasInstance ? info.acct : `${info.username}@${instance}`,
|
||||
type: 'accounts',
|
||||
limit: 1,
|
||||
|
@ -742,9 +816,11 @@ function RelatedActions({
|
|||
|
||||
setRelationshipUIState('loading');
|
||||
|
||||
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
|
||||
currentID,
|
||||
]);
|
||||
const fetchRelationships = currentMasto.v1.accounts.relationships.fetch(
|
||||
{
|
||||
id: [currentID],
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
const relationships = await fetchRelationships;
|
||||
|
@ -778,7 +854,7 @@ function RelatedActions({
|
|||
|
||||
return (
|
||||
<>
|
||||
<p class="actions">
|
||||
<div class="actions">
|
||||
<span>
|
||||
{followedBy ? (
|
||||
<span class="tag">Following you</span>
|
||||
|
@ -913,10 +989,9 @@ function RelatedActions({
|
|||
setRelationshipUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
const newRelationship =
|
||||
await currentMasto.v1.accounts.unmute(
|
||||
currentInfo?.id || id,
|
||||
);
|
||||
const newRelationship = await currentMasto.v1.accounts
|
||||
.$select(currentInfo?.id || id)
|
||||
.unmute();
|
||||
console.log('unmuting', newRelationship);
|
||||
setRelationship(newRelationship);
|
||||
setRelationshipUIState('default');
|
||||
|
@ -962,12 +1037,11 @@ function RelatedActions({
|
|||
(async () => {
|
||||
try {
|
||||
const newRelationship =
|
||||
await currentMasto.v1.accounts.mute(
|
||||
currentInfo?.id || id,
|
||||
{
|
||||
await currentMasto.v1.accounts
|
||||
.$select(currentInfo?.id || id)
|
||||
.mute({
|
||||
duration,
|
||||
},
|
||||
);
|
||||
});
|
||||
console.log('muting', newRelationship);
|
||||
setRelationship(newRelationship);
|
||||
setRelationshipUIState('default');
|
||||
|
@ -1008,19 +1082,17 @@ function RelatedActions({
|
|||
(async () => {
|
||||
try {
|
||||
if (blocking) {
|
||||
const newRelationship =
|
||||
await currentMasto.v1.accounts.unblock(
|
||||
currentInfo?.id || id,
|
||||
);
|
||||
const newRelationship = await currentMasto.v1.accounts
|
||||
.$select(currentInfo?.id || id)
|
||||
.unblock();
|
||||
console.log('unblocking', newRelationship);
|
||||
setRelationship(newRelationship);
|
||||
setRelationshipUIState('default');
|
||||
showToast(`Unblocked @${username}`);
|
||||
} else {
|
||||
const newRelationship =
|
||||
await currentMasto.v1.accounts.block(
|
||||
currentInfo?.id || id,
|
||||
);
|
||||
const newRelationship = await currentMasto.v1.accounts
|
||||
.$select(currentInfo?.id || id)
|
||||
.block();
|
||||
console.log('blocking', newRelationship);
|
||||
setRelationship(newRelationship);
|
||||
setRelationshipUIState('default');
|
||||
|
@ -1089,14 +1161,14 @@ function RelatedActions({
|
|||
// );
|
||||
|
||||
// if (yes) {
|
||||
newRelationship = await currentMasto.v1.accounts.unfollow(
|
||||
accountID.current,
|
||||
);
|
||||
newRelationship = await currentMasto.v1.accounts
|
||||
.$select(accountID.current)
|
||||
.unfollow();
|
||||
// }
|
||||
} else {
|
||||
newRelationship = await currentMasto.v1.accounts.follow(
|
||||
accountID.current,
|
||||
);
|
||||
newRelationship = await currentMasto.v1.accounts
|
||||
.$select(accountID.current)
|
||||
.follow();
|
||||
}
|
||||
|
||||
if (newRelationship) setRelationship(newRelationship);
|
||||
|
@ -1135,7 +1207,7 @@ function RelatedActions({
|
|||
</MenuConfirm>
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
{!!showTranslatedBio && (
|
||||
<Modal
|
||||
class="light"
|
||||
|
@ -1241,9 +1313,9 @@ function AddRemoveListsSheet({ accountID, onClose }) {
|
|||
(async () => {
|
||||
try {
|
||||
const lists = await masto.v1.lists.list();
|
||||
const listsContainingAccount = await masto.v1.accounts.listLists(
|
||||
accountID,
|
||||
);
|
||||
const listsContainingAccount = await masto.v1.accounts
|
||||
.$select(accountID)
|
||||
.lists.list();
|
||||
console.log({ lists, listsContainingAccount });
|
||||
setLists(lists);
|
||||
setListsContainingAccount(listsContainingAccount);
|
||||
|
@ -1285,11 +1357,15 @@ function AddRemoveListsSheet({ accountID, onClose }) {
|
|||
(async () => {
|
||||
try {
|
||||
if (inList) {
|
||||
await masto.v1.lists.removeAccount(list.id, {
|
||||
await masto.v1.lists
|
||||
.$select(list.id)
|
||||
.accounts.remove({
|
||||
accountIds: [accountID],
|
||||
});
|
||||
} else {
|
||||
await masto.v1.lists.addAccount(list.id, {
|
||||
await masto.v1.lists
|
||||
.$select(list.id)
|
||||
.accounts.create({
|
||||
accountIds: [accountID],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ function AccountSheet({ account, instance: propInstance, onClose }) {
|
|||
});
|
||||
return info;
|
||||
} catch (e) {
|
||||
const result = await masto.v2.search({
|
||||
const result = await masto.v2.search.fetch({
|
||||
q: account,
|
||||
type: 'accounts',
|
||||
limit: 1,
|
||||
|
@ -57,7 +57,7 @@ function AccountSheet({ account, instance: propInstance, onClose }) {
|
|||
} else if (/https?:\/\/[^/]+\/@/.test(account)) {
|
||||
const accountURL = new URL(account);
|
||||
const acct = accountURL.pathname.replace(/^\//, '');
|
||||
const result = await masto.v2.search({
|
||||
const result = await masto.v2.search.fetch({
|
||||
q: acct,
|
||||
type: 'accounts',
|
||||
limit: 1,
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
height: 100%;
|
||||
object-fit: cover;
|
||||
background-color: var(--img-bg-color);
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.avatar[data-loaded],
|
||||
|
|
|
@ -11,10 +11,10 @@ export default memo(function BackgroundService({ isLoggedIn }) {
|
|||
// - WebSocket to receive notifications when page is visible
|
||||
const [visible, setVisible] = useState(true);
|
||||
usePageVisibility(setVisible);
|
||||
const notificationStream = useRef();
|
||||
useEffect(() => {
|
||||
let sub;
|
||||
if (isLoggedIn && visible) {
|
||||
const { masto, instance } = api();
|
||||
const { masto, streaming, instance } = api();
|
||||
(async () => {
|
||||
// 1. Get the latest notification
|
||||
if (states.notificationsLast) {
|
||||
|
@ -42,34 +42,26 @@ export default memo(function BackgroundService({ isLoggedIn }) {
|
|||
}
|
||||
|
||||
// 2. Start streaming
|
||||
notificationStream.current = await masto.ws.stream(
|
||||
'/api/v1/streaming',
|
||||
{
|
||||
stream: 'user:notification',
|
||||
},
|
||||
);
|
||||
console.log('🎏 Streaming notification', notificationStream.current);
|
||||
|
||||
notificationStream.current.on('notification', (notification) => {
|
||||
console.log('🔔🔔 Notification', notification);
|
||||
if (notification.status) {
|
||||
saveStatus(notification.status, instance, {
|
||||
if (streaming) {
|
||||
sub = streaming.user.notification.subscribe();
|
||||
console.log('🎏 Streaming notification', sub);
|
||||
for await (const entry of sub) {
|
||||
if (!sub) break;
|
||||
console.log('🔔🔔 Notification entry', entry);
|
||||
if (entry.event === 'notification') {
|
||||
console.log('🔔🔔 Notification', entry);
|
||||
saveStatus(entry.payload, instance, {
|
||||
skipThreading: true,
|
||||
});
|
||||
}
|
||||
states.notificationsShowNew = true;
|
||||
});
|
||||
|
||||
notificationStream.current.ws.onclose = () => {
|
||||
console.log('🔔🔔 Notification stream closed');
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
if (notificationStream.current) {
|
||||
notificationStream.current.ws.close();
|
||||
notificationStream.current = null;
|
||||
}
|
||||
sub?.unsubscribe?.();
|
||||
sub = null;
|
||||
};
|
||||
}, [visible, isLoggedIn]);
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ function Compose({
|
|||
: visibility,
|
||||
);
|
||||
setLanguage(language || prefs.postingDefaultLanguage || DEFAULT_LANG);
|
||||
setSensitive(sensitive);
|
||||
setSensitive(sensitive && !!spoilerText);
|
||||
} else if (editStatus) {
|
||||
const { visibility, language, sensitive, poll, mediaAttachments } =
|
||||
editStatus;
|
||||
|
@ -197,9 +197,9 @@ function Compose({
|
|||
setUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
const statusSource = await masto.v1.statuses.fetchSource(
|
||||
editStatus.id,
|
||||
);
|
||||
const statusSource = await masto.v1.statuses
|
||||
.$select(editStatus.id)
|
||||
.source.fetch();
|
||||
console.log({ statusSource });
|
||||
const { text, spoilerText } = statusSource;
|
||||
textareaRef.current.value = text;
|
||||
|
@ -749,9 +749,7 @@ function Compose({
|
|||
file,
|
||||
description,
|
||||
});
|
||||
return masto.v2.mediaAttachments
|
||||
.create(params)
|
||||
.then((res) => {
|
||||
return masto.v2.media.create(params).then((res) => {
|
||||
if (res.id) {
|
||||
attachment.id = res.id;
|
||||
}
|
||||
|
@ -784,6 +782,8 @@ function Compose({
|
|||
/* NOTE:
|
||||
Using snakecase here because masto.js's `isObject` returns false for `params`, ONLY happens when opening in pop-out window. This is maybe due to `window.masto` variable being passed from the parent window. The check that failed is `x.constructor === Object`, so maybe the `Object` in new window is different than parent window's?
|
||||
Code: https://github.com/neet/masto.js/blob/dd0d649067b6a2b6e60fbb0a96597c373a255b00/src/serializers/is-object.ts#L2
|
||||
|
||||
// TODO: Note above is no longer true in Masto.js v6. Revisit this.
|
||||
*/
|
||||
let params = {
|
||||
status,
|
||||
|
@ -818,10 +818,9 @@ function Compose({
|
|||
|
||||
let newStatus;
|
||||
if (editStatus) {
|
||||
newStatus = await masto.v1.statuses.update(
|
||||
editStatus.id,
|
||||
params,
|
||||
);
|
||||
newStatus = await masto.v1.statuses
|
||||
.$select(editStatus.id)
|
||||
.update(params);
|
||||
saveStatus(newStatus, instance, {
|
||||
skipThreading: true,
|
||||
});
|
||||
|
@ -935,13 +934,13 @@ function Compose({
|
|||
performSearch={(params) => {
|
||||
const { type, q, limit } = params;
|
||||
if (type === 'accounts') {
|
||||
return masto.v1.accounts.search({
|
||||
return masto.v1.accounts.search.list({
|
||||
q,
|
||||
limit,
|
||||
resolve: false,
|
||||
});
|
||||
}
|
||||
return masto.v2.search(params);
|
||||
return masto.v2.search.fetch(params);
|
||||
}}
|
||||
/>
|
||||
{mediaAttachments?.length > 0 && (
|
||||
|
@ -1478,7 +1477,10 @@ function MediaAttachment({
|
|||
}) {
|
||||
const supportsEdit = supports('@mastodon/edit-media-attributes');
|
||||
const { type, id, file } = attachment;
|
||||
const url = file ? URL.createObjectURL(file) : attachment.url;
|
||||
const url = useMemo(
|
||||
() => (file ? URL.createObjectURL(file) : attachment.url),
|
||||
[file, attachment.url],
|
||||
);
|
||||
console.log({ attachment });
|
||||
const [description, setDescription] = useState(attachment.description);
|
||||
const suffixType = type.split('/')[0];
|
||||
|
|
|
@ -128,9 +128,9 @@ function Drafts({ onClose }) {
|
|||
if (replyTo) {
|
||||
setUIState('loading');
|
||||
try {
|
||||
replyToStatus = await masto.v1.statuses.fetch(
|
||||
replyTo.id,
|
||||
);
|
||||
replyToStatus = await masto.v1.statuses
|
||||
.$select(replyTo.id)
|
||||
.fetch();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Error fetching reply-to status!');
|
||||
|
|
|
@ -23,7 +23,9 @@ function FollowRequestButtons({ accountID, onChange }) {
|
|||
setRequestState('accept');
|
||||
(async () => {
|
||||
try {
|
||||
const rel = await masto.v1.followRequests.authorize(accountID);
|
||||
const rel = await masto.v1.followRequests
|
||||
.$select(accountID)
|
||||
.authorize();
|
||||
if (!rel?.followedBy) {
|
||||
throw new Error('Follow request not accepted');
|
||||
}
|
||||
|
@ -47,7 +49,9 @@ function FollowRequestButtons({ accountID, onChange }) {
|
|||
setRequestState('reject');
|
||||
(async () => {
|
||||
try {
|
||||
const rel = await masto.v1.followRequests.reject(accountID);
|
||||
const rel = await masto.v1.followRequests
|
||||
.$select(accountID)
|
||||
.reject();
|
||||
if (rel?.followedBy) {
|
||||
throw new Error('Follow request not rejected');
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { memo } from 'preact/compat';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
|
||||
const SIZES = {
|
||||
|
@ -132,9 +133,6 @@ function Icon({
|
|||
style={{
|
||||
width: `${iconSize}px`,
|
||||
height: `${iconSize}px`,
|
||||
display: 'inline-block',
|
||||
overflow: 'hidden',
|
||||
lineHeight: 0,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
|
@ -155,4 +153,4 @@ function Icon({
|
|||
);
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
export default memo(Icon);
|
||||
|
|
|
@ -56,7 +56,7 @@ function ListAddEdit({ list, onClose }) {
|
|||
let listResult;
|
||||
|
||||
if (editMode) {
|
||||
listResult = await masto.v1.lists.update(list.id, {
|
||||
listResult = await masto.v1.lists.$select(list.id).update({
|
||||
title,
|
||||
replies_policy: repliesPolicy,
|
||||
exclusive,
|
||||
|
@ -141,7 +141,7 @@ function ListAddEdit({ list, onClose }) {
|
|||
|
||||
(async () => {
|
||||
try {
|
||||
await masto.v1.lists.remove(list.id);
|
||||
await masto.v1.lists.$select(list.id).remove();
|
||||
setUIState('default');
|
||||
onClose?.({
|
||||
state: 'deleted',
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { getBlurHashAverageColor } from 'fast-blurhash';
|
||||
import mem from 'mem';
|
||||
import { Fragment } from 'preact';
|
||||
import {
|
||||
useCallback,
|
||||
|
@ -10,6 +9,7 @@ import {
|
|||
} from 'preact/hooks';
|
||||
import QuickPinchZoom, { make3dTransformValue } from 'react-quick-pinch-zoom';
|
||||
|
||||
import mem from '../utils/mem';
|
||||
import states from '../utils/states';
|
||||
|
||||
import Icon from './icon';
|
||||
|
|
|
@ -38,7 +38,7 @@ export default memo(function NotificationService() {
|
|||
? getAccountByAccessToken(accessToken)
|
||||
: getCurrentAccount();
|
||||
(async () => {
|
||||
const notification = await masto.v1.notifications.fetch(id);
|
||||
const notification = await masto.v1.notifications.$select(id).fetch();
|
||||
if (notification && account) {
|
||||
console.log('🛎️ Notification', { id, notification, account });
|
||||
const accountInstance = account.instanceURL;
|
||||
|
|
|
@ -58,14 +58,14 @@ const contentText = {
|
|||
'favourite+reblog+account': (count) =>
|
||||
`boosted & favourited ${count} of your posts.`,
|
||||
'favourite+reblog_reply': 'boosted & favourited your reply.',
|
||||
'admin.signup': 'signed up.',
|
||||
'admin.report': 'reported a post.',
|
||||
'admin.sign_up': 'signed up.',
|
||||
'admin.report': (targetAccount) => <>reported {targetAccount}</>,
|
||||
};
|
||||
|
||||
const AVATARS_LIMIT = 50;
|
||||
|
||||
function Notification({ notification, instance, reload, isStatic }) {
|
||||
const { id, status, account, _accounts, _statuses } = notification;
|
||||
const { id, status, account, report, _accounts, _statuses } = notification;
|
||||
let { type } = notification;
|
||||
|
||||
// status = Attached when type of the notification is favourite, reblog, status, mention, poll, or update
|
||||
|
@ -119,7 +119,15 @@ function Notification({ notification, instance, reload, isStatic }) {
|
|||
}
|
||||
|
||||
if (typeof text === 'function') {
|
||||
text = text(_statuses?.length || _accounts?.length);
|
||||
const count = _statuses?.length || _accounts?.length;
|
||||
if (count) {
|
||||
text = text(count);
|
||||
} else if (type === 'admin.report') {
|
||||
const targetAccount = report?.targetAccount;
|
||||
if (targetAccount) {
|
||||
text = text(<NameText account={targetAccount} showAvatar />);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'mention' && !status) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
compressToEncodedURIComponent,
|
||||
decompressFromEncodedURIComponent,
|
||||
} from 'lz-string';
|
||||
import mem from 'mem';
|
||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
|
@ -13,6 +12,7 @@ import multiColumnUrl from '../assets/multi-column.svg';
|
|||
import tabMenuBarUrl from '../assets/tab-menu-bar.svg';
|
||||
|
||||
import { api } from '../utils/api';
|
||||
import pmem from '../utils/pmem';
|
||||
import showToast from '../utils/show-toast';
|
||||
import states from '../utils/states';
|
||||
|
||||
|
@ -133,15 +133,10 @@ export const SHORTCUTS_META = {
|
|||
},
|
||||
list: {
|
||||
id: 'list',
|
||||
title: mem(
|
||||
async ({ id }) => {
|
||||
const list = await api().masto.v1.lists.fetch(id);
|
||||
title: pmem(async ({ id }) => {
|
||||
const list = await api().masto.v1.lists.$select(id).fetch();
|
||||
return list.title;
|
||||
},
|
||||
{
|
||||
cacheKey: ([{ id }]) => id,
|
||||
},
|
||||
),
|
||||
}),
|
||||
path: ({ id }) => `/l/${id}`,
|
||||
icon: 'list',
|
||||
},
|
||||
|
@ -167,15 +162,10 @@ export const SHORTCUTS_META = {
|
|||
},
|
||||
'account-statuses': {
|
||||
id: 'account-statuses',
|
||||
title: mem(
|
||||
async ({ id }) => {
|
||||
const account = await api().masto.v1.accounts.fetch(id);
|
||||
title: pmem(async ({ id }) => {
|
||||
const account = await api().masto.v1.accounts.$select(id).fetch();
|
||||
return account.username || account.acct || account.displayName;
|
||||
},
|
||||
{
|
||||
cacheKey: ([{ id }]) => id,
|
||||
},
|
||||
),
|
||||
}),
|
||||
path: ({ id }) => `/a/${id}`,
|
||||
icon: 'user',
|
||||
},
|
||||
|
|
|
@ -337,7 +337,7 @@
|
|||
.status > .container > .meta a.time:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -16px;
|
||||
inset: -16px -16px -8px;
|
||||
}
|
||||
.status > .container > .meta .reply-to {
|
||||
opacity: 0.5;
|
||||
|
@ -1020,13 +1020,38 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
|||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:only-child {
|
||||
white-space: pre-line;
|
||||
overflow: auto;
|
||||
text-overflow: unset;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
sup {
|
||||
opacity: 0.75;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Only 4, for now. Would be better if this is a for loop */
|
||||
&:has(.media[data-has-alt]:nth-child(1):is(:hover, :focus))
|
||||
figcaption
|
||||
> div[data-caption-index~='1'],
|
||||
&:has(.media[data-has-alt]:nth-child(2):is(:hover, :focus))
|
||||
figcaption
|
||||
> div[data-caption-index~='2'],
|
||||
&:has(.media[data-has-alt]:nth-child(3):is(:hover, :focus))
|
||||
figcaption
|
||||
> div[data-caption-index~='3'],
|
||||
&:has(.media[data-has-alt]:nth-child(4):is(:hover, :focus))
|
||||
figcaption
|
||||
> div[data-caption-index~='4'] {
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.carousel-item {
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
MenuItem,
|
||||
} from '@szhsin/react-menu';
|
||||
import { decodeBlurHash } from 'fast-blurhash';
|
||||
import mem from 'mem';
|
||||
import pThrottle from 'p-throttle';
|
||||
import { memo } from 'preact/compat';
|
||||
import {
|
||||
|
@ -42,6 +41,7 @@ import htmlContentLength from '../utils/html-content-length';
|
|||
import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';
|
||||
import localeMatch from '../utils/locale-match';
|
||||
import niceDateTime from '../utils/nice-date-time';
|
||||
import pmem from '../utils/pmem';
|
||||
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
||||
import shortenNumber from '../utils/shorten-number';
|
||||
import showToast from '../utils/show-toast';
|
||||
|
@ -67,13 +67,9 @@ const throttle = pThrottle({
|
|||
});
|
||||
|
||||
function fetchAccount(id, masto) {
|
||||
try {
|
||||
return masto.v1.accounts.fetch(id);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
return masto.v1.accounts.$select(id).fetch();
|
||||
}
|
||||
const memFetchAccount = mem(fetchAccount);
|
||||
const memFetchAccount = pmem(fetchAccount);
|
||||
|
||||
const visibilityText = {
|
||||
public: 'Public',
|
||||
|
@ -390,11 +386,11 @@ function Status({
|
|||
reblogsCount: reblogsCount + (reblogged ? -1 : 1),
|
||||
};
|
||||
if (reblogged) {
|
||||
const newStatus = await masto.v1.statuses.unreblog(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).unreblog();
|
||||
saveStatus(newStatus, instance);
|
||||
return true;
|
||||
} else {
|
||||
const newStatus = await masto.v1.statuses.reblog(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).reblog();
|
||||
saveStatus(newStatus, instance);
|
||||
return true;
|
||||
}
|
||||
|
@ -418,11 +414,11 @@ function Status({
|
|||
reblogsCount: reblogsCount + (reblogged ? -1 : 1),
|
||||
};
|
||||
if (reblogged) {
|
||||
const newStatus = await masto.v1.statuses.unreblog(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).unreblog();
|
||||
saveStatus(newStatus, instance);
|
||||
return true;
|
||||
} else {
|
||||
const newStatus = await masto.v1.statuses.reblog(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).reblog();
|
||||
saveStatus(newStatus, instance);
|
||||
return true;
|
||||
}
|
||||
|
@ -446,10 +442,10 @@ function Status({
|
|||
favouritesCount: favouritesCount + (favourited ? -1 : 1),
|
||||
};
|
||||
if (favourited) {
|
||||
const newStatus = await masto.v1.statuses.unfavourite(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).unfavourite();
|
||||
saveStatus(newStatus, instance);
|
||||
} else {
|
||||
const newStatus = await masto.v1.statuses.favourite(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).favourite();
|
||||
saveStatus(newStatus, instance);
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -470,10 +466,10 @@ function Status({
|
|||
bookmarked: !bookmarked,
|
||||
};
|
||||
if (bookmarked) {
|
||||
const newStatus = await masto.v1.statuses.unbookmark(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).unbookmark();
|
||||
saveStatus(newStatus, instance);
|
||||
} else {
|
||||
const newStatus = await masto.v1.statuses.bookmark(id);
|
||||
const newStatus = await masto.v1.statuses.$select(id).bookmark();
|
||||
saveStatus(newStatus, instance);
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -708,9 +704,9 @@ function Status({
|
|||
<MenuItem
|
||||
onClick={async () => {
|
||||
try {
|
||||
const newStatus = await masto.v1.statuses[
|
||||
muted ? 'unmute' : 'mute'
|
||||
](id);
|
||||
const newStatus = await masto.v1.statuses
|
||||
.$select(id)
|
||||
[muted ? 'unmute' : 'mute']();
|
||||
saveStatus(newStatus, instance);
|
||||
showToast(muted ? 'Conversation unmuted' : 'Conversation muted');
|
||||
} catch (e) {
|
||||
|
@ -763,7 +759,7 @@ function Status({
|
|||
// if (yes) {
|
||||
(async () => {
|
||||
try {
|
||||
await masto.v1.statuses.remove(id);
|
||||
await masto.v1.statuses.$select(id).remove();
|
||||
const cachedStatus = getStatus(id, instance);
|
||||
cachedStatus._deleted = true;
|
||||
showToast('Deleted');
|
||||
|
@ -881,6 +877,62 @@ function Status({
|
|||
displayedMediaAttachments.some(
|
||||
(media) => !!media.description && !isMediaCaptionLong(media.description),
|
||||
);
|
||||
const captionChildren = useMemo(() => {
|
||||
if (!showMultipleMediaCaptions) return null;
|
||||
const attachments = [];
|
||||
displayedMediaAttachments.forEach((media, i) => {
|
||||
if (!media.description) return;
|
||||
const index = attachments.findIndex(
|
||||
(attachment) => attachment.media.description === media.description,
|
||||
);
|
||||
if (index === -1) {
|
||||
attachments.push({
|
||||
media,
|
||||
indices: [i],
|
||||
});
|
||||
} else {
|
||||
attachments[index].indices.push(i);
|
||||
}
|
||||
});
|
||||
return attachments.map(({ media, indices }) => (
|
||||
<div
|
||||
key={media.id}
|
||||
data-caption-index={indices.map((i) => i + 1).join(' ')}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
states.showMediaAlt = {
|
||||
alt: media.description,
|
||||
lang: language,
|
||||
};
|
||||
}}
|
||||
title={media.description}
|
||||
>
|
||||
<sup>{indices.map((i) => i + 1).join(' ')}</sup> {media.description}
|
||||
</div>
|
||||
));
|
||||
|
||||
// return displayedMediaAttachments.map(
|
||||
// (media, i) =>
|
||||
// !!media.description && (
|
||||
// <div
|
||||
// key={media.id}
|
||||
// data-caption-index={i + 1}
|
||||
// onClick={(e) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// states.showMediaAlt = {
|
||||
// alt: media.description,
|
||||
// lang: language,
|
||||
// };
|
||||
// }}
|
||||
// title={media.description}
|
||||
// >
|
||||
// <sup>{i + 1}</sup> {media.description}
|
||||
// </div>
|
||||
// ),
|
||||
// );
|
||||
}, [showMultipleMediaCaptions, displayedMediaAttachments, language]);
|
||||
|
||||
return (
|
||||
<article
|
||||
|
@ -1212,7 +1264,8 @@ function Status({
|
|||
}}
|
||||
refresh={() => {
|
||||
return masto.v1.polls
|
||||
.fetch(poll.id)
|
||||
.$select(poll.id)
|
||||
.fetch()
|
||||
.then((pollResponse) => {
|
||||
states.statuses[sKey].poll = pollResponse;
|
||||
})
|
||||
|
@ -1220,7 +1273,8 @@ function Status({
|
|||
}}
|
||||
votePoll={(choices) => {
|
||||
return masto.v1.polls
|
||||
.vote(poll.id, {
|
||||
.$select(poll.id)
|
||||
.votes.create({
|
||||
choices,
|
||||
})
|
||||
.then((pollResponse) => {
|
||||
|
@ -1279,27 +1333,7 @@ function Status({
|
|||
<MultipleMediaFigure
|
||||
lang={language}
|
||||
enabled={showMultipleMediaCaptions}
|
||||
captionChildren={() => {
|
||||
return displayedMediaAttachments.map(
|
||||
(media, i) =>
|
||||
!!media.description && (
|
||||
<div
|
||||
key={media.id}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
states.showMediaAlt = {
|
||||
alt: media.description,
|
||||
lang: language,
|
||||
};
|
||||
}}
|
||||
title={media.description}
|
||||
>
|
||||
<sup>{i + 1}</sup> {media.description}
|
||||
</div>
|
||||
),
|
||||
);
|
||||
}}
|
||||
captionChildren={captionChildren}
|
||||
>
|
||||
<div
|
||||
ref={mediaContainerRef}
|
||||
|
@ -1498,7 +1532,7 @@ function Status({
|
|||
statusID={showEdited}
|
||||
instance={instance}
|
||||
fetchStatusHistory={() => {
|
||||
return masto.v1.statuses.listHistory(showEdited);
|
||||
return masto.v1.statuses.$select(showEdited).history.list();
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowEdited(false);
|
||||
|
@ -1534,7 +1568,7 @@ function MultipleMediaFigure(props) {
|
|||
<figure class="media-figure-multiple">
|
||||
{children}
|
||||
<figcaption lang={lang} dir="auto">
|
||||
{captionChildren?.()}
|
||||
{captionChildren}
|
||||
</figcaption>
|
||||
</figure>
|
||||
);
|
||||
|
@ -1585,7 +1619,7 @@ function Card({ card, instance }) {
|
|||
// NOTE: This is for quote post
|
||||
// (async () => {
|
||||
// const { masto } = api({ instance });
|
||||
// const status = await masto.v1.statuses.fetch(id);
|
||||
// const status = await masto.v1.statuses.$select(id).fetch();
|
||||
// saveStatus(status, instance);
|
||||
// setCardStatusID(id);
|
||||
// })();
|
||||
|
@ -1805,15 +1839,16 @@ function ReactionsModal({ statusID, instance, onClose }) {
|
|||
(async () => {
|
||||
try {
|
||||
if (firstLoad) {
|
||||
reblogIterator.current = masto.v1.statuses.listRebloggedBy(statusID, {
|
||||
reblogIterator.current = masto.v1.statuses
|
||||
.$select(statusID)
|
||||
.rebloggedBy.list({
|
||||
limit: REACTIONS_LIMIT,
|
||||
});
|
||||
favouriteIterator.current = masto.v1.statuses.listFavouritedBy(
|
||||
statusID,
|
||||
{
|
||||
favouriteIterator.current = masto.v1.statuses
|
||||
.$select(statusID)
|
||||
.favouritedBy.list({
|
||||
limit: REACTIONS_LIMIT,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
const [{ value: reblogResults }, { value: favouriteResults }] =
|
||||
await Promise.allSettled([
|
||||
|
@ -2043,7 +2078,10 @@ function _unfurlMastodonLink(instance, url) {
|
|||
if (statusMatch) {
|
||||
const id = statusMatch[3];
|
||||
const { masto } = api({ instance: domain });
|
||||
remoteInstanceFetch = masto.v1.statuses.fetch(id).then((status) => {
|
||||
remoteInstanceFetch = masto.v1.statuses
|
||||
.$select(id)
|
||||
.fetch()
|
||||
.then((status) => {
|
||||
if (status?.id) {
|
||||
return {
|
||||
status,
|
||||
|
@ -2056,8 +2094,8 @@ function _unfurlMastodonLink(instance, url) {
|
|||
}
|
||||
|
||||
const { masto } = api({ instance });
|
||||
const mastoSearchFetch = masto.v2
|
||||
.search({
|
||||
const mastoSearchFetch = masto.v2.search
|
||||
.fetch({
|
||||
q: url,
|
||||
type: 'statuses',
|
||||
resolve: true,
|
||||
|
@ -2127,11 +2165,7 @@ function nicePostURL(url) {
|
|||
);
|
||||
}
|
||||
|
||||
const unfurlMastodonLink = throttle(
|
||||
mem(_unfurlMastodonLink, {
|
||||
cacheKey: (instance, url) => `${instance}:${url}`,
|
||||
}),
|
||||
);
|
||||
const unfurlMastodonLink = throttle(_unfurlMastodonLink);
|
||||
|
||||
function FilteredStatus({ status, filterInfo, instance, containerProps = {} }) {
|
||||
const {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
|||
import sourceLanguages from '../data/lingva-source-languages';
|
||||
import getTranslateTargetLanguage from '../utils/get-translate-target-language';
|
||||
import localeCode2Text from '../utils/localeCode2Text';
|
||||
import pmem from '../utils/pmem';
|
||||
|
||||
import Icon from './icon';
|
||||
import Loader from './loader';
|
||||
|
@ -25,7 +26,7 @@ const LINGVA_INSTANCES = [
|
|||
];
|
||||
let currentLingvaInstance = 0;
|
||||
|
||||
function lingvaTranslate(text, source, target) {
|
||||
function _lingvaTranslate(text, source, target) {
|
||||
console.log('TRANSLATE', text, source, target);
|
||||
const fetchCall = () => {
|
||||
let instance = LINGVA_INSTANCES[currentLingvaInstance];
|
||||
|
@ -55,11 +56,18 @@ function lingvaTranslate(text, source, target) {
|
|||
);
|
||||
},
|
||||
});
|
||||
// return masto.v1.statuses.translate(id, {
|
||||
// return masto.v1.statuses.$select(id).translate({
|
||||
// lang: DEFAULT_LANG,
|
||||
// });
|
||||
}
|
||||
const throttledLingvaTranslate = throttle(lingvaTranslate);
|
||||
const TRANSLATED_MAX_AGE = 1000 * 60 * 60; // 1 hour
|
||||
const lingvaTranslate = pmem(_lingvaTranslate, {
|
||||
maxAge: TRANSLATED_MAX_AGE,
|
||||
});
|
||||
const throttledLingvaTranslate = pmem(throttle(lingvaTranslate), {
|
||||
// I know, this is double-layered memoization
|
||||
maxAge: TRANSLATED_MAX_AGE,
|
||||
});
|
||||
|
||||
function TranslationBlock({
|
||||
forceTranslate,
|
||||
|
|
|
@ -31,7 +31,8 @@ function AccountStatuses() {
|
|||
const results = [];
|
||||
if (firstLoad) {
|
||||
const { value: pinnedStatuses } = await masto.v1.accounts
|
||||
.listStatuses(id, {
|
||||
.$select(id)
|
||||
.statuses.list({
|
||||
pinned: true,
|
||||
})
|
||||
.next();
|
||||
|
@ -53,7 +54,9 @@ function AccountStatuses() {
|
|||
}
|
||||
}
|
||||
if (firstLoad || !accountStatusesIterator.current) {
|
||||
accountStatusesIterator.current = masto.v1.accounts.listStatuses(id, {
|
||||
accountStatusesIterator.current = masto.v1.accounts
|
||||
.$select(id)
|
||||
.statuses.list({
|
||||
limit: LIMIT,
|
||||
exclude_replies: excludeReplies,
|
||||
exclude_reblogs: excludeBoosts,
|
||||
|
@ -86,14 +89,16 @@ function AccountStatuses() {
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const acc = await masto.v1.accounts.fetch(id);
|
||||
const acc = await masto.v1.accounts.$select(id).fetch();
|
||||
console.log(acc);
|
||||
setAccount(acc);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
try {
|
||||
const featuredTags = await masto.v1.accounts.listFeaturedTags(id);
|
||||
const featuredTags = await masto.v1.accounts
|
||||
.$select(id)
|
||||
.featuredTags.list(id);
|
||||
console.log({ featuredTags });
|
||||
setFeaturedTags(featuredTags);
|
||||
} catch (e) {
|
||||
|
@ -113,7 +118,7 @@ function AccountStatuses() {
|
|||
<AccountInfo
|
||||
instance={instance}
|
||||
account={cachedAccount || id}
|
||||
fetchAccount={() => masto.v1.accounts.fetch(id)}
|
||||
fetchAccount={() => masto.v1.accounts.$select(id).fetch()}
|
||||
authenticated={authenticated}
|
||||
standalone
|
||||
/>
|
||||
|
|
|
@ -52,9 +52,9 @@ function Accounts({ onClose }) {
|
|||
onDblClick={async () => {
|
||||
if (isCurrent) {
|
||||
try {
|
||||
const info = await masto.v1.accounts.fetch(
|
||||
account.info.id,
|
||||
);
|
||||
const info = await masto.v1.accounts
|
||||
.$select(account.info.id)
|
||||
.fetch();
|
||||
console.log('fetched account info', info);
|
||||
account.info = info;
|
||||
store.local.setJSON('accounts', accounts);
|
||||
|
|
|
@ -13,7 +13,7 @@ const LIMIT = 20;
|
|||
|
||||
function Following({ title, path, id, ...props }) {
|
||||
useTitle(title || 'Following', path || '/following');
|
||||
const { masto, instance } = api();
|
||||
const { masto, streaming, instance } = api();
|
||||
const snapStates = useSnapshot(states);
|
||||
const homeIterator = useRef();
|
||||
const latestItem = useRef();
|
||||
|
@ -22,7 +22,7 @@ function Following({ title, path, id, ...props }) {
|
|||
|
||||
async function fetchHome(firstLoad) {
|
||||
if (firstLoad || !homeIterator.current) {
|
||||
homeIterator.current = masto.v1.timelines.listHome({ limit: LIMIT });
|
||||
homeIterator.current = masto.v1.timelines.home.list({ limit: LIMIT });
|
||||
}
|
||||
const results = await homeIterator.current.next();
|
||||
let { value } = results;
|
||||
|
@ -53,8 +53,8 @@ function Following({ title, path, id, ...props }) {
|
|||
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const results = await masto.v1.timelines
|
||||
.listHome({
|
||||
const results = await masto.v1.timelines.home
|
||||
.list({
|
||||
limit: 5,
|
||||
since_id: latestItem.current,
|
||||
})
|
||||
|
@ -75,52 +75,33 @@ function Following({ title, path, id, ...props }) {
|
|||
}
|
||||
}
|
||||
|
||||
const ws = useRef();
|
||||
const streamUser = async () => {
|
||||
console.log('🎏 Start streaming user', ws.current);
|
||||
if (
|
||||
ws.current &&
|
||||
(ws.current.readyState === WebSocket.CONNECTING ||
|
||||
ws.current.readyState === WebSocket.OPEN)
|
||||
) {
|
||||
console.log('🎏 Streaming user already open');
|
||||
return;
|
||||
}
|
||||
const stream = await masto.v1.stream.streamUser();
|
||||
ws.current = stream.ws;
|
||||
ws.current.__id = Math.random();
|
||||
console.log('🎏 Streaming user', ws.current);
|
||||
|
||||
stream.on('status.update', (status) => {
|
||||
useEffect(() => {
|
||||
let sub;
|
||||
(async () => {
|
||||
if (streaming) {
|
||||
sub = streaming.user.subscribe();
|
||||
console.log('🎏 Streaming user', sub);
|
||||
for await (const entry of sub) {
|
||||
if (!sub) break;
|
||||
if (entry.event === 'status.update') {
|
||||
const status = entry.payload;
|
||||
console.log(`🔄 Status ${status.id} updated`);
|
||||
saveStatus(status, instance);
|
||||
});
|
||||
|
||||
stream.on('delete', (statusID) => {
|
||||
} else if (entry.event === 'delete') {
|
||||
const statusID = entry.payload;
|
||||
console.log(`❌ Status ${statusID} deleted`);
|
||||
// delete states.statuses[statusID];
|
||||
const s = getStatus(statusID, instance);
|
||||
if (s) s._deleted = true;
|
||||
});
|
||||
|
||||
stream.ws.onclose = () => {
|
||||
console.log('🎏 Streaming user closed');
|
||||
};
|
||||
|
||||
return stream;
|
||||
};
|
||||
useEffect(() => {
|
||||
let stream;
|
||||
(async () => {
|
||||
stream = await streamUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
if (stream) {
|
||||
stream.ws.close();
|
||||
ws.current = null;
|
||||
}
|
||||
sub?.unsubscribe?.();
|
||||
sub = null;
|
||||
};
|
||||
}, []);
|
||||
}, [streaming]);
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
|
|
|
@ -46,7 +46,7 @@ function Hashtags({ columnMode, ...props }) {
|
|||
const maxID = useRef(undefined);
|
||||
async function fetchHashtags(firstLoad) {
|
||||
// if (firstLoad || !hashtagsIterator.current) {
|
||||
// hashtagsIterator.current = masto.v1.timelines.listHashtag(hashtag, {
|
||||
// hashtagsIterator.current = masto.v1.timelines.tag.$select(hashtag).list({
|
||||
// limit: LIMIT,
|
||||
// any: hashtags.slice(1),
|
||||
// });
|
||||
|
@ -54,8 +54,9 @@ function Hashtags({ columnMode, ...props }) {
|
|||
// const results = await hashtagsIterator.current.next();
|
||||
|
||||
// NOTE: Temporary fix for listHashtag not persisting `any` in subsequent calls.
|
||||
const results = await masto.v1.timelines
|
||||
.listHashtag(hashtag, {
|
||||
const results = await masto.v1.timelines.tag
|
||||
.$select(hashtag)
|
||||
.list({
|
||||
limit: LIMIT,
|
||||
any: hashtags.slice(1),
|
||||
maxId: firstLoad ? undefined : maxID.current,
|
||||
|
@ -81,8 +82,9 @@ function Hashtags({ columnMode, ...props }) {
|
|||
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const results = await masto.v1.timelines
|
||||
.listHashtag(hashtag, {
|
||||
const results = await masto.v1.timelines.tag
|
||||
.$select(hashtag)
|
||||
.list({
|
||||
limit: 1,
|
||||
any: hashtags.slice(1),
|
||||
since_id: latestItem.current,
|
||||
|
@ -104,7 +106,7 @@ function Hashtags({ columnMode, ...props }) {
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const info = await masto.v1.tags.fetch(hashtag);
|
||||
const info = await masto.v1.tags.$select(hashtag).fetch();
|
||||
console.log(info);
|
||||
setInfo(info);
|
||||
} catch (e) {
|
||||
|
@ -163,7 +165,8 @@ function Hashtags({ columnMode, ...props }) {
|
|||
// return;
|
||||
// }
|
||||
masto.v1.tags
|
||||
.unfollow(hashtag)
|
||||
.$select(hashtag)
|
||||
.unfollow()
|
||||
.then(() => {
|
||||
setInfo({ ...info, following: false });
|
||||
showToast(`Unfollowed #${hashtag}`);
|
||||
|
@ -177,7 +180,8 @@ function Hashtags({ columnMode, ...props }) {
|
|||
});
|
||||
} else {
|
||||
masto.v1.tags
|
||||
.follow(hashtag)
|
||||
.$select(hashtag)
|
||||
.follow()
|
||||
.then(() => {
|
||||
setInfo({ ...info, following: true });
|
||||
showToast(`Followed #${hashtag}`);
|
||||
|
|
|
@ -32,7 +32,7 @@ function List(props) {
|
|||
const listIterator = useRef();
|
||||
async function fetchList(firstLoad) {
|
||||
if (firstLoad || !listIterator.current) {
|
||||
listIterator.current = masto.v1.timelines.listList(id, {
|
||||
listIterator.current = masto.v1.timelines.list.$select(id).list({
|
||||
limit: LIMIT,
|
||||
});
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ function List(props) {
|
|||
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const results = await masto.v1.timelines.listList(id, {
|
||||
const results = await masto.v1.timelines.list.$select(id).list({
|
||||
limit: 1,
|
||||
since_id: latestItem.current,
|
||||
});
|
||||
|
@ -77,7 +77,7 @@ function List(props) {
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const list = await masto.v1.lists.fetch(id);
|
||||
const list = await masto.v1.lists.$select(id).fetch();
|
||||
setList(list);
|
||||
// setTitle(list.title);
|
||||
} catch (e) {
|
||||
|
@ -200,7 +200,9 @@ function ListManageMembers({ listID, onClose }) {
|
|||
(async () => {
|
||||
try {
|
||||
if (firstLoad || !membersIterator.current) {
|
||||
membersIterator.current = masto.v1.lists.listAccounts(listID, {
|
||||
membersIterator.current = masto.v1.lists
|
||||
.$select(listID)
|
||||
.accounts.list({
|
||||
limit: MEMBERS_LIMIT,
|
||||
});
|
||||
}
|
||||
|
@ -274,7 +276,7 @@ function RemoveAddButton({ account, listID }) {
|
|||
setUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
await masto.v1.lists.addAccount(listID, {
|
||||
await masto.v1.lists.$select(listID).accounts.create({
|
||||
accountIds: [account.id],
|
||||
});
|
||||
setUIState('default');
|
||||
|
@ -290,7 +292,7 @@ function RemoveAddButton({ account, listID }) {
|
|||
|
||||
(async () => {
|
||||
try {
|
||||
await masto.v1.lists.removeAccount(listID, {
|
||||
await masto.v1.lists.$select(listID).accounts.remove({
|
||||
accountIds: [account.id],
|
||||
});
|
||||
setUIState('default');
|
||||
|
|
|
@ -29,7 +29,7 @@ function Public({ local, columnMode, ...props }) {
|
|||
const publicIterator = useRef();
|
||||
async function fetchPublic(firstLoad) {
|
||||
if (firstLoad || !publicIterator.current) {
|
||||
publicIterator.current = masto.v1.timelines.listPublic({
|
||||
publicIterator.current = masto.v1.timelines.public.list({
|
||||
limit: LIMIT,
|
||||
local: isLocal,
|
||||
});
|
||||
|
@ -54,8 +54,8 @@ function Public({ local, columnMode, ...props }) {
|
|||
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const results = await masto.v1.timelines
|
||||
.listPublic({
|
||||
const results = await masto.v1.timelines.public
|
||||
.list({
|
||||
limit: 1,
|
||||
local: isLocal,
|
||||
since_id: latestItem.current,
|
||||
|
|
|
@ -90,7 +90,7 @@ function Search(props) {
|
|||
if (authenticated) params.offset = offsetRef.current;
|
||||
}
|
||||
try {
|
||||
const results = await masto.v2.search(params);
|
||||
const results = await masto.v2.search.fetch(params);
|
||||
console.log(results);
|
||||
if (type) {
|
||||
if (firstLoad) {
|
||||
|
|
|
@ -100,7 +100,7 @@ function StatusPage(params) {
|
|||
if (!heroStatus && showMedia) {
|
||||
(async () => {
|
||||
try {
|
||||
const status = await masto.v1.statuses.fetch(id);
|
||||
const status = await masto.v1.statuses.$select(id).fetch();
|
||||
saveStatus(status, instance);
|
||||
setHeroStatus(status);
|
||||
} catch (err) {
|
||||
|
@ -235,12 +235,15 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
|
||||
(async () => {
|
||||
const heroFetch = () =>
|
||||
pRetry(() => masto.v1.statuses.fetch(id), {
|
||||
pRetry(() => masto.v1.statuses.$select(id).fetch(), {
|
||||
retries: 4,
|
||||
});
|
||||
const contextFetch = pRetry(() => masto.v1.statuses.fetchContext(id), {
|
||||
const contextFetch = pRetry(
|
||||
() => masto.v1.statuses.$select(id).context.fetch(),
|
||||
{
|
||||
retries: 8,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const hasStatus = !!snapStates.statuses[sKey];
|
||||
let heroStatus = snapStates.statuses[sKey];
|
||||
|
@ -647,6 +650,189 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
resetScrollPosition(status.id);
|
||||
}, []);
|
||||
|
||||
const renderStatus = (status) => {
|
||||
const {
|
||||
id: statusID,
|
||||
ancestor,
|
||||
isThread,
|
||||
descendant,
|
||||
thread,
|
||||
replies,
|
||||
repliesCount,
|
||||
weight,
|
||||
} = status;
|
||||
const isHero = statusID === id;
|
||||
// const StatusParent = useCallback(
|
||||
// (props) =>
|
||||
// isThread || thread || ancestor ? (
|
||||
// <Link
|
||||
// class="status-link"
|
||||
// to={
|
||||
// instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`
|
||||
// }
|
||||
// onClick={() => {
|
||||
// resetScrollPosition(statusID);
|
||||
// }}
|
||||
// {...props}
|
||||
// />
|
||||
// ) : (
|
||||
// <div class="status-focus" tabIndex={0} {...props} />
|
||||
// ),
|
||||
// [isThread, thread],
|
||||
// );
|
||||
return (
|
||||
<li
|
||||
key={statusID}
|
||||
ref={isHero ? heroStatusRef : null}
|
||||
class={`${ancestor ? 'ancestor' : ''} ${
|
||||
descendant ? 'descendant' : ''
|
||||
} ${thread ? 'thread' : ''} ${isHero ? 'hero' : ''}`}
|
||||
>
|
||||
{isHero ? (
|
||||
<>
|
||||
<InView
|
||||
threshold={0.1}
|
||||
onChange={onView}
|
||||
class="status-focus"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Status
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
withinContext
|
||||
size="l"
|
||||
enableTranslate
|
||||
forceTranslate={translate}
|
||||
/>
|
||||
</InView>
|
||||
{uiState !== 'loading' && !authenticated ? (
|
||||
<div class="post-status-banner">
|
||||
<p>
|
||||
You're not logged in. Interactions (reply, boost, etc) are not
|
||||
possible.
|
||||
</p>
|
||||
<Link to="/login" class="button">
|
||||
Log in
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
!sameInstance && (
|
||||
<div class="post-status-banner">
|
||||
<p>
|
||||
This post is from another instance (<b>{instance}</b>).
|
||||
Interactions (reply, boost, etc) are not possible.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={uiState === 'loading'}
|
||||
onClick={() => {
|
||||
setUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
const results = await currentMasto.v2.search.fetch({
|
||||
q: heroStatus.url,
|
||||
type: 'statuses',
|
||||
resolve: true,
|
||||
limit: 1,
|
||||
});
|
||||
if (results.statuses.length) {
|
||||
const status = results.statuses[0];
|
||||
location.hash = currentInstance
|
||||
? `/${currentInstance}/s/${status.id}`
|
||||
: `/s/${status.id}`;
|
||||
} else {
|
||||
throw new Error('No results');
|
||||
}
|
||||
} catch (e) {
|
||||
setUIState('default');
|
||||
alert('Error: ' + e);
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
>
|
||||
<Icon icon="transfer" /> Switch to my instance to enable
|
||||
interactions
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// <StatusParent>
|
||||
<Link
|
||||
class="status-link"
|
||||
to={instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`}
|
||||
onClick={() => {
|
||||
resetScrollPosition(statusID);
|
||||
}}
|
||||
>
|
||||
<Status
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
withinContext
|
||||
size={thread || ancestor ? 'm' : 's'}
|
||||
enableTranslate
|
||||
onMediaClick={handleMediaClick}
|
||||
onStatusLinkClick={handleStatusLinkClick}
|
||||
/>
|
||||
{ancestor && isThread && repliesCount > 1 && (
|
||||
<div class="replies-link">
|
||||
<Icon icon="comment" />{' '}
|
||||
<span title={repliesCount}>{shortenNumber(repliesCount)}</span>
|
||||
</div>
|
||||
)}{' '}
|
||||
{/* {replies?.length > LIMIT && (
|
||||
<div class="replies-link">
|
||||
<Icon icon="comment" />{' '}
|
||||
<span title={replies.length}>
|
||||
{shortenNumber(replies.length)}
|
||||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
{/* </StatusParent> */}
|
||||
</Link>
|
||||
)}
|
||||
{descendant && replies?.length > 0 && (
|
||||
<SubComments
|
||||
instance={instance}
|
||||
replies={replies}
|
||||
hasParentThread={thread}
|
||||
level={1}
|
||||
accWeight={weight}
|
||||
openAll={totalDescendants.current < SUBCOMMENTS_OPEN_ALL_LIMIT}
|
||||
/>
|
||||
)}
|
||||
{uiState === 'loading' &&
|
||||
isHero &&
|
||||
!!heroStatus?.repliesCount &&
|
||||
!hasDescendants && (
|
||||
<div class="status-loading">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{uiState === 'error' &&
|
||||
isHero &&
|
||||
!!heroStatus?.repliesCount &&
|
||||
!hasDescendants && (
|
||||
<div class="status-error">
|
||||
Unable to load replies.
|
||||
<br />
|
||||
<button
|
||||
type="button"
|
||||
class="plain"
|
||||
onClick={() => {
|
||||
states.reloadStatusPage++;
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex="-1"
|
||||
|
@ -866,195 +1052,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
uiState === 'loading' ? 'loading' : ''
|
||||
}`}
|
||||
>
|
||||
{statuses.slice(0, limit).map((status) => {
|
||||
const {
|
||||
id: statusID,
|
||||
ancestor,
|
||||
isThread,
|
||||
descendant,
|
||||
thread,
|
||||
replies,
|
||||
repliesCount,
|
||||
weight,
|
||||
} = status;
|
||||
const isHero = statusID === id;
|
||||
// const StatusParent = useCallback(
|
||||
// (props) =>
|
||||
// isThread || thread || ancestor ? (
|
||||
// <Link
|
||||
// class="status-link"
|
||||
// to={
|
||||
// instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`
|
||||
// }
|
||||
// onClick={() => {
|
||||
// resetScrollPosition(statusID);
|
||||
// }}
|
||||
// {...props}
|
||||
// />
|
||||
// ) : (
|
||||
// <div class="status-focus" tabIndex={0} {...props} />
|
||||
// ),
|
||||
// [isThread, thread],
|
||||
// );
|
||||
return (
|
||||
<li
|
||||
key={statusID}
|
||||
ref={isHero ? heroStatusRef : null}
|
||||
class={`${ancestor ? 'ancestor' : ''} ${
|
||||
descendant ? 'descendant' : ''
|
||||
} ${thread ? 'thread' : ''} ${isHero ? 'hero' : ''}`}
|
||||
>
|
||||
{isHero ? (
|
||||
<>
|
||||
<InView
|
||||
threshold={0.1}
|
||||
onChange={onView}
|
||||
class="status-focus"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Status
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
withinContext
|
||||
size="l"
|
||||
enableTranslate
|
||||
forceTranslate={translate}
|
||||
/>
|
||||
</InView>
|
||||
{uiState !== 'loading' && !authenticated ? (
|
||||
<div class="post-status-banner">
|
||||
<p>
|
||||
You're not logged in. Interactions (reply, boost, etc)
|
||||
are not possible.
|
||||
</p>
|
||||
<Link to="/login" class="button">
|
||||
Log in
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
!sameInstance && (
|
||||
<div class="post-status-banner">
|
||||
<p>
|
||||
This post is from another instance (
|
||||
<b>{instance}</b>). Interactions (reply, boost, etc)
|
||||
are not possible.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={uiState === 'loading'}
|
||||
onClick={() => {
|
||||
setUIState('loading');
|
||||
(async () => {
|
||||
try {
|
||||
const results = await currentMasto.v2.search({
|
||||
q: heroStatus.url,
|
||||
type: 'statuses',
|
||||
resolve: true,
|
||||
limit: 1,
|
||||
});
|
||||
if (results.statuses.length) {
|
||||
const status = results.statuses[0];
|
||||
location.hash = currentInstance
|
||||
? `/${currentInstance}/s/${status.id}`
|
||||
: `/s/${status.id}`;
|
||||
} else {
|
||||
throw new Error('No results');
|
||||
}
|
||||
} catch (e) {
|
||||
setUIState('default');
|
||||
alert('Error: ' + e);
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
>
|
||||
<Icon icon="transfer" /> Switch to my instance to
|
||||
enable interactions
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// <StatusParent>
|
||||
<Link
|
||||
class="status-link"
|
||||
to={
|
||||
instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`
|
||||
}
|
||||
onClick={() => {
|
||||
resetScrollPosition(statusID);
|
||||
}}
|
||||
>
|
||||
<Status
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
withinContext
|
||||
size={thread || ancestor ? 'm' : 's'}
|
||||
enableTranslate
|
||||
onMediaClick={handleMediaClick}
|
||||
onStatusLinkClick={handleStatusLinkClick}
|
||||
/>
|
||||
{ancestor && isThread && repliesCount > 1 && (
|
||||
<div class="replies-link">
|
||||
<Icon icon="comment" />{' '}
|
||||
<span title={repliesCount}>
|
||||
{shortenNumber(repliesCount)}
|
||||
</span>
|
||||
</div>
|
||||
)}{' '}
|
||||
{/* {replies?.length > LIMIT && (
|
||||
<div class="replies-link">
|
||||
<Icon icon="comment" />{' '}
|
||||
<span title={replies.length}>
|
||||
{shortenNumber(replies.length)}
|
||||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
{/* </StatusParent> */}
|
||||
</Link>
|
||||
)}
|
||||
{descendant && replies?.length > 0 && (
|
||||
<SubComments
|
||||
instance={instance}
|
||||
replies={replies}
|
||||
hasParentThread={thread}
|
||||
level={1}
|
||||
accWeight={weight}
|
||||
openAll={
|
||||
totalDescendants.current < SUBCOMMENTS_OPEN_ALL_LIMIT
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{uiState === 'loading' &&
|
||||
isHero &&
|
||||
!!heroStatus?.repliesCount &&
|
||||
!hasDescendants && (
|
||||
<div class="status-loading">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{uiState === 'error' &&
|
||||
isHero &&
|
||||
!!heroStatus?.repliesCount &&
|
||||
!hasDescendants && (
|
||||
<div class="status-error">
|
||||
Unable to load replies.
|
||||
<br />
|
||||
<button
|
||||
type="button"
|
||||
class="plain"
|
||||
onClick={() => {
|
||||
states.reloadStatusPage++;
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{statuses.slice(0, limit).map(renderStatus)}
|
||||
{showMore > 0 && (
|
||||
<li>
|
||||
<button
|
||||
|
|
|
@ -30,13 +30,13 @@ function Trending({ columnMode, ...props }) {
|
|||
const trendIterator = useRef();
|
||||
async function fetchTrend(firstLoad) {
|
||||
if (firstLoad || !trendIterator.current) {
|
||||
trendIterator.current = masto.v1.trends.listStatuses({
|
||||
trendIterator.current = masto.v1.trends.statuses.list({
|
||||
limit: LIMIT,
|
||||
});
|
||||
|
||||
// Get hashtags
|
||||
try {
|
||||
const iterator = masto.v1.trends.listTags();
|
||||
const iterator = masto.v1.trends.tags.list();
|
||||
const { value: tags } = await iterator.next();
|
||||
console.log(tags);
|
||||
setHashtags(tags);
|
||||
|
@ -64,8 +64,8 @@ function Trending({ columnMode, ...props }) {
|
|||
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const results = await masto.v1.trends
|
||||
.listStatuses({
|
||||
const results = await masto.v1.trends.statuses
|
||||
.list({
|
||||
limit: 1,
|
||||
// NOT SUPPORTED
|
||||
// since_id: latestItem.current,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createClient } from 'masto';
|
||||
import { createRestAPIClient, createStreamingAPIClient } from 'masto';
|
||||
|
||||
import store from './store';
|
||||
import {
|
||||
|
@ -37,14 +37,17 @@ export function initClient({ instance, accessToken }) {
|
|||
}
|
||||
const url = instance ? `https://${instance}` : `https://${DEFAULT_INSTANCE}`;
|
||||
|
||||
const client = createClient({
|
||||
const masto = createRestAPIClient({
|
||||
url,
|
||||
accessToken, // Can be null
|
||||
disableVersionCheck: true, // Allow non-Mastodon instances
|
||||
timeout: 30_000, // Unfortunatly this is global instead of per-request
|
||||
});
|
||||
client.__instance__ = instance;
|
||||
|
||||
const client = {
|
||||
masto,
|
||||
instance,
|
||||
accessToken,
|
||||
};
|
||||
apis[instance] = client;
|
||||
if (!accountApis[instance]) accountApis[instance] = {};
|
||||
if (accessToken) accountApis[instance][accessToken] = client;
|
||||
|
@ -55,7 +58,8 @@ export function initClient({ instance, accessToken }) {
|
|||
// Get the instance information
|
||||
// The config is needed for composing
|
||||
export async function initInstance(client, instance) {
|
||||
const masto = client;
|
||||
console.log('INIT INSTANCE', client, instance);
|
||||
const { masto, accessToken } = client;
|
||||
// Request v2, fallback to v1 if fail
|
||||
let info;
|
||||
try {
|
||||
|
@ -63,7 +67,7 @@ export async function initInstance(client, instance) {
|
|||
} catch (e) {}
|
||||
if (!info) {
|
||||
try {
|
||||
info = await masto.v1.instances.fetch();
|
||||
info = await masto.v1.instance.fetch();
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!info) return;
|
||||
|
@ -91,17 +95,28 @@ export async function initInstance(client, instance) {
|
|||
store.local.setJSON('instances', instances);
|
||||
// This is a weird place to put this but here's updating the masto instance with the streaming API URL set in the configuration
|
||||
// Reason: Streaming WebSocket URL may change, unlike the standard API REST URLs
|
||||
if (streamingApi || streaming) {
|
||||
const supportsWebSocket = 'WebSocket' in window;
|
||||
if (supportsWebSocket && (streamingApi || streaming)) {
|
||||
console.log('🎏 Streaming API URL:', streaming || streamingApi);
|
||||
masto.config.props.streamingApiUrl = streaming || streamingApi;
|
||||
// masto.config.props.streamingApiUrl = streaming || streamingApi;
|
||||
// Legacy masto.ws
|
||||
const streamClient = createStreamingAPIClient({
|
||||
streamingApiUrl: streaming || streamingApi,
|
||||
accessToken,
|
||||
implementation: WebSocket,
|
||||
});
|
||||
client.streaming = streamClient;
|
||||
// masto.ws = streamClient;
|
||||
console.log('🎏 Streaming API client:', client);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the account information and store it
|
||||
export async function initAccount(client, instance, accessToken, vapidKey) {
|
||||
const masto = client;
|
||||
const { masto } = client;
|
||||
const mastoAccount = await masto.v1.accounts.verifyCredentials();
|
||||
|
||||
console.log('CURRENTACCOUNT SET', mastoAccount.id);
|
||||
store.session.set('currentAccount', mastoAccount.id);
|
||||
|
||||
saveAccount({
|
||||
|
@ -115,7 +130,7 @@ export async function initAccount(client, instance, accessToken, vapidKey) {
|
|||
// Get preferences
|
||||
export async function initPreferences(client) {
|
||||
try {
|
||||
const masto = client;
|
||||
const { masto } = client;
|
||||
const preferences = await masto.v1.preferences.fetch();
|
||||
store.account.set('preferences', preferences);
|
||||
} catch (e) {
|
||||
|
@ -134,10 +149,14 @@ export function api({ instance, accessToken, accountID, account } = {}) {
|
|||
|
||||
// If instance and accessToken are provided, get the masto instance for that account
|
||||
if (instance && accessToken) {
|
||||
return {
|
||||
masto:
|
||||
const client =
|
||||
accountApis[instance]?.[accessToken] ||
|
||||
initClient({ instance, accessToken }),
|
||||
initClient({ instance, accessToken });
|
||||
const { masto, streaming } = client;
|
||||
return {
|
||||
masto,
|
||||
streaming,
|
||||
client,
|
||||
authenticated: true,
|
||||
instance,
|
||||
};
|
||||
|
@ -149,8 +168,12 @@ export function api({ instance, accessToken, accountID, account } = {}) {
|
|||
for (const instance in accountApis) {
|
||||
if (accountApis[instance][accessToken]) {
|
||||
console.log('X 2', accountApis, instance, accessToken);
|
||||
const client = accountApis[instance][accessToken];
|
||||
const { masto, streaming } = client;
|
||||
return {
|
||||
masto: accountApis[instance][accessToken],
|
||||
masto,
|
||||
streaming,
|
||||
client,
|
||||
authenticated: true,
|
||||
instance,
|
||||
};
|
||||
|
@ -160,8 +183,12 @@ export function api({ instance, accessToken, accountID, account } = {}) {
|
|||
if (account) {
|
||||
const accessToken = account.accessToken;
|
||||
const instance = account.instanceURL.toLowerCase().trim();
|
||||
const client = initClient({ instance, accessToken });
|
||||
const { masto, streaming } = client;
|
||||
return {
|
||||
masto: initClient({ instance, accessToken }),
|
||||
masto,
|
||||
streaming,
|
||||
client,
|
||||
authenticated: true,
|
||||
instance,
|
||||
};
|
||||
|
@ -178,10 +205,14 @@ export function api({ instance, accessToken, accountID, account } = {}) {
|
|||
if (account) {
|
||||
const accessToken = account.accessToken;
|
||||
const instance = account.instanceURL.toLowerCase().trim();
|
||||
return {
|
||||
masto:
|
||||
const client =
|
||||
accountApis[instance]?.[accessToken] ||
|
||||
initClient({ instance, accessToken }),
|
||||
initClient({ instance, accessToken });
|
||||
const { masto, streaming } = client;
|
||||
return {
|
||||
masto,
|
||||
streaming,
|
||||
client,
|
||||
authenticated: true,
|
||||
instance,
|
||||
};
|
||||
|
@ -192,10 +223,13 @@ export function api({ instance, accessToken, accountID, account } = {}) {
|
|||
|
||||
// If only instance is provided, get the masto instance for that instance
|
||||
if (instance) {
|
||||
const masto = apis[instance] || initClient({ instance });
|
||||
const client = apis[instance] || initClient({ instance });
|
||||
const { masto, streaming, accessToken } = client;
|
||||
return {
|
||||
masto,
|
||||
authenticated: !!masto.config.props.accessToken,
|
||||
streaming,
|
||||
client,
|
||||
authenticated: !!accessToken,
|
||||
instance,
|
||||
};
|
||||
}
|
||||
|
@ -203,9 +237,11 @@ export function api({ instance, accessToken, accountID, account } = {}) {
|
|||
// If no instance is provided, get the masto instance for the current account
|
||||
if (currentAccountApi) {
|
||||
return {
|
||||
masto: currentAccountApi,
|
||||
masto: currentAccountApi.masto,
|
||||
streaming: currentAccountApi.streaming,
|
||||
client: currentAccountApi,
|
||||
authenticated: true,
|
||||
instance: currentAccountApi.__instance__,
|
||||
instance: currentAccountApi.instance,
|
||||
};
|
||||
}
|
||||
const currentAccount = getCurrentAccount();
|
||||
|
@ -215,15 +251,22 @@ export function api({ instance, accessToken, accountID, account } = {}) {
|
|||
accountApis[instance]?.[accessToken] ||
|
||||
initClient({ instance, accessToken });
|
||||
return {
|
||||
masto: currentAccountApi,
|
||||
masto: currentAccountApi.masto,
|
||||
streaming: currentAccountApi.streaming,
|
||||
client: currentAccountApi,
|
||||
authenticated: true,
|
||||
instance,
|
||||
};
|
||||
}
|
||||
|
||||
// If no instance is provided and no account is logged in, get the masto instance for DEFAULT_INSTANCE
|
||||
const client =
|
||||
apis[DEFAULT_INSTANCE] || initClient({ instance: DEFAULT_INSTANCE });
|
||||
const { masto, streaming } = client;
|
||||
return {
|
||||
masto: apis[DEFAULT_INSTANCE] || initClient({ instance: DEFAULT_INSTANCE }),
|
||||
masto,
|
||||
streaming,
|
||||
client,
|
||||
authenticated: false,
|
||||
instance: DEFAULT_INSTANCE,
|
||||
};
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import emojifyText from './emojify-text';
|
||||
import mem from './mem';
|
||||
|
||||
const fauxDiv = document.createElement('div');
|
||||
const whitelistLinkClasses = ['u-url', 'mention', 'hashtag'];
|
||||
|
||||
function enhanceContent(content, opts = {}) {
|
||||
function _enhanceContent(content, opts = {}) {
|
||||
const { emojis, postEnhanceDOM = () => {} } = opts;
|
||||
let enhancedContent = content;
|
||||
const dom = document.createElement('div');
|
||||
|
@ -250,6 +251,7 @@ function enhanceContent(content, opts = {}) {
|
|||
|
||||
return enhancedContent;
|
||||
}
|
||||
const enhanceContent = mem(_enhanceContent);
|
||||
|
||||
const defaultRejectFilter = [
|
||||
// Document metadata
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { match } from '@formatjs/intl-localematcher';
|
||||
import mem from 'mem';
|
||||
|
||||
import mem from './mem';
|
||||
|
||||
function _localeMatch(...args) {
|
||||
// Wrap in try/catch because localeMatcher throws on invalid locales
|
||||
|
@ -10,8 +11,6 @@ function _localeMatch(...args) {
|
|||
return defaultLocale || false;
|
||||
}
|
||||
}
|
||||
const localeMatch = mem(_localeMatch, {
|
||||
cacheKey: (args) => args.join(),
|
||||
});
|
||||
const localeMatch = mem(_localeMatch);
|
||||
|
||||
export default localeMatch;
|
||||
|
|
5
src/utils/mem.js
Normal file
5
src/utils/mem.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import moize from 'moize';
|
||||
|
||||
export default function mem(fn, opts = {}) {
|
||||
return moize(fn, { ...opts, maxSize: 100 });
|
||||
}
|
5
src/utils/pmem.js
Normal file
5
src/utils/pmem.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import mem from './mem';
|
||||
|
||||
export default function pmem(fn, opts = {}) {
|
||||
return mem(fn, { isPromise: true, ...opts });
|
||||
}
|
|
@ -34,22 +34,22 @@ import { getCurrentAccount } from './store-utils';
|
|||
|
||||
function createBackendPushSubscription(subscription) {
|
||||
const { masto } = api();
|
||||
return masto.v1.webPushSubscriptions.create(subscription);
|
||||
return masto.v1.push.subscription.create(subscription);
|
||||
}
|
||||
|
||||
function fetchBackendPushSubscription() {
|
||||
const { masto } = api();
|
||||
return masto.v1.webPushSubscriptions.fetch();
|
||||
return masto.v1.push.subscription.fetch();
|
||||
}
|
||||
|
||||
function updateBackendPushSubscription(subscription) {
|
||||
const { masto } = api();
|
||||
return masto.v1.webPushSubscriptions.update(subscription);
|
||||
return masto.v1.push.subscription.update(subscription);
|
||||
}
|
||||
|
||||
function removeBackendPushSubscription() {
|
||||
const { masto } = api();
|
||||
return masto.v1.webPushSubscriptions.remove();
|
||||
return masto.v1.push.subscription.remove();
|
||||
}
|
||||
|
||||
// Front-end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import mem from 'mem';
|
||||
import { proxy, subscribe } from 'valtio';
|
||||
import { subscribeKey } from 'valtio/utils';
|
||||
|
||||
import { api } from './api';
|
||||
import pmem from './pmem';
|
||||
import store from './store';
|
||||
|
||||
const states = proxy({
|
||||
|
@ -207,7 +207,7 @@ export function threadifyStatus(status, propInstance) {
|
|||
if (!prevStatus) {
|
||||
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);
|
||||
// prevStatus = await masto.v1.statuses.$.select(inReplyToId).fetch();
|
||||
prevStatus = await fetchStatus(inReplyToId, masto);
|
||||
saveStatus(prevStatus, instance, { skipThreading: true });
|
||||
}
|
||||
|
@ -229,6 +229,6 @@ export function threadifyStatus(status, propInstance) {
|
|||
});
|
||||
}
|
||||
|
||||
const fetchStatus = mem((statusID, masto) => {
|
||||
return masto.v1.statuses.fetch(statusID);
|
||||
const fetchStatus = pmem((statusID, masto) => {
|
||||
return masto.v1.statuses.$select(statusID).fetch();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue