Fix types, at least

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-08-07 16:23:44 +02:00
parent 0fc8a2993f
commit 7fb07b66be
42 changed files with 204 additions and 269 deletions

View file

@ -119,7 +119,7 @@
"html-react-parser": "^5.0.0",
"http-link-header": "^1.0.2",
"immer": "^10.0.0",
"immutable": "^4.2.1",
"immutable": "^4.3.7",
"intersection-observer": "^0.12.2",
"intl-messageformat": "10.5.11",
"intl-pluralrules": "^2.0.0",

View file

@ -20,7 +20,7 @@ import { getSettings } from './settings';
import { createStatus } from './statuses';
import type { EditorState } from 'lexical';
import type { Tag } from 'pl-api';
import type { CreateStatusParams, Tag } from 'pl-api';
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input';
import type { Emoji } from 'soapbox/features/emoji';
import type { Account, Group } from 'soapbox/schemas';
@ -373,22 +373,31 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
const idempotencyKey = compose.idempotencyKey;
const contentType = compose.content_type === 'wysiwyg' ? 'text/markdown' : compose.content_type;
const params: Record<string, any> = {
const params: CreateStatusParams = {
status,
in_reply_to_id: compose.in_reply_to,
quote_id: compose.quote,
media_ids: media.map(item => item.id),
in_reply_to_id: compose.in_reply_to || undefined,
quote_id: compose.quote || undefined,
media_ids: media.map(item => item.id).toArray(),
sensitive: compose.sensitive,
spoiler_text: compose.spoiler_text,
visibility: compose.privacy,
content_type: contentType,
poll: compose.poll,
scheduled_at: compose.schedule,
language: compose.language || compose.suggested_language,
to,
scheduled_at: compose.schedule?.toISOString(),
language: compose.language || compose.suggested_language || undefined,
to: to.size ? to.toArray() : undefined,
federated: compose.federated,
};
if (compose.poll) {
params.poll = {
options: compose.poll.options.toArray(),
expires_in: compose.poll.expires_in,
multiple: compose.poll.multiple,
hide_totals: compose.poll.hide_totals,
options_map: compose.poll.options_map.toJS(),
};
}
if (compose.language && compose.textMap.size) {
params.status_map = compose.textMap.toJS();
params.status_map[compose.language] = status;
@ -398,14 +407,13 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
params.spoiler_text_map[compose.language] = compose.spoiler_text;
}
if (params.poll) {
const poll = params.poll.toJS();
poll.options.forEach((option: any, index: number) => poll.options_map[index][compose.language!] = option);
params.poll = poll;
const poll = params.poll;
if (poll?.options_map) {
poll.options.forEach((option: any, index: number) => poll.options_map![index][compose.language!] = option);
}
}
if (compose.privacy === 'group') {
if (compose.privacy === 'group' && compose.group_id) {
params.group_id = compose.group_id;
}

View file

@ -283,7 +283,10 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
acc.accounts[item.target.id] = item.target;
}
// TODO actually check for type
// @ts-ignore
if (item.status?.id) {
// @ts-ignore
acc.statuses[item.status.id] = item.status;
}

View file

@ -64,16 +64,14 @@ const createStatus = (params: CreateStatusParams, idempotencyKey: string, status
return (statusId === null ? getClient(getState()).statuses.createStatus(params) : getClient(getState()).statuses.editStatus(statusId, params))
.then((status) => {
// The backend might still be processing the rich media attachment
if (!status.card && shouldHaveCard(status)) {
status.expectsCard = true;
}
// The backend might still be processing the rich media attachment
const expectsCard = !status.card && shouldHaveCard(status);
dispatch(importFetchedStatus(status, idempotencyKey));
dispatch(importFetchedStatus({ ...status, expectsCard }, idempotencyKey));
dispatch({ type: STATUS_CREATE_SUCCESS, status, params, idempotencyKey, editing: !!statusId });
// Poll the backend for the updated card
if (status.expectsCard) {
if (expectsCard) {
const delay = 1000;
const poll = (retries = 5) => {

View file

@ -22,7 +22,7 @@ const useAccountLookup = (acct: string | undefined, opts: UseAccountLookupOpts =
const { entity: account, isUnauthorized, ...result } = useEntityLookup<Account>(
Entities.ACCOUNTS,
(account) => account.acct.toLowerCase() === acct?.toLowerCase(),
() => client.accounts.lookupAccount(acct),
() => client.accounts.lookupAccount(acct!),
{ schema: accountSchema, enabled: !!acct },
);

View file

@ -1,8 +1,25 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { announcementReactionSchema, announcementSchema, type Announcement, type AnnouncementReaction } from 'pl-api';
import { announcementReactionSchema, type Announcement as BaseAnnouncement, type AnnouncementReaction } from 'pl-api';
import emojify from 'soapbox/features/emoji';
import { useClient } from 'soapbox/hooks';
import { queryClient } from 'soapbox/queries/client';
import { makeCustomEmojiMap } from 'soapbox/schemas/utils';
interface Announcement extends BaseAnnouncement {
contentHtml: string;
}
const transformAnnouncement = (announcement: BaseAnnouncement) => {
const emojiMap = makeCustomEmojiMap(announcement.emojis);
const contentHtml = emojify(announcement.content, emojiMap);
return {
...announcement,
contentHtml,
};
};
const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => announcementReactionSchema.parse({
...reaction,
@ -25,7 +42,7 @@ const useAnnouncements = () => {
const getAnnouncements = async () => {
const data = await client.announcements.getAnnouncements();
return data;
return data.map(transformAnnouncement);
};
const { data, ...result } = useQuery<ReadonlyArray<Announcement>>({
@ -42,18 +59,18 @@ const useAnnouncements = () => {
retry: false,
onMutate: ({ announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
prevResult.map(value => value.id !== id ? value : {
...value,
reactions: updateReactions(value.reactions, name, 1, true),
})),
}),
);
},
onError: (_, { announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
prevResult.map(value => value.id !== id ? value : {
...value,
reactions: updateReactions(value.reactions, name, -1, false),
})),
}),
);
},
});
@ -66,18 +83,18 @@ const useAnnouncements = () => {
retry: false,
onMutate: ({ announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
prevResult.map(value => value.id !== id ? value : {
...value,
reactions: updateReactions(value.reactions, name, -1, false),
})),
}),
);
},
onError: (_, { announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
prevResult.map(value => value.id !== id ? value : {
...value,
reactions: updateReactions(value.reactions, name, 1, true),
})),
}),
);
},
});
@ -93,4 +110,4 @@ const useAnnouncements = () => {
const compareAnnouncements = (a: Announcement, b: Announcement): number =>
new Date(a.starts_at || a.published_at).getDate() - new Date(b.starts_at || b.published_at).getDate();
export { updateReactions, useAnnouncements };
export { updateReactions, useAnnouncements, type Announcement };

View file

@ -4,7 +4,7 @@ import { useClient } from 'soapbox/hooks';
import { groupSchema } from 'soapbox/schemas';
interface CreateGroupParams {
display_name?: string;
display_name: string;
note?: string;
avatar?: File;
header?: File;

View file

@ -10,7 +10,7 @@ const useBookmarkFolders = () => {
const { entities, ...result } = useEntities<BookmarkFolder>(
[Entities.BOOKMARK_FOLDERS],
() => client.myAccount.getBookmarkFolders,
() => client.myAccount.getBookmarkFolders(),
{ enabled: features.bookmarkFolders, schema: bookmarkFolderSchema },
);

View file

@ -2,12 +2,12 @@ import { Entities } from 'soapbox/entity-store/entities';
import { useDeleteEntity } from 'soapbox/entity-store/hooks';
import { useClient } from 'soapbox/hooks';
const useDeleteBookmarkFolder = (folderId: string) => {
const useDeleteBookmarkFolder = () => {
const client = useClient();
const { deleteEntity, isSubmitting } = useDeleteEntity(
Entities.BOOKMARK_FOLDERS,
() => client.myAccount.deleteBookmarkFolder(folderId),
(folderId: string) => client.myAccount.deleteBookmarkFolder(folderId),
);
return {

View file

@ -3,10 +3,11 @@ import { useHistory } from 'react-router-dom';
import { getTextDirection } from 'soapbox/utils/rtl';
import type { Announcement as AnnouncementEntity, Mention as MentionEntity } from 'soapbox/schemas';
import type { Announcement } from 'soapbox/api/hooks/announcements/useAnnouncements';
import type { Mention as MentionEntity } from 'soapbox/schemas';
interface IAnnouncementContent {
announcement: AnnouncementEntity;
announcement: Announcement;
}
const AnnouncementContent: React.FC<IAnnouncementContent> = ({ announcement }) => {

View file

@ -9,7 +9,7 @@ import AnnouncementContent from './announcement-content';
import ReactionsBar from './reactions-bar';
import type { Map as ImmutableMap } from 'immutable';
import type { Announcement as AnnouncementEntity } from 'soapbox/schemas';
import type { Announcement as AnnouncementEntity } from 'soapbox/api/hooks/announcements/useAnnouncements';
interface IAnnouncement {
announcement: AnnouncementEntity;

View file

@ -31,7 +31,7 @@ const AnnouncementsPanel = () => {
<Widget title={<FormattedMessage id='announcements.title' defaultMessage='Announcements' />}>
<Card className='relative black:rounded-xl black:border black:border-gray-800' size='md' variant='rounded'>
<ReactSwipeableViews animateHeight index={index} onChangeIndex={handleChangeIndex}>
{announcements!.map((announcement) => (
{announcements.map((announcement) => (
<Announcement
key={announcement.id}
announcement={announcement}

View file

@ -3,7 +3,9 @@ import { useHistory, useParams } from 'react-router-dom';
import { toggleMainWindow } from 'soapbox/actions/chats';
import { useAppDispatch, useSettings } from 'soapbox/hooks';
import { IChat, useChat } from 'soapbox/queries/chats';
import { useChat } from 'soapbox/queries/chats';
import type { Chat } from 'pl-api';
const ChatContext = createContext<any>({
isOpen: false,
@ -69,7 +71,7 @@ const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
};
interface IChatContext {
chat: IChat | null;
chat: Chat | null;
isOpen: boolean;
isUsingMainChatPage?: boolean;
toggleChatPane(): void;

View file

@ -23,7 +23,7 @@ interface UseBatchedEntitiesOpts<TEntity extends Entity> {
const useBatchedEntities = <TEntity extends Entity>(
expandedPath: ExpandedEntitiesPath,
ids: string[],
entityFn: EntityFn<TEntity[]>,
entityFn: EntityFn<string[]>,
opts: UseBatchedEntitiesOpts<TEntity> = {},
) => {
const getState = useGetState();

View file

@ -24,7 +24,7 @@ interface UseEntityOpts<TEntity extends Entity> {
const useEntity = <TEntity extends Entity>(
path: EntityPath,
entityFn: EntityFn<TEntity>,
entityFn: EntityFn<void>,
opts: UseEntityOpts<TEntity> = {},
) => {
const [isFetching, setPromise] = useLoading(true);

View file

@ -37,8 +37,8 @@ const createList = (): EntityList => ({
/** Create an empty entity list state. */
const createListState = (): EntityListState => ({
next: undefined,
prev: undefined,
next: null,
prev: null,
totalCount: 0,
error: null,
fetched: false,

View file

@ -13,6 +13,8 @@ import { Checkbox, Form, FormGroup, FormActions, Button, Input, Textarea, Select
import CaptchaField from 'soapbox/features/auth-login/components/captcha';
import { useAppDispatch, useSettings, useFeatures, useInstance } from 'soapbox/hooks';
import type { CreateAccountParams } from 'pl-api';
const messages = defineMessages({
username: { id: 'registration.fields.username_placeholder', defaultMessage: 'Username' },
username_hint: { id: 'registration.fields.username_hint', defaultMessage: 'Only letters, numbers, and underscores are allowed.' },
@ -53,7 +55,13 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const [captchaLoading, setCaptchaLoading] = useState(true);
const [submissionLoading, setSubmissionLoading] = useState(false);
const [params, setParams] = useState(ImmutableMap<string, any>());
const [params, setParams] = useState<CreateAccountParams>({
username: '',
email: '',
password: '',
agreement: false,
locale: '',
});
const [captchaIdempotencyKey, setCaptchaIdempotencyKey] = useState(uuidv4());
const [usernameUnavailable, setUsernameUnavailable] = useState(false);
const [passwordConfirmation, setPasswordConfirmation] = useState('');
@ -61,39 +69,35 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const controller = useRef(new AbortController());
const updateParams = (map: any) => {
setParams(params.merge(ImmutableMap(map)));
};
const onInputChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> = e => {
updateParams({ [e.target.name]: e.target.value });
setParams(params => ({ ...params, [e.target.name]: e.target.value }));
};
const onUsernameChange: React.ChangeEventHandler<HTMLInputElement> = e => {
updateParams({ username: e.target.value });
setParams(params => ({ ...params, username: e.target.value }));
setUsernameUnavailable(false);
controller.current.abort();
controller.current = new AbortController();
const domain = params.get('domain');
const domain = params.domain;
usernameAvailable(e.target.value, domain ? domains!.find(({ id }) => id === domain)?.domain : undefined);
};
const onDomainChange: React.ChangeEventHandler<HTMLSelectElement> = e => {
updateParams({ domain: e.target.value || null });
setParams(params => ({ ...params, domain: e.target.value || undefined }));
setUsernameUnavailable(false);
controller.current.abort();
controller.current = new AbortController();
const username = params.get('username');
const username = params.username;
if (username) {
usernameAvailable(username, domains!.find(({ id }) => id === e.target.value)?.domain);
}
};
const onCheckboxChange: React.ChangeEventHandler<HTMLInputElement> = e => {
updateParams({ [e.target.name]: e.target.checked });
setParams(params => ({ ...params, [e.target.name]: e.target.checked }));
};
const onPasswordChange: React.ChangeEventHandler<HTMLInputElement> = e => {
@ -106,7 +110,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
};
const onPasswordConfirmChange: React.ChangeEventHandler<HTMLInputElement> = e => {
const password = params.get('password', '');
const password = params.password || '';
const passwordConfirmation = e.target.value;
setPasswordConfirmation(passwordConfirmation);
@ -120,7 +124,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
};
const onBirthdayChange = (birthday: string) => {
updateParams({ birthday });
setParams(params => ({ ...params, birthday }));
};
const launchModal = () => {
@ -129,7 +133,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<FormattedMessage
id='confirmations.register.needs_confirmation'
defaultMessage='Please check your inbox at {email} for confirmation instructions. You will need to verify your email address to continue.'
values={{ email: <strong>{params.get('email')}</strong> }}
values={{ email: <strong>{params.email}</strong> }}
/></p>}
{needsApproval && <p>
<FormattedMessage
@ -160,7 +164,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
}
};
const passwordsMatch = () => params.get('password', '') === passwordConfirmation;
const passwordsMatch = () => params.password === passwordConfirmation;
const usernameAvailable = useCallback(debounce((username, domain?: string) => {
if (!supportsAccountLookup) return;
@ -186,19 +190,18 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
return;
}
const normalParams = params.withMutations(params => {
// Locale for confirmation email
params.set('locale', locale);
const normalParams = {
...params,
locale,
};
// Pleroma invites
if (inviteToken) {
params.set('token', inviteToken);
}
});
if (inviteToken) {
params.token = inviteToken;
}
setSubmissionLoading(true);
dispatch(register(normalParams.toJS()))
dispatch(register(normalParams))
.then(postRegisterAction)
.catch(() => {
setSubmissionLoading(false);
@ -212,10 +215,11 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const onFetchCaptcha = (captcha: ImmutableMap<string, any>) => {
setCaptchaLoading(false);
updateParams({
setParams(params => ({
...params,
captcha_token: captcha.get('token'),
captcha_answer_data: captcha.get('answer_data'),
});
}));
};
const onFetchCaptchaFail = () => {
@ -224,7 +228,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const refreshCaptcha = () => {
setCaptchaIdempotencyKey(uuidv4());
updateParams({ captcha_solution: '' });
setParams(params => ({ ...params, captcha_solution: '' }));
};
const isLoading = captchaLoading || submissionLoading;
@ -247,7 +251,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
pattern='^[a-zA-Z\d_-]+'
icon={require('@tabler/icons/outline/at.svg')}
onChange={onUsernameChange}
value={params.get('username', '')}
value={params.username}
required
/>
</FormGroup>
@ -256,7 +260,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<FormGroup>
<Select
onChange={onDomainChange}
value={params.get('domain')}
value={params.domain}
>
{domains.map(({ id, domain }) => (
<option key={id} value={id}>{domain}</option>
@ -273,7 +277,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
autoCorrect='off'
autoCapitalize='off'
onChange={onInputChange}
value={params.get('email', '')}
value={params.email}
required
/>
@ -285,7 +289,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
autoCorrect='off'
autoCapitalize='off'
onChange={onPasswordChange}
value={params.get('password', '')}
value={params.password}
required
/>
@ -308,7 +312,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
{birthdayRequired && (
<BirthdayInput
value={params.get('birthday')}
value={params.birthday || ''}
onChange={onBirthdayChange}
required
/>
@ -323,7 +327,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
placeholder={intl.formatMessage(messages.reasonHint)}
maxLength={500}
onChange={onInputChange}
value={params.get('reason', '')}
value={params.reason || ''}
autoGrow
required
/>
@ -337,7 +341,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
onClick={onCaptchaClick}
idempotencyKey={captchaIdempotencyKey}
name='captcha_solution'
value={params.get('captcha_solution', '')}
value={params.captcha_solution || ''}
/>
<FormGroup
@ -346,7 +350,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<Checkbox
name='agreement'
onChange={onCheckboxChange}
checked={params.get('agreement', false)}
checked={params.agreement}
required
/>
</FormGroup>
@ -356,7 +360,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<Checkbox
name='accepts_email_list'
onChange={onCheckboxChange}
checked={params.get('accepts_email_list', false)}
checked={params.accepts_email_list}
/>
</FormGroup>
)}

View file

@ -9,8 +9,9 @@ import { Avatar, HStack, IconButton, Stack, Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification-badge';
import { useChatContext } from 'soapbox/contexts/chat-context';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { IChat, useChatActions } from 'soapbox/queries/chats';
import { useChatActions } from 'soapbox/queries/chats';
import type { Chat } from 'pl-api';
import type { Menu } from 'soapbox/components/dropdown-menu';
const messages = defineMessages({
@ -23,7 +24,7 @@ const messages = defineMessages({
});
interface IChatListItemInterface {
chat: IChat;
chat: Chat;
onClick: (chat: any) => void;
}

View file

@ -5,10 +5,11 @@ import { Components, Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import { Avatar, Button, Divider, Spinner, Stack, Text } from 'soapbox/components/ui';
import PlaceholderChatMessage from 'soapbox/features/placeholder/components/placeholder-chat-message';
import { useAppSelector } from 'soapbox/hooks';
import { IChat, useChatActions, useChatMessages } from 'soapbox/queries/chats';
import { useChatActions, useChatMessages } from 'soapbox/queries/chats';
import ChatMessage from './chat-message';
import type { Chat } from 'pl-api';
import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';
const messages = defineMessages({
@ -21,7 +22,7 @@ const messages = defineMessages({
type TimeFormat = 'today' | 'date';
const timeChange = (prev: ChatMessageEntity, curr: ChatMessageEntity): TimeFormat | null => {
const timeChange = (prev: Pick<ChatMessageEntity, 'created_at'>, curr: Pick<ChatMessageEntity, 'created_at'>): TimeFormat | null => {
const prevDate = new Date(prev.created_at).getDate();
const currDate = new Date(curr.created_at).getDate();
const nowDate = new Date().getDate();
@ -57,7 +58,7 @@ const Scroller: Components['Scroller'] = React.forwardRef((props, ref) => {
interface IChatMessageList {
/** Chat the messages are being rendered from. */
chat: IChat;
chat: Chat;
}
/** Scrollable list of chat messages. */

View file

@ -11,11 +11,12 @@ import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
import emojify from 'soapbox/features/emoji';
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats';
import { ChatKeys, useChatActions } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { stripHTML } from 'soapbox/utils/html';
import { onlyEmoji } from 'soapbox/utils/rich-content';
import type { Chat } from 'pl-api';
import type { Menu as IMenu } from 'soapbox/components/dropdown-menu';
import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';
@ -43,7 +44,7 @@ const parseContent = (chatMessage: ChatMessageEntity) => {
};
interface IChatMessage {
chat: IChat;
chat: Chat;
chatMessage: ChatMessageEntity;
}

View file

@ -3,10 +3,11 @@ import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { CardTitle, HStack, IconButton, Stack } from 'soapbox/components/ui';
import { IChat } from 'soapbox/queries/chats';
import ChatList from '../../chat-list';
import type { Chat } from 'pl-api';
const messages = defineMessages({
title: { id: 'column.chats', defaultMessage: 'Chats' },
});
@ -15,7 +16,7 @@ const ChatPageSidebar = () => {
const intl = useIntl();
const history = useHistory();
const handleClickChat = (chat: IChat) => {
const handleClickChat = (chat: Chat) => {
history.push(`/chats/${chat.id}`);
};

View file

@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
import { Stack } from 'soapbox/components/ui';
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
import { useStatContext } from 'soapbox/contexts/stat-context';
import { IChat, useChats } from 'soapbox/queries/chats';
import { useChats } from 'soapbox/queries/chats';
import ChatList from '../chat-list';
import ChatSearch from '../chat-search/chat-search';
@ -16,13 +16,15 @@ import { Pane } from '../ui';
import Blankslate from './blankslate';
import type { Chat } from 'pl-api';
const ChatPane = () => {
const { unreadChatsCount } = useStatContext();
const { screen, changeScreen, isOpen, toggleChatPane } = useChatContext();
const { chatsQuery: { data: chats, isLoading } } = useChats();
const handleClickChat = (nextChat: IChat) => {
const handleClickChat = (nextChat: Chat) => {
changeScreen(ChatWidgetScreens.CHAT, nextChat.id);
};

View file

@ -6,7 +6,7 @@ import { Avatar, HStack, Stack, Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification-badge';
import useAccountSearch from 'soapbox/queries/search';
import type { Account } from 'soapbox/types/entities';
import type { Account } from 'pl-api';
interface IResults {
accountSearchResult: ReturnType<typeof useAccountSearch>;

View file

@ -6,12 +6,13 @@ import { uploadMedia } from 'soapbox/actions/media';
import { Stack } from 'soapbox/components/ui';
import { useAppDispatch } from 'soapbox/hooks';
import { normalizeAttachment } from 'soapbox/normalizers';
import { IChat, useChatActions } from 'soapbox/queries/chats';
import { useChatActions } from 'soapbox/queries/chats';
import toast from 'soapbox/toast';
import ChatComposer from './chat-composer';
import ChatMessageList from './chat-message-list';
import type { Chat as ChatEntity } from 'pl-api';
import type { PlfeResponse } from 'soapbox/api';
import type { Attachment } from 'soapbox/types/entities';
@ -23,7 +24,7 @@ const messages = defineMessages({
});
interface ChatInterface {
chat: IChat;
chat: ChatEntity;
inputRef?: MutableRefObject<HTMLTextAreaElement | null>;
className?: string;
}

View file

@ -196,7 +196,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const renderButtons = useCallback(() => (
<HStack alignItems='center' space={2}>
{features.media && <UploadButtonContainer composeId={id} />}
<UploadButtonContainer composeId={id} />
<EmojiPickerDropdown onPickEmoji={handleEmojiPick} condensed={shouldCondense} />
{features.polls && <PollButton composeId={id} />}
{features.scheduledStatuses && <ScheduleButton composeId={id} />}

View file

@ -20,7 +20,7 @@ const ReplyMentions: React.FC<IReplyMentions> = ({ composeId }) => {
const status = useAppSelector<StatusEntity | null>(state => getStatus(state, { id: compose.in_reply_to! }));
const to = compose.to;
if (!features.explicitAddressing || !status || !to) {
if (!features.createStatusExplicitAddressing || !status || !to) {
return null;
}

View file

@ -132,7 +132,7 @@ const Settings = () => {
<Preferences />
</CardBody>
{(features.security || features.accountAliases) && (
{(features.security || features.manageAccountAliases) && (
<>
<CardHeader>
<CardTitle title={intl.formatMessage(messages.other)} />

View file

@ -2,15 +2,15 @@ import React, { useMemo, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { z } from 'zod';
import { useCreateGroup, type CreateGroupParams } from 'soapbox/api/hooks';
import { useCreateGroup } from 'soapbox/api/hooks';
import { Modal, Stack } from 'soapbox/components/ui';
import { type Group } from 'soapbox/schemas';
import toast from 'soapbox/toast';
import ConfirmationStep from './steps/confirmation-step';
import DetailsStep from './steps/details-step';
import PrivacyStep from './steps/privacy-step';
import type { CreateGroupParams } from 'pl-api';
import type { PlfeResponse } from 'soapbox/api';
const messages = defineMessages({
@ -22,7 +22,6 @@ const messages = defineMessages({
enum Steps {
ONE = 'ONE',
TWO = 'TWO',
THREE = 'THREE',
}
interface ICreateGroupModal {
@ -34,7 +33,7 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
const [group, setGroup] = useState<Group | null>(null);
const [params, setParams] = useState<CreateGroupParams>({
group_visibility: 'everyone',
display_name: '',
});
const [currentStep, setCurrentStep] = useState<Steps>(Steps.ONE);
@ -46,24 +45,19 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
const confirmationText = useMemo(() => {
switch (currentStep) {
case Steps.THREE:
return intl.formatMessage(messages.done);
case Steps.TWO:
return intl.formatMessage(messages.create);
return intl.formatMessage(messages.done);
default:
return intl.formatMessage(messages.next);
return intl.formatMessage(messages.create);
}
}, [currentStep]);
const handleNextStep = () => {
switch (currentStep) {
case Steps.ONE:
setCurrentStep(Steps.TWO);
break;
case Steps.TWO:
createGroup(params, {
onSuccess(group) {
setCurrentStep(Steps.THREE);
setCurrentStep(Steps.TWO);
setGroup(group);
},
onError(error: { response?: PlfeResponse }) {
@ -74,7 +68,7 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
},
});
break;
case Steps.THREE:
case Steps.TWO:
handleClose();
break;
default:
@ -85,26 +79,13 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
const renderStep = () => {
switch (currentStep) {
case Steps.ONE:
return <PrivacyStep params={params} onChange={setParams} />;
case Steps.TWO:
return <DetailsStep params={params} onChange={setParams} />;
case Steps.THREE:
case Steps.TWO:
return <ConfirmationStep group={group} />;
}
};
const renderModalTitle = () => {
switch (currentStep) {
case Steps.ONE:
return <FormattedMessage id='navigation_bar.create_group' defaultMessage='Create group' />;
default:
if (params.group_visibility === 'everyone') {
return <FormattedMessage id='navigation_bar.create_group.public' defaultMessage='Create public group' />;
} else {
return <FormattedMessage id='navigation_bar.create_group.private' defaultMessage='Create private group' />;
}
}
};
const renderModalTitle = () => <FormattedMessage id='navigation_bar.create_group' defaultMessage='Create group' />;
return (
<Modal

View file

@ -1,7 +1,6 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { CreateGroupParams } from 'soapbox/api/hooks';
import { Form, FormGroup, Input, Textarea } from 'soapbox/components/ui';
import AvatarPicker from 'soapbox/features/edit-profile/components/avatar-picker';
import HeaderPicker from 'soapbox/features/edit-profile/components/header-picker';
@ -9,6 +8,8 @@ import { useAppSelector, useInstance } from 'soapbox/hooks';
import { usePreview } from 'soapbox/hooks/forms';
import resizeImage from 'soapbox/utils/resize-image';
import type { CreateGroupParams } from 'pl-api';
const messages = defineMessages({
groupNamePlaceholder: { id: 'manage_group.fields.name_placeholder', defaultMessage: 'Group Name' },
groupDescriptionPlaceholder: { id: 'manage_group.fields.description_placeholder', defaultMessage: 'Description' },
@ -17,7 +18,7 @@ const messages = defineMessages({
interface IDetailsStep {
params: CreateGroupParams;
onChange(params: CreateGroupParams): void;
onChange: (params: CreateGroupParams) => void;
}
const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
@ -52,7 +53,10 @@ const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
}
};
const handleImageClear = (property: keyof CreateGroupParams) => () => onChange({ [property]: undefined });
const handleImageClear = (property: keyof CreateGroupParams) => () => onChange({
...params,
[property]: undefined,
});
return (
<Form>

View file

@ -1,58 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { type CreateGroupParams } from 'soapbox/api/hooks';
import List, { ListItem } from 'soapbox/components/list';
import { Form, FormGroup, Stack, Text } from 'soapbox/components/ui';
interface IPrivacyStep {
params: CreateGroupParams;
onChange(params: CreateGroupParams): void;
}
const PrivacyStep: React.FC<IPrivacyStep> = ({ params, onChange }) => {
const visibility = params.group_visibility || 'everyone';
const onChangePrivacy = (group_visibility: CreateGroupParams['group_visibility']) => {
onChange({ ...params, group_visibility });
};
return (
<>
<Stack className='mx-auto max-w-xs py-10' space={2}>
<Text size='3xl' weight='bold' align='center'>
<FormattedMessage id='manage_group.get_started' defaultMessage='Lets get started!' />
</Text>
<Text theme='muted' align='center'>
<FormattedMessage id='manage_group.tagline' defaultMessage='Groups connect you with others based on shared interests.' />
</Text>
</Stack>
<Form>
<FormGroup
labelText={<FormattedMessage id='manage_group.privacy.label' defaultMessage='Privacy settings' />}
>
<List>
<ListItem
label={<Text weight='medium'><FormattedMessage id='manage_group.privacy.public.label' defaultMessage='Public' /></Text>}
hint={<FormattedMessage id='manage_group.privacy.public.hint' defaultMessage='Discoverable. Anyone can join.' />}
onSelect={() => onChangePrivacy('everyone')}
isSelected={visibility === 'everyone'}
/>
<ListItem
label={<Text weight='medium'><FormattedMessage id='manage_group.privacy.private.label' defaultMessage='Private (Owner approval required)' /></Text>}
hint={<FormattedMessage id='manage_group.privacy.private.hint' defaultMessage='Discoverable. Users can join after their request is approved.' />}
onSelect={() => onChangePrivacy('members_only')}
isSelected={visibility === 'members_only'}
/>
</List>
</FormGroup>
<Text size='sm' theme='muted' align='center'>
<FormattedMessage id='manage_group.privacy.hint' defaultMessage='These settings cannot be changed later.' />
</Text>
</Form>
</>
);
};
export { PrivacyStep as default };

View file

@ -1,17 +0,0 @@
import { Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable';
import type { Account, EmbeddedEntity } from 'soapbox/types/entities';
const ChatRecord = ImmutableRecord({
account: null as EmbeddedEntity<Account>,
id: '',
unread: 0,
last_message: '' as string || null,
updated_at: '',
});
const normalizeChat = (chat: Record<string, any>) => ChatRecord(
ImmutableMap(fromJS(chat)),
);
export { ChatRecord, normalizeChat };

View file

@ -2,7 +2,6 @@ export { AccountRecord, FieldRecord, normalizeAccount } from './account';
export { AdminAccountRecord, normalizeAdminAccount } from './admin-account';
export { AdminReportRecord, normalizeAdminReport } from './admin-report';
export { AttachmentRecord, normalizeAttachment } from './attachment';
export { ChatRecord, normalizeChat } from './chat';
export { ChatMessageRecord, normalizeChatMessage } from './chat-message';
export { EmojiRecord, normalizeEmoji } from './emoji';
export { FilterRecord, normalizeFilter } from './filter';

View file

@ -7,42 +7,36 @@ import { useStatContext } from 'soapbox/contexts/stat-context';
import { useAppDispatch, useAppSelector, useClient, useFeatures, useLoggedIn, useOwnAccount } from 'soapbox/hooks';
import { normalizeChatMessage } from 'soapbox/normalizers';
import { reOrderChatListItems } from 'soapbox/utils/chats';
import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries';
import { flattenPages, updatePageItem } from 'soapbox/utils/queries';
import { queryClient } from './client';
import { useFetchRelationships } from './relationships';
import type { Chat, ChatMessage, PaginatedResponse } from 'pl-api';
import type { Account } from 'soapbox/schemas';
import type { Chat, ChatMessage as BaseChatMessage, PaginatedResponse } from 'pl-api';
interface IChat {
account: Account;
created_at: string;
id: string;
last_message: null | {
account_id: string;
chat_id: string;
content: string;
created_at: string;
id: string;
unread: boolean;
};
unread: number;
}
const transformChatMessage = (chatMessage: BaseChatMessage) => ({
...chatMessage,
pending: false as boolean,
});
type ChatMessage = ReturnType<typeof transformChatMessage>;
const ChatKeys = {
chat: (chatId?: string) => ['chats', 'chat', chatId] as const,
chatMessages: (chatId: string) => ['chats', 'messages', chatId] as const,
};
const useChatMessages = (chat: IChat) => {
const useChatMessages = (chat: Chat) => {
const client = useClient();
const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat.account.id, 'blocked_by']));
const getChatMessages = async (chatId: string, pageParam?: Pick<PaginatedResponse<ChatMessage>, 'next'>): Promise<PaginatedResponse<ChatMessage>> => {
const getChatMessages = async (chatId: string, pageParam?: Pick<PaginatedResponse<BaseChatMessage>, 'next'>) => {
const response = await (pageParam?.next ? pageParam.next() : client.chats.getChatMessages(chatId));
return response;
return {
...response,
items: response.items.map(transformChatMessage),
};
};
const queryInfo = useInfiniteQuery({
@ -51,11 +45,11 @@ const useChatMessages = (chat: IChat) => {
enabled: !isBlocked,
gcTime: 0,
staleTime: 0,
initialPageParam: { next: null as (() => Promise<PaginatedResponse<ChatMessage>>) | null },
initialPageParam: { next: null as (() => Promise<PaginatedResponse<BaseChatMessage>>) | null },
getNextPageParam: (config) => config,
});
const data = flattenPages(queryInfo.data)?.reverse();
const data = flattenPages<ChatMessage>(queryInfo.data as any)?.reverse();
return {
...queryInfo,
@ -142,7 +136,7 @@ const useChatActions = (chatId: string) => {
client.chats.markChatAsRead(chatId, lastReadId)
.then((data) => {
updatePageItem(['chats', 'search'], data, (o, n) => o.id === n.id);
const queryData = queryClient.getQueryData<InfiniteData<PaginatedResult<unknown>>>(['chats', 'search']);
const queryData = queryClient.getQueryData<InfiniteData<PaginatedResponse<unknown>>>(['chats', 'search']);
if (queryData) {
const flattenedQueryData: any = flattenPages(queryData)?.map((chat: any) => {
@ -152,7 +146,7 @@ const useChatActions = (chatId: string) => {
return chat;
}
});
setUnreadChatsCount(sumBy(flattenedQueryData, (chat: IChat) => chat.unread));
setUnreadChatsCount(sumBy(flattenedQueryData, (chat: Chat) => chat.unread));
}
return data;
@ -238,4 +232,4 @@ const useChatActions = (chatId: string) => {
};
};
export { type IChat, ChatKeys, useChat, useChatActions, useChats, useChatMessages };
export { ChatKeys, useChat, useChatActions, useChats, useChatMessages, type ChatMessage };

View file

@ -23,7 +23,7 @@ const useAccountSearch = (q: string) => {
placeholderData: keepPreviousData,
initialPageParam: {},
getNextPageParam: () => {
if (queryInfo.data[queryInfo.data.length - 1].length !== 10) {
if (queryInfo.data?.pages[queryInfo.data.pages.length - 1].length !== 10) {
return {};
}

View file

@ -86,6 +86,7 @@ const PollRecord = ImmutableRecord({
options_map: ImmutableList<ImmutableMap<Language, string>>([ImmutableMap(), ImmutableMap()]),
expires_in: 24 * 3600,
multiple: false,
hide_totals: false,
});
const ReducerCompose = ImmutableRecord({

View file

@ -9,6 +9,7 @@ import {
SUGGESTIONS_DISMISS,
} from 'soapbox/actions/suggestions';
import type { Suggestion as SuggestionEntity } from 'pl-api';
import type { AnyAction } from 'redux';
const SuggestionRecord = ImmutableRecord({
@ -24,7 +25,7 @@ const ReducerRecord = ImmutableRecord({
type State = ReturnType<typeof ReducerRecord>;
type Suggestion = ReturnType<typeof SuggestionRecord>;
const importSuggestions = (state: State, suggestions: Suggestion[]) =>
const importSuggestions = (state: State, suggestions: SuggestionEntity[]) =>
state.withMutations(state => {
state.update('items', items => items.concat(suggestions.map(x => ({ ...x, account: x.account.id })).map(suggestion => SuggestionRecord(suggestion))));
state.set('isLoading', false);

View file

@ -5,7 +5,7 @@ import z from 'zod';
* https://docs.joinmastodon.org/entities/CustomEmoji/
*/
const customEmojiSchema = z.object({
category: z.string().catch(''),
category: z.string().nullable().catch(null),
shortcode: z.string(),
static_url: z.string().catch(''),
url: z.string(),

View file

@ -1,6 +1,6 @@
import z from 'zod';
import type { CustomEmoji } from './custom-emoji';
import type { CustomEmoji } from 'pl-api';
/** Ensure HTML content is a string, and drop empty `<p>` tags. */
const contentSchema = z.string().catch('').transform((value) => value === '<p></p>' ? '' : value);

View file

@ -2,7 +2,6 @@ import {
AdminAccountRecord,
AdminReportRecord,
AttachmentRecord,
ChatRecord,
ChatMessageRecord,
EmojiRecord,
FieldRecord,
@ -26,7 +25,6 @@ import type { LegacyMap } from 'soapbox/utils/legacy';
type AdminAccount = ReturnType<typeof AdminAccountRecord>;
type AdminReport = ReturnType<typeof AdminReportRecord>;
type Attachment = ReturnType<typeof AttachmentRecord>;
type Chat = ReturnType<typeof ChatRecord>;
type ChatMessage = ReturnType<typeof ChatMessageRecord>;
type Emoji = ReturnType<typeof EmojiRecord>;
type Field = ReturnType<typeof FieldRecord>;
@ -58,7 +56,6 @@ export {
AdminAccount,
AdminReport,
Attachment,
Chat,
ChatMessage,
Emoji,
Field,

View file

@ -1,13 +1,15 @@
import { InfiniteData } from '@tanstack/react-query';
import sumBy from 'lodash/sumBy';
import { normalizeChatMessage } from 'soapbox/normalizers';
import { ChatKeys } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { Chat, ChatMessage } from 'soapbox/types/entities';
import { compareDate } from './comparators';
import { appendPageItem, flattenPages, PaginatedResult, sortQueryData, updatePageItem } from './queries';
import { appendPageItem, flattenPages, sortQueryData, updatePageItem } from './queries';
import type { InfiniteData } from '@tanstack/react-query';
import type { Chat, PaginatedResponse } from 'pl-api';
import type { ChatMessage } from 'soapbox/types/entities';
interface ChatPayload extends Omit<Chat, 'last_message'> {
last_message: ChatMessage | null;
@ -38,7 +40,7 @@ const reOrderChatListItems = () => {
*/
const checkIfChatExists = (chatId: string) => {
const currentChats = flattenPages(
queryClient.getQueryData<InfiniteData<PaginatedResult<Chat>>>(['chats', 'search']),
queryClient.getQueryData<InfiniteData<PaginatedResponse<Chat>>>(['chats', 'search']),
);
return currentChats?.find((chat: Chat) => chat.id === chatId);
@ -78,17 +80,10 @@ const updateChatListItem = (newChat: ChatPayload) => {
/** Get unread chats count. */
const getUnreadChatsCount = (): number => {
const chats = flattenPages(
queryClient.getQueryData<InfiniteData<PaginatedResult<Chat>>>(['chats', 'search']),
queryClient.getQueryData<InfiniteData<PaginatedResponse<Chat>>>(['chats', 'search']),
);
return sumBy(chats, chat => chat.unread);
};
/** Update the query cache for an individual Chat Message */
const updateChatMessage = (chatMessage: ChatMessage) => updatePageItem(
ChatKeys.chatMessages(chatMessage.chat_id),
normalizeChatMessage(chatMessage),
(o, n) => o.id === n.id,
);
export { updateChatListItem, updateChatMessage, getUnreadChatsCount, reOrderChatListItems };
export { updateChatListItem, getUnreadChatsCount, reOrderChatListItems };

View file

@ -3,12 +3,6 @@ import { queryClient } from 'soapbox/queries/client';
import type { InfiniteData, QueryKey } from '@tanstack/react-query';
import type { PaginatedResponse } from 'pl-api';
interface PaginatedResult<T> {
result: T[];
hasMore: boolean;
next?: () => Promise<PaginatedResponse<any>> | null;
}
interface Entity {
id: string;
}
@ -24,7 +18,7 @@ const deduplicateById = <T extends Entity>(entities: T[]): T[] => {
};
/** Flatten paginated results into a single array. */
const flattenPages = <T>(queryData: InfiniteData<PaginatedResult<T> | PaginatedResponse<T>> | undefined) => {
const flattenPages = <T>(queryData: InfiniteData<PaginatedResponse<T>> | undefined) => {
const data = queryData?.pages.reduce<T[]>(
(prev: T[], curr) => [...prev, ...((curr as any).result || (curr as any).items)],
[],
@ -39,10 +33,10 @@ const flattenPages = <T>(queryData: InfiniteData<PaginatedResult<T> | PaginatedR
/** Traverse pages and update the item inside if found. */
const updatePageItem = <T>(queryKey: QueryKey, newItem: T, isItem: (item: T, newItem: T) => boolean) => {
queryClient.setQueriesData<InfiniteData<PaginatedResult<T>>>({ queryKey }, (data) => {
queryClient.setQueriesData<InfiniteData<PaginatedResponse<T>>>({ queryKey }, (data) => {
if (data) {
const pages = data.pages.map(page => {
const result = page.result.map(item => isItem(item, newItem) ? newItem : item);
const result = page.items.map(item => isItem(item, newItem) ? newItem : item);
return { ...page, result };
});
return { ...data, pages };
@ -52,10 +46,10 @@ const updatePageItem = <T>(queryKey: QueryKey, newItem: T, isItem: (item: T, new
/** Insert the new item at the beginning of the first page. */
const appendPageItem = <T>(queryKey: QueryKey, newItem: T) => {
queryClient.setQueryData<InfiniteData<PaginatedResult<T>>>(queryKey, (data) => {
queryClient.setQueryData<InfiniteData<PaginatedResponse<T>>>(queryKey, (data) => {
if (data) {
const pages = [...data.pages];
pages[0] = { ...pages[0], result: [newItem, ...pages[0].result] };
pages[0] = { ...pages[0], items: [newItem, ...pages[0].items] };
return { ...data, pages };
}
});
@ -63,11 +57,11 @@ const appendPageItem = <T>(queryKey: QueryKey, newItem: T) => {
/** Remove an item inside if found. */
const removePageItem = <T>(queryKey: QueryKey, itemToRemove: T, isItem: (item: T, newItem: T) => boolean) => {
queryClient.setQueriesData<InfiniteData<PaginatedResult<T>>>({ queryKey }, (data) => {
queryClient.setQueriesData<InfiniteData<PaginatedResponse<T>>>({ queryKey }, (data) => {
if (data) {
const pages = data.pages.map(page => {
const result = page.result.filter(item => !isItem(item, itemToRemove));
return { ...page, result };
const items = page.items.filter(item => !isItem(item, itemToRemove));
return { ...page, items };
});
return { ...data, pages };
}
@ -88,7 +82,7 @@ const paginateQueryData = <T>(array: T[] | undefined) =>
}, []);
const sortQueryData = <T>(queryKey: QueryKey, comparator: (a: T, b: T) => number) => {
queryClient.setQueryData<InfiniteData<PaginatedResult<T>>>(queryKey, (prevResult) => {
queryClient.setQueryData<InfiniteData<PaginatedResponse<T>>>(queryKey, (prevResult) => {
if (prevResult) {
const nextResult = { ...prevResult };
const flattenedQueryData = flattenPages(nextResult);
@ -106,7 +100,6 @@ const sortQueryData = <T>(queryKey: QueryKey, comparator: (a: T, b: T) => number
};
export {
type PaginatedResult,
flattenPages,
updatePageItem,
appendPageItem,

View file

@ -6475,11 +6475,16 @@ immer@^10.0.3:
resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.3.tgz#a8de42065e964aa3edf6afc282dfc7f7f34ae3c9"
integrity sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==
immutable@^4.0.0, immutable@^4.2.1:
immutable@^4.0.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f"
integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==
immutable@^4.3.7:
version "4.3.7"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"