From 906ae955fb694588d3c948e8c774937e83933098 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 5 Jun 2026 10:34:39 +0200 Subject: [PATCH] Prefetch collection accounts for statuses and notifications (#39291) --- .../mastodon/actions/importer/index.js | 7 ++++++ .../mastodon/actions/notification_groups.ts | 11 +++++++++ app/javascript/mastodon/actions/search.ts | 6 ++--- .../mastodon/reducers/slices/collections.ts | 24 ++++++++++--------- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index fe31b84bff..efe63f02d5 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -4,6 +4,7 @@ import { importAccounts } from './accounts'; import { importCustomEmoji } from './emoji'; import { normalizeStatus } from './normalizer'; import { importPolls } from './polls'; +import { fetchAccountsForCollectionPreview } from '@/mastodon/reducers/slices/collections'; export const STATUS_IMPORT = 'STATUS_IMPORT'; export const STATUSES_IMPORT = 'STATUSES_IMPORT'; @@ -61,6 +62,7 @@ export function importFetchedStatuses(statuses, options = {}) { const normalStatuses = []; const polls = []; const filters = []; + const collections = []; function processStatus(status) { pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), options)); @@ -82,6 +84,10 @@ export function importFetchedStatuses(statuses, options = {}) { pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id])); } + if (status.tagged_collections.length) { + status.tagged_collections.forEach(collection => pushUnique(collections, collection)); + } + if (status.card) { status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account)); } @@ -97,5 +103,6 @@ export function importFetchedStatuses(statuses, options = {}) { dispatch(importFetchedAccounts(accounts)); dispatch(importStatuses(normalStatuses)); dispatch(importFilters(filters)); + fetchAccountsForCollectionPreview(collections, dispatch); }; } diff --git a/app/javascript/mastodon/actions/notification_groups.ts b/app/javascript/mastodon/actions/notification_groups.ts index eddd6a9300..6d2f00ece8 100644 --- a/app/javascript/mastodon/actions/notification_groups.ts +++ b/app/javascript/mastodon/actions/notification_groups.ts @@ -5,6 +5,7 @@ import { apiFetchNotificationGroups, } from 'mastodon/api/notifications'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; +import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; import type { ApiNotificationGroupJSON, ApiNotificationJSON, @@ -26,6 +27,8 @@ import { createDataLoadingThunk, } from 'mastodon/store/typed_functions'; +import { fetchAccountsForCollectionPreview } from '../reducers/slices/collections'; + import { importFetchedAccounts, importFetchedStatuses } from './importer'; import { NOTIFICATIONS_FILTER_SET } from './notifications'; import { saveSettings } from './settings'; @@ -70,6 +73,7 @@ function dispatchAssociatedRecords( ) { const fetchedAccounts: ApiAccountJSON[] = []; const fetchedStatuses: ApiStatusJSON[] = []; + const collections: ApiCollectionJSON[] = []; notifications.forEach((notification) => { if (notification.type === 'admin.report') { @@ -83,6 +87,10 @@ function dispatchAssociatedRecords( if ('status' in notification && notification.status) { fetchedStatuses.push(notification.status); } + + if ('collection' in notification) { + collections.push(notification.collection); + } }); if (fetchedAccounts.length > 0) @@ -90,6 +98,9 @@ function dispatchAssociatedRecords( if (fetchedStatuses.length > 0) dispatch(importFetchedStatuses(fetchedStatuses)); + + if (collections.length > 0) + void fetchAccountsForCollectionPreview(collections, dispatch); } function selectNotificationGroupedTypes(state: RootState) { diff --git a/app/javascript/mastodon/actions/search.ts b/app/javascript/mastodon/actions/search.ts index 4b97682a74..b3ec7df0eb 100644 --- a/app/javascript/mastodon/actions/search.ts +++ b/app/javascript/mastodon/actions/search.ts @@ -13,7 +13,7 @@ import { } from 'mastodon/store/typed_functions'; import { - importAccountsForPreviewCard, + fetchAccountsForCollectionPreview, importFetchedCollections, } from '../reducers/slices/collections'; @@ -46,7 +46,7 @@ export const submitSearch = createDataLoadingThunk( if (data.collections.length > 0) { dispatch(importFetchedCollections(data.collections)); - await importAccountsForPreviewCard(data.collections, dispatch); + await fetchAccountsForCollectionPreview(data.collections, dispatch); } return data; @@ -82,7 +82,7 @@ export const expandSearch = createDataLoadingThunk( if (data.collections.length > 0) { dispatch(importFetchedCollections(data.collections)); - await importAccountsForPreviewCard(data.collections, dispatch); + await fetchAccountsForCollectionPreview(data.collections, dispatch); } return data; diff --git a/app/javascript/mastodon/reducers/slices/collections.ts b/app/javascript/mastodon/reducers/slices/collections.ts index 41bda5aa95..2f54cd256d 100644 --- a/app/javascript/mastodon/reducers/slices/collections.ts +++ b/app/javascript/mastodon/reducers/slices/collections.ts @@ -337,7 +337,7 @@ const collectionSlice = createSlice({ /** * Prefetch accounts whose avatars will be displayed in the collection list */ -export async function importAccountsForPreviewCard( +export async function fetchAccountsForCollectionPreview( collections: ApiCollectionJSON[], dispatch: AppDispatch, ) { @@ -347,15 +347,17 @@ export async function importAccountsForPreviewCard( ) .filter((id): id is string => !!id); - // fetchAccounts can only process up to 40 item ids, so we'll - // batch the list of ids - const batchedAccountIdLists = batchArray(previewAccountIds, 40); + if (previewAccountIds.length > 0) { + // fetchAccounts can only process up to 40 item ids, so we'll + // batch the list of ids + const batchedAccountIdLists = batchArray(previewAccountIds, 40); - await Promise.allSettled( - batchedAccountIdLists.map((accountIds) => - dispatch(fetchAccounts({ accountIds })), - ), - ); + await Promise.allSettled( + batchedAccountIdLists.map((accountIds) => + dispatch(fetchAccounts({ accountIds })), + ), + ); + } } export const fetchCollectionsCreatedByAccount = createDataLoadingThunk( @@ -363,7 +365,7 @@ export const fetchCollectionsCreatedByAccount = createDataLoadingThunk( ({ accountId }: { accountId: string }) => apiGetCollectionsCreatedByAccount(accountId), async ({ collections }, { dispatch }) => { - await importAccountsForPreviewCard(collections, dispatch); + await fetchAccountsForCollectionPreview(collections, dispatch); }, ); @@ -372,7 +374,7 @@ export const fetchCollectionsFeaturingAccount = createDataLoadingThunk( ({ accountId }: { accountId: string }) => apiGetCollectionsFeaturingAccount(accountId), async ({ collections }, { dispatch }) => { - await importAccountsForPreviewCard(collections, dispatch); + await fetchAccountsForCollectionPreview(collections, dispatch); }, );