From 34663552b9e86b5c0a5db049a2adaf763215c162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20My=C5=9Bli=C5=84ski?= Date: Sat, 14 Mar 2026 13:03:44 +0100 Subject: [PATCH 01/25] nicolium: respect `with_replies` search param when selecting active tab when `?with_replies=true` was present in the profile url, the profile page still highlighted the "Posts" tab because active tab selection only checked the pathname and ignored the search param --- packages/nicolium/src/features/ui/router/index.tsx | 3 +++ packages/nicolium/src/layouts/profile-layout.tsx | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/nicolium/src/features/ui/router/index.tsx b/packages/nicolium/src/features/ui/router/index.tsx index 669325035..9c75401d1 100644 --- a/packages/nicolium/src/features/ui/router/index.tsx +++ b/packages/nicolium/src/features/ui/router/index.tsx @@ -145,6 +145,9 @@ const layouts = { getParentRoute: () => rootRoute, path: '/@{$username}', component: ProfileLayout, + validateSearch: v.object({ + with_replies: v.optional(v.boolean()), + }), }), remoteInstance: createRoute({ getParentRoute: () => rootRoute, diff --git a/packages/nicolium/src/layouts/profile-layout.tsx b/packages/nicolium/src/layouts/profile-layout.tsx index b25942688..2c6a85530 100644 --- a/packages/nicolium/src/layouts/profile-layout.tsx +++ b/packages/nicolium/src/layouts/profile-layout.tsx @@ -26,6 +26,7 @@ import { LOCAL_STORAGE_REDIRECT_KEY } from '@/utils/redirect'; /** Layout to display a user's profile. */ const ProfileLayout: React.FC = () => { const { username } = layouts.profile.useParams(); + const { with_replies: withReplies } = layouts.profile.useSearch(); const location = useLocation(); const { data: account, isUnauthorized } = useAccountLookup(username, true); @@ -80,7 +81,7 @@ const ProfileLayout: React.FC = () => { let activeItem; const pathname = location.pathname.replace(`@${username}/`, ''); - if (pathname.endsWith('/with_replies')) { + if (withReplies) { activeItem = 'replies'; } else if (pathname.endsWith('/media')) { activeItem = 'media'; From 888810f9064adff05f96bf370b40121f371210ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sat, 14 Mar 2026 13:55:09 +0100 Subject: [PATCH 02/25] nicolium: A likely working Redux to Zustand translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/package.json | 4 - packages/nicolium/src/actions/accounts.ts | 10 +- packages/nicolium/src/actions/admin.ts | 96 +--- packages/nicolium/src/actions/auth.ts | 507 +++++++++--------- packages/nicolium/src/actions/chats.ts | 6 +- packages/nicolium/src/actions/circle.ts | 11 +- .../nicolium/src/actions/consumer-auth.ts | 43 +- packages/nicolium/src/actions/events.ts | 103 ++-- packages/nicolium/src/actions/export-data.ts | 16 +- .../nicolium/src/actions/external-auth.ts | 14 +- .../nicolium/src/actions/frontend-config.ts | 95 +--- packages/nicolium/src/actions/instance.ts | 71 +-- packages/nicolium/src/actions/me.ts | 173 ------ packages/nicolium/src/actions/media.ts | 174 +++--- packages/nicolium/src/actions/moderation.tsx | 68 +-- packages/nicolium/src/actions/oauth.ts | 17 +- packages/nicolium/src/actions/preload.ts | 73 ++- .../actions/push-notifications/registerer.ts | 70 +-- .../src/actions/push-notifications/setter.ts | 34 -- .../src/actions/push-subscriptions.ts | 9 +- .../nicolium/src/actions/remote-timeline.ts | 18 +- packages/nicolium/src/actions/reports.ts | 29 +- packages/nicolium/src/actions/security.ts | 72 +-- packages/nicolium/src/actions/settings.ts | 109 ++-- packages/nicolium/src/actions/statuses.ts | 319 ++++++----- .../hooks/streaming/use-timeline-stream.ts | 10 +- .../api/hooks/streaming/use-user-stream.ts | 52 +- packages/nicolium/src/api/index.ts | 12 +- .../nicolium/src/columns/notifications.tsx | 4 +- packages/nicolium/src/columns/timeline.tsx | 4 +- .../accounts/account-hover-card.tsx | 4 +- .../src/components/accounts/account.tsx | 4 +- packages/nicolium/src/components/helmet.tsx | 2 +- .../navigation/dropdown-navigation.tsx | 17 +- .../navigation/sidebar-navigation.tsx | 2 +- .../navigation/thumb-navigation.tsx | 5 +- .../nicolium/src/components/polls/poll.tsx | 4 +- .../src/components/statuses/event-preview.tsx | 4 +- .../components/statuses/status-action-bar.tsx | 28 +- .../src/components/statuses/status.tsx | 48 +- .../components/statuses/translate-button.tsx | 6 +- .../nicolium/src/contexts/chat-context.tsx | 4 +- .../src/contexts/current-account-context.tsx | 12 +- .../account/components/account-menu.tsx | 10 +- .../components/registration-mode-picker.tsx | 2 +- .../auth-login/components/captcha.tsx | 5 +- .../auth-login/components/consumer-button.tsx | 4 +- .../auth-login/components/consumers-list.tsx | 2 +- .../auth-login/components/otp-auth-form.tsx | 8 +- .../components/registration-form.tsx | 8 +- .../chats/components/chat-composer.tsx | 2 +- .../chats/components/chat-list-shoutbox.tsx | 2 +- .../chats/components/chat-message.tsx | 4 +- .../chat-widget/shoutbox-window.tsx | 2 +- .../src/features/chats/components/chat.tsx | 4 +- .../components/chats-page-settings.tsx | 4 +- .../components/chats-page-shoutbox.tsx | 2 +- .../chats/components/shoutbox-composer.tsx | 2 +- .../components/shoutbox-message-list.tsx | 4 +- .../components/upload-button.tsx | 9 +- .../compose-event/tabs/edit-event.tsx | 50 +- .../compose/components/compose-form.tsx | 2 +- .../components/content-type-button.tsx | 2 +- .../compose/components/drive-button.tsx | 2 +- .../components/hashtag-casing-suggestion.tsx | 6 +- .../compose/components/polls/poll-form.tsx | 2 +- .../compose/components/upload-button.tsx | 2 +- .../features/compose/components/upload.tsx | 2 +- .../editor/plugins/autosuggest-plugin.tsx | 4 +- .../floating-block-type-toolbar-plugin.tsx | 26 +- .../compose/editor/plugins/state-plugin.tsx | 4 +- .../components/crypto-donate-panel.tsx | 2 +- .../components/draft-status-action-bar.tsx | 4 +- .../components/emoji-picker-dropdown.tsx | 6 +- .../event/components/event-action-button.tsx | 4 +- .../event/components/event-header.tsx | 4 +- .../components/external-login-form.tsx | 4 +- .../components/instance-restrictions.tsx | 2 +- .../notifications/components/notification.tsx | 2 +- .../src/features/preferences/index.tsx | 69 +-- .../scheduled-status-action-bar.tsx | 7 +- .../security/mfa/disable-otp-form.tsx | 4 +- .../components/status-interaction-bar.tsx | 3 - .../src/features/ui/components/modal-root.tsx | 6 +- .../components/panels/account-note-panel.tsx | 4 +- .../components/panels/instance-info-panel.tsx | 6 +- .../components/panels/profile-info-panel.tsx | 4 +- .../ui/components/panels/promo-panel.tsx | 2 +- .../ui/components/panels/sign-up-panel.tsx | 20 +- .../ui/components/profile-dropdown.tsx | 6 +- .../features/ui/components/theme-toggle.tsx | 4 +- packages/nicolium/src/features/ui/index.tsx | 31 +- .../nicolium/src/features/ui/router/index.tsx | 7 +- .../nicolium/src/features/ui/router/util.tsx | 5 +- packages/nicolium/src/hooks/use-acct.ts | 7 +- .../nicolium/src/hooks/use-app-dispatch.ts | 7 - .../nicolium/src/hooks/use-app-selector.ts | 7 - .../nicolium/src/hooks/use-can-interact.ts | 4 +- packages/nicolium/src/hooks/use-client.ts | 6 +- packages/nicolium/src/hooks/use-features.ts | 4 +- .../nicolium/src/hooks/use-frontend-config.ts | 17 +- packages/nicolium/src/hooks/use-instance.ts | 6 - packages/nicolium/src/hooks/use-logged-in.ts | 7 +- .../src/hooks/use-registration-status.ts | 3 +- packages/nicolium/src/init/nicolium-load.tsx | 24 +- packages/nicolium/src/init/nicolium.tsx | 26 +- .../nicolium/src/layouts/default-layout.tsx | 5 +- .../nicolium/src/layouts/event-layout.tsx | 5 +- .../src/layouts/external-login-layout.tsx | 8 +- packages/nicolium/src/layouts/home-layout.tsx | 4 +- .../nicolium/src/layouts/landing-layout.tsx | 5 +- .../nicolium/src/layouts/profile-layout.tsx | 4 +- .../src/layouts/remote-instance-layout.tsx | 5 +- .../nicolium/src/layouts/search-layout.tsx | 5 +- .../nicolium/src/layouts/status-layout.tsx | 5 +- packages/nicolium/src/middleware/errors.ts | 35 -- packages/nicolium/src/middleware/sounds.ts | 22 - .../nicolium/src/modals/alt-text-modal.tsx | 4 +- .../manage-group-modal/steps/details-step.tsx | 7 +- .../src/modals/report-modal/index.tsx | 7 +- .../report-modal/steps/confirmation-step.tsx | 4 +- .../modals/report-modal/steps/reason-step.tsx | 2 +- .../src/modals/unauthorized-modal.tsx | 2 +- packages/nicolium/src/normalizers/status.ts | 7 +- .../src/pages/account-lists/directory.tsx | 6 +- packages/nicolium/src/pages/auth/login.tsx | 18 +- packages/nicolium/src/pages/auth/logout.tsx | 4 +- .../src/pages/auth/password-reset.tsx | 4 +- .../src/pages/auth/register-with-invite.tsx | 2 +- .../nicolium/src/pages/auth/registration.tsx | 2 +- .../nicolium/src/pages/dashboard/account.tsx | 28 +- .../src/pages/dashboard/dashboard.tsx | 2 +- .../src/pages/dashboard/frontend-config.tsx | 9 +- .../src/pages/dashboard/theme-editor.tsx | 10 +- .../src/pages/developers/settings-store.tsx | 6 +- packages/nicolium/src/pages/fun/circle.tsx | 20 +- .../nicolium/src/pages/groups/edit-group.tsx | 7 +- .../nicolium/src/pages/settings/aliases.tsx | 5 +- .../src/pages/settings/auth-token-list.tsx | 18 +- .../src/pages/settings/delete-account.tsx | 6 +- .../src/pages/settings/domain-blocks.tsx | 10 +- .../src/pages/settings/edit-email.tsx | 6 +- .../src/pages/settings/edit-password.tsx | 6 +- .../src/pages/settings/edit-profile.tsx | 13 +- .../src/pages/settings/export-data.tsx | 9 +- .../pages/settings/interaction-policies.tsx | 6 +- .../nicolium/src/pages/settings/migration.tsx | 6 +- .../nicolium/src/pages/settings/privacy.tsx | 20 +- .../src/pages/statuses/event-discussion.tsx | 4 +- .../nicolium/src/pages/statuses/status.tsx | 7 +- .../src/pages/timelines/home-timeline.tsx | 4 +- .../src/pages/timelines/landing-timeline.tsx | 2 +- .../src/pages/timelines/public-timeline.tsx | 8 +- .../src/pages/utils/crypto-donate.tsx | 2 +- .../pages/utils/federation-restrictions.tsx | 7 +- .../nicolium/src/pages/utils/server-info.tsx | 2 +- .../src/queries/accounts/selectors.ts | 10 +- .../accounts/use-account-credentials.ts | 11 +- .../accounts/use-logged-in-accounts.ts | 28 +- .../src/queries/accounts/use-relationship.ts | 8 +- .../src/queries/admin/use-accounts.ts | 51 ++ .../nicolium/src/queries/admin/use-config.ts | 14 +- .../nicolium/src/queries/admin/use-reports.ts | 6 +- .../instance/use-translation-languages.ts | 2 +- .../src/queries/security/oauth-tokens.ts | 25 +- .../src/queries/settings/domain-blocks.ts | 72 +-- .../queries/statuses/scheduled-statuses.ts | 16 +- .../queries/statuses/use-draft-statuses.ts | 5 +- .../src/queries/statuses/use-status.ts | 2 - .../src/queries/timelines/use-events-lists.ts | 30 +- .../utils/make-paginated-response-query.ts | 14 +- packages/nicolium/src/reducers/auth.ts | 467 ---------------- .../nicolium/src/reducers/frontend-config.ts | 86 --- packages/nicolium/src/reducers/index.ts | 47 -- packages/nicolium/src/reducers/instance.ts | 110 ---- packages/nicolium/src/reducers/me.ts | 49 -- packages/nicolium/src/reducers/meta.ts | 17 - .../src/reducers/push-notifications.ts | 60 --- packages/nicolium/src/selectors/index.ts | 2 +- packages/nicolium/src/store.ts | 25 - packages/nicolium/src/stores/auth.ts | 481 +++++++++++++++++ packages/nicolium/src/stores/compose.ts | 81 ++- .../nicolium/src/stores/frontend-config.ts | 96 ++++ packages/nicolium/src/stores/instance.ts | 104 ++++ .../nicolium/src/stores/push-notifications.ts | 65 +++ packages/nicolium/src/stores/settings.ts | 42 +- packages/nicolium/src/utils/auth.ts | 39 +- packages/nicolium/src/utils/scopes.ts | 6 +- packages/nicolium/src/utils/state.ts | 38 +- packages/nicolium/src/utils/url-purify.ts | 2 +- pnpm-lock.yaml | 83 +-- 191 files changed, 2297 insertions(+), 3155 deletions(-) delete mode 100644 packages/nicolium/src/actions/me.ts delete mode 100644 packages/nicolium/src/actions/push-notifications/setter.ts delete mode 100644 packages/nicolium/src/hooks/use-app-dispatch.ts delete mode 100644 packages/nicolium/src/hooks/use-app-selector.ts delete mode 100644 packages/nicolium/src/hooks/use-instance.ts delete mode 100644 packages/nicolium/src/middleware/errors.ts delete mode 100644 packages/nicolium/src/middleware/sounds.ts delete mode 100644 packages/nicolium/src/reducers/auth.ts delete mode 100644 packages/nicolium/src/reducers/frontend-config.ts delete mode 100644 packages/nicolium/src/reducers/index.ts delete mode 100644 packages/nicolium/src/reducers/instance.ts delete mode 100644 packages/nicolium/src/reducers/me.ts delete mode 100644 packages/nicolium/src/reducers/meta.ts delete mode 100644 packages/nicolium/src/reducers/push-notifications.ts delete mode 100644 packages/nicolium/src/store.ts create mode 100644 packages/nicolium/src/stores/auth.ts create mode 100644 packages/nicolium/src/stores/frontend-config.ts create mode 100644 packages/nicolium/src/stores/instance.ts create mode 100644 packages/nicolium/src/stores/push-notifications.ts diff --git a/packages/nicolium/package.json b/packages/nicolium/package.json index 784dbfc9f..27a96ef2f 100644 --- a/packages/nicolium/package.json +++ b/packages/nicolium/package.json @@ -56,7 +56,6 @@ "@reach/rect": "^0.18.0", "@reach/tabs": "^0.18.0", "@react-spring/web": "^10.0.3", - "@reduxjs/toolkit": "^2.11.2", "@sentry/browser": "^10.43.0", "@sentry/core": "^10.43.0", "@sentry/react": "^10.43.0", @@ -114,13 +113,10 @@ "react-hot-toast": "^2.6.0", "react-inlinesvg": "^4.2.0", "react-intl": "^8.1.3", - "react-redux": "^9.2.0", "react-sparklines": "^1.7.0", "react-sticky-box": "^2.0.5", "react-swipeable-views": "^0.14.1", "react-virtuoso": "^4.18.3", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", "reselect": "^5.1.1", "sass-embedded": "^1.98.0", "stringz": "^2.1.0", diff --git a/packages/nicolium/src/actions/accounts.ts b/packages/nicolium/src/actions/accounts.ts index 0b3f5adc4..a576ae119 100644 --- a/packages/nicolium/src/actions/accounts.ts +++ b/packages/nicolium/src/actions/accounts.ts @@ -1,12 +1,10 @@ import { getClient } from '@/api'; -import type { AppDispatch, RootState } from '@/store'; import type { CreateAccountParams } from 'pl-api'; -const createAccount = - (params: CreateAccountParams) => (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState()) - .settings.createAccount(params) - .then((response) => ({ params, response })); +const createAccount = (params: CreateAccountParams) => + getClient() + .settings.createAccount(params) + .then((response) => ({ params, response })); export { createAccount }; diff --git a/packages/nicolium/src/actions/admin.ts b/packages/nicolium/src/actions/admin.ts index 442421875..e564cded8 100644 --- a/packages/nicolium/src/actions/admin.ts +++ b/packages/nicolium/src/actions/admin.ts @@ -3,74 +3,26 @@ import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; import { useComposeStore } from '@/stores/compose'; import { useModalsStore } from '@/stores/modals'; -import { filterBadges, getTagDiff } from '@/utils/badges'; -import type { AppDispatch, RootState } from '@/store'; -import type { PleromaConfig } from 'pl-api'; +const promoteToAdmin = (accountId: string) => getClient().admin.accounts.promoteToAdmin(accountId); -const ADMIN_CONFIG_UPDATE_SUCCESS = 'ADMIN_CONFIG_UPDATE_SUCCESS' as const; +const promoteToModerator = (accountId: string) => + getClient().admin.accounts.promoteToModerator(accountId); -const deactivateUser = - (accountId: string, report_id?: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); +const demoteToUser = (accountId: string) => getClient().admin.accounts.demoteToUser(accountId); - return getClient(state).admin.accounts.performAccountAction(accountId, 'suspend', { - report_id, - }); - }; +const setRole = (accountId: string, role: 'user' | 'moderator' | 'admin') => { + switch (role) { + case 'user': + return demoteToUser(accountId); + case 'moderator': + return promoteToModerator(accountId); + case 'admin': + return promoteToAdmin(accountId); + } +}; -const deleteUser = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).admin.accounts.deleteAccount(accountId); - -const tagUser = - (accountId: string, tags: string[]) => (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).admin.accounts.tagUser(accountId, tags); - -const untagUser = - (accountId: string, tags: string[]) => (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).admin.accounts.untagUser(accountId, tags); - -/** Synchronizes user tags to the backend. */ -const setTags = - (accountId: string, oldTags: string[], newTags: string[]) => async (dispatch: AppDispatch) => { - const diff = getTagDiff(oldTags, newTags); - - if (diff.added.length) await dispatch(tagUser(accountId, diff.added)); - if (diff.removed.length) await dispatch(untagUser(accountId, diff.removed)); - }; - -/** Synchronizes badges to the backend. */ -const setBadges = - (accountId: string, oldTags: string[], newTags: string[]) => (dispatch: AppDispatch) => { - const oldBadges = filterBadges(oldTags); - const newBadges = filterBadges(newTags); - - return dispatch(setTags(accountId, oldBadges, newBadges)); - }; - -const promoteToAdmin = (accountId: string) => (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).admin.accounts.promoteToAdmin(accountId); - -const promoteToModerator = - (accountId: string) => (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).admin.accounts.promoteToModerator(accountId); - -const demoteToUser = (accountId: string) => (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).admin.accounts.demoteToUser(accountId); - -const setRole = - (accountId: string, role: 'user' | 'moderator' | 'admin') => (dispatch: AppDispatch) => { - switch (role) { - case 'user': - return dispatch(demoteToUser(accountId)); - case 'moderator': - return dispatch(promoteToModerator(accountId)); - case 'admin': - return dispatch(promoteToAdmin(accountId)); - } - }; - -const redactStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { +const redactStatus = (statusId: string) => { const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); if (!status) return; @@ -78,7 +30,7 @@ const redactStatus = (statusId: string) => (dispatch: AppDispatch, getState: () ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) : undefined; - return getClient(getState()) + return getClient() .statuses.getStatusSource(statusId) .then((source) => { useComposeStore @@ -88,18 +40,4 @@ const redactStatus = (statusId: string) => (dispatch: AppDispatch, getState: () }); }; -type AdminActions = { - type: typeof ADMIN_CONFIG_UPDATE_SUCCESS; - configs: PleromaConfig['configs']; - needsReboot: boolean; -}; - -export { - ADMIN_CONFIG_UPDATE_SUCCESS, - deactivateUser, - deleteUser, - setBadges, - setRole, - redactStatus, - type AdminActions, -}; +export { setRole, redactStatus }; diff --git a/packages/nicolium/src/actions/auth.ts b/packages/nicolium/src/actions/auth.ts index 294c3df60..b8db5b83b 100644 --- a/packages/nicolium/src/actions/auth.ts +++ b/packages/nicolium/src/actions/auth.ts @@ -1,54 +1,36 @@ /** * Auth: login & registration workflow. - * This file contains abstractions over auth concepts. * @module @/actions/auth - * @see module:@/actions/apps - * @see module:@/actions/oauth - * @see module:@/actions/security */ import { PlApiClient, - type Account, type CreateAccountParams, type CredentialAccount, type CredentialApplication, type Token, + type UpdateCredentialsParams, } from 'pl-api'; import { defineMessages } from 'react-intl'; import { createAccount } from '@/actions/accounts'; import { createApp } from '@/actions/apps'; -import { fetchMeSuccess, fetchMeFail } from '@/actions/me'; import { obtainOAuthToken, revokeOAuthToken } from '@/actions/oauth'; -import { type NicoliumResponse, getClient } from '@/api'; import * as BuildConfig from '@/build-config'; -import { selectAccount } from '@/queries/accounts/selectors'; +import { selectAccount, selectOwnAccount } from '@/queries/accounts/selectors'; import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; -import { unsetSentryAccount } from '@/sentry'; +import { setSentryAccount, unsetSentryAccount } from '@/sentry'; import KVStore from '@/storage/kv-store'; +import { useAuthStore } from '@/stores/auth'; +import { useComposeStore } from '@/stores/compose'; +import { useSettingsStore } from '@/stores/settings'; import toast from '@/toast'; -import { getLoggedInAccount, parseBaseURL } from '@/utils/auth'; +import { parseBaseURL } from '@/utils/auth'; import sourceCode from '@/utils/code'; import { normalizeUsername } from '@/utils/input'; import { getScopes } from '@/utils/scopes'; import { isStandalone } from '@/utils/state'; -import type { AppDispatch, RootState } from '@/store'; - -const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT' as const; - -const AUTH_APP_CREATED = 'AUTH_APP_CREATED' as const; -const AUTH_APP_AUTHORIZED = 'AUTH_APP_AUTHORIZED' as const; -const AUTH_LOGGED_IN = 'AUTH_LOGGED_IN' as const; -const AUTH_LOGGED_OUT = 'AUTH_LOGGED_OUT' as const; - -const VERIFY_CREDENTIALS_REQUEST = 'VERIFY_CREDENTIALS_REQUEST' as const; -const VERIFY_CREDENTIALS_SUCCESS = 'VERIFY_CREDENTIALS_SUCCESS' as const; -const VERIFY_CREDENTIALS_FAIL = 'VERIFY_CREDENTIALS_FAIL' as const; - -const AUTH_ACCOUNT_REMEMBER_SUCCESS = 'AUTH_ACCOUNT_REMEMBER_SUCCESS' as const; - const messages = defineMessages({ loggedOut: { id: 'auth.logged_out', defaultMessage: 'Logged out.' }, awaitingApproval: { @@ -61,296 +43,305 @@ const messages = defineMessages({ }, }); -const noOp = () => - new Promise((f) => { - f(undefined); - }); +const getAuth = () => useAuthStore.getState(); +const getActions = () => getAuth().actions; -const createAppAndToken = () => (dispatch: AppDispatch) => - dispatch(createAuthApp()).then(() => dispatch(createAppToken())); - -interface AuthAppCreatedAction { - type: typeof AUTH_APP_CREATED; - app: CredentialApplication; -} - -const createAuthApp = () => (dispatch: AppDispatch, getState: () => RootState) => { +const createAuthApp = async () => { const params = { client_name: `${sourceCode.displayName} (${new URL(window.origin).host})`, redirect_uris: 'urn:ietf:wg:oauth:2.0:oob', - scopes: getScopes(getState()), + scopes: getScopes(), website: sourceCode.homepage, }; - return createApp(params).then((app) => - dispatch({ type: AUTH_APP_CREATED, app }), - ); + const app = await createApp(params); + getActions().setApp(app); + return app; }; -interface AuthAppAuthorizedAction { - type: typeof AUTH_APP_AUTHORIZED; - app: CredentialApplication; - token: Token; -} - -const createAppToken = () => (dispatch: AppDispatch, getState: () => RootState) => { - const app = getState().auth.app!; +const createAppToken = async () => { + const { app } = getAuth(); const params = { - client_id: app.client_id, - client_secret: app.client_secret, + client_id: app?.client_id!, + client_secret: app?.client_secret!, redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', grant_type: 'client_credentials', - scope: getScopes(getState()), + scope: getScopes(), }; - return obtainOAuthToken(params).then((token) => - dispatch({ type: AUTH_APP_AUTHORIZED, app, token }), - ); + const token = await obtainOAuthToken(params); + getActions().setAppToken(token); + return token; }; -const createUserToken = - (username: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const app = getState().auth.app; +const createAppAndToken = async () => { + const app = await createAuthApp(); + await createAppToken(); + return { app }; +}; - const params = { - client_id: app?.client_id!, - client_secret: app?.client_secret!, - redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', - grant_type: 'password', - username: username, - password: password, - scope: getScopes(getState()), - }; - - return obtainOAuthToken(params).then((token) => dispatch(authLoggedIn(token, app))); - }; - -const otpVerify = - (code: string, mfa_token: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const app = state.auth.app; - const baseUrl = parseBaseURL(state.me || undefined) || BuildConfig.BACKEND_URL; - const client = new PlApiClient(baseUrl); - return client.oauth - .mfaChallenge({ - client_id: app?.client_id!, - client_secret: app?.client_secret!, - mfa_token: mfa_token, - code: code, - challenge_type: 'totp', - // redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', - // scope: getScopes(getState()), - }) - .then((token) => dispatch(authLoggedIn(token, app))); - }; - -interface VerifyCredentialsRequestAction { - type: typeof VERIFY_CREDENTIALS_REQUEST; - token: string; -} - -interface VerifyCredentialsSuccessAction { - type: typeof VERIFY_CREDENTIALS_SUCCESS; - token: string; - account: CredentialAccount; -} - -interface VerifyCredentialsFailAction { - type: typeof VERIFY_CREDENTIALS_FAIL; - token: string; - error: unknown; -} - -const verifyCredentials = - (token: string, accountUrl?: string) => - async (dispatch: AppDispatch, getState: () => RootState) => { - const baseURL = parseBaseURL(accountUrl) || BuildConfig.BACKEND_URL; - - dispatch({ type: VERIFY_CREDENTIALS_REQUEST, token }); - - const client = new PlApiClient(baseURL, token); - - await client.instance.getInstance(); - - return client.settings - .verifyCredentials() - .then((account) => { - queryClient.setQueryData(queryKeys.accounts.show(account.id), account); - dispatch({ - type: VERIFY_CREDENTIALS_SUCCESS, - token, - account, - }); - if (account.id === getState().me) dispatch(fetchMeSuccess(account)); - return account; - }) - .catch((error) => { - if (getState().me === null) dispatch(fetchMeFail(error)); - dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error }); - throw error; - }); - }; - -interface AuthAccountRememberSuccessAction { - type: typeof AUTH_ACCOUNT_REMEMBER_SUCCESS; - accountUrl: string; - account: CredentialAccount; -} - -const rememberAuthAccount = - (accountUrl: string) => (dispatch: AppDispatch, getState: () => RootState) => - KVStore.getItemOrError(`authAccount:${accountUrl}`).then((account) => { - queryClient.setQueryData(queryKeys.accounts.show(account.id), account); - dispatch({ - type: AUTH_ACCOUNT_REMEMBER_SUCCESS, - account, - accountUrl, - }); - if (account.id === getState().me) dispatch(fetchMeSuccess(account)); - return account; - }); - -const loadCredentials = (token: string, accountUrl: string) => (dispatch: AppDispatch) => - dispatch(rememberAuthAccount(accountUrl)).finally(() => - dispatch(verifyCredentials(token, accountUrl)), - ); - -const logIn = (username: string, password: string) => (dispatch: AppDispatch) => - dispatch(createAuthApp()) - .then(() => dispatch(createUserToken(normalizeUsername(username), password))) - .catch((error: { response: NicoliumResponse }) => { - if (error.response?.json?.error === 'mfa_required') { - // If MFA is required, throw the error and handle it in the component. - throw error; - } else if (error.response?.json?.identifier === 'awaiting_approval') { - toast.error(messages.awaitingApproval); - } else { - // Return "wrong password" message. - toast.error(messages.invalidCredentials); - } - throw error; - }); - -interface AuthLoggedOutAction { - type: typeof AUTH_LOGGED_OUT; - account: Account; - standalone: boolean; -} - -const logOut = () => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const account = getLoggedInAccount(state); - const standalone = isStandalone(state); - - if (!account) return dispatch(noOp); - - const token = state.auth.users[account.url].access_token; +const createUserToken = async (username: string, password: string) => { + const { app } = getAuth(); const params = { - client_id: state.auth.tokens[token]?.client_id ?? state.auth.app?.client_id!, - client_secret: state.auth.tokens[token]?.client_secret ?? state.auth.app?.client_secret!, + client_id: app?.client_id!, + client_secret: app?.client_secret!, + redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', + grant_type: 'password', + username, + password, + scope: getScopes(), + }; + + const token = await obtainOAuthToken(params); + authLoggedIn(token, app); + return token; +}; + +const otpVerify = async (code: string, mfa_token: string) => { + const { app, me } = getAuth(); + const baseUrl = parseBaseURL(me || undefined) || BuildConfig.BACKEND_URL; + const client = new PlApiClient(baseUrl); + + const token = await client.oauth.mfaChallenge({ + client_id: app?.client_id!, + client_secret: app?.client_secret!, + mfa_token, + code, + challenge_type: 'totp', + }); + + authLoggedIn(token, app); + return token; +}; + +const verifyCredentials = async (token: string, accountUrl?: string) => { + const baseURL = parseBaseURL(accountUrl) || BuildConfig.BACKEND_URL; + const client = new PlApiClient(baseURL, token); + + await client.instance.getInstance(); + + try { + const account = await client.settings.verifyCredentials(); + queryClient.setQueryData(queryKeys.accounts.show(account.id), account); + getActions().importCredentials(token, account); + if (account.id === getAuth().currentAccountId) fetchMeSuccess(account); + return account; + } catch (error: any) { + if (getAuth().currentAccountId === null) getActions().importCredentialsFailed(error); + getActions().removeFailedToken(token, error); + throw error; + } +}; + +const rememberAuthAccount = async (accountUrl: string) => { + const account = await KVStore.getItemOrError(`authAccount:${accountUrl}`); + queryClient.setQueryData(queryKeys.accounts.show(account.id), account); + getActions().setCurrentAccountIfUnset(account); + if (account.id === getAuth().currentAccountId) fetchMeSuccess(account); + return account; +}; + +const loadCredentials = async (token: string, accountUrl: string) => { + try { + await rememberAuthAccount(accountUrl); + } finally { + await verifyCredentials(token, accountUrl); + } +}; + +const logIn = async (username: string, password: string) => { + try { + await createAuthApp(); + return await createUserToken(normalizeUsername(username), password); + } catch (error: any) { + if (error.response?.json?.error === 'mfa_required') { + throw error; + } else if (error.response?.json?.identifier === 'awaiting_approval') { + toast.error(messages.awaitingApproval); + } else { + toast.error(messages.invalidCredentials); + } + throw error; + } +}; + +const logOut = async () => { + const state = getAuth(); + const account = selectOwnAccount(); + const standalone = isStandalone(); + + if (!account) return; + + const token = state.users[account.url]?.access_token; + + const params = { + client_id: state.tokens[token]?.client_id ?? state.app?.client_id!, + client_secret: state.tokens[token]?.client_secret ?? state.app?.client_secret!, token, }; - return dispatch(revokeOAuthToken(params)).finally(() => { - // Clear all stored cache from React Query + try { + await revokeOAuthToken(params); + } finally { queryClient.invalidateQueries(); queryClient.clear(); - - // Clear the account from Sentry. unsetSentryAccount(); - - dispatch({ type: AUTH_LOGGED_OUT, account, standalone }); - + getActions().removeToken(account, standalone); toast.success(messages.loggedOut); - }); + } }; -interface SwitchAccountAction { - type: typeof SWITCH_ACCOUNT; - account: Account; -} - -const switchAccount = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { +const switchAccount = (accountId: string) => { const account = selectAccount(accountId); if (!account) return; - if (typeof getState().me === 'string' && getState().me !== account.id) { - // Clear all stored cache from React Query + const { currentAccountId } = getAuth(); + if (typeof currentAccountId === 'string' && currentAccountId !== account.id) { queryClient.invalidateQueries(); queryClient.clear(); } - return dispatch({ type: SWITCH_ACCOUNT, account }); + getActions().switchAccount(account); }; -const fetchOwnAccounts = () => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - Object.values(state.auth.users).forEach((user) => { +const fetchOwnAccounts = () => { + const { users } = getAuth(); + Object.values(users).forEach((user) => { const account = selectAccount(user.id); if (!account) { - dispatch(verifyCredentials(user.access_token, user.url)).catch(() => { + verifyCredentials(user.access_token, user.url).catch(() => { console.warn(`Failed to load account: ${user.url}`); }); } }); }; -const register = (params: CreateAccountParams) => async (dispatch: AppDispatch) => { +const register = async (params: CreateAccountParams) => { params.fullname = params.username; - const { app } = await dispatch(createAppAndToken()); + const { app } = await createAppAndToken(); - return dispatch(createAccount(params)).then(({ response }) => { - if ('identifier' in response) { - toast.info(response.message); - } else { - return dispatch(authLoggedIn(response, app)); - } - }); + const { response } = await createAccount(params); + if ('identifier' in response) { + toast.info(response.message); + } else { + authLoggedIn(response, app); + return response; + } }; -const fetchCaptcha = () => (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).oauth.getCaptcha(); +const fetchCaptcha = () => getAuth().client.oauth.getCaptcha(); -interface AuthLoggedInAction { - type: typeof AUTH_LOGGED_IN; - token: Token; - app?: CredentialApplication; -} +const authLoggedIn = (token: Token, app?: CredentialApplication | null) => { + getActions().importToken(token, app ?? undefined); + return token; +}; -const authLoggedIn = - (token: Token, app?: CredentialApplication | null) => (dispatch: AppDispatch) => { - dispatch({ type: AUTH_LOGGED_IN, token, app: app ?? undefined }); - return token; - }; +const getMeUrl = () => { + const state = getAuth(); + const accountId = state.currentAccountId; + if (typeof accountId === 'string') { + return selectAccount(accountId)?.url ?? state.me; + } + return state.me; +}; -type AuthAction = - | SwitchAccountAction - | AuthAppCreatedAction - | AuthAppAuthorizedAction - | AuthLoggedInAction - | AuthLoggedOutAction - | VerifyCredentialsRequestAction - | VerifyCredentialsSuccessAction - | VerifyCredentialsFailAction - | AuthAccountRememberSuccessAction; +const getMeToken = () => { + const state = getAuth(); + const accountUrl = getMeUrl() ?? state.me; + return state.users[accountUrl!]?.access_token; +}; + +const fetchMe = async () => { + const token = getMeToken(); + const accountUrl = getMeUrl(); + + if (!token) { + getActions().importCredentialsSkip(); + return; + } + + try { + return await loadCredentials(token, accountUrl!); + } catch (error: any) { + getActions().importCredentialsFailed(error); + } +}; + +const patchMe = async (params: UpdateCredentialsParams) => { + const response = await getAuth().client.settings.updateCredentials(params); + persistAuthAccount(response, params); + patchMeSuccess(response); + return response; +}; + +const persistAuthAccount = (account: CredentialAccount, params?: Record) => { + if (!account?.url) return; + const key = `authAccount:${account.url}`; + KVStore.getItem(key) + .then((oldAccount: any) => { + const settings = oldAccount?.settings_store ?? {}; + account.settings_store ??= settings; + if (params) account.settings_store ??= params.pleroma_settings_store ?? {}; + KVStore.setItem(key, account); + }) + .catch(console.error); +}; + +const fetchMeSuccess = async (account: CredentialAccount) => { + const { client } = getAuth(); + + setSentryAccount(account); + + const settings = + account.settings_store?.['nicolium'] ?? + account.settings_store?.['nicolium_dev'] ?? + account.settings_store?.['pl_fe'] ?? + account.settings_store?.['pl_fe_dev']; + + if (settings) { + useSettingsStore.getState().actions.loadUserSettings(settings); + } + + if (!client.features.frontendConfigurations && client.features.notes) { + const note = await client.accounts + .getRelationships([account.id]) + .then((relationships) => relationships[0]?.note); + + if (note) { + const match = note.match(/(.*)<\/nicolium-config>/); + if (match) { + try { + const frontendConfig = JSON.parse(decodeURIComponent(match[1])); + if (typeof frontendConfig === 'object' && frontendConfig !== null) { + frontendConfig.storeSettingsInNotes = true; + } + useSettingsStore.getState().actions.loadUserSettings(frontendConfig); + getActions().setCurrentAccount(account); + return frontendConfig; + } catch (error) { + console.error('Failed to parse frontend config from account note', error); + } + } + } + } + + useComposeStore.getState().actions.importDefaultSettings(account); + getActions().setCurrentAccount(account); +}; + +const patchMeSuccess = (me: CredentialAccount) => { + queryClient.setQueryData(queryKeys.accounts.show(me.id), me); + useComposeStore.getState().actions.importDefaultSettings(me); + getActions().setCurrentAccount(me); +}; export { - SWITCH_ACCOUNT, - AUTH_APP_CREATED, - AUTH_APP_AUTHORIZED, - AUTH_LOGGED_IN, - AUTH_LOGGED_OUT, - VERIFY_CREDENTIALS_REQUEST, - VERIFY_CREDENTIALS_SUCCESS, - VERIFY_CREDENTIALS_FAIL, - AUTH_ACCOUNT_REMEMBER_SUCCESS, messages, otpVerify, verifyCredentials, - loadCredentials, logIn, logOut, switchAccount, @@ -358,5 +349,7 @@ export { register, fetchCaptcha, authLoggedIn, - type AuthAction, + fetchMe, + patchMe, + patchMeSuccess, }; diff --git a/packages/nicolium/src/actions/chats.ts b/packages/nicolium/src/actions/chats.ts index 27af22ca2..aa95f08cc 100644 --- a/packages/nicolium/src/actions/chats.ts +++ b/packages/nicolium/src/actions/chats.ts @@ -1,12 +1,10 @@ import { changeSetting } from '@/actions/settings'; import { useSettingsStore } from '@/stores/settings'; -import type { AppDispatch } from '@/store'; - -const toggleChatPane = () => (dispatch: AppDispatch) => { +const toggleChatPane = () => { const main = useSettingsStore.getState().settings.chats.mainWindow; const state = main === 'minimized' ? 'open' : 'minimized'; - dispatch(changeSetting(['chats', 'mainWindow'], state)); + changeSetting(['chats', 'mainWindow'], state); }; export { toggleChatPane }; diff --git a/packages/nicolium/src/actions/circle.ts b/packages/nicolium/src/actions/circle.ts index 018605756..ddd3d9bb4 100644 --- a/packages/nicolium/src/actions/circle.ts +++ b/packages/nicolium/src/actions/circle.ts @@ -1,9 +1,9 @@ // Loosely adapted from twitter-interaction-circles, licensed under MIT License // https://github.com/duiker101/twitter-interaction-circles import { getClient } from '@/api'; +import { useAuthStore } from '@/stores/auth'; -import type { AppDispatch, RootState } from '@/store'; -import type { PaginatedResponse, Status } from 'pl-api'; +import type { PaginatedResponse, PlApiClient, Status } from 'pl-api'; interface Interaction { acct: string; @@ -16,6 +16,7 @@ interface Interaction { const processCircle = ( + client: PlApiClient, setProgress: (progress: { state: | 'pending' @@ -27,11 +28,11 @@ const processCircle = progress: number; }) => void, ) => - async (dispatch: AppDispatch, getState: () => RootState) => { + async () => { setProgress({ state: 'pending', progress: 0 }); - const client = getClient(getState()); - const me = getState().me as string; + const client = getClient(); + const me = useAuthStore.getState().currentAccountId as string; const interactions: Record = {}; diff --git a/packages/nicolium/src/actions/consumer-auth.ts b/packages/nicolium/src/actions/consumer-auth.ts index 03f2e54af..15b5b0af8 100644 --- a/packages/nicolium/src/actions/consumer-auth.ts +++ b/packages/nicolium/src/actions/consumer-auth.ts @@ -7,10 +7,8 @@ import { getScopes } from '@/utils/scopes'; import { createApp } from './apps'; -import type { AppDispatch, RootState } from '@/store'; - -const createProviderApp = () => (dispatch: AppDispatch, getState: () => RootState) => { - const scopes = getScopes(getState(), undefined, true); +const createProviderApp = () => { + const scopes = getScopes(undefined, true); const params = { client_name: `${sourceCode.displayName} (${new URL(window.origin).host})`, @@ -22,28 +20,27 @@ const createProviderApp = () => (dispatch: AppDispatch, getState: () => RootStat return createApp(params); }; -const prepareRequest = - (provider: string) => async (dispatch: AppDispatch, getState: () => RootState) => { - const baseURL = isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : ''; +const prepareRequest = async (provider: string) => { + const baseURL = isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : ''; - const scopes = getScopes(getState(), undefined, true); - const app = await dispatch(createProviderApp()); - const { client_id, redirect_uri } = app; + const scopes = getScopes(undefined, true); + const app = await createProviderApp(); + const { client_id, redirect_uri } = app; - localStorage.setItem('nicolium:external:app', JSON.stringify(app)); - localStorage.setItem('nicolium:external:baseurl', baseURL); - localStorage.setItem('nicolium:external:scopes', scopes); + localStorage.setItem('nicolium:external:app', JSON.stringify(app)); + localStorage.setItem('nicolium:external:baseurl', baseURL); + localStorage.setItem('nicolium:external:scopes', scopes); - const params = { - provider, - 'authorization[client_id]': client_id, - 'authorization[redirect_uri]': redirect_uri, - 'authorization[scope]': scopes, - }; - - const query = queryString.stringify(params); - - location.href = `${baseURL}/oauth/prepare_request?${query.toString()}`; + const params = { + provider, + 'authorization[client_id]': client_id, + 'authorization[redirect_uri]': redirect_uri, + 'authorization[scope]': scopes, }; + const query = queryString.stringify(params); + + location.href = `${baseURL}/oauth/prepare_request?${query.toString()}`; +}; + export { prepareRequest }; diff --git a/packages/nicolium/src/actions/events.ts b/packages/nicolium/src/actions/events.ts index 05f1a540b..a91a61d12 100644 --- a/packages/nicolium/src/actions/events.ts +++ b/packages/nicolium/src/actions/events.ts @@ -6,7 +6,6 @@ import toast from '@/toast'; import { importEntities } from './importer'; -import type { AppDispatch, RootState } from '@/store'; import type { CreateEventParams, Location, MediaAttachment } from 'pl-api'; const messages = defineMessages({ @@ -27,61 +26,57 @@ const messages = defineMessages({ }, }); -const submitEvent = - ({ - statusId, +const submitEvent = async ({ + statusId, + name, + status, + banner, + startTime, + endTime, + joinMode, + location, +}: { + statusId: string | null; + name: string; + status: string; + banner: MediaAttachment | null; + startTime: Date; + endTime: Date | null; + joinMode: 'restricted' | 'free'; + location: Location | null; +}) => { + if (!name || !name.length) { + return; + } + + const params: CreateEventParams = { name, status, - banner, - startTime, - endTime, - joinMode, - location, - }: { - statusId: string | null; - name: string; - status: string; - banner: MediaAttachment | null; - startTime: Date; - endTime: Date | null; - joinMode: 'restricted' | 'free'; - location: Location | null; - }) => - async (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - - if (!name || !name.length) { - return; - } - - const params: CreateEventParams = { - name, - status, - start_time: startTime.toISOString(), - join_mode: joinMode, - content_type: 'text/markdown', - }; - - if (endTime) params.end_time = endTime?.toISOString(); - if (banner) params.banner_id = banner.id; - if (location) params.location_id = location.origin_id; - - const data = await (statusId === null - ? getClient(state).events.createEvent(params) - : getClient(state).events.editEvent(statusId, params)); - - importEntities({ statuses: [data] }); - toast.success(statusId ? messages.editSuccess : messages.success, { - actionLabel: messages.view, - actionLinkOptions: { - to: '/@{$username}/events/$statusId', - params: { username: data.account.acct, statusId: data.id }, - }, - }); - - return data; + start_time: startTime.toISOString(), + join_mode: joinMode, + content_type: 'text/markdown', }; + if (endTime) params.end_time = endTime?.toISOString(); + if (banner) params.banner_id = banner.id; + if (location) params.location_id = location.origin_id; + + const data = await (statusId === null + ? getClient().events.createEvent(params) + : getClient().events.editEvent(statusId, params)); + + importEntities({ statuses: [data] }); + toast.success(statusId ? messages.editSuccess : messages.success, { + actionLabel: messages.view, + actionLinkOptions: { + to: '/@{$username}/events/$statusId', + params: { username: data.account.acct, statusId: data.id }, + }, + }); + + return data; +}; + // todo: move to compose store? const cancelEventCompose = () => { useComposeStore.getState().actions.updateCompose('event-compose-modal', (draft) => { @@ -89,8 +84,8 @@ const cancelEventCompose = () => { }); }; -const initEventEdit = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - return getClient(getState()) +const initEventEdit = (statusId: string) => { + return getClient() .statuses.getStatusSource(statusId) .then((response) => { useComposeStore diff --git a/packages/nicolium/src/actions/export-data.ts b/packages/nicolium/src/actions/export-data.ts index 6949d9867..9c60a1894 100644 --- a/packages/nicolium/src/actions/export-data.ts +++ b/packages/nicolium/src/actions/export-data.ts @@ -1,9 +1,9 @@ import { defineMessages } from 'react-intl'; import { getClient } from '@/api'; +import { useAuthStore } from '@/stores/auth'; import toast from '@/toast'; -import type { AppDispatch, RootState } from '@/store'; import type { Account, PaginatedResponse } from 'pl-api'; const messages = defineMessages({ @@ -41,11 +41,11 @@ const listAccounts = async (response: PaginatedResponse) => { return Array.from(new Set(accounts)); }; -const exportFollows = () => async (_dispatch: AppDispatch, getState: () => RootState) => { - const me = getState().me; +const exportFollows = async () => { + const me = useAuthStore.getState().currentAccountId; if (!me) return; - const response = await getClient(getState()).accounts.getAccountFollowing(me, { limit: 40 }); + const response = await getClient().accounts.getAccountFollowing(me, { limit: 40 }); const followings = await listAccounts(response); const followingsCsv = followings.map((fqn) => fqn + ',true'); followingsCsv.unshift('Account address,Show boosts'); @@ -54,16 +54,16 @@ const exportFollows = () => async (_dispatch: AppDispatch, getState: () => RootS toast.success(messages.followersSuccess); }; -const exportBlocks = () => async (_dispatch: AppDispatch, getState: () => RootState) => { - const response = await getClient(getState()).filtering.getBlocks({ limit: 40 }); +const exportBlocks = async () => { + const response = await getClient().filtering.getBlocks({ limit: 40 }); const blocks = await listAccounts(response); fileExport(blocks.join('\n'), 'export_block.csv'); toast.success(messages.blocksSuccess); }; -const exportMutes = () => async (_dispatch: AppDispatch, getState: () => RootState) => { - const response = await getClient(getState()).filtering.getMutes({ limit: 40 }); +const exportMutes = async () => { + const response = await getClient().filtering.getMutes({ limit: 40 }); const mutes = await listAccounts(response); fileExport(mutes.join('\n'), 'export_mutes.csv'); diff --git a/packages/nicolium/src/actions/external-auth.ts b/packages/nicolium/src/actions/external-auth.ts index 69a2a1b7a..c4ca71b1d 100644 --- a/packages/nicolium/src/actions/external-auth.ts +++ b/packages/nicolium/src/actions/external-auth.ts @@ -16,8 +16,6 @@ import { parseBaseURL } from '@/utils/auth'; import sourceCode from '@/utils/code'; import { getInstanceScopes } from '@/utils/scopes'; -import type { AppDispatch } from '@/store'; - const fetchExternalInstance = (baseURL: string) => new PlApiClient(baseURL).instance .getInstance() @@ -72,7 +70,7 @@ const externalLogin = (host: string) => { }); }; -const loginWithCode = (code: string) => (dispatch: AppDispatch) => { +const loginWithCode = async (code: string) => { const app = JSON.parse(localStorage.getItem('nicolium:external:app')!); const { client_id, client_secret, redirect_uri } = app; const baseURL = localStorage.getItem('nicolium:external:baseurl')!; @@ -87,11 +85,11 @@ const loginWithCode = (code: string) => (dispatch: AppDispatch) => { code, }; - return obtainOAuthToken(params, baseURL) - .then((token) => dispatch(authLoggedIn(token, app))) - .then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL))) - .then((account) => dispatch(switchAccount(account.id))) - .then(() => (window.location.href = '/')); + const token = await obtainOAuthToken(params, baseURL); + authLoggedIn(token, app); + const account = await verifyCredentials(token.access_token, baseURL); + switchAccount(account.id); + window.location.href = '/'; }; export { externalLogin, loginWithCode }; diff --git a/packages/nicolium/src/actions/frontend-config.ts b/packages/nicolium/src/actions/frontend-config.ts index acd1af99d..8ccb4a446 100644 --- a/packages/nicolium/src/actions/frontend-config.ts +++ b/packages/nicolium/src/actions/frontend-config.ts @@ -1,111 +1,68 @@ import { getHost } from '@/actions/instance'; import { getClient, staticFetch } from '@/api'; import KVStore from '@/storage/kv-store'; +import { useAuthStore } from '@/stores/auth'; +import { useFrontendConfigStore } from '@/stores/frontend-config'; import { useSettingsStore } from '@/stores/settings'; -import type { AppDispatch, RootState } from '@/store'; import type { APIEntity } from '@/types/entities'; -const FRONTEND_CONFIG_REQUEST_SUCCESS = 'FRONTEND_CONFIG_REQUEST_SUCCESS' as const; -const FRONTEND_CONFIG_REQUEST_FAIL = 'FRONTEND_CONFIG_REQUEST_FAIL' as const; - -const FRONTEND_CONFIG_REMEMBER_SUCCESS = 'FRONTEND_CONFIG_REMEMBER_SUCCESS' as const; - -const rememberFrontendConfig = (host: string | null) => (dispatch: AppDispatch) => +const rememberFrontendConfig = (host: string | null) => KVStore.getItemOrError(`frontendConfig:${host}`) .then((frontendConfig) => { - dispatch({ - type: FRONTEND_CONFIG_REMEMBER_SUCCESS, - host, - frontendConfig, - }); + useFrontendConfigStore.getState().actions.rememberConfig(frontendConfig); return true; }) .catch(() => false); -const fetchFrontendConfigurations = () => (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).instance.getFrontendConfigurations(); - /** Conditionally fetches Nicolium config depending on backend features */ -const fetchFrontendConfig = - (host: string | null) => async (dispatch: AppDispatch, getState: () => RootState) => { - const features = getState().auth.client.features; +const fetchFrontendConfig = async (host: string | null) => { + const features = useAuthStore.getState().client.features; - if (features.frontendConfigurations) { - const data = await dispatch(fetchFrontendConfigurations()); - const legacyKey = 'pl_fe'; - const key = 'nicolium'; + if (features.frontendConfigurations) { + const data = await getClient().instance.getFrontendConfigurations(); + const foundData = data['nicolium'] || data['pl_fe']; - const foundData = data[key] || data[legacyKey]; - - if (foundData) { - dispatch(importFrontendConfig(foundData, host)); - return foundData; - } + if (foundData) { + importFrontendConfig(foundData, host); + return foundData; } - return dispatch(fetchFrontendConfigJson(host)); - }; + } + return fetchFrontendConfigJson(host); +}; /** Tries to remember the config from browser storage before fetching it */ -const loadFrontendConfig = () => async (dispatch: AppDispatch, getState: () => RootState) => { - const host = getHost(getState()); +const loadFrontendConfig = async () => { + const host = getHost(); - const result = await dispatch(rememberFrontendConfig(host)); + const result = await rememberFrontendConfig(host); if (result) { - dispatch(fetchFrontendConfig(host)); + fetchFrontendConfig(host); } else { - return dispatch(fetchFrontendConfig(host)); + return fetchFrontendConfig(host); } }; -const fetchFrontendConfigJson = (host: string | null) => (dispatch: AppDispatch) => +const fetchFrontendConfigJson = (host: string | null) => staticFetch('/instance/nicolium.json') .then(({ json: data }) => { if (!isObject(data)) throw 'nicolium.json fetch failed'; - dispatch(importFrontendConfig(data, host)); + importFrontendConfig(data, host); return data; }) - .catch((error) => { - dispatch(frontendConfigFail(error, host)); + .catch(() => { + useFrontendConfigStore.getState().actions.configFetchFailed(); }); const importFrontendConfig = (frontendConfig: APIEntity, host: string | null) => { frontendConfig.brandColor ??= '#d80482'; useSettingsStore.getState().actions.loadDefaultSettings(frontendConfig?.defaultSettings); - - return { - type: FRONTEND_CONFIG_REQUEST_SUCCESS, - frontendConfig, - host, - }; + useFrontendConfigStore.getState().actions.importConfig(frontendConfig, host || ''); }; -const frontendConfigFail = (error: unknown, host: string | null) => ({ - type: FRONTEND_CONFIG_REQUEST_FAIL, - error, - skipAlert: true, - host, -}); - // https://stackoverflow.com/a/46663081 const isObject = (o: any) => o instanceof Object && o.constructor === Object; -type FrontendConfigAction = - | ReturnType - | ReturnType - | { - type: typeof FRONTEND_CONFIG_REMEMBER_SUCCESS; - frontendConfig: APIEntity; - host: string | null; - }; - -export { - FRONTEND_CONFIG_REQUEST_SUCCESS, - FRONTEND_CONFIG_REQUEST_FAIL, - FRONTEND_CONFIG_REMEMBER_SUCCESS, - fetchFrontendConfig, - loadFrontendConfig, - type FrontendConfigAction, -}; +export { fetchFrontendConfig, loadFrontendConfig }; diff --git a/packages/nicolium/src/actions/instance.ts b/packages/nicolium/src/actions/instance.ts index 8d1fc7abd..cbcc7f5fe 100644 --- a/packages/nicolium/src/actions/instance.ts +++ b/packages/nicolium/src/actions/instance.ts @@ -1,17 +1,11 @@ import { getClient, staticFetch } from '@/api'; import { useComposeStore } from '@/stores/compose'; +import { useInstanceStore } from '@/stores/instance'; import { getAuthUserUrl, getMeUrl } from '@/utils/auth'; -import type { AppDispatch, RootState } from '@/store'; -import type { Instance } from 'pl-api'; - -const INSTANCE_FETCH_SUCCESS = 'INSTANCE_FETCH_SUCCESS' as const; -const INSTANCE_FETCH_FAIL = 'INSTANCE_FETCH_FAIL' as const; -const STANDALONE_CHECK_SUCCESS = 'STANDALONE_CHECK_SUCCESS' as const; - /** Figure out the appropriate instance to fetch depending on the state */ -const getHost = (state: RootState) => { - const accountUrl = getMeUrl(state) ?? (getAuthUserUrl(state) as string); +const getHost = () => { + const accountUrl = getMeUrl() ?? (getAuthUserUrl() as string); try { return new URL(accountUrl).host; @@ -20,58 +14,25 @@ const getHost = (state: RootState) => { } }; -interface InstanceFetchSuccessAction { - type: typeof INSTANCE_FETCH_SUCCESS; - instance: Instance; -} - -interface InstanceFetchFailAction { - type: typeof INSTANCE_FETCH_FAIL; - error: unknown; -} - -const fetchInstance = () => async (dispatch: AppDispatch, getState: () => RootState) => { +const fetchInstance = async () => { try { - const instance = await getClient(getState).instance.getInstance(); + const instance = await getClient().instance.getInstance(); - dispatch({ type: INSTANCE_FETCH_SUCCESS, instance }); + useInstanceStore.getState().actions.loadInstance(instance); useComposeStore.getState().actions.importDefaultContentType(instance); } catch (error) { - dispatch({ type: INSTANCE_FETCH_FAIL, error }); + useInstanceStore.getState().actions.instanceFetchFailed(error); } }; -interface StandaloneCheckSuccessAction { - type: typeof STANDALONE_CHECK_SUCCESS; - ok: boolean; -} - -const checkIfStandalone = () => (dispatch: AppDispatch) => +const checkIfStandalone = () => staticFetch('/api/v1/instance', { method: 'GET' }) - .then(({ ok, headers }) => - dispatch({ - type: STANDALONE_CHECK_SUCCESS, - ok: ok && !!headers.get('content-type')?.includes('application/json'), - }), - ) - .catch((err) => - dispatch({ - type: STANDALONE_CHECK_SUCCESS, - ok: err.response?.ok, - }), - ); + .then(({ ok, headers }) => { + const isOk = ok && !!headers.get('content-type')?.includes('application/json'); + useInstanceStore.getState().actions.setInstanceFetchFailed(!isOk); + }) + .catch((err) => { + useInstanceStore.getState().actions.setInstanceFetchFailed(!err.response?.ok); + }); -type InstanceAction = - | InstanceFetchSuccessAction - | InstanceFetchFailAction - | StandaloneCheckSuccessAction; - -export { - INSTANCE_FETCH_SUCCESS, - INSTANCE_FETCH_FAIL, - STANDALONE_CHECK_SUCCESS, - getHost, - fetchInstance, - checkIfStandalone, - type InstanceAction, -}; +export { getHost, fetchInstance, checkIfStandalone }; diff --git a/packages/nicolium/src/actions/me.ts b/packages/nicolium/src/actions/me.ts deleted file mode 100644 index d63210fad..000000000 --- a/packages/nicolium/src/actions/me.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { getClient } from '@/api'; -import { selectAccount } from '@/queries/accounts/selectors'; -import { queryClient } from '@/queries/client'; -import { queryKeys } from '@/queries/keys'; -import { setSentryAccount } from '@/sentry'; -import KVStore from '@/storage/kv-store'; -import { useComposeStore } from '@/stores/compose'; -import { useSettingsStore } from '@/stores/settings'; -import { getAuthUserId, getAuthUserUrl } from '@/utils/auth'; - -import { loadCredentials } from './auth'; -import { FE_NAME, LEGACY_FE_NAME } from './settings'; - -import type { AppDispatch, RootState } from '@/store'; -import type { CredentialAccount, UpdateCredentialsParams } from 'pl-api'; - -const ME_FETCH_SUCCESS = 'ME_FETCH_SUCCESS' as const; -const ME_FETCH_FAIL = 'ME_FETCH_FAIL' as const; -const ME_FETCH_SKIP = 'ME_FETCH_SKIP' as const; - -const ME_PATCH_SUCCESS = 'ME_PATCH_SUCCESS' as const; - -const noOp = () => - new Promise((f) => { - f(undefined); - }); - -const getMeId = (state: RootState) => state.me ?? getAuthUserId(state); - -const getMeUrl = (state: RootState) => { - const accountId = getMeId(state); - if (accountId) { - return selectAccount(accountId)?.url ?? getAuthUserUrl(state); - } -}; - -const getMeToken = (state: RootState) => { - // Fallback for upgrading IDs to URLs - const accountUrl = getMeUrl(state) ?? state.auth.me; - return state.auth.users[accountUrl!]?.access_token; -}; - -interface MeFetchSkipAction { - type: typeof ME_FETCH_SKIP; -} - -const fetchMe = () => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const token = getMeToken(state); - const accountUrl = getMeUrl(state); - - if (!token) { - dispatch({ type: ME_FETCH_SKIP }); - return noOp(); - } - - return dispatch(loadCredentials(token, accountUrl!)).catch((error) => - dispatch(fetchMeFail(error)), - ); -}; - -/** Update the auth account in IndexedDB for Mastodon, etc. */ -const persistAuthAccount = (account: CredentialAccount, params: Record) => { - if (account && account.url) { - const key = `authAccount:${account.url}`; - KVStore.getItem(key) - .then((oldAccount: any) => { - const settings = oldAccount?.settings_store ?? {}; - account.settings_store ??= settings; - KVStore.setItem(key, account); - }) - .catch(console.error); - } - if (account && account.url) { - account.settings_store ??= params.pleroma_settings_store ?? {}; - KVStore.setItem(`authAccount:${account.url}`, account).catch(console.error); - } -}; - -const patchMe = - (params: UpdateCredentialsParams) => (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState) - .settings.updateCredentials(params) - .then((response) => { - persistAuthAccount(response, params); - dispatch(patchMeSuccess(response)); - }); - -interface MeFetchSuccessAction { - type: typeof ME_FETCH_SUCCESS; - me: CredentialAccount; -} - -const fetchMeSuccess = - (account: CredentialAccount) => async (dispatch: AppDispatch, getState: () => RootState) => { - const client = getClient(getState); - - setSentryAccount(account); - - const settings = account.settings_store?.[FE_NAME] || account.settings_store?.[LEGACY_FE_NAME]; - - if (settings) { - useSettingsStore.getState().actions.loadUserSettings(settings); - } - - if (!client.features.frontendConfigurations && client.features.notes) { - const note = await getClient(getState) - .accounts.getRelationships([account.id]) - .then((relationships) => relationships[0]?.note); - - if (note) { - const match = note.match(/(.*)<\/nicolium-config>/); - - if (match) { - try { - const frontendConfig = JSON.parse(decodeURIComponent(match[1])); - if (typeof frontendConfig === 'object' && frontendConfig !== null) { - frontendConfig.storeSettingsInNotes = true; - } - useSettingsStore.getState().actions.loadUserSettings(frontendConfig); - return frontendConfig; - } catch (error) { - console.error('Failed to parse frontend config from account note', error); - } - } - } - } - useComposeStore.getState().actions.importDefaultSettings(account); - - return dispatch({ - type: ME_FETCH_SUCCESS, - me: account, - }); - }; - -const fetchMeFail = (error: unknown) => ({ - type: ME_FETCH_FAIL, - error, - skipAlert: true, -}); - -interface MePatchSuccessAction { - type: typeof ME_PATCH_SUCCESS; - me: CredentialAccount; -} - -const patchMeSuccess = (me: CredentialAccount) => (dispatch: AppDispatch) => { - queryClient.setQueryData(queryKeys.accounts.show(me.id), me); - useComposeStore.getState().actions.importDefaultSettings(me); - dispatch({ - type: ME_PATCH_SUCCESS, - me, - }); -}; - -type MeAction = - | MeFetchSuccessAction - | ReturnType - | MeFetchSkipAction - | MePatchSuccessAction; - -export { - ME_FETCH_SUCCESS, - ME_FETCH_FAIL, - ME_FETCH_SKIP, - ME_PATCH_SUCCESS, - fetchMe, - patchMe, - fetchMeSuccess, - fetchMeFail, - patchMeSuccess, - type MeAction, -}; diff --git a/packages/nicolium/src/actions/media.ts b/packages/nicolium/src/actions/media.ts index c3561b21b..6986edcd7 100644 --- a/packages/nicolium/src/actions/media.ts +++ b/packages/nicolium/src/actions/media.ts @@ -1,13 +1,13 @@ import { defineMessages, type IntlShape } from 'react-intl'; import { getClient } from '@/api'; +import { useInstanceStore } from '@/stores/instance'; import { useSettingsStore } from '@/stores/settings'; import toast from '@/toast'; import { isLoggedIn } from '@/utils/auth'; import { formatBytes, getVideoDuration } from '@/utils/media'; import resizeImage from '@/utils/resize-image'; -import type { AppDispatch, RootState } from '@/store'; import type { MediaAttachment, UpdateMediaParams, UploadMediaParams } from 'pl-api'; const messages = defineMessages({ @@ -28,99 +28,97 @@ const messages = defineMessages({ const noOp = () => {}; -const updateMedia = - (mediaId: string, params: UpdateMediaParams) => - (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState()).media.updateMedia(mediaId, params); +const updateMedia = (mediaId: string, params: UpdateMediaParams) => + getClient().media.updateMedia(mediaId, params); -const uploadMedia = - (body: UploadMediaParams, onUploadProgress: (e: ProgressEvent) => void = noOp) => - (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState()).media.uploadMedia(body, { onUploadProgress }); +const uploadMedia = ( + body: UploadMediaParams, + onUploadProgress: (e: ProgressEvent) => void = noOp, +) => getClient().media.uploadMedia(body, { onUploadProgress }); -const uploadFile = - ( - file: File, - intl: IntlShape, - onSuccess: (data: MediaAttachment) => void = () => {}, - onFail: (error: unknown) => void = () => {}, - onProgress: (e: ProgressEvent) => void = () => {}, - changeTotal: (value: number) => void = () => {}, - ) => - async (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; - const { stripMetadata } = useSettingsStore.getState().settings; +const uploadFile = async ( + file: File, + intl: IntlShape, + onSuccess: (data: MediaAttachment) => void = () => {}, + onFail: (error: unknown) => void = () => {}, + onProgress: (e: ProgressEvent) => void = () => {}, + changeTotal: (value: number) => void = () => {}, +) => { + if (!isLoggedIn()) return; + const { stripMetadata } = useSettingsStore.getState().settings; - const maxImageSize = getState().instance.configuration.media_attachments.image_size_limit; - const maxVideoSize = getState().instance.configuration.media_attachments.video_size_limit; - const maxVideoDuration = - getState().instance.configuration.media_attachments.video_duration_limit; + const { + configuration: { media_attachments }, + } = useInstanceStore.getState().instance; + const maxImageSize = media_attachments.image_size_limit; + const maxVideoSize = media_attachments.video_size_limit; + const maxVideoDuration = media_attachments.video_duration_limit; - const imageMatrixLimit = getState().instance.configuration.media_attachments.image_matrix_limit; + const imageMatrixLimit = media_attachments.image_matrix_limit; - const isImage = file.type.match(/image.*/); - const isVideo = file.type.match(/video.*/); - const videoDurationInSeconds = isVideo && maxVideoDuration ? await getVideoDuration(file) : 0; + const isImage = file.type.match(/image.*/); + const isVideo = file.type.match(/video.*/); + const videoDurationInSeconds = isVideo && maxVideoDuration ? await getVideoDuration(file) : 0; - if (isImage && maxImageSize && file.size > maxImageSize) { - const limit = formatBytes(maxImageSize); - const message = intl.formatMessage(messages.exceededImageSizeLimit, { limit }); - toast.error(message); - onFail(true); - return; - } else if (isVideo && maxVideoSize && file.size > maxVideoSize) { - const limit = formatBytes(maxVideoSize); - const message = intl.formatMessage(messages.exceededVideoSizeLimit, { limit }); - toast.error(message); - onFail(true); - return; - } else if (isVideo && maxVideoDuration && videoDurationInSeconds > maxVideoDuration) { - const message = intl.formatMessage(messages.exceededVideoDurationLimit, { - limit: maxVideoDuration, + if (isImage && maxImageSize && file.size > maxImageSize) { + const limit = formatBytes(maxImageSize); + const message = intl.formatMessage(messages.exceededImageSizeLimit, { limit }); + toast.error(message); + onFail(true); + return; + } else if (isVideo && maxVideoSize && file.size > maxVideoSize) { + const limit = formatBytes(maxVideoSize); + const message = intl.formatMessage(messages.exceededVideoSizeLimit, { limit }); + toast.error(message); + onFail(true); + return; + } else if (isVideo && maxVideoDuration && videoDurationInSeconds > maxVideoDuration) { + const message = intl.formatMessage(messages.exceededVideoDurationLimit, { + limit: maxVideoDuration, + }); + toast.error(message); + onFail(true); + return; + } + + // FIXME: Don't define const in loop + resizeImage(file, imageMatrixLimit, stripMetadata) + .then((resized) => { + const data = new FormData(); + data.append('file', resized); + // Account for disparity in size of original image and resized data + changeTotal(resized.size - file.size); + + return uploadMedia({ file: resized }, onProgress).then((data) => { + // If server-side processing of the media attachment has not completed yet, + // poll the server until it is, before showing the media attachment as uploaded + if (data.url) { + onSuccess(data); + } else if (data.url === null) { + const poll = () => { + getClient() + .media.getMedia(data.id) + .then((data) => { + if (data.url) { + onSuccess(data); + } else if (data.url === null) { + setTimeout(() => { + poll(); + }, 1000); + } + }) + .catch((error) => { + onFail(error); + }); + }; + + poll(); + } }); - toast.error(message); - onFail(true); - return; - } - - // FIXME: Don't define const in loop - resizeImage(file, imageMatrixLimit, stripMetadata) - .then((resized) => { - const data = new FormData(); - data.append('file', resized); - // Account for disparity in size of original image and resized data - changeTotal(resized.size - file.size); - - return dispatch(uploadMedia({ file: resized }, onProgress)).then((data) => { - // If server-side processing of the media attachment has not completed yet, - // poll the server until it is, before showing the media attachment as uploaded - if (data.url) { - onSuccess(data); - } else if (data.url === null) { - const poll = () => { - getClient(getState()) - .media.getMedia(data.id) - .then((data) => { - if (data.url) { - onSuccess(data); - } else if (data.url === null) { - setTimeout(() => { - poll(); - }, 1000); - } - }) - .catch((error) => { - onFail(error); - }); - }; - - poll(); - } - }); - }) - .catch((error) => { - onFail(error); - }); - }; + }) + .catch((error) => { + onFail(error); + }); +}; export { updateMedia, uploadMedia, uploadFile }; diff --git a/packages/nicolium/src/actions/moderation.tsx b/packages/nicolium/src/actions/moderation.tsx index 50d56ddc1..34ca3e2fa 100644 --- a/packages/nicolium/src/actions/moderation.tsx +++ b/packages/nicolium/src/actions/moderation.tsx @@ -1,23 +1,23 @@ import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; -import { defineMessages, FormattedMessage, useIntl, type IntlShape } from 'react-intl'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { deactivateUser, deleteUser } from '@/actions/admin'; import OutlineBox from '@/components/outline-box'; import Text from '@/components/ui/text'; import AccountContainer from '@/containers/account-container'; import { selectAccount } from '@/queries/accounts/selectors'; +import { + useAdminDeleteAccountMutation, + useAdminPerformAccountActionMutation, +} from '@/queries/admin/use-accounts'; import { useAdminDeleteStatusMutation, useAdminUpdateStatusMutation, } from '@/queries/admin/use-statuses'; -import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; -import { useModalsActions, useModalsStore } from '@/stores/modals'; +import { useModalsActions } from '@/stores/modals'; import toast from '@/toast'; -import type { AppDispatch } from '@/store'; - const messages = defineMessages({ deactivateUserHeading: { id: 'confirmations.admin.deactivate_user.heading', @@ -99,9 +99,15 @@ const messages = defineMessages({ }, }); -const deactivateUserModal = - (intl: IntlShape, accountId: string, afterConfirm = () => {}) => - (dispatch: AppDispatch) => { +const useDeactivateUserModal = (accountId: string) => { + const intl = useIntl(); + const { mutate: performAccountAction } = useAdminPerformAccountActionMutation( + accountId, + 'suspend', + ); + const { openModal } = useModalsActions(); + + return () => { const acct = selectAccount(accountId)!.acct; const name = selectAccount(accountId)!.username; @@ -121,25 +127,29 @@ const deactivateUserModal = ); - useModalsStore.getState().actions.openModal('CONFIRM', { + openModal('CONFIRM', { heading: intl.formatMessage(messages.deactivateUserHeading, { acct }), message, confirm: intl.formatMessage(messages.deactivateUserConfirm, { name }), onConfirm: () => { - dispatch(deactivateUser(accountId)) - .then(() => { + performAccountAction(undefined, { + onSuccess: () => { const message = intl.formatMessage(messages.userDeactivated, { acct }); toast.success(message); - afterConfirm(); - }) - .catch(() => {}); + }, + }); }, }); }; +}; -const deleteUserModal = - (intl: IntlShape, accountId: string, afterConfirm = () => {}) => - (dispatch: AppDispatch) => { +const useDeleteUserModal = (accountId: string) => { + const intl = useIntl(); + const { mutate: deleteUser } = useAdminDeleteAccountMutation(accountId); + const { openModal } = useModalsActions(); + const queryClient = useQueryClient(); + + return () => { const account = selectAccount(accountId)!; const acct = account.acct; const name = account.username; @@ -164,28 +174,28 @@ const deleteUserModal = const confirm = intl.formatMessage(messages.deleteUserConfirm, { name }); const checkbox = local ? intl.formatMessage(messages.deleteLocalUserCheckbox) : false; - useModalsStore.getState().actions.openModal('CONFIRM', { + openModal('CONFIRM', { heading: intl.formatMessage(messages.deleteUserHeading, { acct }), message, confirm, checkbox, onConfirm: () => { - dispatch(deleteUser(accountId)) - .then(() => { + deleteUser(undefined, { + onSuccess: () => { const message = intl.formatMessage(messages.userDeleted, { acct }); queryClient.invalidateQueries({ queryKey: queryKeys.accounts.show(accountId) }); queryClient.invalidateQueries({ queryKey: queryKeys.accounts.lookup(acct.toLocaleLowerCase()), }); toast.success(message); - afterConfirm(); - }) - .catch(() => {}); + }, + }); }, }); }; +}; -const useToggleStatusSensitivityModal = (statusId: string, afterConfirm = () => {}) => { +const useToggleStatusSensitivityModal = (statusId: string) => { const intl = useIntl(); const { mutate: updateStatus } = useAdminUpdateStatusMutation(statusId); const { openModal } = useModalsActions(); @@ -217,7 +227,6 @@ const useToggleStatusSensitivityModal = (statusId: string, afterConfirm = () => { acct }, ); toast.success(message); - afterConfirm(); }, }, ); @@ -226,7 +235,7 @@ const useToggleStatusSensitivityModal = (statusId: string, afterConfirm = () => }; }; -const useDeleteStatusModal = (statusId: string, afterConfirm = () => {}) => { +const useDeleteStatusModal = (statusId: string) => { const intl = useIntl(); const { mutate: deleteStatus } = useAdminDeleteStatusMutation(statusId); const { openModal } = useModalsActions(); @@ -246,7 +255,6 @@ const useDeleteStatusModal = (statusId: string, afterConfirm = () => {}) => { onSuccess: () => { const message = intl.formatMessage(messages.statusDeleted, { acct }); toast.success(message); - afterConfirm(); }, }); }, @@ -255,8 +263,8 @@ const useDeleteStatusModal = (statusId: string, afterConfirm = () => {}) => { }; export { - deactivateUserModal, - deleteUserModal, + useDeactivateUserModal, + useDeleteUserModal, useToggleStatusSensitivityModal, useDeleteStatusModal, }; diff --git a/packages/nicolium/src/actions/oauth.ts b/packages/nicolium/src/actions/oauth.ts index b682e27b9..e9a2ce308 100644 --- a/packages/nicolium/src/actions/oauth.ts +++ b/packages/nicolium/src/actions/oauth.ts @@ -9,9 +9,8 @@ import { PlApiClient, type GetTokenParams, type RevokeTokenParams } from 'pl-api'; import * as BuildConfig from '@/build-config'; -import { getBaseURL } from '@/utils/state'; - -import type { AppDispatch, RootState } from '@/store'; +import { useAuthStore } from '@/stores/auth'; +import { parseBaseURL } from '@/utils/auth'; const obtainOAuthToken = async (params: GetTokenParams, baseURL?: string) => { const client = new PlApiClient((baseURL ?? BuildConfig.BACKEND_URL) || ''); @@ -20,11 +19,11 @@ const obtainOAuthToken = async (params: GetTokenParams, baseURL?: string) => { return client.oauth.getToken(params); }; -const revokeOAuthToken = - (params: RevokeTokenParams) => (dispatch: AppDispatch, getState: () => RootState) => { - const baseURL = getBaseURL(getState()); - const client = new PlApiClient(baseURL || ''); - return client.oauth.revokeToken(params); - }; +const revokeOAuthToken = async (params: RevokeTokenParams) => { + const { me } = useAuthStore.getState(); + const baseURL = parseBaseURL(me || undefined) || BuildConfig.BACKEND_URL; + const client = new PlApiClient(baseURL || ''); + return client.oauth.revokeToken(params); +}; export { obtainOAuthToken, revokeOAuthToken }; diff --git a/packages/nicolium/src/actions/preload.ts b/packages/nicolium/src/actions/preload.ts index 545f52d27..503f8d9bd 100644 --- a/packages/nicolium/src/actions/preload.ts +++ b/packages/nicolium/src/actions/preload.ts @@ -4,14 +4,12 @@ import * as v from 'valibot'; import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; +import { useAuthStore } from '@/stores/auth'; +import { useFrontendConfigStore } from '@/stores/frontend-config'; +import { useInstanceStore } from '@/stores/instance'; import { verifyCredentials } from './auth'; -import type { AppDispatch } from '@/store'; - -const PLEROMA_PRELOAD_IMPORT = 'PLEROMA_PRELOAD_IMPORT' as const; -const MASTODON_PRELOAD_IMPORT = 'MASTODON_PRELOAD_IMPORT' as const; - // https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1176/diffs const decodeUTF8Base64 = (data: string) => { const rawData = atob(data); @@ -32,32 +30,25 @@ const decodeFromMarkup = (elementId: string, decoder: (json: string) => Record Record, - action: (data: Record) => any, - ) => - (dispatch: AppDispatch) => { - try { - const data = decodeFromMarkup(elementId, decoder); - dispatch(action(data)); - } catch { - // Do nothing - } - }; - -const preload = () => (dispatch: AppDispatch) => { - dispatch(preloadFromMarkup('initial-results', pleromaDecoder, preloadPleroma)); - dispatch(preloadFromMarkup('initial-state', JSON.parse, preloadMastodon)); +const preloadFromMarkup = ( + elementId: string, + decoder: (json: string) => Record, + action: (data: Record) => void, +) => { + try { + const data = decodeFromMarkup(elementId, decoder); + action(data); + } catch { + // Do nothing + } }; -const preloadPleroma = (data: Record): PreloadAction => ({ - type: PLEROMA_PRELOAD_IMPORT, - data, -}); +const preloadPleroma = (data: Record) => { + useInstanceStore.getState().actions.importPreload(data); + useFrontendConfigStore.getState().actions.importPreload(data); +}; -const preloadMastodon = (data: Record) => (dispatch: AppDispatch) => { +const preloadMastodon = (data: Record) => { const { me, access_token } = data.meta; const { url } = data.accounts[me]; @@ -69,20 +60,20 @@ const preloadMastodon = (data: Record) => (dispatch: AppDispatch) = // } } - dispatch(verifyCredentials(access_token, url)); - dispatch({ type: MASTODON_PRELOAD_IMPORT, data }); + + useAuthStore.getState().actions.importMastodonPreload(data); + verifyCredentials(access_token, url); }; -interface PreloadAction { - type: typeof PLEROMA_PRELOAD_IMPORT | typeof MASTODON_PRELOAD_IMPORT; - data: Record; -} +const preload = () => { + preloadFromMarkup('initial-results', pleromaDecoder, preloadPleroma); -export { - PLEROMA_PRELOAD_IMPORT, - MASTODON_PRELOAD_IMPORT, - pleromaDecoder, - decodeFromMarkup, - preload, - type PreloadAction, + try { + const data = decodeFromMarkup('initial-state', JSON.parse); + preloadMastodon(data); + } catch { + // Do nothing + } }; + +export { pleromaDecoder, decodeFromMarkup, preload }; diff --git a/packages/nicolium/src/actions/push-notifications/registerer.ts b/packages/nicolium/src/actions/push-notifications/registerer.ts index 9c40d443f..da36f6b5d 100644 --- a/packages/nicolium/src/actions/push-notifications/registerer.ts +++ b/packages/nicolium/src/actions/push-notifications/registerer.ts @@ -1,13 +1,10 @@ import { createPushSubscription } from '@/actions/push-subscriptions'; import { pushNotificationsSettings } from '@/settings'; +import { useAuthStore, type Me } from '@/stores/auth'; +import { usePushNotificationsStore } from '@/stores/push-notifications'; import { getVapidKey } from '@/utils/auth'; import { decode as decodeBase64 } from '@/utils/base64'; -import { setBrowserSupport, setSubscription, clearSubscription } from './setter'; - -import type { Me } from '@/reducers/me'; -import type { AppDispatch, RootState } from '@/store'; - // Taken from https://www.npmjs.com/package/web-push const urlBase64ToUint8Array = (base64String: string) => { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); @@ -29,10 +26,10 @@ const getPushSubscription = (registration: ServiceWorkerRegistration) => .getSubscription() .then((subscription) => ({ registration, subscription })); -const subscribe = (registration: ServiceWorkerRegistration, getState: () => RootState) => +const subscribe = (registration: ServiceWorkerRegistration) => registration.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: urlBase64ToUint8Array(getVapidKey(getState())), + applicationServerKey: urlBase64ToUint8Array(getVapidKey()), }); const unsubscribe = ({ @@ -48,32 +45,31 @@ const unsubscribe = ({ r(registration); }); -const sendSubscriptionToBackend = - (subscription: PushSubscription, me: Me) => - (dispatch: AppDispatch, getState: () => RootState) => { - const alerts = getState().pushNotifications.alerts; - const params = { subscription, data: { alerts } }; - - if (me) { - const data = pushNotificationsSettings.get(me); - if (data) { - params.data = data; - } - } - - return dispatch(createPushSubscription(params)); +const sendSubscriptionToBackend = (subscription: PushSubscription, me: Me) => { + const alerts = usePushNotificationsStore.getState().alerts; + const params: { subscription: PushSubscription; data: { alerts: Record } } = { + subscription, + data: { alerts }, }; + if (me) { + const data = pushNotificationsSettings.get(me); + if (data) { + params.data = data; + } + } + + return createPushSubscription(params); +}; + // Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload // eslint-disable-next-line compat/compat const supportsPushNotifications = 'serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype; -const register = () => (dispatch: AppDispatch, getState: () => RootState) => { - const me = getState().me; - const vapidKey = getVapidKey(getState()); - - dispatch(setBrowserSupport(supportsPushNotifications)); +const register = () => { + const me = useAuthStore.getState().currentAccountId; + const vapidKey = getVapidKey(); if (!supportsPushNotifications) { console.warn('Your browser does not support Web Push Notifications.'); @@ -91,38 +87,31 @@ const register = () => (dispatch: AppDispatch, getState: () => RootState) => { .then(getPushSubscription) .then(async ({ registration, subscription }) => { if (subscription !== null) { - // We have a subscription, check if it is still valid const currentServerKey = new Uint8Array( subscription.options.applicationServerKey!, ).toString(); const subscriptionServerKey = urlBase64ToUint8Array(vapidKey).toString(); - const serverEndpoint = getState().pushNotifications.subscription?.endpoint; + const serverEndpoint = usePushNotificationsStore.getState().subscription?.endpoint; - // If the VAPID public key did not change and the endpoint corresponds - // to the endpoint saved in the backend, the subscription is valid if ( subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint ) { return subscription; } else { - // Something went wrong, try to subscribe again const swRegistration = await unsubscribe({ registration, subscription }); - const pushSubscription = await subscribe(swRegistration, getState); - await dispatch(sendSubscriptionToBackend(pushSubscription, me)); + const pushSubscription = await subscribe(swRegistration); + await sendSubscriptionToBackend(pushSubscription, me); } } - // No subscription, try to subscribe - return subscribe(registration, getState).then((pushSubscription) => - dispatch(sendSubscriptionToBackend(pushSubscription, me)), + return subscribe(registration).then((pushSubscription) => + sendSubscriptionToBackend(pushSubscription, me), ); }) .then((subscription) => { - // If we got a PushSubscription (and not a subscription object from the backend) - // it means that the backend subscription is valid (and was set during hydration) if (!(subscription instanceof PushSubscription)) { - dispatch(setSubscription(subscription)); + usePushNotificationsStore.getState().actions.setSubscription(subscription); if (me) { pushNotificationsSettings.set(me, { alerts: subscription.alerts }); } @@ -137,8 +126,7 @@ const register = () => (dispatch: AppDispatch, getState: () => RootState) => { console.error('The VAPID public key seems to be invalid:', vapidKey); } - // Clear alerts and hide UI settings - dispatch(clearSubscription()); + usePushNotificationsStore.getState().actions.clearSubscription(); if (me) { pushNotificationsSettings.remove(me); diff --git a/packages/nicolium/src/actions/push-notifications/setter.ts b/packages/nicolium/src/actions/push-notifications/setter.ts deleted file mode 100644 index dfa931d4b..000000000 --- a/packages/nicolium/src/actions/push-notifications/setter.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { WebPushSubscription } from 'pl-api'; - -const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT' as const; -const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION' as const; -const CLEAR_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_CLEAR_SUBSCRIPTION' as const; - -const setBrowserSupport = (value: boolean) => ({ - type: SET_BROWSER_SUPPORT, - value, -}); - -const setSubscription = (subscription: WebPushSubscription) => ({ - type: SET_SUBSCRIPTION, - subscription, -}); - -const clearSubscription = () => ({ - type: CLEAR_SUBSCRIPTION, -}); - -type SetterAction = - | ReturnType - | ReturnType - | ReturnType; - -export { - SET_BROWSER_SUPPORT, - SET_SUBSCRIPTION, - CLEAR_SUBSCRIPTION, - setBrowserSupport, - setSubscription, - clearSubscription, - type SetterAction, -}; diff --git a/packages/nicolium/src/actions/push-subscriptions.ts b/packages/nicolium/src/actions/push-subscriptions.ts index dccb3836d..98e0c6094 100644 --- a/packages/nicolium/src/actions/push-subscriptions.ts +++ b/packages/nicolium/src/actions/push-subscriptions.ts @@ -1,11 +1,8 @@ -import { getClient } from '@/api'; +import { useAuthStore } from '@/stores/auth'; -import type { AppDispatch, RootState } from '@/store'; import type { CreatePushNotificationsSubscriptionParams } from 'pl-api'; -const createPushSubscription = - (params: CreatePushNotificationsSubscriptionParams) => - (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).pushNotifications.createSubscription(params); +const createPushSubscription = (params: CreatePushNotificationsSubscriptionParams) => + useAuthStore.getState().client.pushNotifications.createSubscription(params); export { createPushSubscription }; diff --git a/packages/nicolium/src/actions/remote-timeline.ts b/packages/nicolium/src/actions/remote-timeline.ts index 27ca34583..ed3f6fe33 100644 --- a/packages/nicolium/src/actions/remote-timeline.ts +++ b/packages/nicolium/src/actions/remote-timeline.ts @@ -1,27 +1,21 @@ import { changeSetting } from '@/actions/settings'; import { useSettingsStore } from '@/stores/settings'; -import type { AppDispatch } from '@/store'; - const getPinnedHosts = () => { const { settings } = useSettingsStore.getState(); return settings.remote_timeline.pinnedHosts; }; -const pinHost = (host: string) => (dispatch: AppDispatch) => { +const pinHost = (host: string) => { const pinnedHosts = getPinnedHosts(); - - dispatch(changeSetting(['remote_timeline', 'pinnedHosts'], [...pinnedHosts, host])); + changeSetting(['remote_timeline', 'pinnedHosts'], [...pinnedHosts, host]); }; -const unpinHost = (host: string) => (dispatch: AppDispatch) => { +const unpinHost = (host: string) => { const pinnedHosts = getPinnedHosts(); - - dispatch( - changeSetting( - ['remote_timeline', 'pinnedHosts'], - pinnedHosts.filter((value) => value !== host), - ), + changeSetting( + ['remote_timeline', 'pinnedHosts'], + pinnedHosts.filter((value) => value !== host), ); }; diff --git a/packages/nicolium/src/actions/reports.ts b/packages/nicolium/src/actions/reports.ts index fe7473d42..3eaf92181 100644 --- a/packages/nicolium/src/actions/reports.ts +++ b/packages/nicolium/src/actions/reports.ts @@ -2,7 +2,6 @@ import { getClient } from '@/api'; import { useModalsStore } from '@/stores/modals'; import type { NormalizedStatus as Status } from '@/normalizers/status'; -import type { AppDispatch, RootState } from '@/store'; import type { Account } from 'pl-api'; enum ReportableEntities { @@ -29,20 +28,18 @@ const initReport = ( }); }; -const submitReport = - ( - accountId: string, - statusIds: string[], - ruleIds?: string[], - comment?: string, - forward?: boolean, - ) => - (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState()).accounts.reportAccount(accountId, { - status_ids: statusIds, - rule_ids: ruleIds, - comment: comment, - forward: forward, - }); +const submitReport = ( + accountId: string, + statusIds: string[], + ruleIds?: string[], + comment?: string, + forward?: boolean, +) => + getClient().accounts.reportAccount(accountId, { + status_ids: statusIds, + rule_ids: ruleIds, + comment: comment, + forward: forward, + }); export { ReportableEntities, initReport, submitReport }; diff --git a/packages/nicolium/src/actions/security.ts b/packages/nicolium/src/actions/security.ts index bf4d5e323..f6257fd7c 100644 --- a/packages/nicolium/src/actions/security.ts +++ b/packages/nicolium/src/actions/security.ts @@ -4,62 +4,42 @@ * @see module:@/actions/auth */ -import { getClient } from '@/api'; +import { selectOwnAccount } from '@/queries/accounts/selectors'; +import { useAuthStore } from '@/stores/auth'; import toast from '@/toast'; -import { getLoggedInAccount } from '@/utils/auth'; import { normalizeUsername } from '@/utils/input'; -import { AUTH_LOGGED_OUT, messages } from './auth'; +import { messages } from './auth'; -import type { AppDispatch, RootState } from '@/store'; -import type { Account } from 'pl-api'; +const changePassword = (oldPassword: string, newPassword: string) => + useAuthStore.getState().client.settings.changePassword(oldPassword, newPassword); -const changePassword = - (oldPassword: string, newPassword: string) => - (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).settings.changePassword(oldPassword, newPassword); - -const resetPassword = - (usernameOrEmail: string) => (_dispatch: AppDispatch, getState: () => RootState) => { - const input = normalizeUsername(usernameOrEmail); - - return getClient(getState).settings.resetPassword( +const resetPassword = (usernameOrEmail: string) => { + const input = normalizeUsername(usernameOrEmail); + return useAuthStore + .getState() + .client.settings.resetPassword( input.includes('@') ? input : undefined, input.includes('@') ? undefined : input, ); - }; - -const changeEmail = - (email: string, password: string) => (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).settings.changeEmail(email, password); - -const deleteAccount = (password: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const account = getLoggedInAccount(getState())!; - - const client = getClient(getState); - - return ( - client.features.deleteAccount - ? client.settings.deleteAccount(password) - : client.settings.deleteAccountWithoutPassword() - ).then(() => { - dispatch({ type: AUTH_LOGGED_OUT, account }); - toast.success(messages.loggedOut); - }); }; -const moveAccount = - (targetAccount: string, password: string) => - (_dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).settings.moveAccount(targetAccount, password); +const changeEmail = (email: string, password: string) => + useAuthStore.getState().client.settings.changeEmail(email, password); -type SecurityAction = { type: typeof AUTH_LOGGED_OUT; account: Account }; +const deleteAccount = async (password: string) => { + const account = selectOwnAccount()!; + const client = useAuthStore.getState().client; -export { - changePassword, - resetPassword, - changeEmail, - deleteAccount, - moveAccount, - type SecurityAction, + await (client.features.deleteAccount + ? client.settings.deleteAccount(password) + : client.settings.deleteAccountWithoutPassword()); + + useAuthStore.getState().actions.removeToken(account); + toast.success(messages.loggedOut); }; + +const moveAccount = (targetAccount: string, password: string) => + useAuthStore.getState().client.settings.moveAccount(targetAccount, password); + +export { changePassword, resetPassword, changeEmail, deleteAccount, moveAccount }; diff --git a/packages/nicolium/src/actions/settings.ts b/packages/nicolium/src/actions/settings.ts index 42978a50b..16b32453e 100644 --- a/packages/nicolium/src/actions/settings.ts +++ b/packages/nicolium/src/actions/settings.ts @@ -1,16 +1,14 @@ import { defineMessage } from 'react-intl'; -import { patchMe } from '@/actions/me'; -import { getClient } from '@/api'; +import { patchMe } from '@/actions/auth'; import { NODE_ENV } from '@/build-config'; import { selectOwnAccount } from '@/queries/accounts/selectors'; import KVStore from '@/storage/kv-store'; +import { useAuthStore } from '@/stores/auth'; import { useSettingsStore } from '@/stores/settings'; import toast from '@/toast'; -import { isLoggedIn } from '@/utils/auth'; import type { Settings } from '@/schemas/frontend-settings'; -import type { AppDispatch, RootState } from '@/store'; const LEGACY_FE_NAME = NODE_ENV === 'production' ? 'pl_fe' : 'pl_fe_dev'; const FE_NAME = NODE_ENV === 'production' ? 'nicolium' : 'nicolium_dev'; @@ -31,34 +29,31 @@ const changeSetting = (path: string[], value: any, opts?: SettingOpts) => { useSettingsStore.getState().actions.changeSetting(path, value); if (opts?.save !== false) return saveSettings(opts, path[0] === 'storeSettingsInNotes'); - return () => {}; }; -const saveSettings = - (opts?: SettingOpts, isNotesChange?: boolean) => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; +const saveSettings = (opts?: SettingOpts, isNotesChange?: boolean) => { + const { currentAccountId } = useAuthStore.getState(); + if (typeof currentAccountId !== 'string') return; - const { - userSettings, - actions: { userSettingsSaving }, - } = useSettingsStore.getState(); - if (userSettings.saved) return; + const { + userSettings, + actions: { userSettingsSaving }, + } = useSettingsStore.getState(); + if (userSettings.saved) return; - const { saved, ...data } = userSettings; + const { saved, ...data } = userSettings; - dispatch(updateSettingsStore(data, isNotesChange)) - .then(() => { - userSettingsSaving(); - - if (opts?.showAlert) { - toast.success(saveSuccessMessage); - } - }) - .catch((error) => { - toast.showAlertForError(error); - }); - }; + updateSettingsStore(data, isNotesChange) + .then(() => { + userSettingsSaving(); + if (opts?.showAlert) { + toast.success(saveSuccessMessage); + } + }) + .catch((error) => { + toast.showAlertForError(error); + }); +}; /** Update settings store for Mastodon, etc. */ const updateAuthAccount = async (url: string, settings: any) => { @@ -74,44 +69,38 @@ const updateAuthAccount = async (url: string, settings: any) => { } }; -const updateSettingsStore = - (settings: Partial, isNotesChange?: boolean) => - async (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const client = getClient(state); +const updateSettingsStore = async (settings: Partial, isNotesChange?: boolean) => { + const { client, currentAccountId } = useAuthStore.getState(); - if (client.features.frontendConfigurations) { - return dispatch( - patchMe({ - settings_store: { - [FE_NAME]: settings, - }, - }), - ); - } else { - if (client.features.notes && (settings.storeSettingsInNotes || isNotesChange)) { - // Inspired by Phanpy and designed for compatibility with other software doing this - // https://github.com/cheeaun/phanpy/commit/a8b5c8cd64d456d30aab09dc56da7e4e20100e67 - const note = (await client.accounts.getRelationships([state.me as string]))[0]?.note; - const settingsNote = `${encodeURIComponent(JSON.stringify(settings))}`; + if (client.features.frontendConfigurations) { + return patchMe({ + settings_store: { + [FE_NAME]: settings, + }, + }); + } else { + if (client.features.notes && (settings.storeSettingsInNotes || isNotesChange)) { + const note = (await client.accounts.getRelationships([currentAccountId as string]))[0]?.note; + const settingsNote = `${encodeURIComponent(JSON.stringify(settings))}`; - let newNote; - if (settings.storeSettingsInNotes) { - if (/(.*)<\/nicolium-config>/.test(note || '')) { - newNote = note!.replace(/(.*)<\/nicolium-config>/, settingsNote); - } else { - newNote = `${note || ''}\n\n${settingsNote}`; - } + let newNote; + if (settings.storeSettingsInNotes) { + if (/(.*)<\/nicolium-config>/.test(note || '')) { + newNote = note!.replace(/(.*)<\/nicolium-config>/, settingsNote); } else { - newNote = note ? note.replace(/(.*)<\/nicolium-config>/, '') : ''; + newNote = `${note || ''}\n\n${settingsNote}`; } - client.accounts.updateAccountNote(state.me as string, newNote); + } else { + newNote = note ? note.replace(/(.*)<\/nicolium-config>/, '') : ''; } - - const accountUrl = selectOwnAccount(state)!.url; - - return updateAuthAccount(accountUrl, settings); + client.accounts.updateAccountNote(currentAccountId as string, newNote); } - }; + + const account = selectOwnAccount(); + if (!account) return; + + return updateAuthAccount(account.url, settings); + } +}; export { FE_NAME, LEGACY_FE_NAME, changeSetting, saveSettings, updateSettingsStore }; diff --git a/packages/nicolium/src/actions/statuses.ts b/packages/nicolium/src/actions/statuses.ts index 91e754957..7542be664 100644 --- a/packages/nicolium/src/actions/statuses.ts +++ b/packages/nicolium/src/actions/statuses.ts @@ -15,7 +15,6 @@ import { shouldHaveCard } from '@/utils/status'; import { importEntities } from './importer'; import type { NormalizedStatus as Status } from '@/normalizers/status'; -import type { AppDispatch, RootState } from '@/store'; import type { CreateStatusParams, Status as BaseStatus } from 'pl-api'; import type { IntlShape } from 'react-intl'; @@ -67,96 +66,92 @@ const decrementReplyCount = ( } }; -const createStatus = - ( - params: CreateStatusParams, - idempotencyKey: string, - editedId: string | null, - redacting = false, - ) => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!params.preview) { - usePendingStatusesStore.getState().actions.importStatus(params, idempotencyKey); - useContextStore.getState().actions.importPendingStatus(params.in_reply_to_id, idempotencyKey); - useTimelinesStore.getState().actions.importPendingStatus(params, idempotencyKey); - if (!editedId) { - incrementReplyCount(params); - } +const createStatus = ( + params: CreateStatusParams, + idempotencyKey: string, + editedId: string | null, + redacting = false, +) => { + if (!params.preview) { + usePendingStatusesStore.getState().actions.importStatus(params, idempotencyKey); + useContextStore.getState().actions.importPendingStatus(params.in_reply_to_id, idempotencyKey); + useTimelinesStore.getState().actions.importPendingStatus(params, idempotencyKey); + if (!editedId) { + incrementReplyCount(params); } + } - const client = getClient(getState()); + const client = getClient(); - return ( - editedId === null - ? client.statuses.createStatus(params) - : redacting - ? client.admin.statuses.redactStatus(editedId, params) - : client.statuses.editStatus(editedId, params) - ) - .then((status) => { - if (params.preview) return status; + return ( + editedId === null + ? client.statuses.createStatus(params) + : redacting + ? client.admin.statuses.redactStatus(editedId, params) + : client.statuses.editStatus(editedId, params) + ) + .then((status) => { + if (params.preview) return status; - // The backend might still be processing the rich media attachment - const expectsCard = status.scheduled_at === null && !status.card && shouldHaveCard(status); + // The backend might still be processing the rich media attachment + const expectsCard = status.scheduled_at === null && !status.card && shouldHaveCard(status); - if (status.scheduled_at === null) { - importEntities( - { statuses: [{ ...status, expectsCard }] }, - { idempotencyKey, withParents: true }, - ); - } else { - queryClient.invalidateQueries(scheduledStatusesQueryOptions); - } + if (status.scheduled_at === null) { + importEntities( + { statuses: [{ ...status, expectsCard }] }, + { idempotencyKey, withParents: true }, + ); + } else { + queryClient.invalidateQueries(scheduledStatusesQueryOptions); + } - useContextStore - .getState() - .actions.deletePendingStatus( - 'in_reply_to_id' in status ? status.in_reply_to_id : null, - idempotencyKey, - ); + useContextStore + .getState() + .actions.deletePendingStatus( + 'in_reply_to_id' in status ? status.in_reply_to_id : null, + idempotencyKey, + ); - if (status.scheduled_at === null) { - useTimelinesStore.getState().actions.replacePendingStatus(idempotencyKey, status); - } else { - useTimelinesStore.getState().actions.deletePendingStatus(idempotencyKey); - } - - // Poll the backend for the updated card - if (expectsCard) { - const delay = 1000; - - const poll = (retries = 5) => { - return getClient(getState()) - .statuses.getStatus(status.id) - .then((response) => { - if (response.card) { - importEntities({ statuses: [response] }); - } else if (retries > 0 && response) { - setTimeout(() => poll(retries - 1), delay); - } - }) - .catch(console.error); - }; - - setTimeout(() => poll(), delay); - } - - return status; - }) - .catch((error) => { - usePendingStatusesStore.getState().actions.deleteStatus(idempotencyKey); + if (status.scheduled_at === null) { + useTimelinesStore.getState().actions.replacePendingStatus(idempotencyKey, status); + } else { useTimelinesStore.getState().actions.deletePendingStatus(idempotencyKey); - useContextStore - .getState() - .actions.deletePendingStatus(params.in_reply_to_id, idempotencyKey); - if (!editedId) { - decrementReplyCount(params); - } - throw error; - }); - }; + } -const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { + // Poll the backend for the updated card + if (expectsCard) { + const delay = 1000; + + const poll = (retries = 5) => { + return getClient() + .statuses.getStatus(status.id) + .then((response) => { + if (response.card) { + importEntities({ statuses: [response] }); + } else if (retries > 0 && response) { + setTimeout(() => poll(retries - 1), delay); + } + }) + .catch(console.error); + }; + + setTimeout(() => poll(), delay); + } + + return status; + }) + .catch((error) => { + usePendingStatusesStore.getState().actions.deleteStatus(idempotencyKey); + useTimelinesStore.getState().actions.deletePendingStatus(idempotencyKey); + useContextStore.getState().actions.deletePendingStatus(params.in_reply_to_id, idempotencyKey); + if (!editedId) { + decrementReplyCount(params); + } + throw error; + }); +}; + +const editStatus = (statusId: string) => { const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); if (!status) return; @@ -164,7 +159,7 @@ const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) : undefined; - return getClient(getState()) + return getClient() .statuses.getStatusSource(statusId) .then((response) => { useComposeStore.getState().actions.setComposeToStatus(status, poll, response); @@ -172,91 +167,87 @@ const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => }); }; -const fetchStatus = - (statusId: string, intl?: IntlShape) => (dispatch: AppDispatch, getState: () => RootState) => { - const params = - intl && useSettingsStore.getState().settings.autoTranslate - ? { - language: intl.locale, - } - : undefined; - - return getClient(getState()) - .statuses.getStatus(statusId, params) - .then((status) => { - importEntities({ statuses: [status] }); - return status; - }); - }; - -const deleteStatus = - (statusId: string, withRedraft = false) => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return null; - - const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); - if (!status) return null; - - const poll = status.poll_id - ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) +const fetchStatus = (statusId: string, intl?: IntlShape) => { + const params = + intl && useSettingsStore.getState().settings.autoTranslate + ? { + language: intl.locale, + } : undefined; - decrementReplyCount(status); + return getClient() + .statuses.getStatus(statusId, params) + .then((status) => { + importEntities({ statuses: [status] }); + return status; + }); +}; - return getClient(getState()) - .statuses.deleteStatus(statusId) - .then((source) => { - usePendingStatusesStore.getState().actions.deleteStatus(statusId); - useTimelinesStore.getState().actions.deleteStatus(statusId); - updateStatus( - statusId, - (s) => { - s.deleted = true; - }, - queryClient, - ); +const deleteStatus = (statusId: string, withRedraft = false) => { + if (!isLoggedIn()) return null; - if (withRedraft) { - useComposeStore.getState().actions.setComposeToStatus(status, poll, source, withRedraft); - useModalsStore.getState().actions.openModal('COMPOSE'); - } - }) - .catch(() => { - incrementReplyCount(status); - }); - }; + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + if (!status) return null; -const deleteStatusFromGroup = - (statusId: string, groupId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return null; + const poll = status.poll_id + ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) + : undefined; - const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); - if (!status) return null; + decrementReplyCount(status); - decrementReplyCount(status); + return getClient() + .statuses.deleteStatus(statusId) + .then((source) => { + usePendingStatusesStore.getState().actions.deleteStatus(statusId); + useTimelinesStore.getState().actions.deleteStatus(statusId); + updateStatus( + statusId, + (s) => { + s.deleted = true; + }, + queryClient, + ); - return getClient(getState()) - .experimental.groups.deleteGroupStatus(statusId, groupId) - .then(() => { - usePendingStatusesStore.getState().actions.deleteStatus(statusId); - useTimelinesStore.getState().actions.deleteStatus(statusId); - updateStatus( - statusId, - (s) => { - s.deleted = true; - }, - queryClient, - ); - }) - .catch(() => { - incrementReplyCount(status); - }); - }; + if (withRedraft) { + useComposeStore.getState().actions.setComposeToStatus(status, poll, source, withRedraft); + useModalsStore.getState().actions.openModal('COMPOSE'); + } + }) + .catch(() => { + incrementReplyCount(status); + }); +}; -const muteStatus = (statusId: string) => (_dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; +const deleteStatusFromGroup = (statusId: string, groupId: string) => { + if (!isLoggedIn()) return null; - return getClient(getState()) + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + if (!status) return null; + + decrementReplyCount(status); + + return getClient() + .experimental.groups.deleteGroupStatus(statusId, groupId) + .then(() => { + usePendingStatusesStore.getState().actions.deleteStatus(statusId); + useTimelinesStore.getState().actions.deleteStatus(statusId); + updateStatus( + statusId, + (s) => { + s.deleted = true; + }, + queryClient, + ); + }) + .catch(() => { + incrementReplyCount(status); + }); +}; + +const muteStatus = (statusId: string) => { + if (!isLoggedIn()) return; + + return getClient() .statuses.muteStatus(statusId) .then(() => { updateStatus( @@ -269,10 +260,10 @@ const muteStatus = (statusId: string) => (_dispatch: AppDispatch, getState: () = }); }; -const unmuteStatus = (statusId: string) => (_dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; +const unmuteStatus = (statusId: string) => { + if (!isLoggedIn()) return; - return getClient(getState()) + return getClient() .statuses.unmuteStatus(statusId) .then(() => { updateStatus( @@ -291,9 +282,8 @@ const toggleMuteStatus = (status: Pick) => // let TRANSLATIONS_QUEUE: Set = new Set(); // let TRANSLATIONS_TIMEOUT: NodeJS.Timeout | null = null; -// const translateStatus = (statusId: string, targetLanguage: string, lazy?: boolean) => -// (dispatch: AppDispatch, getState: () => RootState) => { -// const client = getClient(getState); +// const translateStatus = (statusId: string, targetLanguage: string, lazy?: boolean) => { +// const client = getClient(); // const features = client.features; // const handleTranslateMany = () => { @@ -334,7 +324,6 @@ const toggleMuteStatus = (status: Pick) => // TRANSLATIONS_QUEUE.add(statusId); // handleTranslateMany(); -// } // }; const unfilterStatus = (statusId: string) => { diff --git a/packages/nicolium/src/api/hooks/streaming/use-timeline-stream.ts b/packages/nicolium/src/api/hooks/streaming/use-timeline-stream.ts index 970668a91..224cf6edd 100644 --- a/packages/nicolium/src/api/hooks/streaming/use-timeline-stream.ts +++ b/packages/nicolium/src/api/hooks/streaming/use-timeline-stream.ts @@ -1,9 +1,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useClient } from '@/hooks/use-client'; -import { useInstance } from '@/hooks/use-instance'; -import { getAccessToken } from '@/utils/auth'; +import { useAuthStore } from '@/stores/auth'; +import { useInstance } from '@/stores/instance'; import type { StreamingEvent, StreamingParams } from 'pl-api'; @@ -28,7 +27,10 @@ const useTimelineStream = ( } | null>(null); const disconnectCleanup = useRef<(() => void) | null>(null); - const accessToken = useAppSelector(getAccessToken); + const accessToken = useAuthStore((state) => { + const me = state.me; + return me ? state.users[me]?.access_token : undefined; + }); const streamingUrl = instance.configuration.urls.streaming; const [connected, setConnected] = useState(false); diff --git a/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts b/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts index fb6140801..c827081a6 100644 --- a/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts +++ b/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts @@ -2,7 +2,6 @@ import { useCallback } from 'react'; import { importEntities } from '@/actions/importer'; import { useStatContext } from '@/contexts/stat-context'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useLoggedIn } from '@/hooks/use-logged-in'; import { updateReactions } from '@/queries/announcements/use-announcements'; import { queryClient } from '@/queries/client'; @@ -10,13 +9,12 @@ import { updateConversations } from '@/queries/conversations/use-conversations'; import { queryKeys } from '@/queries/keys'; import { useProcessStreamNotification } from '@/queries/notifications/use-notifications'; import { useSettings } from '@/stores/settings'; -import { useTimelinesStore } from '@/stores/timelines'; +import { useTimelinesActions } from '@/stores/timelines'; import { getUnreadChatsCount, updateChatListItem } from '@/utils/chats'; import { play, soundCache } from '@/utils/sounds'; import { useTimelineStream } from './use-timeline-stream'; -import type { AppDispatch, RootState } from '@/store'; import type { Announcement, AnnouncementReaction, @@ -68,25 +66,20 @@ const followStateToRelationship = (followState: FollowRelationshipUpdate['state' } }; -const updateFollowRelationships = - (update: FollowRelationshipUpdate) => (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - - const me = state.me; - - if (update.follower.id === me) { - queryClient.setQueryData( - queryKeys.accountRelationships.show(update.following.id), - (relationship) => - relationship - ? { - ...relationship, - ...followStateToRelationship(update.state), - } - : undefined, - ); - } - }; +const updateFollowRelationships = (update: FollowRelationshipUpdate, me: string) => { + if (update.follower.id === me) { + queryClient.setQueryData( + queryKeys.accountRelationships.show(update.following.id), + (relationship) => + relationship + ? { + ...relationship, + ...followStateToRelationship(update.state), + } + : undefined, + ); + } +}; const getTimelineFromStream = (stream: Array) => { switch (stream[0]) { @@ -102,25 +95,25 @@ const getTimelineFromStream = (stream: Array) => { }; const useUserStream = () => { - const { isLoggedIn } = useLoggedIn(); - const dispatch = useAppDispatch(); + const { isLoggedIn, me } = useLoggedIn(); const statContext = useStatContext(); const settings = useSettings(); const processStreamNotification = useProcessStreamNotification(); + const { deleteStatus, receiveStreamingStatus } = useTimelinesActions(); const listener = useCallback((event: StreamingEvent) => { switch (event.event) { case 'update': { const timelineId = getTimelineFromStream(event.stream); importEntities({ statuses: [event.payload] }); - useTimelinesStore.getState().actions.receiveStreamingStatus(timelineId, event.payload); + receiveStreamingStatus(timelineId, event.payload); break; } case 'status.update': importEntities({ statuses: [event.payload] }); break; case 'delete': - useTimelinesStore.getState().actions.deleteStatus(event.payload); + deleteStatus(event.payload); break; case 'notification': processStreamNotification(event.payload); @@ -132,9 +125,8 @@ const useUserStream = () => { queryClient.invalidateQueries({ queryKey: queryKeys.filters.all }); break; case 'chat_update': - dispatch((_dispatch, getState) => { + { const chat = event.payload; - const me = getState().me; const messageOwned = chat.last_message?.account_id === me; // Don't update own messages from streaming @@ -148,10 +140,10 @@ const useUserStream = () => { // Increment unread counter statContext?.setUnreadChatsCount(getUnreadChatsCount()); } - }); + } break; case 'follow_relationships_update': - dispatch(updateFollowRelationships(event.payload)); + updateFollowRelationships(event.payload, me as string); break; case 'announcement': updateAnnouncement(event.payload); diff --git a/packages/nicolium/src/api/index.ts b/packages/nicolium/src/api/index.ts index 49ec28788..00d137e18 100644 --- a/packages/nicolium/src/api/index.ts +++ b/packages/nicolium/src/api/index.ts @@ -3,13 +3,9 @@ * @module @/api */ import * as BuildConfig from '@/build-config'; +import { useAuthStore } from '@/stores/auth'; import { buildFullPath } from '@/utils/url'; -import type { RootState, Store } from '@/store'; - -let store: Store; -import('@/store').then((value) => (store = value.store)).catch(() => {}); - type NicoliumResponse = Response & { data: string; json: T }; /** @@ -47,10 +43,6 @@ const staticFetch = async (input: URL | RequestInfo, init?: RequestInit) => { } as any as NicoliumResponse; }; -const getClient = (state: RootState | (() => RootState) = store?.getState()) => { - if (typeof state === 'function') state = state(); - - return state.auth.client; -}; +const getClient = () => useAuthStore.getState().client; export { type NicoliumResponse, staticFetch, getClient }; diff --git a/packages/nicolium/src/columns/notifications.tsx b/packages/nicolium/src/columns/notifications.tsx index 09a8ae676..01381b037 100644 --- a/packages/nicolium/src/columns/notifications.tsx +++ b/packages/nicolium/src/columns/notifications.tsx @@ -15,7 +15,6 @@ import Portal from '@/components/ui/portal'; import Tabs from '@/components/ui/tabs'; import Notification from '@/features/notifications/components/notification'; import PlaceholderNotification from '@/features/placeholder/components/placeholder-notification'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useFeatures } from '@/hooks/use-features'; import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; @@ -56,7 +55,6 @@ const messages = defineMessages({ const FilterBar = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const settings = useSettings(); const { changeSetting } = useSettingsStoreActions(); const features = useFeatures(); @@ -66,7 +64,7 @@ const FilterBar = () => { const onClick = (filterType: FilterType) => () => { changeSetting(['notifications', 'quickFilter', 'active'], filterType); - dispatch(saveSettings()); + saveSettings(); if (filterType === selectedFilter) { queryClient.refetchQueries({ queryKey: queryKeys.notifications.list(filterType), diff --git a/packages/nicolium/src/columns/timeline.tsx b/packages/nicolium/src/columns/timeline.tsx index a7115b586..efd2688e6 100644 --- a/packages/nicolium/src/columns/timeline.tsx +++ b/packages/nicolium/src/columns/timeline.tsx @@ -11,10 +11,10 @@ import StatusInfo from '@/components/statuses/status-info'; import Tombstone from '@/components/statuses/tombstone'; import Icon from '@/components/ui/icon'; import Portal from '@/components/ui/portal'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import Emojify from '@/features/emoji/emojify'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; import PendingStatus from '@/features/ui/components/pending-status'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; import { useAccounts } from '@/queries/accounts/use-accounts'; import { type SelectedStatus, useStatus } from '@/queries/statuses/use-status'; @@ -560,7 +560,7 @@ const getRestoredPosition = (me: string) => { }; const HomeTimelineColumn: React.FC = (props) => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { timelines: { home: timelineFilters }, diff --git a/packages/nicolium/src/components/accounts/account-hover-card.tsx b/packages/nicolium/src/components/accounts/account-hover-card.tsx index e6b8b7a35..b655287f4 100644 --- a/packages/nicolium/src/components/accounts/account-hover-card.tsx +++ b/packages/nicolium/src/components/accounts/account-hover-card.tsx @@ -8,10 +8,10 @@ import Badge from '@/components/badge'; import Card, { CardBody } from '@/components/ui/card'; import Icon from '@/components/ui/icon'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import ActionButton from '@/features/ui/components/action-button'; import { isTimezoneLabel } from '@/features/ui/components/profile-field'; import { UserPanel } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useAccountScrobbleQuery } from '@/queries/accounts/account-scrobble'; import { useAccount } from '@/queries/accounts/use-account'; import { useAccountHoverCardActions, useAccountHoverCardStore } from '@/stores/account-hover-card'; @@ -72,7 +72,7 @@ const AccountHoverCard: React.FC = ({ visible = true }) => { const { accountId, ref } = useAccountHoverCardStore(); const { updateAccountHoverCard, closeAccountHoverCard } = useAccountHoverCardActions(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: account } = useAccount(accountId ?? undefined, true); const { data: scrobble } = useAccountScrobbleQuery(account?.id); const badges = getBadges(account); diff --git a/packages/nicolium/src/components/accounts/account.tsx b/packages/nicolium/src/components/accounts/account.tsx index 32961ce54..fba99bb03 100644 --- a/packages/nicolium/src/components/accounts/account.tsx +++ b/packages/nicolium/src/components/accounts/account.tsx @@ -10,10 +10,10 @@ import Emoji from '@/components/ui/emoji'; import Icon from '@/components/ui/icon'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import Emojify from '@/features/emoji/emojify'; import ActionButton from '@/features/ui/components/action-button'; import { useAcct } from '@/hooks/use-acct'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useSettings } from '@/stores/settings'; import Badge from '../badge'; @@ -163,7 +163,7 @@ const Account = ({ const [style, setStyle] = useState({}); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const username = useAcct(account); const { disableUserProvidedMedia } = useSettings(); diff --git a/packages/nicolium/src/components/helmet.tsx b/packages/nicolium/src/components/helmet.tsx index 551f60209..f9dac5bd0 100644 --- a/packages/nicolium/src/components/helmet.tsx +++ b/packages/nicolium/src/components/helmet.tsx @@ -1,10 +1,10 @@ import React, { useEffect } from 'react'; import { useStatContext } from '@/contexts/stat-context'; -import { useInstance } from '@/hooks/use-instance'; import { usePendingUsersCount } from '@/queries/admin/use-accounts'; import { usePendingReportsCount } from '@/queries/admin/use-reports'; import { useNotificationsUnreadCount } from '@/queries/notifications/use-notifications'; +import { useInstance } from '@/stores/instance'; import { useSettings } from '@/stores/settings'; import FaviconService from '@/utils/favicon-service'; diff --git a/packages/nicolium/src/components/navigation/dropdown-navigation.tsx b/packages/nicolium/src/components/navigation/dropdown-navigation.tsx index 8daedf3e6..87c1e232b 100644 --- a/packages/nicolium/src/components/navigation/dropdown-navigation.tsx +++ b/packages/nicolium/src/components/navigation/dropdown-navigation.tsx @@ -10,11 +10,9 @@ import Account from '@/components/accounts/account'; import Divider from '@/components/ui/divider'; import Icon from '@/components/ui/icon'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import ProfileStats from '@/features/ui/components/profile-stats'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useRegistrationStatus } from '@/hooks/use-registration-status'; import { useAccount } from '@/queries/accounts/use-account'; import { useFollowRequestsCount } from '@/queries/accounts/use-follow-requests'; @@ -22,6 +20,7 @@ import { useLoggedInAccounts } from '@/queries/accounts/use-logged-in-accounts'; import { scheduledStatusesCountQueryOptions } from '@/queries/statuses/scheduled-statuses'; import { useDraftStatusesCountQuery } from '@/queries/statuses/use-draft-statuses'; import { useInteractionRequestsCount } from '@/queries/statuses/use-interaction-requests'; +import { useInstance } from '@/stores/instance'; import { useSettings } from '@/stores/settings'; import { useIsSidebarOpen, useUiStoreActions } from '@/stores/ui'; import sourceCode from '@/utils/code'; @@ -33,8 +32,6 @@ interface IAccountSwitcher { } const AccountSwitcher: React.FC = ({ handleClose }) => { - const dispatch = useAppDispatch(); - const { accounts: otherAccounts } = useLoggedInAccounts(); const handleSwitchAccount = @@ -43,7 +40,7 @@ const AccountSwitcher: React.FC = ({ handleClose }) => { e.preventDefault(); e.stopPropagation(); - dispatch(switchAccount(account.id)); + switchAccount(account.id); }; const renderAccount = (account: AccountEntity) => ( @@ -125,12 +122,10 @@ const DropdownNavigationLink: React.FC = React.memo( DropdownNavigationLink.displayName = 'DropdownNavigationLink'; const DropdownNavigation: React.FC = React.memo((): React.JSX.Element | null => { - const dispatch = useAppDispatch(); - const isSidebarOpen = useIsSidebarOpen(); const { closeSidebar } = useUiStoreActions(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); const authenticatedScheduledStatusesCountQueryOptions = useMemo( @@ -179,7 +174,7 @@ const DropdownNavigation: React.FC = React.memo((): React.JSX.Element | null => e.preventDefault(); e.stopPropagation(); - dispatch(logOut()); + logOut(); }; const handleKeyDown: React.KeyboardEventHandler = (e) => { @@ -199,7 +194,7 @@ const DropdownNavigation: React.FC = React.memo((): React.JSX.Element | null => }; useEffect(() => { - dispatch(fetchOwnAccounts()); + fetchOwnAccounts(); }, []); useEffect(() => { diff --git a/packages/nicolium/src/components/navigation/sidebar-navigation.tsx b/packages/nicolium/src/components/navigation/sidebar-navigation.tsx index 5444354b1..833879165 100644 --- a/packages/nicolium/src/components/navigation/sidebar-navigation.tsx +++ b/packages/nicolium/src/components/navigation/sidebar-navigation.tsx @@ -8,7 +8,6 @@ import { useStatContext } from '@/contexts/stat-context'; import ComposeButton from '@/features/ui/components/compose-button'; import ProfileDropdown from '@/features/ui/components/profile-dropdown'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useRegistrationStatus } from '@/hooks/use-registration-status'; import { useFollowRequestsCount } from '@/queries/accounts/use-follow-requests'; @@ -18,6 +17,7 @@ import { useNotificationsUnreadCount } from '@/queries/notifications/use-notific import { scheduledStatusesCountQueryOptions } from '@/queries/statuses/scheduled-statuses'; import { useDraftStatusesCountQuery } from '@/queries/statuses/use-draft-statuses'; import { useInteractionRequestsCount } from '@/queries/statuses/use-interaction-requests'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import sourceCode from '@/utils/code'; diff --git a/packages/nicolium/src/components/navigation/thumb-navigation.tsx b/packages/nicolium/src/components/navigation/thumb-navigation.tsx index b589d7deb..80c81fec5 100644 --- a/packages/nicolium/src/components/navigation/thumb-navigation.tsx +++ b/packages/nicolium/src/components/navigation/thumb-navigation.tsx @@ -7,7 +7,6 @@ import ThumbNavigationLink from '@/components/navigation/thumb-navigation-link'; import Icon from '@/components/ui/icon'; import { useStatContext } from '@/contexts/stat-context'; import { layouts } from '@/features/ui/router'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; import { queryKeys } from '@/queries/keys'; @@ -15,7 +14,7 @@ import { useNotificationsUnreadCount } from '@/queries/notifications/use-notific import { useComposeActions } from '@/stores/compose'; import { useModalsActions } from '@/stores/modals'; import { useIsSidebarOpen, useUiStoreActions } from '@/stores/ui'; -import { isStandalone } from '@/utils/state'; +import { useIsStandalone } from '@/utils/state'; const messages = defineMessages({ home: { id: 'column.home', defaultMessage: 'Home' }, @@ -41,7 +40,7 @@ const ThumbNavigation: React.FC = React.memo((): React.JSX.Element => { const { groupComposeModal } = useComposeActions(); const { unreadChatsCount } = useStatContext(); - const standalone = useAppSelector(isStandalone); + const standalone = useIsStandalone(); const notificationCount = useNotificationsUnreadCount(); const handleOpenComposeModal = () => { diff --git a/packages/nicolium/src/components/polls/poll.tsx b/packages/nicolium/src/components/polls/poll.tsx index 506414cef..33b4eace9 100644 --- a/packages/nicolium/src/components/polls/poll.tsx +++ b/packages/nicolium/src/components/polls/poll.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import Text from '@/components/ui/text'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { usePollQuery, usePollVoteMutation } from '@/queries/statuses/use-poll'; import { useModalsActions } from '@/stores/modals'; import { useStatusMeta } from '@/stores/status-meta'; @@ -24,7 +24,7 @@ interface IPoll { const Poll: React.FC = ({ id, status, language, truncate }): React.JSX.Element | null => { const { openModal } = useModalsActions(); - const isLoggedIn = useAppSelector((state) => state.me); + const isLoggedIn = useCurrentAccount(); const { data: poll } = usePollQuery(id); // TODO: handle pending mutation state diff --git a/packages/nicolium/src/components/statuses/event-preview.tsx b/packages/nicolium/src/components/statuses/event-preview.tsx index b0a189416..e0cc51555 100644 --- a/packages/nicolium/src/components/statuses/event-preview.tsx +++ b/packages/nicolium/src/components/statuses/event-preview.tsx @@ -5,10 +5,10 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import VerificationBadge from '@/components/accounts/verification-badge'; import Icon from '@/components/icon'; import Button from '@/components/ui/button'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import Emojify from '@/features/emoji/emojify'; import EventActionButton from '@/features/event/components/event-action-button'; import EventDate from '@/features/event/components/event-date'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useAccount } from '@/queries/accounts/use-account'; import type { NormalizedStatus as StatusEntity } from '@/normalizers/status'; @@ -38,7 +38,7 @@ const EventPreview: React.FC = ({ }) => { const intl = useIntl(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: account } = useAccount(status.account_id); const event = status.event!; diff --git a/packages/nicolium/src/components/statuses/status-action-bar.tsx b/packages/nicolium/src/components/statuses/status-action-bar.tsx index 9411ed7cd..81ed92849 100644 --- a/packages/nicolium/src/components/statuses/status-action-bar.tsx +++ b/packages/nicolium/src/components/statuses/status-action-bar.tsx @@ -15,15 +15,13 @@ import { } from '@/actions/statuses'; import DropdownMenu from '@/components/dropdown-menu'; import StatusActionButton from '@/components/statuses/status-action-button'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import EmojiPickerDropdown from '@/features/emoji/containers/emoji-picker-dropdown-container'; import { languages } from '@/features/preferences'; import { layouts } from '@/features/ui/router'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useCanInteract } from '@/hooks/use-can-interact'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useUnblockAccountMutation } from '@/queries/accounts/use-relationship'; import { useChats } from '@/queries/chats'; @@ -46,6 +44,7 @@ import { useUnreblogStatus, } from '@/queries/statuses/use-status-interactions'; import { useComposeActions } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import { useSettings } from '@/stores/settings'; import { useStatusMeta, useStatusMetaActions } from '@/stores/status-meta'; @@ -59,7 +58,7 @@ import type { Menu } from '@/components/dropdown-menu'; import type { Emoji as EmojiType } from '@/features/emoji'; import type { UnauthorizedModalAction } from '@/modals/unauthorized-modal'; import type { SelectedStatus } from '@/queries/statuses/use-status'; -import type { Me } from '@/reducers/me'; +import type { Me } from '@/stores/auth'; const messages = defineMessages({ adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' }, @@ -722,7 +721,6 @@ const MenuButton: React.FC = ({ }) => { const intl = useIntl(); const navigate = useNavigate(); - const dispatch = useAppDispatch(); const { mentionCompose, directCompose } = useComposeActions(); const match = useMatch({ from: layouts.group.id, shouldThrow: false }); const { boostModal } = useSettings(); @@ -791,7 +789,7 @@ const MenuButton: React.FC = ({ const doDeleteStatus = (withRedraft = false) => { if (!deleteModal) { - dispatch(deleteStatus(status.id, withRedraft)); + deleteStatus(status.id, withRedraft); } else { openModal('CONFIRM', { heading: intl.formatMessage( @@ -803,7 +801,7 @@ const MenuButton: React.FC = ({ confirm: intl.formatMessage( withRedraft ? messages.redraftConfirm : messages.deleteConfirm, ), - onConfirm: () => dispatch(deleteStatus(status.id, withRedraft)), + onConfirm: () => deleteStatus(status.id, withRedraft), }); } }; @@ -822,7 +820,7 @@ const MenuButton: React.FC = ({ to: '/@{$username}/events/$statusId/edit', params: { username: status.account.acct, statusId: status.id }, }); - else dispatch(editStatus(status.id)); + else editStatus(status.id); }; const handlePinClick: React.EventHandler = () => { @@ -890,7 +888,7 @@ const MenuButton: React.FC = ({ }; const handleConversationMuteClick: React.EventHandler = () => { - dispatch(toggleMuteStatus(status)); + toggleMuteStatus(status); }; const handleLoadConversationClick = () => { @@ -939,7 +937,7 @@ const MenuButton: React.FC = ({ }), confirm: intl.formatMessage(messages.deleteConfirm), onConfirm: () => { - dispatch(deleteStatusFromGroup(status.id, group!.id)); + deleteStatusFromGroup(status.id, group!.id); }, }); }; @@ -962,9 +960,7 @@ const MenuButton: React.FC = ({ }; const handleIgnoreLanguage = () => { - dispatch( - changeSetting(['autoTranslate'], [...knownLanguages, status.language], { showAlert: true }), - ); + changeSetting(['autoTranslate'], [...knownLanguages, status.language], { showAlert: true }); }; const handleTranslate = () => { @@ -976,7 +972,7 @@ const MenuButton: React.FC = ({ }; const handleRedactStatus: React.EventHandler = () => { - dispatch(redactStatus(status.id)); + redactStatus(status.id); }; const menu: Menu = []; @@ -1308,7 +1304,7 @@ const MenuButton: React.FC = ({ status.emoji_reactions.length > 0, status.pinned, status.reblogged, - status.account.relationship, + status.account?.relationship, ]); return useMemo( @@ -1343,7 +1339,7 @@ const StatusActionBar: React.FC = ({ }) => { const { openModal } = useModalsActions(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const publicStatus = useMemo( () => (status ? ['public', 'unlisted', 'group'].includes(status.visibility) : false), diff --git a/packages/nicolium/src/components/statuses/status.tsx b/packages/nicolium/src/components/statuses/status.tsx index deef148f2..8ee9cedc7 100644 --- a/packages/nicolium/src/components/statuses/status.tsx +++ b/packages/nicolium/src/components/statuses/status.tsx @@ -415,39 +415,21 @@ const Status: React.FC = React.memo((props) => { /> ); } else if (isReblog) { - const accounts = status.accounts ?? [status.account]; - - const renderedAccounts = accounts.slice(0, 2).map( - (account) => - !!account && ( - - - - - - - - ), - ); - - if (accounts.length > 2) { - renderedAccounts.push( - , - ); - } - const values = { - name: , - count: accounts.length, + name: ( + + + + + + + + ), }; return ( @@ -535,7 +517,7 @@ const Status: React.FC = React.memo((props) => { ) ); } - }, [status.accounts, group?.id]); + }, [status.account, group?.id]); if (!status) return null; diff --git a/packages/nicolium/src/components/statuses/translate-button.tsx b/packages/nicolium/src/components/statuses/translate-button.tsx index 02a0bfc58..d5abf13c6 100644 --- a/packages/nicolium/src/components/statuses/translate-button.tsx +++ b/packages/nicolium/src/components/statuses/translate-button.tsx @@ -2,13 +2,13 @@ import React, { useEffect } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import Icon from '@/components/ui/icon'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { selectAccount } from '@/queries/accounts/selectors'; import { useTranslationLanguages } from '@/queries/instance/use-translation-languages'; import { useLocalStatusTranslation } from '@/queries/statuses/use-local-status-translation'; import { useStatusTranslation } from '@/queries/statuses/use-status-translation'; +import { useInstance } from '@/stores/instance'; import { useLanguageModelAvailability, useLanguageModelAvailabilityActions, @@ -79,7 +79,7 @@ const TranslateButton: React.FC = ({ status }) => { const autoTranslate = settings.autoTranslate; const knownLanguages = autoTranslate ? [...settings.knownLanguages, intl.locale] : [intl.locale]; - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: translationLanguages = {} } = useTranslationLanguages(); const { fetchTranslation, hideTranslation } = useStatusMetaActions(); const { fetchLocalTranslation, hideLocalTranslation } = useStatusMetaActions(); diff --git a/packages/nicolium/src/contexts/chat-context.tsx b/packages/nicolium/src/contexts/chat-context.tsx index e2805a035..7c4dfa2de 100644 --- a/packages/nicolium/src/contexts/chat-context.tsx +++ b/packages/nicolium/src/contexts/chat-context.tsx @@ -3,7 +3,6 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from ' import { toggleChatPane } from '@/actions/chats'; import { chatRoute, layouts } from '@/features/ui/router'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useChat } from '@/queries/chats'; import { useSettings } from '@/stores/settings'; @@ -26,7 +25,6 @@ interface IChatProvider { } const ChatProvider: React.FC = ({ children }) => { - const dispatch = useAppDispatch(); const { chats } = useSettings(); const isUsingMainChatPage = !!useMatch({ from: layouts.chats.id, shouldThrow: false }); @@ -46,7 +44,7 @@ const ChatProvider: React.FC = ({ children }) => { }; const handleChatPaneToggle = () => { - dispatch(toggleChatPane()); + toggleChatPane(); }; const value = useMemo( diff --git a/packages/nicolium/src/contexts/current-account-context.tsx b/packages/nicolium/src/contexts/current-account-context.tsx index 85be5cb0c..8dad192d7 100644 --- a/packages/nicolium/src/contexts/current-account-context.tsx +++ b/packages/nicolium/src/contexts/current-account-context.tsx @@ -1,21 +1,17 @@ import React, { createContext, useContext } from 'react'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useMe, type Me } from '@/stores/auth'; -const CurrentAccountContext = createContext<'unauthenticated' | string>('unauthenticated'); +const CurrentAccountContext = createContext(null); interface ICurrentAccountProvider { children: React.ReactNode; } const DefaultCurrentAccountProvider: React.FC = ({ children }) => { - const me = useAppSelector((state) => state.me); + const me = useMe(); - return ( - - {children} - - ); + return {children}; }; const useCurrentAccount = () => useContext(CurrentAccountContext); diff --git a/packages/nicolium/src/features/account/components/account-menu.tsx b/packages/nicolium/src/features/account/components/account-menu.tsx index a753eefef..909818317 100644 --- a/packages/nicolium/src/features/account/components/account-menu.tsx +++ b/packages/nicolium/src/features/account/components/account-menu.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import { GOTOSOCIAL, MASTODON } from 'pl-api'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; @@ -18,10 +17,7 @@ import { useUnpinAccountMutation, useUpdateAccountNoteMutation, } from '@/queries/accounts/use-relationship'; -import { - blockDomainMutationOptions, - unblockDomainMutationOptions, -} from '@/queries/settings/domain-blocks'; +import { useBlockDomainMutation, useUnblockDomainMutation } from '@/queries/settings/domain-blocks'; import { useComposeActions } from '@/stores/compose'; import { useModalsActions } from '@/stores/modals'; import { useSettings } from '@/stores/settings'; @@ -122,8 +118,8 @@ const AccountMenu: React.FC = ({ account }) => { const { software } = features.version; - const { mutate: blockDomain } = useMutation(blockDomainMutationOptions); - const { mutate: unblockDomain } = useMutation(unblockDomainMutationOptions); + const { mutate: blockDomain } = useBlockDomainMutation(); + const { mutate: unblockDomain } = useUnblockDomainMutation(); const onBlock = () => { if (account.relationship?.blocking) { diff --git a/packages/nicolium/src/features/admin/components/registration-mode-picker.tsx b/packages/nicolium/src/features/admin/components/registration-mode-picker.tsx index c40ac2ac1..f4965e6c4 100644 --- a/packages/nicolium/src/features/admin/components/registration-mode-picker.tsx +++ b/packages/nicolium/src/features/admin/components/registration-mode-picker.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import { RadioGroup, RadioItem } from '@/components/ui/radio'; -import { useInstance } from '@/hooks/use-instance'; import { useUpdateAdminConfig } from '@/queries/admin/use-config'; +import { useInstance } from '@/stores/instance'; import toast from '@/toast'; import type { Instance } from 'pl-api'; diff --git a/packages/nicolium/src/features/auth-login/components/captcha.tsx b/packages/nicolium/src/features/auth-login/components/captcha.tsx index 7e250601c..918cf4369 100644 --- a/packages/nicolium/src/features/auth-login/components/captcha.tsx +++ b/packages/nicolium/src/features/auth-login/components/captcha.tsx @@ -4,7 +4,6 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import { fetchCaptcha } from '@/actions/auth'; import Input from '@/components/ui/input'; import Text from '@/components/ui/text'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; const noOp = () => {}; @@ -37,13 +36,11 @@ const CaptchaField: React.FC = ({ refreshInterval = 5 * 60 * 1000, // 5 minutes, Pleroma default idempotencyKey, }) => { - const dispatch = useAppDispatch(); - const [captcha, setCaptcha] = useState>({}); const [refresh, setRefresh] = useState(undefined); const getCaptcha = () => { - dispatch(fetchCaptcha()) + fetchCaptcha() .then((captcha) => { setCaptcha(captcha); onFetch(captcha); diff --git a/packages/nicolium/src/features/auth-login/components/consumer-button.tsx b/packages/nicolium/src/features/auth-login/components/consumer-button.tsx index 4a31b6770..9718ce0df 100644 --- a/packages/nicolium/src/features/auth-login/components/consumer-button.tsx +++ b/packages/nicolium/src/features/auth-login/components/consumer-button.tsx @@ -4,7 +4,6 @@ import { useIntl, defineMessages } from 'react-intl'; import { prepareRequest } from '@/actions/consumer-auth'; import IconButton from '@/components/ui/icon-button'; import Tooltip from '@/components/ui/tooltip'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { capitalize } from '@/utils/strings'; const messages = defineMessages({ @@ -28,12 +27,11 @@ interface IConsumerButton { /** OAuth consumer button for logging in with a third-party service. */ const ConsumerButton: React.FC = ({ provider }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const icon = BRAND_ICONS[provider] || require('@phosphor-icons/core/regular/key.svg'); const handleClick = () => { - dispatch(prepareRequest(provider)); + prepareRequest(provider); }; return ( diff --git a/packages/nicolium/src/features/auth-login/components/consumers-list.tsx b/packages/nicolium/src/features/auth-login/components/consumers-list.tsx index 2849cf3d3..3f0ccc459 100644 --- a/packages/nicolium/src/features/auth-login/components/consumers-list.tsx +++ b/packages/nicolium/src/features/auth-login/components/consumers-list.tsx @@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl'; import Card from '@/components/ui/card'; import Text from '@/components/ui/text'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import ConsumerButton from './consumer-button'; diff --git a/packages/nicolium/src/features/auth-login/components/otp-auth-form.tsx b/packages/nicolium/src/features/auth-login/components/otp-auth-form.tsx index ecde2244c..721ca5255 100644 --- a/packages/nicolium/src/features/auth-login/components/otp-auth-form.tsx +++ b/packages/nicolium/src/features/auth-login/components/otp-auth-form.tsx @@ -10,7 +10,6 @@ import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; const messages = defineMessages({ otpLoginFail: { id: 'login.otp_log_in.fail', defaultMessage: 'Invalid code, please try again.' }, @@ -22,7 +21,6 @@ interface IOtpAuthForm { } const OtpAuthForm: React.FC = ({ mfa_token, small }) => { - const dispatch = useAppDispatch(); const intl = useIntl(); const [isLoading, setIsLoading] = useState(false); @@ -36,14 +34,14 @@ const OtpAuthForm: React.FC = ({ mfa_token, small }) => { const handleSubmit = (event: React.SubmitEvent) => { const { code } = getFormData(event.target); - dispatch(otpVerify(code, mfa_token)) + otpVerify(code, mfa_token) .then(({ access_token }) => { setCodeError(false); - return dispatch(verifyCredentials(access_token)); + return verifyCredentials(access_token); }) .then((account: Record) => { setShouldRedirect(true); - return dispatch(switchAccount(account.id)); + return switchAccount(account.id); }) .catch(() => { setIsLoading(false); diff --git a/packages/nicolium/src/features/auth-login/components/registration-form.tsx b/packages/nicolium/src/features/auth-login/components/registration-form.tsx index 7a413e212..f3a77fa49 100644 --- a/packages/nicolium/src/features/auth-login/components/registration-form.tsx +++ b/packages/nicolium/src/features/auth-login/components/registration-form.tsx @@ -14,10 +14,9 @@ import Input from '@/components/ui/input'; import Select from '@/components/ui/select'; import Textarea from '@/components/ui/textarea'; import CaptchaField from '@/features/auth-login/components/captcha'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import { useSettings } from '@/stores/settings'; @@ -50,7 +49,6 @@ interface IRegistrationForm { const RegistrationForm: React.FC = ({ inviteToken }) => { const intl = useIntl(); const navigate = useNavigate(); - const dispatch = useAppDispatch(); const client = useClient(); const { locale } = useSettings(); @@ -192,7 +190,7 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { launchModal(); return; } else { - return dispatch(verifyCredentials(access_token)).then(() => { + return verifyCredentials(access_token).then(() => { navigate({ to: '/' }); }); } @@ -244,7 +242,7 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { setSubmissionLoading(true); - dispatch(register(normalParams)) + register(normalParams) .then(postRegisterAction) .catch(() => { setSubmissionLoading(false); diff --git a/packages/nicolium/src/features/chats/components/chat-composer.tsx b/packages/nicolium/src/features/chats/components/chat-composer.tsx index e39b3f3e1..b5e106143 100644 --- a/packages/nicolium/src/features/chats/components/chat-composer.tsx +++ b/packages/nicolium/src/features/chats/components/chat-composer.tsx @@ -13,12 +13,12 @@ import Text from '@/components/ui/text'; import { useChatContext } from '@/contexts/chat-context'; import UploadButton from '@/features/compose/components/upload-button'; import emojiSearch from '@/features/emoji/search'; -import { useInstance } from '@/hooks/use-instance'; import { useRelationshipQuery, useUnblockAccountMutation, } from '@/queries/accounts/use-relationship'; import { useCustomEmojis } from '@/queries/instance/use-custom-emojis'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import { textAtCursorMatchesToken } from '@/utils/suggestions'; diff --git a/packages/nicolium/src/features/chats/components/chat-list-shoutbox.tsx b/packages/nicolium/src/features/chats/components/chat-list-shoutbox.tsx index 4919626df..a0267547e 100644 --- a/packages/nicolium/src/features/chats/components/chat-list-shoutbox.tsx +++ b/packages/nicolium/src/features/chats/components/chat-list-shoutbox.tsx @@ -6,8 +6,8 @@ import Avatar from '@/components/ui/avatar'; import Emojify from '@/features/emoji/emojify'; import { Hotkeys } from '@/features/ui/components/hotkeys'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; -import { useInstance } from '@/hooks/use-instance'; import { useAccount } from '@/queries/accounts/use-account'; +import { useInstance } from '@/stores/instance'; import { useShoutboxMessages } from '@/stores/shoutbox'; import type { Chat } from 'pl-api'; diff --git a/packages/nicolium/src/features/chats/components/chat-message.tsx b/packages/nicolium/src/features/chats/components/chat-message.tsx index 1fdd91109..1acb75f93 100644 --- a/packages/nicolium/src/features/chats/components/chat-message.tsx +++ b/packages/nicolium/src/features/chats/components/chat-message.tsx @@ -7,8 +7,8 @@ import DropdownMenu from '@/components/dropdown-menu'; import { ParsedContent } from '@/components/statuses/parsed-content'; import Icon from '@/components/ui/icon'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { MediaGallery } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useDeleteChatMessage, type ChatMessage as ChatMessageEntity } from '@/queries/chats'; import { useModalsActions } from '@/stores/modals'; import { stripHTML } from '@/utils/html'; @@ -48,7 +48,7 @@ const ChatMessage: React.FC = React.memo((props) => { const { openModal } = useModalsActions(); const intl = useIntl(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const deleteChatMessage = useDeleteChatMessage(chat.id); const [isMenuOpen, setIsMenuOpen] = useState(false); diff --git a/packages/nicolium/src/features/chats/components/chat-widget/shoutbox-window.tsx b/packages/nicolium/src/features/chats/components/chat-widget/shoutbox-window.tsx index b2667f157..df1139de7 100644 --- a/packages/nicolium/src/features/chats/components/chat-widget/shoutbox-window.tsx +++ b/packages/nicolium/src/features/chats/components/chat-widget/shoutbox-window.tsx @@ -6,7 +6,7 @@ import Icon from '@/components/ui/icon'; import Text from '@/components/ui/text'; import { ChatWidgetScreens, useChatContext } from '@/contexts/chat-context'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import Shoutbox from '../shoutbox'; diff --git a/packages/nicolium/src/features/chats/components/chat.tsx b/packages/nicolium/src/features/chats/components/chat.tsx index c2957ebc7..29c1f5494 100644 --- a/packages/nicolium/src/features/chats/components/chat.tsx +++ b/packages/nicolium/src/features/chats/components/chat.tsx @@ -3,7 +3,6 @@ import React, { type MutableRefObject, useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { uploadMedia } from '@/actions/media'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useCreateChatMessage } from '@/queries/chats'; import toast from '@/toast'; @@ -51,7 +50,6 @@ const clearNativeInputValue = (element: HTMLTextAreaElement) => { */ const Chat: React.FC = ({ chat, inputRef, className }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const createChatMessage = useCreateChatMessage(); @@ -142,7 +140,7 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { setUploading(true); - dispatch(uploadMedia({ file: files[0] }, onUploadProgress)) + uploadMedia({ file: files[0] }, onUploadProgress) .then((response) => { setAttachment(response); setUploading(false); diff --git a/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-settings.tsx b/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-settings.tsx index 1cc6d9dbb..ea8e14ad6 100644 --- a/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-settings.tsx +++ b/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-settings.tsx @@ -10,7 +10,6 @@ import Form from '@/components/ui/form'; import IconButton from '@/components/ui/icon-button'; import Toggle from '@/components/ui/toggle'; import SettingToggle from '@/features/settings/components/setting-toggle'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useUpdateCredentials } from '@/queries/accounts/use-account-credentials'; import { useSettings } from '@/stores/settings'; @@ -45,7 +44,6 @@ const ChatsPageSettings = () => { const { data: account } = useOwnAccount(); const intl = useIntl(); const navigate = useNavigate(); - const dispatch = useAppDispatch(); const settings = useSettings(); const updateCredentials = useUpdateCredentials(); @@ -54,7 +52,7 @@ const ChatsPageSettings = () => { }); const onToggleChange = (key: string[], checked: boolean) => { - dispatch(changeSetting(key, checked, { showAlert: true })); + changeSetting(key, checked, { showAlert: true }); }; const handleSubmit: React.SubmitEventHandler = (event) => { diff --git a/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-shoutbox.tsx b/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-shoutbox.tsx index 1e9f6f74f..21528cbf7 100644 --- a/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-shoutbox.tsx +++ b/packages/nicolium/src/features/chats/components/chats-page/components/chats-page-shoutbox.tsx @@ -6,7 +6,7 @@ import Avatar from '@/components/ui/avatar'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import Shoutbox from '../../shoutbox'; diff --git a/packages/nicolium/src/features/chats/components/shoutbox-composer.tsx b/packages/nicolium/src/features/chats/components/shoutbox-composer.tsx index c3f82a94d..8d80b0537 100644 --- a/packages/nicolium/src/features/chats/components/shoutbox-composer.tsx +++ b/packages/nicolium/src/features/chats/components/shoutbox-composer.tsx @@ -4,7 +4,7 @@ import { defineMessages, useIntl } from 'react-intl'; import Combobox, { ComboboxInput } from '@/components/ui/combobox'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import ChatTextarea from './chat-textarea'; diff --git a/packages/nicolium/src/features/chats/components/shoutbox-message-list.tsx b/packages/nicolium/src/features/chats/components/shoutbox-message-list.tsx index 3fd9e37d2..aff3c6709 100644 --- a/packages/nicolium/src/features/chats/components/shoutbox-message-list.tsx +++ b/packages/nicolium/src/features/chats/components/shoutbox-message-list.tsx @@ -7,9 +7,9 @@ import HoverAccountWrapper from '@/components/accounts/hover-account-wrapper'; import { ParsedContent } from '@/components/statuses/parsed-content'; import Avatar from '@/components/ui/avatar'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import Emojify from '@/features/emoji/emojify'; import PlaceholderChatMessage from '@/features/placeholder/components/placeholder-chat-message'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useAccount } from '@/queries/accounts/use-account'; import { useShoutboxIsLoading, useShoutboxMessages, type ShoutMessage } from '@/stores/shoutbox'; @@ -95,7 +95,7 @@ const ShoutboxMessageList: React.FC = () => { const node = useRef(null); const [firstItemIndex, setFirstItemIndex] = useState(START_INDEX - 20); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const shoutboxMessages = useShoutboxMessages() || []; const isLoading = useShoutboxIsLoading(); diff --git a/packages/nicolium/src/features/compose-event/components/upload-button.tsx b/packages/nicolium/src/features/compose-event/components/upload-button.tsx index b4a43b6dd..fd5e913b0 100644 --- a/packages/nicolium/src/features/compose-event/components/upload-button.tsx +++ b/packages/nicolium/src/features/compose-event/components/upload-button.tsx @@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl'; import Icon from '@/components/icon'; import Text from '@/components/ui/text'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useInstance } from '@/stores/instance'; interface IUploadButton { disabled?: boolean; @@ -13,9 +13,10 @@ interface IUploadButton { const UploadButton: React.FC = ({ disabled, onSelectFile }) => { const fileElement = useRef(null); - const attachmentTypes = useAppSelector( - (state) => state.instance.configuration.media_attachments.supported_mime_types, - )?.filter((type) => type.startsWith('image/')); + const attachmentTypes = + useInstance().configuration.media_attachments.supported_mime_types?.filter((type) => + type.startsWith('image/'), + ); let accept = attachmentTypes?.join(','); if (accept === 'application/octet-stream') accept = undefined; diff --git a/packages/nicolium/src/features/compose-event/tabs/edit-event.tsx b/packages/nicolium/src/features/compose-event/tabs/edit-event.tsx index f39cd874c..d98828b0d 100644 --- a/packages/nicolium/src/features/compose-event/tabs/edit-event.tsx +++ b/packages/nicolium/src/features/compose-event/tabs/edit-event.tsx @@ -20,10 +20,9 @@ import Toggle from '@/components/ui/toggle'; import ContentTypeButton from '@/features/compose/components/content-type-button'; import { isCurrentOrFutureDate } from '@/features/compose/components/schedule-form'; import { ComposeEditor, DatePicker } from '@/features/ui/util/async-components'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useInstance } from '@/hooks/use-instance'; import { useMinimalStatus } from '@/queries/statuses/use-status'; import { useChangeUploadCompose, useComposeActions } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import toast from '@/toast'; @@ -66,7 +65,6 @@ interface IEditEvent { const EditEvent: React.FC = ({ statusId }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const navigate = useNavigate(); const { openModal } = useModalsActions(); @@ -128,18 +126,16 @@ const EditEvent: React.FC = ({ statusId }) => { const handleFiles = (files: FileList) => { setIsUploading(true); - dispatch( - uploadFile( - files[0], - intl, - (data) => { - setBanner(data); - setIsUploading(false); - }, - () => { - setIsUploading(false); - }, - ), + uploadFile( + files[0], + intl, + (data) => { + setBanner(data); + setIsUploading(false); + }, + () => { + setIsUploading(false); + }, ); }; @@ -170,18 +166,16 @@ const EditEvent: React.FC = ({ statusId }) => { const handleSubmit = () => { setIsDisabled(true); - dispatch( - submitEvent({ - statusId, - name, - status: text, - banner, - startTime, - endTime, - joinMode: approvalRequired ? 'restricted' : 'free', - location, - }), - ) + submitEvent({ + statusId, + name, + status: text, + banner, + startTime, + endTime, + joinMode: approvalRequired ? 'restricted' : 'free', + location, + }) .then((status) => { if (status) navigate({ @@ -195,7 +189,7 @@ const EditEvent: React.FC = ({ statusId }) => { useEffect(() => { if (statusId) { - Promise.all([dispatch(initEventEdit(statusId)), dispatch(fetchStatus(statusId))]) + Promise.all([initEventEdit(statusId), fetchStatus(statusId)]) .then(([source, status]) => { if (!source || !status) throw new Error(); diff --git a/packages/nicolium/src/features/compose/components/compose-form.tsx b/packages/nicolium/src/features/compose/components/compose-form.tsx index 25cbff5ad..86fd5728b 100644 --- a/packages/nicolium/src/features/compose/components/compose-form.tsx +++ b/packages/nicolium/src/features/compose/components/compose-form.tsx @@ -13,7 +13,6 @@ import EmojiPickerDropdown from '@/features/emoji/containers/emoji-picker-dropdo import { ComposeEditor } from '@/features/ui/util/async-components'; import { useDraggedFiles } from '@/hooks/use-dragged-files'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { usePersistDraftStatus } from '@/queries/statuses/use-draft-statuses'; import { useCompose, @@ -21,6 +20,7 @@ import { useUploadCompose, useSubmitCompose, } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import toast from '@/toast'; diff --git a/packages/nicolium/src/features/compose/components/content-type-button.tsx b/packages/nicolium/src/features/compose/components/content-type-button.tsx index ac8fbcd32..b12a15e66 100644 --- a/packages/nicolium/src/features/compose/components/content-type-button.tsx +++ b/packages/nicolium/src/features/compose/components/content-type-button.tsx @@ -3,8 +3,8 @@ import { defineMessages, useIntl } from 'react-intl'; import DropdownMenu from '@/components/dropdown-menu'; import Icon from '@/components/ui/icon'; -import { useInstance } from '@/hooks/use-instance'; import { useCompose, useComposeActions } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; const messages = defineMessages({ contentTypePlaintext: { diff --git a/packages/nicolium/src/features/compose/components/drive-button.tsx b/packages/nicolium/src/features/compose/components/drive-button.tsx index 6181b9577..f1bce4a91 100644 --- a/packages/nicolium/src/features/compose/components/drive-button.tsx +++ b/packages/nicolium/src/features/compose/components/drive-button.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import * as v from 'valibot'; -import { useInstance } from '@/hooks/use-instance'; import { appendMedia, useComposeActions } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import ComposeFormButton from './compose-form-button'; diff --git a/packages/nicolium/src/features/compose/components/hashtag-casing-suggestion.tsx b/packages/nicolium/src/features/compose/components/hashtag-casing-suggestion.tsx index d382a1d40..ef890e546 100644 --- a/packages/nicolium/src/features/compose/components/hashtag-casing-suggestion.tsx +++ b/packages/nicolium/src/features/compose/components/hashtag-casing-suggestion.tsx @@ -3,7 +3,6 @@ import { defineMessages, FormattedMessage } from 'react-intl'; import { changeSetting } from '@/actions/settings'; import Button from '@/components/ui/button'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useCompose, useComposeActions } from '@/stores/compose'; import toast from '@/toast'; @@ -21,7 +20,6 @@ interface IHashtagCasingSuggestion { } const HashtagCasingSuggestion = ({ composeId }: IHashtagCasingSuggestion) => { - const dispatch = useAppDispatch(); const { updateCompose } = useComposeActions(); const compose = useCompose(composeId); @@ -35,9 +33,7 @@ const HashtagCasingSuggestion = ({ composeId }: IHashtagCasingSuggestion) => { }; const onDontAskAgain = () => { - dispatch( - changeSetting(['ignoreHashtagCasingSuggestions'], true, { showAlert: false, save: true }), - ); + changeSetting(['ignoreHashtagCasingSuggestions'], true, { showAlert: false, save: true }); toast.info(messages.hashtagCasingSuggestionsDisabled); onIgnore(); }; diff --git a/packages/nicolium/src/features/compose/components/polls/poll-form.tsx b/packages/nicolium/src/features/compose/components/polls/poll-form.tsx index f7f99a601..e30b9bd83 100644 --- a/packages/nicolium/src/features/compose/components/polls/poll-form.tsx +++ b/packages/nicolium/src/features/compose/components/polls/poll-form.tsx @@ -7,8 +7,8 @@ import Divider from '@/components/ui/divider'; import Text from '@/components/ui/text'; import Toggle from '@/components/ui/toggle'; import { useComposeSuggestions } from '@/hooks/use-compose-suggestions'; -import { useInstance } from '@/hooks/use-instance'; import { useCompose, useComposeActions } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; import DurationSelector from './duration-selector'; diff --git a/packages/nicolium/src/features/compose/components/upload-button.tsx b/packages/nicolium/src/features/compose/components/upload-button.tsx index 4e5876bc0..044a5b6dd 100644 --- a/packages/nicolium/src/features/compose/components/upload-button.tsx +++ b/packages/nicolium/src/features/compose/components/upload-button.tsx @@ -2,7 +2,7 @@ import React, { useRef } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import IconButton from '@/components/ui/icon-button'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; const messages = defineMessages({ upload: { id: 'upload_button.label', defaultMessage: 'Add media attachment' }, diff --git a/packages/nicolium/src/features/compose/components/upload.tsx b/packages/nicolium/src/features/compose/components/upload.tsx index 810390140..7da0b8bfe 100644 --- a/packages/nicolium/src/features/compose/components/upload.tsx +++ b/packages/nicolium/src/features/compose/components/upload.tsx @@ -1,8 +1,8 @@ import React, { useCallback } from 'react'; import Upload from '@/components/upload'; -import { useInstance } from '@/hooks/use-instance'; import { useChangeUploadCompose, useCompose, useComposeActions } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; interface IUploadCompose { id: string; diff --git a/packages/nicolium/src/features/compose/editor/plugins/autosuggest-plugin.tsx b/packages/nicolium/src/features/compose/editor/plugins/autosuggest-plugin.tsx index 0f4f69fcb..572511cca 100644 --- a/packages/nicolium/src/features/compose/editor/plugins/autosuggest-plugin.tsx +++ b/packages/nicolium/src/features/compose/editor/plugins/autosuggest-plugin.tsx @@ -34,7 +34,6 @@ import ReactDOM from 'react-dom'; import { saveSettings } from '@/actions/settings'; import AutosuggestEmoji from '@/components/autosuggest-emoji'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useComposeSuggestions } from '@/hooks/use-compose-suggestions'; import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; @@ -271,7 +270,6 @@ const AutosuggestPlugin = ({ setSuggestionsHidden, }: AutosuggestPluginProps): React.JSX.Element | null => { const { rememberEmojiUse } = useSettingsStoreActions(); - const dispatch = useAppDispatch(); const [editor] = useLexicalComposerContext(); const [resolution, setResolution] = useState(null); @@ -309,7 +307,7 @@ const AutosuggestPlugin = ({ if (!suggestion.id) return; rememberEmojiUse(suggestion as Emoji); - dispatch(saveSettings()); + saveSettings(); replaceMatch($createEmojiNode(suggestion as Emoji)); } else if (typeof suggestion === 'string') { diff --git a/packages/nicolium/src/features/compose/editor/plugins/floating-block-type-toolbar-plugin.tsx b/packages/nicolium/src/features/compose/editor/plugins/floating-block-type-toolbar-plugin.tsx index 82064c05f..76d026681 100644 --- a/packages/nicolium/src/features/compose/editor/plugins/floating-block-type-toolbar-plugin.tsx +++ b/packages/nicolium/src/features/compose/editor/plugins/floating-block-type-toolbar-plugin.tsx @@ -23,9 +23,8 @@ import { createPortal } from 'react-dom'; import { defineMessages, useIntl } from 'react-intl'; import { uploadFile } from '@/actions/media'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import { $createImageNode } from '../nodes/image-node'; import { setFloatingElemPosition } from '../utils/set-floating-elem-position'; @@ -47,7 +46,6 @@ interface IUploadButton { const UploadButton: React.FC = ({ onSelectFile }) => { const intl = useIntl(); const { configuration } = useInstance(); - const dispatch = useAppDispatch(); const [disabled, setDisabled] = useState(false); const fileElement = useRef(null); @@ -57,18 +55,16 @@ const UploadButton: React.FC = ({ onSelectFile }) => { if (e.target.files?.length) { setDisabled(true); - dispatch( - uploadFile( - e.target.files.item(0) as File, - intl, - ({ url }) => { - onSelectFile(url); - setDisabled(false); - }, - () => { - setDisabled(false); - }, - ), + uploadFile( + e.target.files.item(0) as File, + intl, + ({ url }) => { + onSelectFile(url); + setDisabled(false); + }, + () => { + setDisabled(false); + }, ); } }; diff --git a/packages/nicolium/src/features/compose/editor/plugins/state-plugin.tsx b/packages/nicolium/src/features/compose/editor/plugins/state-plugin.tsx index 11471bf86..c63ed0508 100644 --- a/packages/nicolium/src/features/compose/editor/plugins/state-plugin.tsx +++ b/packages/nicolium/src/features/compose/editor/plugins/state-plugin.tsx @@ -8,7 +8,6 @@ import { useCallback, useEffect } from 'react'; import { useIntl } from 'react-intl'; import { fetchStatus } from '@/actions/statuses'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useFeatures } from '@/hooks/use-features'; import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; @@ -30,7 +29,6 @@ interface IStatePlugin { const StatePlugin: React.FC = ({ composeId, isWysiwyg }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const [editor] = useLexicalComposerContext(); const features = useFeatures(); const { urlPrivacy, ignoreHashtagCasingSuggestions } = useSettings(); @@ -137,7 +135,7 @@ const StatePlugin: React.FC = ({ composeId, isWysiwyg }) => { break; } - const status = await dispatch(fetchStatus(id, intl)); + const status = await fetchStatus(id, intl); if (status) { quoteId = status.id; diff --git a/packages/nicolium/src/features/crypto-donate/components/crypto-donate-panel.tsx b/packages/nicolium/src/features/crypto-donate/components/crypto-donate-panel.tsx index b66147d6a..0490eb910 100644 --- a/packages/nicolium/src/features/crypto-donate/components/crypto-donate-panel.tsx +++ b/packages/nicolium/src/features/crypto-donate/components/crypto-donate-panel.tsx @@ -5,7 +5,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import Text from '@/components/ui/text'; import Widget from '@/components/ui/widget'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import SiteWallet from './site-wallet'; diff --git a/packages/nicolium/src/features/draft-statuses/components/draft-status-action-bar.tsx b/packages/nicolium/src/features/draft-statuses/components/draft-status-action-bar.tsx index 7c93abb96..1978fc635 100644 --- a/packages/nicolium/src/features/draft-statuses/components/draft-status-action-bar.tsx +++ b/packages/nicolium/src/features/draft-statuses/components/draft-status-action-bar.tsx @@ -3,7 +3,6 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { fetchStatus } from '@/actions/statuses'; import Button from '@/components/ui/button'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; import { useCancelDraftStatus } from '@/queries/statuses/use-draft-statuses'; @@ -37,7 +36,6 @@ const DraftStatusActionBar: React.FC = ({ source, status const { openModal } = useModalsActions(); const { setComposeToStatus } = useComposeActions(); const settings = useSettings(); - const dispatch = useAppDispatch(); const cancelDraftStatus = useCancelDraftStatus(); const handleCancelClick = () => { @@ -55,7 +53,7 @@ const DraftStatusActionBar: React.FC = ({ source, status }; const handleEditClick = () => { - if (status.in_reply_to_id) dispatch(fetchStatus(status.in_reply_to_id)); + if (status.in_reply_to_id) fetchStatus(status.in_reply_to_id); const poll = status.poll_id ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) : undefined; diff --git a/packages/nicolium/src/features/emoji/components/emoji-picker-dropdown.tsx b/packages/nicolium/src/features/emoji/components/emoji-picker-dropdown.tsx index e8dc4d2b2..f16307413 100644 --- a/packages/nicolium/src/features/emoji/components/emoji-picker-dropdown.tsx +++ b/packages/nicolium/src/features/emoji/components/emoji-picker-dropdown.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useLayoutEffect, useMemo, useState, Suspense } from ' import { defineMessages, useIntl } from 'react-intl'; import { changeSetting, saveSettings } from '@/actions/settings'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useTheme } from '@/hooks/use-theme'; import { useCustomEmojis } from '@/queries/instance/use-custom-emojis'; import { useSettings, useSettingsStoreActions } from '@/stores/settings'; @@ -128,7 +127,6 @@ const EmojiPickerDropdown: React.FC = ({ withCustom = true, }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const title = intl.formatMessage(messages.emoji); const theme = useTheme(); const { rememberEmojiUse } = useSettingsStoreActions(); @@ -164,7 +162,7 @@ const EmojiPickerDropdown: React.FC = ({ } rememberEmojiUse(pickedEmoji); - dispatch(saveSettings()); + saveSettings(); if (onPickEmoji) { onPickEmoji(pickedEmoji); @@ -172,7 +170,7 @@ const EmojiPickerDropdown: React.FC = ({ }; const handleSkinTone = (skinTone: string) => { - dispatch(changeSetting(['skinTone'], skinTone)); + changeSetting(['skinTone'], skinTone); }; const getI18n = () => ({ diff --git a/packages/nicolium/src/features/event/components/event-action-button.tsx b/packages/nicolium/src/features/event/components/event-action-button.tsx index 6c02ede0c..235a11475 100644 --- a/packages/nicolium/src/features/event/components/event-action-button.tsx +++ b/packages/nicolium/src/features/event/components/event-action-button.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import Button from '@/components/ui/button'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useJoinEventMutation, useLeaveEventMutation, @@ -31,7 +31,7 @@ const EventActionButton: React.FC = ({ status, theme = 'secondary' const intl = useIntl(); const { openModal } = useModalsActions(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { mutate: joinEvent } = useJoinEventMutation(status.id); const { mutate: leaveEvent } = useLeaveEventMutation(status.id); diff --git a/packages/nicolium/src/features/event/components/event-header.tsx b/packages/nicolium/src/features/event/components/event-header.tsx index e3664d9e3..3f93dcaad 100644 --- a/packages/nicolium/src/features/event/components/event-header.tsx +++ b/packages/nicolium/src/features/event/components/event-header.tsx @@ -13,7 +13,6 @@ import Button from '@/components/ui/button'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; import Emojify from '@/features/emoji/emojify'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; @@ -110,7 +109,6 @@ interface IEventHeader { const EventHeader: React.FC = ({ status }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const navigate = useNavigate(); const { quoteCompose, mentionCompose, directCompose } = useComposeActions(); @@ -212,7 +210,7 @@ const EventHeader: React.FC = ({ status }) => { /> ), confirm: , - onConfirm: () => dispatch(deleteStatus(status.id)), + onConfirm: () => deleteStatus(status.id), }); }; diff --git a/packages/nicolium/src/features/external-login/components/external-login-form.tsx b/packages/nicolium/src/features/external-login/components/external-login-form.tsx index 17257798b..a8402458e 100644 --- a/packages/nicolium/src/features/external-login/components/external-login-form.tsx +++ b/packages/nicolium/src/features/external-login/components/external-login-form.tsx @@ -6,7 +6,6 @@ import Form from '@/components/ui/form'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; import Spinner from '@/components/ui/spinner'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import toast from '@/toast'; const messages = defineMessages({ @@ -29,7 +28,6 @@ const ExternalLoginForm: React.FC = () => { const server = query.get('server'); const intl = useIntl(); - const dispatch = useAppDispatch(); const [host, setHost] = useState(server ?? ''); const [isLoading, setLoading] = useState(false); @@ -67,7 +65,7 @@ const ExternalLoginForm: React.FC = () => { useEffect(() => { if (code) { - dispatch(loginWithCode(code)); + loginWithCode(code); } }, [code]); diff --git a/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx b/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx index 7e781f075..389aed035 100644 --- a/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx +++ b/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx @@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl'; import Icon from '@/components/icon'; import Text from '@/components/ui/text'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import type { RemoteInstance } from '@/selectors'; diff --git a/packages/nicolium/src/features/notifications/components/notification.tsx b/packages/nicolium/src/features/notifications/components/notification.tsx index ea108973e..3602c61dc 100644 --- a/packages/nicolium/src/features/notifications/components/notification.tsx +++ b/packages/nicolium/src/features/notifications/components/notification.tsx @@ -21,7 +21,6 @@ import AccountContainer from '@/containers/account-container'; import StatusContainer from '@/containers/status-container'; import Emojify from '@/features/emoji/emojify'; import { Hotkeys } from '@/features/ui/components/hotkeys'; -import { useInstance } from '@/hooks/use-instance'; import { useLoggedIn } from '@/hooks/use-logged-in'; import { useNotification } from '@/queries/notifications/use-notifications'; import { @@ -31,6 +30,7 @@ import { useUnreblogStatus, } from '@/queries/statuses/use-status-interactions'; import { useComposeActions } from '@/stores/compose'; +import { useInstance } from '@/stores/instance'; import { useModalsActions } from '@/stores/modals'; import { useSettings } from '@/stores/settings'; import { useStatusMetaActions } from '@/stores/status-meta'; diff --git a/packages/nicolium/src/features/preferences/index.tsx b/packages/nicolium/src/features/preferences/index.tsx index 4c20e1f92..66ed6431c 100644 --- a/packages/nicolium/src/features/preferences/index.tsx +++ b/packages/nicolium/src/features/preferences/index.tsx @@ -10,21 +10,17 @@ import { Multiselect } from '@/components/ui/multiselect'; import StepSlider from '@/components/ui/step-slider'; import { SelectDropdown } from '@/features/forms'; import SettingToggle from '@/features/settings/components/setting-toggle'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; -import { useInstance } from '@/hooks/use-instance'; import { PaletteListItem } from '@/pages/dashboard/theme-editor'; +import { useInstance } from '@/stores/instance'; import { useDefaultSettings, useSettings } from '@/stores/settings'; import sourceCode from '@/utils/code'; import colors from '@/utils/colors'; -import { isStandalone } from '@/utils/state'; +import { useIsStandalone } from '@/utils/state'; import ThemeToggle from '../ui/components/theme-toggle'; -import type { AppDispatch } from '@/store'; - const languages = { en: 'English', ar: 'العربية', @@ -148,38 +144,35 @@ const messages = defineMessages({ black: { id: 'theme_toggle.black', defaultMessage: 'Black' }, }); -const debouncedSave = debounce((dispatch: AppDispatch) => { - dispatch(saveSettings({ showAlert: true })); +const debouncedSave = debounce(() => { + saveSettings({ showAlert: true }); }, 1000); const Preferences = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const features = useFeatures(); const settings = useSettings(); const defaultSettings = useDefaultSettings(); const frontendConfig = useFrontendConfig(); const instance = useInstance(); - const standalone = useAppSelector(isStandalone); + const standalone = useIsStandalone(); const brandColor = (settings.theme?.brandColor ?? frontendConfig.brandColor) || '#d80482'; const onSelectChange = (event: React.ChangeEvent, path: string[]) => { - dispatch(changeSetting(path, event.target.value, { showAlert: true })); + changeSetting(path, event.target.value, { showAlert: true }); }; const onSelectMultiple = (selectedList: string[], path: string[]) => { - dispatch( - changeSetting( - path, - selectedList.toSorted((a, b) => a.localeCompare(b)), - { showAlert: true }, - ), + changeSetting( + path, + selectedList.toSorted((a, b) => a.localeCompare(b)), + { showAlert: true }, ); }; const onToggleChange = (key: string[], checked: boolean) => { - dispatch(changeSetting(key, checked)); + changeSetting(key, checked); }; const onBrandColorChange = (newBrandColor: string) => { @@ -187,39 +180,35 @@ const Preferences = () => { const theme = settings.theme ?? frontendConfig.defaultSettings.theme; - dispatch( - changeSetting( - ['theme'], - { - ...theme, - brandColor: newBrandColor, - }, - { showAlert: true, save: false }, - ), + changeSetting( + ['theme'], + { + ...theme, + brandColor: newBrandColor, + }, + { showAlert: true, save: false }, ); - debouncedSave(dispatch); + debouncedSave(); }; const onInterfaceSizeChange = (value: number) => { const theme = settings.theme ?? frontendConfig.defaultSettings.theme; - dispatch( - changeSetting( - ['theme'], - { - ...theme, - interfaceSize: INTERFACE_SIZES[value], - }, - { showAlert: true, save: false }, - ), + changeSetting( + ['theme'], + { + ...theme, + interfaceSize: INTERFACE_SIZES[value], + }, + { showAlert: true, save: false }, ); - debouncedSave(dispatch); + debouncedSave(); }; const onThemeReset = () => { - dispatch(changeSetting(['themeMode'], defaultSettings.themeMode, { save: false })); - dispatch(changeSetting(['theme'], defaultSettings.theme, { showAlert: true })); + changeSetting(['themeMode'], defaultSettings.themeMode, { save: false }); + changeSetting(['theme'], defaultSettings.theme, { showAlert: true }); }; const displayMediaOptions = React.useMemo( diff --git a/packages/nicolium/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx b/packages/nicolium/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx index 3834544f4..02f7c771e 100644 --- a/packages/nicolium/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx +++ b/packages/nicolium/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx @@ -1,9 +1,8 @@ -import { useMutation } from '@tanstack/react-query'; import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import Button from '@/components/ui/button'; -import { cancelScheduledStatusMutationOptions } from '@/queries/statuses/scheduled-statuses'; +import { useCancelScheduledStatusMutation } from '@/queries/statuses/scheduled-statuses'; import { useModalsActions } from '@/stores/modals'; import { useSettings } from '@/stores/settings'; @@ -29,9 +28,7 @@ interface IScheduledStatusActionBar { const ScheduledStatusActionBar: React.FC = ({ status }) => { const intl = useIntl(); - const { mutate: cancelScheduledStatus } = useMutation( - cancelScheduledStatusMutationOptions(status.id), - ); + const { mutate: cancelScheduledStatus } = useCancelScheduledStatusMutation(status.id); const { openModal } = useModalsActions(); const settings = useSettings(); diff --git a/packages/nicolium/src/features/security/mfa/disable-otp-form.tsx b/packages/nicolium/src/features/security/mfa/disable-otp-form.tsx index 700209a2e..3160f23b8 100644 --- a/packages/nicolium/src/features/security/mfa/disable-otp-form.tsx +++ b/packages/nicolium/src/features/security/mfa/disable-otp-form.tsx @@ -8,7 +8,6 @@ import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; import Text from '@/components/ui/text'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useDisableMfa } from '@/queries/security/use-mfa'; import toast from '@/toast'; @@ -22,7 +21,6 @@ const DisableOtpForm: React.FC = () => { const [password, setPassword] = useState(''); const intl = useIntl(); - const dispatch = useAppDispatch(); const navigate = useNavigate(); const { mutate: disableMfa, isPending } = useDisableMfa(); @@ -37,7 +35,7 @@ const DisableOtpForm: React.FC = () => { toast.error(intl.formatMessage(messages.disableFail)); }, }); - }, [password, dispatch, intl]); + }, [password, intl]); const handleInputChange = (event: React.ChangeEvent) => { setPassword(event.target.value); diff --git a/packages/nicolium/src/features/status/components/status-interaction-bar.tsx b/packages/nicolium/src/features/status/components/status-interaction-bar.tsx index d62af9b1c..89013f9b3 100644 --- a/packages/nicolium/src/features/status/components/status-interaction-bar.tsx +++ b/packages/nicolium/src/features/status/components/status-interaction-bar.tsx @@ -48,7 +48,6 @@ const StatusInteractionBar: React.FC = ({ const handleOpenReblogsModal: React.EventHandler = (e) => { e.preventDefault(); - // if (!me) onOpenUnauthorizedModal(); onOpenReblogsModal(status.id); }; @@ -93,14 +92,12 @@ const StatusInteractionBar: React.FC = ({ ) => { e.preventDefault(); - // if (!me) onOpenUnauthorizedModal(); onOpenFavouritesModal(status.id); }; const handleOpenDislikesModal: React.EventHandler> = (e) => { e.preventDefault(); - // if (!me) onOpenUnauthorizedModal(); onOpenDislikesModal(status.id); }; diff --git a/packages/nicolium/src/features/ui/components/modal-root.tsx b/packages/nicolium/src/features/ui/components/modal-root.tsx index a6c3395d6..c1967ee64 100644 --- a/packages/nicolium/src/features/ui/components/modal-root.tsx +++ b/packages/nicolium/src/features/ui/components/modal-root.tsx @@ -1,7 +1,7 @@ import React, { Suspense, lazy } from 'react'; import Base from '@/components/modal-root'; -import { useComposeStore } from '@/stores/compose'; +import { useComposeActions } from '@/stores/compose'; import { useModals, useModalsActions } from '@/stores/modals'; import ModalLoading from './modal-loading'; @@ -63,6 +63,8 @@ const ModalRoot: React.FC = () => { const modals = useModals(); const { closeModal } = useModalsActions(); + const { resetCompose } = useComposeActions(); + const { modalType: type, modalProps: props } = modals.at(-1) ?? { modalProps: {}, modalType: null, @@ -72,7 +74,7 @@ const ModalRoot: React.FC = () => { const onClickClose = (type?: ModalType, all?: boolean) => { switch (type) { case 'COMPOSE': - useComposeStore.getState().actions.resetCompose('compose-modal'); + resetCompose('compose-modal'); break; default: break; diff --git a/packages/nicolium/src/features/ui/components/panels/account-note-panel.tsx b/packages/nicolium/src/features/ui/components/panels/account-note-panel.tsx index ce013443d..f016b4ee6 100644 --- a/packages/nicolium/src/features/ui/components/panels/account-note-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/account-note-panel.tsx @@ -4,7 +4,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import Textarea from '@/components/ui/textarea'; import Widget from '@/components/ui/widget'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useUpdateAccountNoteMutation } from '@/queries/accounts/use-relationship'; import type { Account as AccountEntity } from 'pl-api'; @@ -19,7 +19,7 @@ interface IAccountNotePanel { const AccountNotePanel: React.FC = ({ account }) => { const intl = useIntl(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { mutate: updateAccountNote } = useUpdateAccountNoteMutation(account.id); diff --git a/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx b/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx index 8afa72ab8..32dcdbb93 100644 --- a/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx @@ -3,7 +3,6 @@ import { useIntl, defineMessages } from 'react-intl'; import { pinHost, unpinHost } from '@/actions/remote-timeline'; import Widget from '@/components/ui/widget'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useRemoteInstance } from '@/selectors'; import { useSettings } from '@/stores/settings'; @@ -20,7 +19,6 @@ interface IInstanceInfoPanel { /** Widget that displays information about a remote instance to users. */ const InstanceInfoPanel: React.FC = ({ host }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const settings = useSettings(); const remoteInstance = useRemoteInstance(host); @@ -28,9 +26,9 @@ const InstanceInfoPanel: React.FC = ({ host }) => { const handlePinHost = () => { if (!pinned) { - dispatch(pinHost(host)); + pinHost(host); } else { - dispatch(unpinHost(host)); + unpinHost(host); } }; diff --git a/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx b/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx index 4285cf0ca..27f88f9c8 100644 --- a/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx @@ -8,9 +8,9 @@ import { dateFormatOptions } from '@/components/relative-timestamp'; import { ParsedContent } from '@/components/statuses/parsed-content'; import Icon from '@/components/ui/icon'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import Emojify from '@/features/emoji/emojify'; import { useAcct } from '@/hooks/use-acct'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useAccountScrobbleQuery } from '@/queries/accounts/account-scrobble'; import { capitalize } from '@/utils/strings'; @@ -45,7 +45,7 @@ interface IProfileInfoPanel { const ProfileInfoPanel: React.FC = ({ account, username }) => { const intl = useIntl(); const acct = useAcct(account); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const ownAccount = account?.id === me; const { data: scrobble } = useAccountScrobbleQuery(account?.id); diff --git a/packages/nicolium/src/features/ui/components/panels/promo-panel.tsx b/packages/nicolium/src/features/ui/components/panels/promo-panel.tsx index 42f0c3e14..38defc140 100644 --- a/packages/nicolium/src/features/ui/components/panels/promo-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/promo-panel.tsx @@ -4,7 +4,7 @@ import ForkAwesomeIcon from '@/components/fork-awesome-icon'; import List, { ListItem } from '@/components/list'; import Widget from '@/components/ui/widget'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import { useSettings } from '@/stores/settings'; const PromoPanel: React.FC = () => { diff --git a/packages/nicolium/src/features/ui/components/panels/sign-up-panel.tsx b/packages/nicolium/src/features/ui/components/panels/sign-up-panel.tsx index af3f80bdc..1b05860d7 100644 --- a/packages/nicolium/src/features/ui/components/panels/sign-up-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/sign-up-panel.tsx @@ -6,24 +6,22 @@ import { logIn, switchAccount, verifyCredentials } from '@/actions/auth'; import { fetchInstance } from '@/actions/instance'; import Button from '@/components/ui/button'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import LoginForm from '@/features/auth-login/components/login-form'; import OtpAuthForm from '@/features/auth-login/components/otp-auth-form'; import ExternalLoginForm from '@/features/external-login/components/external-login-form'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; -import { useInstance } from '@/hooks/use-instance'; import { useRegistrationStatus } from '@/hooks/use-registration-status'; +import { useInstance } from '@/stores/instance'; import { getRedirectUrl } from '@/utils/redirect'; -import { isStandalone } from '@/utils/state'; +import { useIsStandalone } from '@/utils/state'; import type { NicoliumResponse } from '@/api'; const SignUpPanel = () => { - const dispatch = useAppDispatch(); const instance = useInstance(); const { isOpen } = useRegistrationStatus(); - const me = useAppSelector((state) => state.me); - const standalone = useAppSelector(isStandalone); + const me = useCurrentAccount(); + const standalone = useIsStandalone(); const token = new URLSearchParams(window.location.search).get('token'); @@ -39,15 +37,15 @@ const SignUpPanel = () => { const handleSubmit: React.SubmitEventHandler = (event) => { const { username, password } = getFormData(event.target); - dispatch(logIn(username, password)) - .then(({ access_token }) => dispatch(verifyCredentials(access_token))) + logIn(username, password) + .then(({ access_token }) => verifyCredentials(access_token)) // Refetch the instance for authenticated fetch .then(async (account) => { - await dispatch(fetchInstance()); + await fetchInstance(); return account; }) .then((account: { id: string }) => { - dispatch(switchAccount(account.id)); + switchAccount(account.id); if (typeof me !== 'string') { setShouldRedirect(true); } diff --git a/packages/nicolium/src/features/ui/components/profile-dropdown.tsx b/packages/nicolium/src/features/ui/components/profile-dropdown.tsx index 26c8e0c64..0d478e937 100644 --- a/packages/nicolium/src/features/ui/components/profile-dropdown.tsx +++ b/packages/nicolium/src/features/ui/components/profile-dropdown.tsx @@ -7,7 +7,6 @@ import { logOut, switchAccount } from '@/actions/auth'; import Account from '@/components/accounts/account'; import DropdownMenu from '@/components/dropdown-menu'; import PlaceholderAccount from '@/features/placeholder/components/placeholder-account'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useFeatures } from '@/hooks/use-features'; import { useLoggedInAccount, @@ -53,18 +52,17 @@ type IMenuItem = { }; const ProfileDropdown: React.FC = ({ account, children }) => { - const dispatch = useAppDispatch(); const features = useFeatures(); const intl = useIntl(); const otherAccountIds = useLoggedInAccountIds(); const handleLogOut = () => { - dispatch(logOut()); + logOut(); }; const handleSwitchAccount = (otherAccountId: string) => () => { - dispatch(switchAccount(otherAccountId)); + switchAccount(otherAccountId); }; const renderAccount = (account: AccountEntity) => ( diff --git a/packages/nicolium/src/features/ui/components/theme-toggle.tsx b/packages/nicolium/src/features/ui/components/theme-toggle.tsx index f83dcc7b6..ef56d576e 100644 --- a/packages/nicolium/src/features/ui/components/theme-toggle.tsx +++ b/packages/nicolium/src/features/ui/components/theme-toggle.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { changeSetting } from '@/actions/settings'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useSettings } from '@/stores/settings'; import ThemeSelector from './theme-selector'; @@ -12,11 +11,10 @@ interface IThemeToggle { /** Stateful theme selector. */ const ThemeToggle: React.FC = ({ id }) => { - const dispatch = useAppDispatch(); const { themeMode } = useSettings(); const handleChange = (themeMode: string) => { - dispatch(changeSetting(['themeMode'], themeMode)); + changeSetting(['themeMode'], themeMode); }; return ; diff --git a/packages/nicolium/src/features/ui/index.tsx b/packages/nicolium/src/features/ui/index.tsx index 19964b43b..fa31eed14 100644 --- a/packages/nicolium/src/features/ui/index.tsx +++ b/packages/nicolium/src/features/ui/index.tsx @@ -8,12 +8,10 @@ import { useUserStream } from '@/api/hooks/streaming/use-user-stream'; import SidebarNavigation from '@/components/navigation/sidebar-navigation'; import ThumbNavigation from '@/components/navigation/thumb-navigation'; import Layout from '@/components/ui/layout'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useClient } from '@/hooks/use-client'; import { useDraggedFiles } from '@/hooks/use-dragged-files'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useOwnAccount } from '@/hooks/use-own-account'; import { prefetchFollowRequests } from '@/queries/accounts/use-follow-requests'; import { useAdminConfig } from '@/queries/admin/use-config'; @@ -23,13 +21,15 @@ import { usePrefetchNotificationsMarker } from '@/queries/markers/use-markers'; import { usePrefetchNotifications } from '@/queries/notifications/use-notifications'; import { useFilters } from '@/queries/settings/use-filters'; import { scheduledStatusesQueryOptions } from '@/queries/statuses/scheduled-statuses'; -import { useShoutboxSubscription } from '@/stores/shoutbox'; -import { useIsDropdownMenuOpen } from '@/stores/ui'; -import { getVapidKey } from '@/utils/auth'; +import { useAuthStore } from '@/stores/auth'; +import { useInstance } from '@/stores/instance'; +import { useInstanceStore } from '@/stores/instance'; // Dummy import, to make sure that ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. import '@/components/statuses/status'; -import { isStandalone } from '@/utils/state'; +import { useShoutboxSubscription } from '@/stores/shoutbox'; +import { useIsDropdownMenuOpen } from '@/stores/ui'; +import { useIsStandalone } from '@/utils/state'; import { ModalRoot, @@ -42,17 +42,18 @@ import GlobalHotkeys from './util/global-hotkeys'; const UI: React.FC = React.memo(() => { const navigate = useNavigate(); - const dispatch = useAppDispatch(); const node = useRef(null); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: account } = useOwnAccount(); const features = useFeatures(); - const vapidKey = useAppSelector((state) => getVapidKey(state)); - const client = useClient(); const instance = useInstance(); + const vapidKey = + useAuthStore((state) => state.app?.vapid_key) ?? instance.configuration.vapid.public_key; + const client = useClient(); const isDropdownMenuOpen = useIsDropdownMenuOpen(); - const standalone = useAppSelector(isStandalone); + const standalone = useIsStandalone(); + const instanceFetched = useInstanceStore((state) => state.fetched); useAdminConfig(); useShoutboxSubscription(); @@ -127,11 +128,11 @@ const UI: React.FC = React.memo(() => { // The user has logged in useEffect(() => { - if (instance.fetched) loadAccountData(); - }, [!!account, instance.fetched]); + if (instanceFetched) loadAccountData(); + }, [!!account, instanceFetched]); useEffect(() => { - dispatch(registerPushNotifications()); + registerPushNotifications(); }, [vapidKey]); // Wait for login to succeed or fail diff --git a/packages/nicolium/src/features/ui/router/index.tsx b/packages/nicolium/src/features/ui/router/index.tsx index 669325035..0e6a4e110 100644 --- a/packages/nicolium/src/features/ui/router/index.tsx +++ b/packages/nicolium/src/features/ui/router/index.tsx @@ -9,15 +9,16 @@ import { redirect, RouterProvider, } from '@tanstack/react-router'; +import { instanceSchema } from 'pl-api'; import React, { useMemo } from 'react'; import * as v from 'valibot'; +import * as val from 'valibot'; import { FE_SUBDIRECTORY } from '@/build-config'; import SiteError from '@/components/site-error'; import Layout from '@/components/ui/layout'; import { useFeatures } from '@/hooks/use-features'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; -import { useInstance } from '@/hooks/use-instance'; import { useOwnAccount } from '@/hooks/use-own-account'; import AdminLayout from '@/layouts/admin-layout'; import ChatsLayout from '@/layouts/chats-layout'; @@ -34,7 +35,7 @@ import ProfileLayout from '@/layouts/profile-layout'; import RemoteInstanceLayout from '@/layouts/remote-instance-layout'; import SearchLayout from '@/layouts/search-layout'; import StatusLayout from '@/layouts/status-layout'; -import { instanceInitialState } from '@/reducers/instance'; +import { useInstance } from '@/stores/instance'; import { LOCAL_STORAGE_REDIRECT_KEY } from '@/utils/redirect'; import ChatsPageChat from '../../chats/components/chats-page/components/chats-page-chat'; @@ -1455,7 +1456,7 @@ const router = createRouter({ routeTree, basepath: FE_SUBDIRECTORY, context: { - instance: instanceInitialState, + instance: val.parse(instanceSchema, {}), features: {} as Features, isLoggedIn: false, isAdmin: false, diff --git a/packages/nicolium/src/features/ui/router/util.tsx b/packages/nicolium/src/features/ui/router/util.tsx index 79de2a9c1..be0db2637 100644 --- a/packages/nicolium/src/features/ui/router/util.tsx +++ b/packages/nicolium/src/features/ui/router/util.tsx @@ -2,10 +2,9 @@ import { lazyRouteComponent, Navigate } from '@tanstack/react-router'; import React from 'react'; import { WITH_LANDING_PAGE } from '@/build-config'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; import { useLoggedIn } from '@/hooks/use-logged-in'; -import { isStandalone } from '@/utils/state'; +import { useIsStandalone } from '@/utils/state'; const HomeTimeline = lazyRouteComponent(() => import('@/pages/timelines/home-timeline')); const LandingPage = lazyRouteComponent(() => import('@/pages/utils/landing')); @@ -13,7 +12,7 @@ const LandingTimeline = lazyRouteComponent(() => import('@/pages/timelines/landi const HomeRoute = () => { const { redirectRootNoLogin } = useFrontendConfig(); - const standalone = useAppSelector(isStandalone); + const standalone = useIsStandalone(); const { isLoggedIn } = useLoggedIn(); if (!isLoggedIn && redirectRootNoLogin) return ; diff --git a/packages/nicolium/src/hooks/use-acct.ts b/packages/nicolium/src/hooks/use-acct.ts index f0a5b7402..356e856fa 100644 --- a/packages/nicolium/src/hooks/use-acct.ts +++ b/packages/nicolium/src/hooks/use-acct.ts @@ -1,15 +1,14 @@ import { useMemo } from 'react'; -import { displayFqn } from '@/utils/state'; +import { useInstance } from '@/stores/instance'; -import { useAppSelector } from './use-app-selector'; -import { useInstance } from './use-instance'; +import { useFrontendConfig } from './use-frontend-config'; import { useOwnAccount } from './use-own-account'; import type { Account } from 'pl-api'; const useAcct = (account?: Pick): string | undefined => { - const fqn = useAppSelector((state) => displayFqn(state)); + const { displayFqn: fqn } = useFrontendConfig(); const instance = useInstance(); const localUrl = useOwnAccount().data?.url; diff --git a/packages/nicolium/src/hooks/use-app-dispatch.ts b/packages/nicolium/src/hooks/use-app-dispatch.ts deleted file mode 100644 index 1040bf482..000000000 --- a/packages/nicolium/src/hooks/use-app-dispatch.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { useDispatch } from 'react-redux'; - -import type { AppDispatch } from '@/store'; - -const useAppDispatch = () => useDispatch(); - -export { useAppDispatch }; diff --git a/packages/nicolium/src/hooks/use-app-selector.ts b/packages/nicolium/src/hooks/use-app-selector.ts deleted file mode 100644 index de82e940a..000000000 --- a/packages/nicolium/src/hooks/use-app-selector.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type TypedUseSelectorHook, useSelector } from 'react-redux'; - -import type { RootState } from '@/store'; - -const useAppSelector: TypedUseSelectorHook = useSelector; - -export { useAppSelector }; diff --git a/packages/nicolium/src/hooks/use-can-interact.ts b/packages/nicolium/src/hooks/use-can-interact.ts index c03d76851..88f331132 100644 --- a/packages/nicolium/src/hooks/use-can-interact.ts +++ b/packages/nicolium/src/hooks/use-can-interact.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { useAppSelector } from './use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import type { NormalizedStatus } from '@/normalizers/status'; import type { InteractionPolicy, InteractionPolicyEntry } from 'pl-api'; @@ -16,7 +16,7 @@ const useCanInteract = ( approvalRequired: boolean | null; allowed?: Array; } => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); return useMemo(() => { if (type === 'can_quote') { diff --git a/packages/nicolium/src/hooks/use-client.ts b/packages/nicolium/src/hooks/use-client.ts index 038465b35..3a8c7d451 100644 --- a/packages/nicolium/src/hooks/use-client.ts +++ b/packages/nicolium/src/hooks/use-client.ts @@ -1,7 +1,5 @@ -import { getClient } from '@/api'; +import { useAuthStore } from '@/stores/auth'; -import { useAppSelector } from './use-app-selector'; - -const useClient = () => useAppSelector(getClient); +const useClient = () => useAuthStore((state) => state.client); export { useClient }; diff --git a/packages/nicolium/src/hooks/use-features.ts b/packages/nicolium/src/hooks/use-features.ts index e00a2fb6a..70628a172 100644 --- a/packages/nicolium/src/hooks/use-features.ts +++ b/packages/nicolium/src/hooks/use-features.ts @@ -1,10 +1,10 @@ -import { useAppSelector } from './use-app-selector'; +import { useAuthStore } from '@/stores/auth'; import type { Features } from 'pl-api'; /** Get features for the current instance. */ const useFeatures = (): Features => ({ - ...useAppSelector((state) => state.auth.client.features), + ...useAuthStore((state) => state.client.features), filtersV2BlurAction: true, }); diff --git a/packages/nicolium/src/hooks/use-frontend-config.ts b/packages/nicolium/src/hooks/use-frontend-config.ts index 92758eb1d..6440682b7 100644 --- a/packages/nicolium/src/hooks/use-frontend-config.ts +++ b/packages/nicolium/src/hooks/use-frontend-config.ts @@ -1,17 +1,6 @@ -import { useMemo } from 'react'; -import * as v from 'valibot'; +import { useFrontendConfigStore } from '@/stores/frontend-config'; -import { frontendConfigSchema } from '@/schemas/frontend-config'; - -import { useAppSelector } from './use-app-selector'; - -const defaultFrontendConfig = v.parse(frontendConfigSchema, {}); - -/** Get the Nicolium config from the store */ -const useFrontendConfig = () => { - const partialConfig = useAppSelector((state) => state.frontendConfig); - - return useMemo(() => ({ ...defaultFrontendConfig, ...partialConfig }), [partialConfig]); -}; +/** Get the Nicolium config from the store. */ +const useFrontendConfig = () => useFrontendConfigStore((state) => state.config); export { useFrontendConfig }; diff --git a/packages/nicolium/src/hooks/use-instance.ts b/packages/nicolium/src/hooks/use-instance.ts deleted file mode 100644 index 4c94a4ab2..000000000 --- a/packages/nicolium/src/hooks/use-instance.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useAppSelector } from './use-app-selector'; - -/** Get the Instance for the current backend. */ -const useInstance = () => useAppSelector((state) => state.instance); - -export { useInstance }; diff --git a/packages/nicolium/src/hooks/use-logged-in.ts b/packages/nicolium/src/hooks/use-logged-in.ts index b8c2fff61..b5db245a4 100644 --- a/packages/nicolium/src/hooks/use-logged-in.ts +++ b/packages/nicolium/src/hooks/use-logged-in.ts @@ -1,12 +1,9 @@ -import { useAppSelector } from './use-app-selector'; - +import { useCurrentAccount } from '@/contexts/current-account-context'; const useLoggedIn = () => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); return { isLoggedIn: typeof me === 'string', - isLoginLoading: me === null, - isLoginFailed: me === false, me, }; }; diff --git a/packages/nicolium/src/hooks/use-registration-status.ts b/packages/nicolium/src/hooks/use-registration-status.ts index aa4700e24..ea957361c 100644 --- a/packages/nicolium/src/hooks/use-registration-status.ts +++ b/packages/nicolium/src/hooks/use-registration-status.ts @@ -1,5 +1,6 @@ +import { useInstance } from '@/stores/instance'; + import { useFeatures } from './use-features'; -import { useInstance } from './use-instance'; const useRegistrationStatus = () => { const instance = useInstance(); diff --git a/packages/nicolium/src/init/nicolium-load.tsx b/packages/nicolium/src/init/nicolium-load.tsx index 70e744995..df25d8bcc 100644 --- a/packages/nicolium/src/init/nicolium-load.tsx +++ b/packages/nicolium/src/init/nicolium-load.tsx @@ -1,27 +1,23 @@ import React, { useState, useEffect } from 'react'; import { IntlProvider } from 'react-intl'; +import { fetchMe } from '@/actions/auth'; import { loadFrontendConfig } from '@/actions/frontend-config'; import { checkIfStandalone, fetchInstance } from '@/actions/instance'; -import { fetchMe } from '@/actions/me'; import LoadingScreen from '@/components/loading-screen'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useLocale } from '@/hooks/use-locale'; import { useOwnAccount } from '@/hooks/use-own-account'; import MESSAGES from '@/messages'; - -import type { AppDispatch } from '@/store'; - /** Load initial data from the backend */ -const loadInitial = () => async (dispatch: AppDispatch) => { - dispatch(checkIfStandalone()); +const loadInitial = async () => { + checkIfStandalone(); // Await for authenticated fetch - await dispatch(fetchMe()); + await fetchMe(); // Await for feature detection - await dispatch(fetchInstance()); + await fetchInstance(); // Await for configuration - await dispatch(loadFrontendConfig()); + await loadFrontendConfig(); }; interface INicoliumLoad { @@ -30,9 +26,7 @@ interface INicoliumLoad { /** Initial data loader. */ const NicoliumLoad: React.FC = ({ children }) => { - const dispatch = useAppDispatch(); - - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: account } = useOwnAccount(); const locale = useLocale(); @@ -55,7 +49,7 @@ const NicoliumLoad: React.FC = ({ children }) => { // Load initial data from the API useEffect(() => { - dispatch(loadInitial()) + loadInitial() .then(() => { setIsLoaded(true); }) diff --git a/packages/nicolium/src/init/nicolium.tsx b/packages/nicolium/src/init/nicolium.tsx index 469830bcb..27887a6ce 100644 --- a/packages/nicolium/src/init/nicolium.tsx +++ b/packages/nicolium/src/init/nicolium.tsx @@ -1,12 +1,10 @@ import { QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; -import { Provider } from 'react-redux'; import { preload } from '@/actions/preload'; import { DefaultCurrentAccountProvider } from '@/contexts/current-account-context'; import { StatProvider } from '@/contexts/stat-context'; import { queryClient } from '@/queries/client'; -import { store } from '@/store'; import NicoliumHead from './nicolium-head'; import NicoliumLoad from './nicolium-load'; @@ -14,22 +12,20 @@ import NicoliumMount from './nicolium-mount'; import '../polyfills'; // Preload happens synchronously -store.dispatch(preload()); +preload(); /** The root React node of the application. */ const Nicolium: React.FC = () => ( - - - - - - - - - - - - + + + + + + + + + + ); export { Nicolium as default }; diff --git a/packages/nicolium/src/layouts/default-layout.tsx b/packages/nicolium/src/layouts/default-layout.tsx index 5e12a5de5..8cbf88b40 100644 --- a/packages/nicolium/src/layouts/default-layout.tsx +++ b/packages/nicolium/src/layouts/default-layout.tsx @@ -2,13 +2,12 @@ import { Outlet } from '@tanstack/react-router'; import React from 'react'; import Layout from '@/components/ui/layout'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import LinkFooter from '@/features/ui/components/link-footer'; import { WhoToFollowPanel, TrendsPanel, SignUpPanel } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; - const DefaultLayout: React.FC = () => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); return ( diff --git a/packages/nicolium/src/layouts/event-layout.tsx b/packages/nicolium/src/layouts/event-layout.tsx index 967804233..251a87062 100644 --- a/packages/nicolium/src/layouts/event-layout.tsx +++ b/packages/nicolium/src/layouts/event-layout.tsx @@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl'; import Column from '@/components/ui/column'; import Layout from '@/components/ui/layout'; import Tabs, { type Item } from '@/components/ui/tabs'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; import LinkFooter from '@/features/ui/components/link-footer'; import { layouts } from '@/features/ui/router'; @@ -14,14 +15,12 @@ import { TrendsPanel, WhoToFollowPanel, } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; import { useStatus } from '@/queries/statuses/use-status'; - const EventLayout = () => { const { statusId } = layouts.event.useParams(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); const navigate = useNavigate(); diff --git a/packages/nicolium/src/layouts/external-login-layout.tsx b/packages/nicolium/src/layouts/external-login-layout.tsx index 26254da3b..88ebcfacb 100644 --- a/packages/nicolium/src/layouts/external-login-layout.tsx +++ b/packages/nicolium/src/layouts/external-login-layout.tsx @@ -2,16 +2,16 @@ import { Outlet } from '@tanstack/react-router'; import React from 'react'; import Layout from '@/components/ui/layout'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import LinkFooter from '@/features/ui/components/link-footer'; import { WhoToFollowPanel, TrendsPanel, SignUpPanel } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; -import { isStandalone } from '@/utils/state'; +import { useIsStandalone } from '@/utils/state'; const ExternalLoginLayout = () => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); - const standalone = useAppSelector(isStandalone); + const standalone = useIsStandalone(); return ( <> diff --git a/packages/nicolium/src/layouts/home-layout.tsx b/packages/nicolium/src/layouts/home-layout.tsx index a99465404..3671ec20c 100644 --- a/packages/nicolium/src/layouts/home-layout.tsx +++ b/packages/nicolium/src/layouts/home-layout.tsx @@ -6,6 +6,7 @@ import { BANNER_HTML } from '@/build-config'; import Avatar from '@/components/ui/avatar'; import Layout from '@/components/ui/layout'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import Warning from '@/features/compose/components/warning'; import LinkFooter from '@/features/ui/components/link-footer'; import { @@ -18,7 +19,6 @@ import { AnnouncementsPanel, ComposeForm, } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useDraggedFiles } from '@/hooks/use-dragged-files'; import { useFeatures } from '@/hooks/use-features'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; @@ -27,7 +27,7 @@ import { useUploadCompose } from '@/stores/compose'; import { useSettings } from '@/stores/settings'; const HomeLayout = () => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: account } = useOwnAccount(); const features = useFeatures(); const frontendConfig = useFrontendConfig(); diff --git a/packages/nicolium/src/layouts/landing-layout.tsx b/packages/nicolium/src/layouts/landing-layout.tsx index a68c5abe2..91c1154a4 100644 --- a/packages/nicolium/src/layouts/landing-layout.tsx +++ b/packages/nicolium/src/layouts/landing-layout.tsx @@ -2,13 +2,12 @@ import { Outlet } from '@tanstack/react-router'; import React from 'react'; import Layout from '@/components/ui/layout'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import LinkFooter from '@/features/ui/components/link-footer'; import { TrendsPanel, SignUpPanel } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; - const LandingLayout = () => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); return ( diff --git a/packages/nicolium/src/layouts/profile-layout.tsx b/packages/nicolium/src/layouts/profile-layout.tsx index b25942688..84c451aed 100644 --- a/packages/nicolium/src/layouts/profile-layout.tsx +++ b/packages/nicolium/src/layouts/profile-layout.tsx @@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl'; import Column from '@/components/ui/column'; import Layout from '@/components/ui/layout'; import Tabs, { type Item } from '@/components/ui/tabs'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import Header from '@/features/account/components/header'; import LinkFooter from '@/features/ui/components/link-footer'; import { layouts } from '@/features/ui/router'; @@ -18,7 +19,6 @@ import { AccountNotePanel, } from '@/features/ui/util/async-components'; import { useAcct } from '@/hooks/use-acct'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; import { useAccountLookup } from '@/queries/accounts/use-account-lookup'; import { LOCAL_STORAGE_REDIRECT_KEY } from '@/utils/redirect'; @@ -30,7 +30,7 @@ const ProfileLayout: React.FC = () => { const { data: account, isUnauthorized } = useAccountLookup(username, true); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); const acct = useAcct(account); diff --git a/packages/nicolium/src/layouts/remote-instance-layout.tsx b/packages/nicolium/src/layouts/remote-instance-layout.tsx index 44a403bdb..e62323593 100644 --- a/packages/nicolium/src/layouts/remote-instance-layout.tsx +++ b/packages/nicolium/src/layouts/remote-instance-layout.tsx @@ -9,16 +9,15 @@ import { InstanceInfoPanel, InstanceModerationPanel, } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useOwnAccount } from '@/hooks/use-own-account'; -import { federationRestrictionsDisclosed } from '@/utils/state'; +import { useFederationRestrictionsDisclosed } from '@/utils/state'; /** Layout for viewing a remote instance timeline. */ const RemoteInstanceLayout = () => { const { instance } = layouts.remoteInstance.useParams(); const { data: account } = useOwnAccount(); - const disclosed = useAppSelector(federationRestrictionsDisclosed); + const disclosed = useFederationRestrictionsDisclosed(); return ( <> diff --git a/packages/nicolium/src/layouts/search-layout.tsx b/packages/nicolium/src/layouts/search-layout.tsx index 2eb772147..e8c6fe5dd 100644 --- a/packages/nicolium/src/layouts/search-layout.tsx +++ b/packages/nicolium/src/layouts/search-layout.tsx @@ -2,13 +2,12 @@ import { Outlet } from '@tanstack/react-router'; import React from 'react'; import Layout from '@/components/ui/layout'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import LinkFooter from '@/features/ui/components/link-footer'; import { WhoToFollowPanel, TrendsPanel, SignUpPanel } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; - const SearchLayout = () => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); return ( diff --git a/packages/nicolium/src/layouts/status-layout.tsx b/packages/nicolium/src/layouts/status-layout.tsx index e91e6cbf0..0d4ce575c 100644 --- a/packages/nicolium/src/layouts/status-layout.tsx +++ b/packages/nicolium/src/layouts/status-layout.tsx @@ -2,13 +2,12 @@ import { Outlet } from '@tanstack/react-router'; import React from 'react'; import Layout from '@/components/ui/layout'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import LinkFooter from '@/features/ui/components/link-footer'; import { WhoToFollowPanel, TrendsPanel, SignUpPanel } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; - const StatusLayout = () => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const features = useFeatures(); return ( diff --git a/packages/nicolium/src/middleware/errors.ts b/packages/nicolium/src/middleware/errors.ts deleted file mode 100644 index b84bedfdc..000000000 --- a/packages/nicolium/src/middleware/errors.ts +++ /dev/null @@ -1,35 +0,0 @@ -import toast from '@/toast'; - -import type { AnyAction, Middleware } from 'redux'; - -/** Whether the action is considered a failure. */ -const isFailType = (type: string): boolean => type.endsWith('_FAIL'); - -/** Whether the action is a failure to fetch from browser storage. */ -const isRememberFailType = (type: string): boolean => type.endsWith('_REMEMBER_FAIL'); - -/** Whether the error contains an Axios response. */ -const hasResponse = (error: any): boolean => Boolean(error && error.response); - -/** Don't show 401's. */ -const authorized = (error: any): boolean => error?.response?.status !== 401; - -/** Whether the error should be shown to the user. */ -const shouldShowError = ({ type, skipAlert, error }: AnyAction): boolean => - !skipAlert && - hasResponse(error) && - authorized(error) && - isFailType(type) && - !isRememberFailType(type); - -/** Middleware to display Redux errors to the user. */ -const errorsMiddleware = (): Middleware => () => (next) => (anyAction) => { - const action = anyAction as AnyAction; - if (shouldShowError(action)) { - toast.showAlertForError(action.error); - } - - return next(action); -}; - -export { errorsMiddleware as default }; diff --git a/packages/nicolium/src/middleware/sounds.ts b/packages/nicolium/src/middleware/sounds.ts deleted file mode 100644 index 68a6d0056..000000000 --- a/packages/nicolium/src/middleware/sounds.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { play, soundCache } from '@/utils/sounds'; - -import type { Sounds } from '@/utils/sounds'; -import type { AnyAction, Middleware } from 'redux'; - -interface Action extends AnyAction { - meta: { - sound: Sounds; - }; -} - -/** Middleware to play sounds in response to certain Redux actions. */ -const soundsMiddleware = (): Middleware => () => (next) => (anyAction) => { - const action = anyAction as Action; - if (action.meta?.sound && soundCache[action.meta.sound]) { - play(soundCache[action.meta.sound]); - } - - return next(action); -}; - -export { soundsMiddleware as default }; diff --git a/packages/nicolium/src/modals/alt-text-modal.tsx b/packages/nicolium/src/modals/alt-text-modal.tsx index 52cf01d8e..2f2198c64 100644 --- a/packages/nicolium/src/modals/alt-text-modal.tsx +++ b/packages/nicolium/src/modals/alt-text-modal.tsx @@ -10,7 +10,6 @@ import Modal from '@/components/ui/modal'; import Textarea from '@/components/ui/textarea'; import { MIMETYPE_ICONS } from '@/components/upload'; import { getPointerPosition } from '@/features/video'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useCompose } from '@/hooks/use-compose'; import { useFeatures } from '@/hooks/use-features'; import toast from '@/toast'; @@ -192,7 +191,6 @@ const AltTextModal: React.FC = ({ previousDescription, previousPosition, }) => { - const dispatch = useAppDispatch(); const intl = useIntl(); const { language } = useCompose(composeId ?? 'default'); @@ -232,7 +230,7 @@ const AltTextModal: React.FC = ({ setIsSaving(false); toast.error(messages.savingFailed); }); - }, [dispatch, setIsSaving, media.id, onClose, description, position]); + }, [setIsSaving, media.id, onClose, description, position]); const handleKeyUp: React.KeyboardEventHandler = useCallback( (e) => { diff --git a/packages/nicolium/src/modals/manage-group-modal/steps/details-step.tsx b/packages/nicolium/src/modals/manage-group-modal/steps/details-step.tsx index 9d98a5fb7..ccb947fe4 100644 --- a/packages/nicolium/src/modals/manage-group-modal/steps/details-step.tsx +++ b/packages/nicolium/src/modals/manage-group-modal/steps/details-step.tsx @@ -8,8 +8,7 @@ import Textarea from '@/components/ui/textarea'; import AvatarPicker from '@/features/edit-profile/components/avatar-picker'; import HeaderPicker from '@/features/edit-profile/components/header-picker'; import { usePreview } from '@/hooks/forms/use-preview'; -import { useAppSelector } from '@/hooks/use-app-selector'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import { useSettings } from '@/stores/settings'; import resizeImage from '@/utils/resize-image'; @@ -45,9 +44,7 @@ const DetailsStep: React.FC = ({ params, onChange }) => { const avatarSrc = usePreview(params.avatar); const headerSrc = usePreview(params.header); - const attachmentTypes = useAppSelector( - (state) => state.instance.configuration.media_attachments.supported_mime_types, - ) + const attachmentTypes = instance.configuration.media_attachments.supported_mime_types ?.filter((type) => type.startsWith('image/')) .join(','); diff --git a/packages/nicolium/src/modals/report-modal/index.tsx b/packages/nicolium/src/modals/report-modal/index.tsx index c8424bbdb..c7aaff909 100644 --- a/packages/nicolium/src/modals/report-modal/index.tsx +++ b/packages/nicolium/src/modals/report-modal/index.tsx @@ -8,12 +8,11 @@ import Modal from '@/components/ui/modal'; import ProgressBar from '@/components/ui/progress-bar'; import Text from '@/components/ui/text'; import AccountContainer from '@/containers/account-container'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useInstance } from '@/hooks/use-instance'; import { useAccount } from '@/queries/accounts/use-account'; import { useBlockAccountMutation } from '@/queries/accounts/use-relationship'; import { useMinimalStatus } from '@/queries/statuses/use-status'; import { useAccountTimeline } from '@/queries/timelines/use-timelines'; +import { useInstance } from '@/stores/instance'; import ConfirmationStep from './steps/confirmation-step'; import OtherActionsStep from './steps/other-actions-step'; @@ -69,8 +68,6 @@ const ReportModal: React.FC = ({ entityType, statusIds, }) => { - const dispatch = useAppDispatch(); - const { data: account } = useAccount(accountId || undefined); const { mutate: blockAccount } = useBlockAccountMutation(accountId); @@ -93,7 +90,7 @@ const ReportModal: React.FC = ({ const handleSubmit = () => { setIsSubmitting(true); - dispatch(submitReport(accountId, selectedStatusIds, [...ruleIds], comment, forward)) + submitReport(accountId, selectedStatusIds, [...ruleIds], comment, forward) .then(() => { setIsSubmitting(false); setCurrentStep(Steps.THREE); diff --git a/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx b/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx index a9170370f..4493941d6 100644 --- a/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx +++ b/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import Text from '@/components/ui/text'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useFrontendConfig } from '@/hooks/use-frontend-config'; const termsOfServiceText = ; @@ -17,7 +17,7 @@ const renderTermsOfServiceLink = (href: string) => ( ); const ConfirmationStep: React.FC = () => { - const links = useAppSelector((state) => state.frontendConfig.links); + const { links } = useFrontendConfig(); return (
diff --git a/packages/nicolium/src/modals/report-modal/steps/reason-step.tsx b/packages/nicolium/src/modals/report-modal/steps/reason-step.tsx index dc8c7a773..c2f6ce37e 100644 --- a/packages/nicolium/src/modals/report-modal/steps/reason-step.tsx +++ b/packages/nicolium/src/modals/report-modal/steps/reason-step.tsx @@ -5,7 +5,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import FormGroup from '@/components/ui/form-group'; import Text from '@/components/ui/text'; import Textarea from '@/components/ui/textarea'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import type { Account } from 'pl-api'; diff --git a/packages/nicolium/src/modals/unauthorized-modal.tsx b/packages/nicolium/src/modals/unauthorized-modal.tsx index eb1187481..0a60c52dc 100644 --- a/packages/nicolium/src/modals/unauthorized-modal.tsx +++ b/packages/nicolium/src/modals/unauthorized-modal.tsx @@ -9,9 +9,9 @@ import Modal from '@/components/ui/modal'; import Text from '@/components/ui/text'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useRegistrationStatus } from '@/hooks/use-registration-status'; import { useAccount } from '@/queries/accounts/use-account'; +import { useInstance } from '@/stores/instance'; import toast from '@/toast'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; diff --git a/packages/nicolium/src/normalizers/status.ts b/packages/nicolium/src/normalizers/status.ts index 9eff1743d..f0fdd05b4 100644 --- a/packages/nicolium/src/normalizers/status.ts +++ b/packages/nicolium/src/normalizers/status.ts @@ -13,7 +13,10 @@ const domParser = new DOMParser(); type StatusApprovalStatus = Exclude; -type OldStatus = Pick & { search_index: string }; +type OldStatus = Pick & { + search_index: string; + account_id: string; +}; // Gets titles of poll options from status const getPollOptionTitles = (poll?: BaseStatus['poll']): readonly string[] => { @@ -84,7 +87,7 @@ const normalizeStatus = ( } }); - const accountId = (account || window.__PL_API_FALLBACK_ACCOUNT).id; + const accountId = account?.id || oldStatus?.account_id || window.__PL_API_FALLBACK_ACCOUNT.id; // Add self to mentions if it's a reply to self const isSelfReply = accountId === status.in_reply_to_account_id; diff --git a/packages/nicolium/src/pages/account-lists/directory.tsx b/packages/nicolium/src/pages/account-lists/directory.tsx index 01079f78d..cd85b4a29 100644 --- a/packages/nicolium/src/pages/account-lists/directory.tsx +++ b/packages/nicolium/src/pages/account-lists/directory.tsx @@ -14,13 +14,13 @@ import Avatar from '@/components/ui/avatar'; import { CardTitle } from '@/components/ui/card'; import Column from '@/components/ui/column'; import { RadioGroup, RadioItem } from '@/components/ui/radio'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import ActionButton from '@/features/ui/components/action-button'; import { directoryRoute } from '@/features/ui/router'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useAccount } from '@/queries/accounts/use-account'; import { useDirectory } from '@/queries/accounts/use-directory'; +import { useInstance } from '@/stores/instance'; import { shortNumberFormat } from '@/utils/numbers'; const messages = defineMessages({ @@ -36,7 +36,7 @@ interface IAccountCard { } const AccountCard: React.FC = ({ id }) => { - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: account } = useAccount(id); if (!account) return null; diff --git a/packages/nicolium/src/pages/auth/login.tsx b/packages/nicolium/src/pages/auth/login.tsx index 672d820e7..dda504c5b 100644 --- a/packages/nicolium/src/pages/auth/login.tsx +++ b/packages/nicolium/src/pages/auth/login.tsx @@ -7,23 +7,21 @@ import { fetchInstance } from '@/actions/instance'; import { BigCard } from '@/components/ui/big-card'; import Button from '@/components/ui/button'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import ConsumersList from '@/features/auth-login/components/consumers-list'; import LoginForm from '@/features/auth-login/components/login-form'; import OtpAuthForm from '@/features/auth-login/components/otp-auth-form'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useModalsActions } from '@/stores/modals'; import { getRedirectUrl } from '@/utils/redirect'; -import { isStandalone } from '@/utils/state'; +import { useIsStandalone } from '@/utils/state'; import type { NicoliumResponse } from '@/api'; const LoginPage = () => { - const dispatch = useAppDispatch(); const { closeModal } = useModalsActions(); - const me = useAppSelector((state) => state.me); - const standalone = useAppSelector((state) => isStandalone(state)); + const me = useCurrentAccount(); + const standalone = useIsStandalone(); const token = new URLSearchParams(window.location.search).get('token'); @@ -37,16 +35,16 @@ const LoginPage = () => { const handleSubmit: React.SubmitEventHandler = (event) => { const { username, password } = getFormData(event.target); - dispatch(logIn(username, password)) - .then(({ access_token }) => dispatch(verifyCredentials(access_token))) + logIn(username, password) + .then(({ access_token }) => verifyCredentials(access_token)) // Refetch the instance for authenticated fetch .then(async (account) => { - await dispatch(fetchInstance()); + await fetchInstance(); return account; }) .then((account: { id: string }) => { closeModal(); - dispatch(switchAccount(account.id)); + switchAccount(account.id); if (typeof me !== 'string') { setShouldRedirect(true); } diff --git a/packages/nicolium/src/pages/auth/logout.tsx b/packages/nicolium/src/pages/auth/logout.tsx index b6d3ec8e3..744f93a31 100644 --- a/packages/nicolium/src/pages/auth/logout.tsx +++ b/packages/nicolium/src/pages/auth/logout.tsx @@ -1,17 +1,15 @@ import { Navigate } from '@tanstack/react-router'; import React, { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { logOut } from '@/actions/auth'; import Spinner from '@/components/ui/spinner'; /** Component that logs the user out when rendered */ const LogoutPage: React.FC = () => { - const dispatch = useDispatch(); const [done, setDone] = useState(false); useEffect(() => { - dispatch(logOut() as any) + logOut() .then(() => { setDone(true); }) diff --git a/packages/nicolium/src/pages/auth/password-reset.tsx b/packages/nicolium/src/pages/auth/password-reset.tsx index a21cb181f..7a0199fdd 100644 --- a/packages/nicolium/src/pages/auth/password-reset.tsx +++ b/packages/nicolium/src/pages/auth/password-reset.tsx @@ -9,7 +9,6 @@ import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useFeatures } from '@/hooks/use-features'; import toast from '@/toast'; @@ -21,7 +20,6 @@ const messages = defineMessages({ }); const PasswordResetPage = () => { - const dispatch = useAppDispatch(); const intl = useIntl(); const features = useFeatures(); @@ -31,7 +29,7 @@ const PasswordResetPage = () => { const handleSubmit: React.SubmitEventHandler = (e) => { const nicknameOrEmail = e.target.nickname_or_email.value; setIsLoading(true); - dispatch(resetPassword(nicknameOrEmail)) + resetPassword(nicknameOrEmail) .then(() => { setIsLoading(false); setSuccess(true); diff --git a/packages/nicolium/src/pages/auth/register-with-invite.tsx b/packages/nicolium/src/pages/auth/register-with-invite.tsx index 1925cc7f7..29ef33367 100644 --- a/packages/nicolium/src/pages/auth/register-with-invite.tsx +++ b/packages/nicolium/src/pages/auth/register-with-invite.tsx @@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl'; import { BigCard } from '@/components/ui/big-card'; import RegistrationForm from '@/features/auth-login/components/registration-form'; import { inviteRoute } from '@/features/ui/router'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; /** Page to register with an invitation. */ const RegisterWithInvitePage: React.FC = () => { diff --git a/packages/nicolium/src/pages/auth/registration.tsx b/packages/nicolium/src/pages/auth/registration.tsx index 110ea0f5c..6eb4111d1 100644 --- a/packages/nicolium/src/pages/auth/registration.tsx +++ b/packages/nicolium/src/pages/auth/registration.tsx @@ -4,8 +4,8 @@ import { FormattedMessage } from 'react-intl'; import { BigCard } from '@/components/ui/big-card'; import Text from '@/components/ui/text'; import RegistrationForm from '@/features/auth-login/components/registration-form'; -import { useInstance } from '@/hooks/use-instance'; import { useRegistrationStatus } from '@/hooks/use-registration-status'; +import { useInstance } from '@/stores/instance'; const RegistrationPage: React.FC = () => { const instance = useInstance(); diff --git a/packages/nicolium/src/pages/dashboard/account.tsx b/packages/nicolium/src/pages/dashboard/account.tsx index eb2287a82..a7dc36efe 100644 --- a/packages/nicolium/src/pages/dashboard/account.tsx +++ b/packages/nicolium/src/pages/dashboard/account.tsx @@ -2,8 +2,8 @@ import { PLEROMA } from 'pl-api'; import React, { type ChangeEventHandler, useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, type MessageDescriptor, useIntl } from 'react-intl'; -import { setBadges as saveBadges, setRole } from '@/actions/admin'; -import { deactivateUserModal, deleteUserModal } from '@/actions/moderation'; +import { setRole } from '@/actions/admin'; +import { useDeactivateUserModal, useDeleteUserModal } from '@/actions/moderation'; import Account from '@/components/accounts/account'; import List, { ListItem } from '@/components/list'; import MissingIndicator from '@/components/missing-indicator'; @@ -16,10 +16,10 @@ import Toggle from '@/components/ui/toggle'; import { SelectDropdown } from '@/features/forms'; import ColumnLoading from '@/features/ui/components/column-loading'; import { adminAccountRoute } from '@/features/ui/router'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useAccount } from '@/queries/accounts/use-account'; +import { useAdminUpdateTagsMutation } from '@/queries/admin/use-accounts'; import { useAdminSuggestAccountMutation, useAdminUnsuggestAccountMutation, @@ -93,7 +93,6 @@ interface IStaffRolePicker { /** Picker for setting the staff role of an account. */ const StaffRolePicker: React.FC = ({ account }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); const roles: Record = useMemo( () => ({ @@ -107,7 +106,7 @@ const StaffRolePicker: React.FC = ({ account }) => { const handleRoleChange: React.ChangeEventHandler = (e) => { const role = e.target.value as AccountRole; - dispatch(setRole(account.id, role)) + setRole(account.id, role) .then(() => { let message: MessageDescriptor | undefined; @@ -163,7 +162,6 @@ const AdminAccountPage: React.FC = () => { const { accountId } = adminAccountRoute.useParams(); const intl = useIntl(); - const dispatch = useAppDispatch(); const { mutate: suggest } = useAdminSuggestAccountMutation(accountId); const { mutate: unsuggest } = useAdminUnsuggestAccountMutation(accountId); @@ -172,6 +170,9 @@ const AdminAccountPage: React.FC = () => { const { data: ownAccount } = useOwnAccount(); const features = useFeatures(); const { data: account, isLoading } = useAccount(accountId); + const deactivateUserModal = useDeactivateUserModal(accountId); + const deleteUserModal = useDeleteUserModal(accountId); + const { mutate: updateTags } = useAdminUpdateTagsMutation(accountId); const accountBadges = account ? getBadges(account) : []; const [badges, setBadges] = useState(accountBadges); @@ -219,19 +220,20 @@ const AdminAccountPage: React.FC = () => { }; const handleDeactivate = () => { - dispatch(deactivateUserModal(intl, account.id)); + deactivateUserModal(); }; const handleDelete = () => { - dispatch(deleteUserModal(intl, account.id)); + deleteUserModal(); }; const handleSaveBadges = () => { - dispatch(saveBadges(account.id, accountBadges, badges)) - .then(() => { - toast.success(intl.formatMessage(messages.badgesSaved)); - }) - .catch(() => {}); + updateTags( + { oldTags: accountBadges, newTags: badges }, + { + onSuccess: () => toast.success(intl.formatMessage(messages.badgesSaved)), + }, + ); }; return ( diff --git a/packages/nicolium/src/pages/dashboard/dashboard.tsx b/packages/nicolium/src/pages/dashboard/dashboard.tsx index e97d64bc5..62b803f9f 100644 --- a/packages/nicolium/src/pages/dashboard/dashboard.tsx +++ b/packages/nicolium/src/pages/dashboard/dashboard.tsx @@ -11,10 +11,10 @@ import { Dimension } from '@/features/admin/components/dimension'; import RegistrationModePicker from '@/features/admin/components/registration-mode-picker'; import { Retention } from '@/features/admin/components/retention'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useOwnAccount } from '@/hooks/use-own-account'; import { usePendingUsersCount } from '@/queries/admin/use-accounts'; import { usePendingReportsCount } from '@/queries/admin/use-reports'; +import { useInstance } from '@/stores/instance'; import sourceCode from '@/utils/code'; const messages = defineMessages({ diff --git a/packages/nicolium/src/pages/dashboard/frontend-config.tsx b/packages/nicolium/src/pages/dashboard/frontend-config.tsx index 7b6217adb..de1e88226 100644 --- a/packages/nicolium/src/pages/dashboard/frontend-config.tsx +++ b/packages/nicolium/src/pages/dashboard/frontend-config.tsx @@ -23,8 +23,6 @@ import FooterLinkInput from '@/features/frontend-config/components/footer-link-i import PromoPanelInput from '@/features/frontend-config/components/promo-panel-input'; import SitePreview from '@/features/frontend-config/components/site-preview'; import ThemeSelector from '@/features/ui/components/theme-selector'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFeatures } from '@/hooks/use-features'; import { getUpdateFrontendConfigParams, useUpdateAdminConfig } from '@/queries/admin/use-config'; import { @@ -34,6 +32,7 @@ import { promoPanelItemSchema, type FrontendConfig, } from '@/schemas/frontend-config'; +import { useFrontendConfigStore } from '@/stores/frontend-config'; import toast from '@/toast'; const messages = defineMessages({ @@ -64,12 +63,10 @@ type ThemeChangeHandler = (theme: 'system' | 'light' | 'dark' | 'black') => void const FrontendConfigEditor: React.FC = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const features = useFeatures(); - const initialData = useAppSelector((state) => state.frontendConfig); - console.log(initialData); + const initialData = useFrontendConfigStore((state) => state.partialConfig); const { mutate: updateConfig, isPending } = useUpdateAdminConfig(); const [data, setData] = useState(v.parse(frontendConfigSchema, initialData)); @@ -122,7 +119,7 @@ const FrontendConfigEditor: React.FC = () => { const file = e.target.files?.item(0); if (file) { - dispatch(uploadMedia({ file })) + uploadMedia({ file }) .then((data) => { handleChange(path, () => data.url)(e); }) diff --git a/packages/nicolium/src/pages/dashboard/theme-editor.tsx b/packages/nicolium/src/pages/dashboard/theme-editor.tsx index 9a857def6..1fc0058f6 100644 --- a/packages/nicolium/src/pages/dashboard/theme-editor.tsx +++ b/packages/nicolium/src/pages/dashboard/theme-editor.tsx @@ -12,12 +12,11 @@ import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import ColorPicker from '@/features/frontend-config/components/color-picker'; import Palette, { type ColorGroup } from '@/features/theme-editor/components/palette'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; import { normalizeColors } from '@/hooks/use-theme-css'; import { getUpdateFrontendConfigParams, useUpdateAdminConfig } from '@/queries/admin/use-config'; import { frontendConfigSchema } from '@/schemas/frontend-config'; +import { useFrontendConfigStore } from '@/stores/frontend-config'; import toast from '@/toast'; import { download } from '@/utils/download'; @@ -51,11 +50,10 @@ const messages = defineMessages({ /** UI for editing Tailwind theme colors. */ const ThemeEditorPage: React.FC = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const frontendConfig = useFrontendConfig(); - const host = useAppSelector((state) => getHost(state)); - const rawConfig = useAppSelector((state) => state.frontendConfig); + const host = getHost(); + const rawConfig = useFrontendConfigStore((state) => state.partialConfig); const { mutate: updateConfig } = useUpdateAdminConfig(); const [colors, setColors] = useState(normalizeColors(frontendConfig)); @@ -140,7 +138,7 @@ const ThemeEditorPage: React.FC = () => { setSubmitting(true); try { - await dispatch(fetchFrontendConfig(host)); + await fetchFrontendConfig(host); await updateTheme(); toast.success(intl.formatMessage(messages.saved)); setSubmitting(false); diff --git a/packages/nicolium/src/pages/developers/settings-store.tsx b/packages/nicolium/src/pages/developers/settings-store.tsx index cc7092e2b..c23ded91b 100644 --- a/packages/nicolium/src/pages/developers/settings-store.tsx +++ b/packages/nicolium/src/pages/developers/settings-store.tsx @@ -11,7 +11,6 @@ import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Textarea from '@/components/ui/textarea'; import SettingToggle from '@/features/settings/components/setting-toggle'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useSettingsStore, useSettingsStoreActions } from '@/stores/settings'; import toast from '@/toast'; @@ -30,7 +29,6 @@ const messages = defineMessages({ const SettingsStore: React.FC = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const { settings, userSettings } = useSettingsStore(); const { loadUserSettings } = useSettingsStoreActions(); @@ -45,14 +43,14 @@ const SettingsStore: React.FC = () => { }; const onToggleChange = (key: string[], checked: boolean) => { - dispatch(changeSetting(key, checked, { showAlert: true })); + changeSetting(key, checked, { showAlert: true }); }; const handleSubmit: React.SubmitEventHandler = () => { const settings = JSON.parse(rawJSON); setLoading(true); - dispatch(updateSettingsStore(settings)) + updateSettingsStore(settings) .then(() => { loadUserSettings(settings); setLoading(false); diff --git a/packages/nicolium/src/pages/fun/circle.tsx b/packages/nicolium/src/pages/fun/circle.tsx index 729310756..db9b32522 100644 --- a/packages/nicolium/src/pages/fun/circle.tsx +++ b/packages/nicolium/src/pages/fun/circle.tsx @@ -13,7 +13,7 @@ import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import ProgressBar from '@/components/ui/progress-bar'; import Text from '@/components/ui/text'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; +import { useClient } from '@/hooks/use-client'; import { useOwnAccount } from '@/hooks/use-own-account'; import { appendMedia, useComposeActions } from '@/stores/compose'; import { useModalsActions } from '@/stores/modals'; @@ -70,9 +70,9 @@ const CirclePage: React.FC = () => { useState>(); const intl = useIntl(); - const dispatch = useAppDispatch(); const canvasRef = useRef(null); + const client = useClient(); const { openModal } = useModalsActions(); const { resetCompose, updateCompose } = useComposeActions(); const { data: account } = useOwnAccount(); @@ -96,21 +96,19 @@ const CirclePage: React.FC = () => { canvasRef.current!.toBlob((blob) => { const file = new File([blob!], 'interactions_circle.png', { type: 'image/png' }); - dispatch( - uploadFile(file, intl, (data) => { - updateCompose('compose-modal', (draft) => { - appendMedia(draft, data); - }); - openModal('COMPOSE'); - }), - ); + uploadFile(file, intl, (data) => { + updateCompose('compose-modal', (draft) => { + appendMedia(draft, data); + }); + openModal('COMPOSE'); + }); }, 'image/png'); }; const handleRequest = () => { setProgress({ state: 'pending', progress: 0 }); - dispatch(processCircle(setProgress)) + processCircle(client, setProgress)() .then(async (users) => { setUsers(users); diff --git a/packages/nicolium/src/pages/groups/edit-group.tsx b/packages/nicolium/src/pages/groups/edit-group.tsx index f1b5a8c0d..bddea7db6 100644 --- a/packages/nicolium/src/pages/groups/edit-group.tsx +++ b/packages/nicolium/src/pages/groups/edit-group.tsx @@ -15,9 +15,8 @@ import HeaderPicker from '@/features/edit-profile/components/header-picker'; import { editGroupRoute } from '@/features/ui/router'; import { useImageField } from '@/hooks/forms/use-image-field'; import { useTextField } from '@/hooks/forms/use-text-field'; -import { useAppSelector } from '@/hooks/use-app-selector'; -import { useInstance } from '@/hooks/use-instance'; import { useGroupQuery, useUpdateGroupMutation } from '@/queries/groups/use-group'; +import { useInstance } from '@/stores/instance'; import toast from '@/toast'; import { unescapeHTML } from '@/utils/html'; @@ -60,9 +59,7 @@ const EditGroup: React.FC = () => { const maxName = Number(instance.configuration.groups.max_characters_name); const maxNote = Number(instance.configuration.groups.max_characters_description); - const attachmentTypes = useAppSelector( - (state) => state.instance.configuration.media_attachments.supported_mime_types, - ) + const attachmentTypes = instance.configuration.media_attachments.supported_mime_types ?.filter((type) => type.startsWith('image/')) .join(','); diff --git a/packages/nicolium/src/pages/settings/aliases.tsx b/packages/nicolium/src/pages/settings/aliases.tsx index dd5b752c2..98b76cfc9 100644 --- a/packages/nicolium/src/pages/settings/aliases.tsx +++ b/packages/nicolium/src/pages/settings/aliases.tsx @@ -10,7 +10,7 @@ import { CardHeader, CardTitle } from '@/components/ui/card'; import Column from '@/components/ui/column'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useFeatures } from '@/hooks/use-features'; import { useAccount } from '@/queries/accounts/use-account'; import { useSearchAccounts } from '@/queries/search/use-search'; @@ -19,7 +19,6 @@ import { useAddAccountAlias, useDeleteAccountAlias, } from '@/queries/settings/use-account-aliases'; - const messages = defineMessages({ heading: { id: 'column.aliases', defaultMessage: 'Account aliases' }, delete: { id: 'column.aliases.delete', defaultMessage: 'Delete' }, @@ -37,7 +36,7 @@ const Account: React.FC = ({ accountId, aliases }) => { const intl = useIntl(); const features = useFeatures(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const { data: account } = useAccount(accountId); const { mutate: addAccountAlias } = useAddAccountAlias(); diff --git a/packages/nicolium/src/pages/settings/auth-token-list.tsx b/packages/nicolium/src/pages/settings/auth-token-list.tsx index 8504f9100..b9aafab27 100644 --- a/packages/nicolium/src/pages/settings/auth-token-list.tsx +++ b/packages/nicolium/src/pages/settings/auth-token-list.tsx @@ -1,4 +1,3 @@ -import { useInfiniteQuery, useMutation } from '@tanstack/react-query'; import clsx from 'clsx'; import React from 'react'; import { defineMessages, FormattedDate, FormattedMessage, useIntl } from 'react-intl'; @@ -8,11 +7,8 @@ import Card, { CardBody, CardHeader, CardTitle } from '@/components/ui/card'; import Column from '@/components/ui/column'; import Icon from '@/components/ui/icon'; import Spinner from '@/components/ui/spinner'; -import { useAppSelector } from '@/hooks/use-app-selector'; -import { - oauthTokensQueryOptions, - revokeOauthTokenMutationOptions, -} from '@/queries/security/oauth-tokens'; +import { useOauthTokensQuery, useRevokeOauthTokenMutation } from '@/queries/security/oauth-tokens'; +import { useAuthStore } from '@/stores/auth'; import { useModalsActions } from '@/stores/modals'; import type { OauthToken } from 'pl-api'; @@ -33,7 +29,7 @@ interface IAuthToken { const AuthToken: React.FC = ({ token, isCurrent }) => { const intl = useIntl(); - const revokeMutation = useMutation(revokeOauthTokenMutationOptions(token.id)); + const revokeMutation = useRevokeOauthTokenMutation(token.id); const { openModal } = useModalsActions(); @@ -156,12 +152,10 @@ const AuthToken: React.FC = ({ token, isCurrent }) => { const AuthTokenListPage: React.FC = () => { const intl = useIntl(); - const { data: tokens } = useInfiniteQuery(oauthTokensQueryOptions); + const { data: tokens } = useOauthTokensQuery(); - const currentTokenId = useAppSelector((state) => { - const currentToken = Object.values(state.auth.tokens).find( - (token) => token.me === state.auth.me, - ); + const currentTokenId = useAuthStore((state) => { + const currentToken = Object.values(state.tokens).find((token) => token.me === state.me); return currentToken?.id; }); diff --git a/packages/nicolium/src/pages/settings/delete-account.tsx b/packages/nicolium/src/pages/settings/delete-account.tsx index 69522b9ab..0fc557903 100644 --- a/packages/nicolium/src/pages/settings/delete-account.tsx +++ b/packages/nicolium/src/pages/settings/delete-account.tsx @@ -9,7 +9,6 @@ import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; import Text from '@/components/ui/text'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useFeatures } from '@/hooks/use-features'; import toast from '@/toast'; @@ -26,7 +25,6 @@ const messages = defineMessages({ const DeleteAccountPage = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const features = useFeatures(); const [password, setPassword] = React.useState(''); @@ -40,7 +38,7 @@ const DeleteAccountPage = () => { const handleSubmit = React.useCallback(() => { setLoading(true); - dispatch(deleteAccount(password)) + deleteAccount(password) .then(() => { setPassword(''); toast.success(intl.formatMessage(messages.deleteAccountSuccess)); @@ -52,7 +50,7 @@ const DeleteAccountPage = () => { setPassword(''); toast.error(intl.formatMessage(messages.deleteAccountFail)); }); - }, [password, dispatch, intl]); + }, [password, intl]); return ( diff --git a/packages/nicolium/src/pages/settings/domain-blocks.tsx b/packages/nicolium/src/pages/settings/domain-blocks.tsx index 93eaf9abe..2edd55de7 100644 --- a/packages/nicolium/src/pages/settings/domain-blocks.tsx +++ b/packages/nicolium/src/pages/settings/domain-blocks.tsx @@ -1,5 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; -import { useInfiniteQuery } from '@tanstack/react-query'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; @@ -8,8 +6,8 @@ import Column from '@/components/ui/column'; import IconButton from '@/components/ui/icon-button'; import Spinner from '@/components/ui/spinner'; import Text from '@/components/ui/text'; -import { unblockDomainMutationOptions } from '@/queries/settings/domain-blocks'; -import { domainBlocksQueryOptions } from '@/queries/settings/domain-blocks'; +import { useUnblockDomainMutation } from '@/queries/settings/domain-blocks'; +import { useDomainBlocksQuery } from '@/queries/settings/domain-blocks'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Domain blocks' }, @@ -23,7 +21,7 @@ interface IDomain { const Domain: React.FC = ({ domain }) => { const intl = useIntl(); - const { mutate: unblockDomain } = useMutation(unblockDomainMutationOptions); + const { mutate: unblockDomain } = useUnblockDomainMutation(); const handleDomainUnblock = () => { unblockDomain(domain); @@ -45,7 +43,7 @@ const Domain: React.FC = ({ domain }) => { const DomainBlocksPage: React.FC = () => { const intl = useIntl(); - const { data: domains, hasNextPage, fetchNextPage } = useInfiniteQuery(domainBlocksQueryOptions); + const { data: domains, hasNextPage, fetchNextPage } = useDomainBlocksQuery(); const handleLoadMore = () => { if (hasNextPage) { diff --git a/packages/nicolium/src/pages/settings/edit-email.tsx b/packages/nicolium/src/pages/settings/edit-email.tsx index cd1e42fb2..e4d0f35dc 100644 --- a/packages/nicolium/src/pages/settings/edit-email.tsx +++ b/packages/nicolium/src/pages/settings/edit-email.tsx @@ -8,7 +8,6 @@ import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import toast from '@/toast'; const messages = defineMessages({ @@ -25,7 +24,6 @@ const initialState = { email: '', password: '' }; const EditEmailPage = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const [state, setState] = React.useState(initialState); const [isLoading, setLoading] = React.useState(false); @@ -43,7 +41,7 @@ const EditEmailPage = () => { const handleSubmit = React.useCallback(() => { setLoading(true); - dispatch(changeEmail(email, password)) + changeEmail(email, password) .then(() => { setState(initialState); toast.success(intl.formatMessage(messages.updateEmailSuccess)); @@ -55,7 +53,7 @@ const EditEmailPage = () => { setState((prevState) => ({ ...prevState, password: '' })); toast.error(intl.formatMessage(messages.updateEmailFail)); }); - }, [email, password, dispatch, intl]); + }, [email, password, intl]); return ( diff --git a/packages/nicolium/src/pages/settings/edit-password.tsx b/packages/nicolium/src/pages/settings/edit-password.tsx index 3d66ab94c..f6ea24a42 100644 --- a/packages/nicolium/src/pages/settings/edit-password.tsx +++ b/packages/nicolium/src/pages/settings/edit-password.tsx @@ -8,7 +8,6 @@ import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import toast from '@/toast'; const messages = defineMessages({ @@ -31,7 +30,6 @@ const initialState = { currentPassword: '', newPassword: '', newPasswordConfirma const EditPasswordPage = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const [state, setState] = React.useState(initialState); const [isLoading, setLoading] = React.useState(false); @@ -58,7 +56,7 @@ const EditPasswordPage = () => { } setLoading(true); - dispatch(changePassword(currentPassword, newPassword)) + changePassword(currentPassword, newPassword) .then(() => { resetState(); toast.success(intl.formatMessage(messages.updatePasswordSuccess)); @@ -70,7 +68,7 @@ const EditPasswordPage = () => { resetState(); toast.error(intl.formatMessage(messages.updatePasswordFail)); }); - }, [currentPassword, newPassword, newPasswordConfirmation, dispatch, intl]); + }, [currentPassword, newPassword, newPasswordConfirmation, intl]); return ( diff --git a/packages/nicolium/src/pages/settings/edit-profile.tsx b/packages/nicolium/src/pages/settings/edit-profile.tsx index 9fdaa3675..ad17aea9e 100644 --- a/packages/nicolium/src/pages/settings/edit-profile.tsx +++ b/packages/nicolium/src/pages/settings/edit-profile.tsx @@ -3,7 +3,7 @@ import { type CredentialAccount, GOTOSOCIAL, type UpdateCredentialsParams } from import React, { useState, useEffect } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { patchMe } from '@/actions/me'; +import { patchMe } from '@/actions/auth'; import BirthdayInput from '@/components/birthday-input'; import List, { ListItem } from '@/components/list'; import Accordion from '@/components/ui/accordion'; @@ -20,12 +20,10 @@ import AvatarPicker from '@/features/edit-profile/components/avatar-picker'; import HeaderPicker from '@/features/edit-profile/components/header-picker'; import { SelectDropdown } from '@/features/forms'; import { useImageField } from '@/hooks/forms/use-image-field'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useOwnAccount } from '@/hooks/use-own-account'; +import { useInstance } from '@/stores/instance'; import toast from '@/toast'; import type { StreamfieldComponent } from '@/components/ui/streamfield'; @@ -271,7 +269,6 @@ const ProfileField: StreamfieldComponent = ({ /** Edit profile page. */ const EditProfilePage: React.FC = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const instance = useInstance(); const client = useClient(); @@ -281,9 +278,7 @@ const EditProfilePage: React.FC = () => { ? instance.configuration.accounts.max_profile_fields : instance.pleroma.metadata.fields_limits.max_fields; - const attachmentTypes = useAppSelector( - (state) => state.instance.configuration.media_attachments.supported_mime_types, - ) + const attachmentTypes = instance.configuration.media_attachments.supported_mime_types ?.filter((type) => type.startsWith('image/')) .join(','); @@ -341,7 +336,7 @@ const EditProfilePage: React.FC = () => { if (!instance.configuration.accounts?.allow_custom_css) delete params.custom_css; - promises.push(dispatch(patchMe(params as any))); + promises.push(patchMe(params as any)); if (features.muteStrangers) { promises.push( diff --git a/packages/nicolium/src/pages/settings/export-data.tsx b/packages/nicolium/src/pages/settings/export-data.tsx index 0dcf6ef68..c6da02530 100644 --- a/packages/nicolium/src/pages/settings/export-data.tsx +++ b/packages/nicolium/src/pages/settings/export-data.tsx @@ -7,25 +7,20 @@ import Column from '@/components/ui/column'; import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import Text from '@/components/ui/text'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; - -import type { AppDispatch, RootState } from '@/store'; interface ICSVExporter { inputLabel: React.ReactNode; inputHint: React.ReactNode; submitText: React.ReactNode; - action: () => (dispatch: AppDispatch, getState: () => RootState) => Promise; + action: () => Promise; } const CSVExporter: React.FC = ({ inputLabel, inputHint, submitText, action }) => { - const dispatch = useAppDispatch(); - const [isLoading, setIsLoading] = useState(false); const handleClick: React.MouseEventHandler = () => { setIsLoading(true); - dispatch(action()) + action() .then(() => { setIsLoading(false); }) diff --git a/packages/nicolium/src/pages/settings/interaction-policies.tsx b/packages/nicolium/src/pages/settings/interaction-policies.tsx index 0ba9816f1..f2d3a88a1 100644 --- a/packages/nicolium/src/pages/settings/interaction-policies.tsx +++ b/packages/nicolium/src/pages/settings/interaction-policies.tsx @@ -2,7 +2,7 @@ import { create } from 'mutative'; import React, { useEffect, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { patchMe } from '@/actions/me'; +import { patchMe } from '@/actions/auth'; import List, { ListItem } from '@/components/list'; import Button from '@/components/ui/button'; import Column from '@/components/ui/column'; @@ -13,7 +13,6 @@ import Tabs from '@/components/ui/tabs'; import Text from '@/components/ui/text'; import Warning from '@/features/compose/components/warning'; import { SelectDropdown } from '@/features/forms'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { useInteractionPolicies } from '@/queries/settings/use-interaction-policies'; @@ -258,7 +257,6 @@ const InteractionPolicyConfig: React.FC = ({ const InteractionPoliciesPage = () => { const client = useClient(); - const dispatch = useAppDispatch(); const features = useFeatures(); const [quotePolicy, setQuotePolicy] = useState('public'); @@ -322,7 +320,7 @@ const InteractionPoliciesPage = () => { } if (features.quoteApprovalPolicies && !features.interactionRequests) { - promises.push(dispatch(patchMe({ source: { quote_policy: quotePolicy } }))); + promises.push(patchMe({ source: { quote_policy: quotePolicy } })); } Promise.all(promises) diff --git a/packages/nicolium/src/pages/settings/migration.tsx b/packages/nicolium/src/pages/settings/migration.tsx index 4e9a25f90..63850af5c 100644 --- a/packages/nicolium/src/pages/settings/migration.tsx +++ b/packages/nicolium/src/pages/settings/migration.tsx @@ -10,8 +10,7 @@ import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; import Text from '@/components/ui/text'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import toast from '@/toast'; const messages = defineMessages({ @@ -36,7 +35,6 @@ const messages = defineMessages({ const MigrationPage = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); const instance = useInstance(); const cooldownPeriod = instance.pleroma.metadata.migration_cooldown_period; @@ -57,7 +55,7 @@ const MigrationPage = () => { const handleSubmit: React.SubmitEventHandler = () => { setIsLoading(true); - return dispatch(moveAccount(targetAccount, password)) + return moveAccount(targetAccount, password) .then(() => { clearForm(); toast.success(intl.formatMessage(messages.moveAccountSuccess)); diff --git a/packages/nicolium/src/pages/settings/privacy.tsx b/packages/nicolium/src/pages/settings/privacy.tsx index 5a4d4c69e..ac1fdc80e 100644 --- a/packages/nicolium/src/pages/settings/privacy.tsx +++ b/packages/nicolium/src/pages/settings/privacy.tsx @@ -13,9 +13,8 @@ import FormActions from '@/components/ui/form-actions'; import FormGroup from '@/components/ui/form-group'; import Input from '@/components/ui/input'; import Toggle from '@/components/ui/toggle'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { SelectDropdown } from '@/features/forms'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useAppSelector } from '@/hooks/use-app-selector'; import KVStore from '@/storage/kv-store'; import { useSettings } from '@/stores/settings'; import { hasCanvasExtractPermission } from '@/utils/favicon-service'; @@ -43,8 +42,7 @@ const messages = defineMessages({ }); const Privacy = () => { - const dispatch = useAppDispatch(); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const intl = useIntl(); const settings = useSettings(); @@ -86,14 +84,12 @@ const Privacy = () => { break; } - dispatch(changeSetting(['urlPrivacy'], value)); - dispatch(changeSetting(['stripMetadata'], stripMetadata)); + changeSetting(['urlPrivacy'], value); + changeSetting(['stripMetadata'], stripMetadata); - dispatch( - saveSettings({ - showAlert: true, - }), - ); + saveSettings({ + showAlert: true, + }); }; const handleChangeRedirectLinksMode = (event: React.ChangeEvent) => { @@ -118,7 +114,7 @@ const Privacy = () => { setRedirectLinksMode(event.target.value as 'off'); }; - useEffect(() => {}, [dispatch]); + useEffect(() => {}, []); return ( diff --git a/packages/nicolium/src/pages/statuses/event-discussion.tsx b/packages/nicolium/src/pages/statuses/event-discussion.tsx index 5bedda81d..2631d1bf2 100644 --- a/packages/nicolium/src/pages/statuses/event-discussion.tsx +++ b/packages/nicolium/src/pages/statuses/event-discussion.tsx @@ -4,12 +4,12 @@ import { FormattedMessage } from 'react-intl'; import MissingIndicator from '@/components/missing-indicator'; import ScrollableList from '@/components/scrollable-list'; import Tombstone from '@/components/statuses/tombstone'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; import ThreadStatus from '@/features/status/components/thread-status'; import PendingStatus from '@/features/ui/components/pending-status'; import { eventDiscussionRoute } from '@/features/ui/router'; import { ComposeForm } from '@/features/ui/util/async-components'; -import { useAppSelector } from '@/hooks/use-app-selector'; import { useStatus } from '@/queries/statuses/use-status'; import { useComposeActions } from '@/stores/compose'; import { useDescendantsIds } from '@/stores/contexts'; @@ -24,7 +24,7 @@ const EventDiscussionPage: React.FC = () => { const { data: status, isPending } = useStatus(statusId); - const me = useAppSelector((state) => state.me); + const me = useCurrentAccount(); const descendantsIds = useDescendantsIds(statusId); diff --git a/packages/nicolium/src/pages/statuses/status.tsx b/packages/nicolium/src/pages/statuses/status.tsx index 20c905ec0..b3da65223 100644 --- a/packages/nicolium/src/pages/statuses/status.tsx +++ b/packages/nicolium/src/pages/statuses/status.tsx @@ -10,7 +10,6 @@ import Column from '@/components/ui/column'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; import Thread from '@/features/status/components/thread'; import { statusRoute } from '@/features/ui/router'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useStatus } from '@/queries/statuses/use-status'; import { useSettings } from '@/stores/settings'; @@ -46,8 +45,6 @@ const messages = defineMessages({ const StatusPage: React.FC = () => { const { username, statusId } = statusRoute.useParams(); - - const dispatch = useAppDispatch(); const intl = useIntl(); const { @@ -74,7 +71,7 @@ const StatusPage: React.FC = () => { { text: intl.formatMessage(messages.treeView), action: () => { - dispatch(changeSetting(['threads', 'displayMode'], 'tree')); + changeSetting(['threads', 'displayMode'], 'tree'); }, icon: require('@phosphor-icons/core/regular/tree-view.svg'), type: 'radio', @@ -83,7 +80,7 @@ const StatusPage: React.FC = () => { { text: intl.formatMessage(messages.linearView), action: () => { - dispatch(changeSetting(['threads', 'displayMode'], 'linear')); + changeSetting(['threads', 'displayMode'], 'linear'); }, icon: require('@phosphor-icons/core/regular/list-bullets.svg'), type: 'radio', diff --git a/packages/nicolium/src/pages/timelines/home-timeline.tsx b/packages/nicolium/src/pages/timelines/home-timeline.tsx index ab5bbbc8b..278e4b7b0 100644 --- a/packages/nicolium/src/pages/timelines/home-timeline.tsx +++ b/packages/nicolium/src/pages/timelines/home-timeline.tsx @@ -6,7 +6,7 @@ import { Link } from '@/components/link'; import Column from '@/components/ui/column'; import Text from '@/components/ui/text'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import { useUiStore } from '@/stores/ui'; const messages = defineMessages({ @@ -21,7 +21,7 @@ const messages = defineMessages({ // const checkIfReloadNeeded = useCallback((isPartial: boolean) => { // if (isPartial) { // polling.current = setInterval(() => { -// dispatch(fetchHomeTimeline()); +// fetchHomeTimeline(); // }, 3000); // } else if (polling.current) { // clearInterval(polling.current); diff --git a/packages/nicolium/src/pages/timelines/landing-timeline.tsx b/packages/nicolium/src/pages/timelines/landing-timeline.tsx index 0cf76fcf5..c15dc8da6 100644 --- a/packages/nicolium/src/pages/timelines/landing-timeline.tsx +++ b/packages/nicolium/src/pages/timelines/landing-timeline.tsx @@ -7,10 +7,10 @@ import Markup from '@/components/markup'; import { ParsedContent } from '@/components/statuses/parsed-content'; import Button from '@/components/ui/button'; import Column from '@/components/ui/column'; -import { useInstance } from '@/hooks/use-instance'; import { useRegistrationStatus } from '@/hooks/use-registration-status'; import { About } from '@/pages/utils/about'; import { usePublicTimeline } from '@/queries/timelines/use-timelines'; +import { useInstance } from '@/stores/instance'; import { getTextDirection } from '@/utils/rtl'; interface ILogoText extends Pick, 'className' | 'dir'> { diff --git a/packages/nicolium/src/pages/timelines/public-timeline.tsx b/packages/nicolium/src/pages/timelines/public-timeline.tsx index ad2a77112..c0ffbd33e 100644 --- a/packages/nicolium/src/pages/timelines/public-timeline.tsx +++ b/packages/nicolium/src/pages/timelines/public-timeline.tsx @@ -7,8 +7,7 @@ import { PublicTimelineColumn } from '@/columns/timeline'; import Accordion from '@/components/ui/accordion'; import Column from '@/components/ui/column'; import PinnedHostsPicker from '@/features/remote-timeline/components/pinned-hosts-picker'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; import { useSettings } from '@/stores/settings'; const messages = defineMessages({ @@ -17,7 +16,6 @@ const messages = defineMessages({ }); const PublicTimelinePage = () => { - const dispatch = useAppDispatch(); const intl = useIntl(); const instance = useInstance(); @@ -27,11 +25,11 @@ const PublicTimelinePage = () => { const showExplanationBox = settings.showExplanationBox; const dismissExplanationBox = () => { - dispatch(changeSetting(['showExplanationBox'], false)); + changeSetting(['showExplanationBox'], false); }; const toggleExplanationBox = (setting: boolean) => { - dispatch(changeSetting(['explanationBox'], setting)); + changeSetting(['explanationBox'], setting); }; return ( diff --git a/packages/nicolium/src/pages/utils/crypto-donate.tsx b/packages/nicolium/src/pages/utils/crypto-donate.tsx index d8566343d..799157749 100644 --- a/packages/nicolium/src/pages/utils/crypto-donate.tsx +++ b/packages/nicolium/src/pages/utils/crypto-donate.tsx @@ -4,7 +4,7 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import Accordion from '@/components/ui/accordion'; import Column from '@/components/ui/column'; import SiteWallet from '@/features/crypto-donate/components/site-wallet'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; const messages = defineMessages({ heading: { id: 'column.crypto_donate', defaultMessage: 'Donate cryptocurrency' }, diff --git a/packages/nicolium/src/pages/utils/federation-restrictions.tsx b/packages/nicolium/src/pages/utils/federation-restrictions.tsx index c952ba5ac..8cd7a7cfc 100644 --- a/packages/nicolium/src/pages/utils/federation-restrictions.tsx +++ b/packages/nicolium/src/pages/utils/federation-restrictions.tsx @@ -5,10 +5,9 @@ import ScrollableList from '@/components/scrollable-list'; import Accordion from '@/components/ui/accordion'; import Column from '@/components/ui/column'; import RestrictedInstance from '@/features/federation-restrictions/components/restricted-instance'; -import { useAppSelector } from '@/hooks/use-app-selector'; -import { useInstance } from '@/hooks/use-instance'; import { useHosts } from '@/selectors'; -import { federationRestrictionsDisclosed } from '@/utils/state'; +import { useInstance } from '@/stores/instance'; +import { useFederationRestrictionsDisclosed } from '@/utils/state'; const messages = defineMessages({ heading: { id: 'column.federation_restrictions', defaultMessage: 'Federation restrictions' }, @@ -28,7 +27,7 @@ const FederationRestrictionsPage = () => { const instance = useInstance(); const hosts = useHosts(); - const disclosed = useAppSelector((state) => federationRestrictionsDisclosed(state)); + const disclosed = useFederationRestrictionsDisclosed(); const [explanationBoxExpanded, setExplanationBoxExpanded] = useState(true); diff --git a/packages/nicolium/src/pages/utils/server-info.tsx b/packages/nicolium/src/pages/utils/server-info.tsx index 4078a64f7..2d5cb02b5 100644 --- a/packages/nicolium/src/pages/utils/server-info.tsx +++ b/packages/nicolium/src/pages/utils/server-info.tsx @@ -6,7 +6,7 @@ import Divider from '@/components/ui/divider'; import Text from '@/components/ui/text'; import LinkFooter from '@/features/ui/components/link-footer'; import PromoPanel from '@/features/ui/components/panels/promo-panel'; -import { useInstance } from '@/hooks/use-instance'; +import { useInstance } from '@/stores/instance'; const messages = defineMessages({ heading: { id: 'column.info', defaultMessage: 'Server information' }, diff --git a/packages/nicolium/src/queries/accounts/selectors.ts b/packages/nicolium/src/queries/accounts/selectors.ts index 5b76f4b2f..fd6c28cb8 100644 --- a/packages/nicolium/src/queries/accounts/selectors.ts +++ b/packages/nicolium/src/queries/accounts/selectors.ts @@ -1,14 +1,14 @@ import { queryClient } from '@/queries/client'; import { queryKeys } from '@/queries/keys'; - -import type { RootState } from '@/store'; +import { useAuthStore } from '@/stores/auth'; const selectAccount = (accountId: string) => queryClient.getQueryData(queryKeys.accounts.show(accountId)); -const selectOwnAccount = (state: RootState) => { - if (state.me) { - return selectAccount(state.me); +const selectOwnAccount = () => { + const accountId = useAuthStore.getState().currentAccountId; + if (typeof accountId === 'string') { + return selectAccount(accountId); } }; diff --git a/packages/nicolium/src/queries/accounts/use-account-credentials.ts b/packages/nicolium/src/queries/accounts/use-account-credentials.ts index 8af9fe01b..3d3263d2a 100644 --- a/packages/nicolium/src/queries/accounts/use-account-credentials.ts +++ b/packages/nicolium/src/queries/accounts/use-account-credentials.ts @@ -1,10 +1,10 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { patchMeSuccess } from '@/actions/me'; import { useCurrentAccount } from '@/contexts/current-account-context'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useClient } from '@/hooks/use-client'; import { queryKeys } from '@/queries/keys'; +import { useAuthActions } from '@/stores/auth'; +import { useComposeActions } from '@/stores/compose'; import type { UpdateCredentialsParams } from 'pl-api'; @@ -22,8 +22,9 @@ const useCredentialAccount = (enabled = true) => { const useUpdateCredentials = () => { const client = useClient(); const currentAccount = useCurrentAccount(); - const dispatch = useAppDispatch(); const queryClient = useQueryClient(); + const { setCurrentAccount } = useAuthActions(); + const { importDefaultSettings } = useComposeActions(); return useMutation({ mutationKey: queryKeys.accountCredentials.show(currentAccount as string), @@ -33,7 +34,9 @@ const useUpdateCredentials = () => { queryKeys.accountCredentials.show(currentAccount as string), response, ); - dispatch(patchMeSuccess(response)); + queryClient.setQueryData(queryKeys.accounts.show(response.id), response); + importDefaultSettings(response); + setCurrentAccount(response); }, }); }; diff --git a/packages/nicolium/src/queries/accounts/use-logged-in-accounts.ts b/packages/nicolium/src/queries/accounts/use-logged-in-accounts.ts index 563027cb3..0ef8c9eba 100644 --- a/packages/nicolium/src/queries/accounts/use-logged-in-accounts.ts +++ b/packages/nicolium/src/queries/accounts/use-logged-in-accounts.ts @@ -1,22 +1,13 @@ import { skipToken, useQueries, useQuery } from '@tanstack/react-query'; -import { createSelector } from 'reselect'; +import { useMemo } from 'react'; -import { useAppSelector } from '@/hooks/use-app-selector'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { queryKeys } from '@/queries/keys'; +import { useAuthStore } from '@/stores/auth'; import { validId } from '@/utils/auth'; -import type { RootState } from '@/store'; import type { Account } from 'pl-api'; -const selectOtherAccountIds = createSelector( - (state: RootState) => state.auth.users, - (state: RootState) => state.me, - (users, me) => - Object.values(users) - .map((authUser) => authUser?.id) - .filter((id): id is string => validId(id) && id !== me), -); - const useLoggedInAccount = (accountId: string) => { const query = useQuery({ queryKey: queryKeys.accounts.show(accountId), @@ -26,7 +17,18 @@ const useLoggedInAccount = (accountId: string) => { return query; }; -const useLoggedInAccountIds = () => useAppSelector(selectOtherAccountIds); +const useLoggedInAccountIds = () => { + const users = useAuthStore((state) => state.users); + const currentAccountId = useCurrentAccount(); + + return useMemo( + () => + Object.values(users) + .map((authUser) => authUser?.id) + .filter((id): id is string => validId(id) && id !== currentAccountId), + [users, currentAccountId], + ); +}; /** doesn't fetch because it's a hack that should not exist like this */ const useLoggedInAccounts = () => { diff --git a/packages/nicolium/src/queries/accounts/use-relationship.ts b/packages/nicolium/src/queries/accounts/use-relationship.ts index dbca83b6f..79652647f 100644 --- a/packages/nicolium/src/queries/accounts/use-relationship.ts +++ b/packages/nicolium/src/queries/accounts/use-relationship.ts @@ -7,7 +7,7 @@ import { useLoggedIn } from '@/hooks/use-logged-in'; import { useOwnAccount } from '@/hooks/use-own-account'; import { queryKeys } from '@/queries/keys'; import { useContextsActions } from '@/stores/contexts'; -import { useTimelinesStore } from '@/stores/timelines'; +import { useTimelinesActions } from '@/stores/timelines'; import type { BlockAccountParams, @@ -145,6 +145,7 @@ const useBlockAccountMutation = (accountId: string) => { const client = useClient(); const queryClient = useQueryClient(); const { filterContexts } = useContextsActions(); + const { filterTimelines } = useTimelinesActions(); return useMutation({ mutationKey: queryKeys.accountRelationships.show(accountId), @@ -179,7 +180,7 @@ const useBlockAccountMutation = (accountId: string) => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers filterContexts(data); - useTimelinesStore.getState().actions.filterTimelines(data.id); + filterTimelines(data.id); }, }); }; @@ -212,6 +213,7 @@ const useMuteAccountMutation = (accountId: string) => { const client = useClient(); const queryClient = useQueryClient(); const { filterContexts } = useContextsActions(); + const { filterTimelines } = useTimelinesActions(); return useMutation({ mutationKey: queryKeys.accountRelationships.show(accountId), @@ -242,7 +244,7 @@ const useMuteAccountMutation = (accountId: string) => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers filterContexts(data); - useTimelinesStore.getState().actions.filterTimelines(data.id); + filterTimelines(data.id); }, }); }; diff --git a/packages/nicolium/src/queries/admin/use-accounts.ts b/packages/nicolium/src/queries/admin/use-accounts.ts index f5ce21381..c49909bae 100644 --- a/packages/nicolium/src/queries/admin/use-accounts.ts +++ b/packages/nicolium/src/queries/admin/use-accounts.ts @@ -9,6 +9,7 @@ import { import { useClient } from '@/hooks/use-client'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useAccount } from '@/queries/accounts/use-account'; +import { getTagDiff } from '@/utils/badges'; import { queryKeys } from '../keys'; import { filterById } from '../utils/filter-id'; @@ -218,6 +219,53 @@ const useAdminUnsensitiveAccountMutation = (accountId: string) => { }); }; +const useAdminTagUserMutation = (accountId: string) => { + const client = useClient(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ['admin', 'acounts', accountId, 'tag'], + mutationFn: (tags: Array) => client.admin.accounts.tagUser(accountId, tags), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.admin.accounts.show(accountId) }); + queryClient.invalidateQueries({ queryKey: queryKeys.accounts.show(accountId) }); + }, + }); +}; + +const useAdminUntagUserMutation = (accountId: string) => { + const client = useClient(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ['admin', 'acounts', accountId, 'untag'], + mutationFn: (tags: Array) => client.admin.accounts.untagUser(accountId, tags), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.accounts.show(accountId) }); + }, + }); +}; + +const useAdminUpdateTagsMutation = (accountId: string) => { + const client = useClient(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ['admin', 'acounts', accountId, 'updateTags'], + mutationFn: ({ oldTags, newTags }: { oldTags: Array; newTags: Array }) => { + const { added, removed } = getTagDiff(oldTags, newTags); + + return Promise.all([ + added.length ? client.admin.accounts.tagUser(accountId, added) : null, + removed.length ? client.admin.accounts.untagUser(accountId, removed) : null, + ]); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.accounts.show(accountId) }); + }, + }); +}; + export { useAdminAccount, useAdminAccounts, @@ -230,4 +278,7 @@ export { useAdminUnsilenceAccountMutation, useAdminUnsuspendAccountMutation, useAdminUnsensitiveAccountMutation, + useAdminTagUserMutation, + useAdminUntagUserMutation, + useAdminUpdateTagsMutation, }; diff --git a/packages/nicolium/src/queries/admin/use-config.ts b/packages/nicolium/src/queries/admin/use-config.ts index bceb8b04b..6a8476e4c 100644 --- a/packages/nicolium/src/queries/admin/use-config.ts +++ b/packages/nicolium/src/queries/admin/use-config.ts @@ -1,10 +1,10 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { ADMIN_CONFIG_UPDATE_SUCCESS, type AdminActions } from '@/actions/admin'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; +import { useFrontendConfigActions } from '@/stores/frontend-config'; +import { useInstanceActions } from '@/stores/instance'; import { queryKeys } from '../keys'; @@ -22,19 +22,17 @@ const useAdminConfig = () => { const useUpdateAdminConfig = () => { const client = useClient(); - const dispatch = useAppDispatch(); const queryClient = useQueryClient(); + const instanceActions = useInstanceActions(); + const frontendConfigActions = useFrontendConfigActions(); return useMutation({ mutationFn: (params: Parameters[0]) => client.admin.config.updatePleromaConfig(params), retry: false, onSuccess: (data) => { - dispatch({ - type: ADMIN_CONFIG_UPDATE_SUCCESS, - configs: data.configs, - needsReboot: data.need_reboot, - }); + instanceActions.importAdminConfigs(data.configs); + frontendConfigActions.importAdminConfigs(data.configs); queryClient.setQueryData(queryKeys.admin.config, data); }, }); diff --git a/packages/nicolium/src/queries/admin/use-reports.ts b/packages/nicolium/src/queries/admin/use-reports.ts index d8fd05b10..d061374fa 100644 --- a/packages/nicolium/src/queries/admin/use-reports.ts +++ b/packages/nicolium/src/queries/admin/use-reports.ts @@ -2,8 +2,8 @@ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tansta import { useMemo } from 'react'; import { useClient } from '@/hooks/use-client'; -import { useInstance } from '@/hooks/use-instance'; import { useOwnAccount } from '@/hooks/use-own-account'; +import { useInstanceStore } from '@/stores/instance'; import { useAccount } from '../accounts/use-account'; import { queryKeys } from '../keys'; @@ -61,13 +61,13 @@ const pendingReportsQuery = makePaginatedResponseQueryOptions( const usePendingReportsCount = () => { const { data: account } = useOwnAccount(); - const instance = useInstance(); + const fetched = useInstanceStore((state) => state.fetched); return useInfiniteQuery({ ...pendingReportsQuery, select: (data) => (data.pages.at(-1)?.total ?? data.pages.flatMap((page) => page.items).length) || 0, - enabled: !!instance.fetched && !!(account?.is_admin ?? account?.is_moderator), + enabled: fetched && !!(account?.is_admin ?? account?.is_moderator), }); }; diff --git a/packages/nicolium/src/queries/instance/use-translation-languages.ts b/packages/nicolium/src/queries/instance/use-translation-languages.ts index b448ae637..31a0fa4d6 100644 --- a/packages/nicolium/src/queries/instance/use-translation-languages.ts +++ b/packages/nicolium/src/queries/instance/use-translation-languages.ts @@ -2,8 +2,8 @@ import { useQuery } from '@tanstack/react-query'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { useLoggedIn } from '@/hooks/use-logged-in'; +import { useInstance } from '@/stores/instance'; import { queryKeys } from '../keys'; diff --git a/packages/nicolium/src/queries/security/oauth-tokens.ts b/packages/nicolium/src/queries/security/oauth-tokens.ts index 925f3012c..0b565d3bf 100644 --- a/packages/nicolium/src/queries/security/oauth-tokens.ts +++ b/packages/nicolium/src/queries/security/oauth-tokens.ts @@ -1,19 +1,21 @@ -import { getClient } from '@/api'; +import { useMutation } from '@tanstack/react-query'; + +import { useClient } from '@/hooks/use-client'; import { removePageItem } from '@/utils/queries'; import { queryKeys } from '../keys'; -import { makePaginatedResponseQueryOptions } from '../utils/make-paginated-response-query-options'; -import { mutationOptions } from '../utils/mutation-options'; +import { makePaginatedResponseQuery } from '../utils/make-paginated-response-query'; -const oauthTokensQueryOptions = makePaginatedResponseQueryOptions( - queryKeys.security.oauthTokens, - (client) => client.settings.getOauthTokens(), -)(); +const useOauthTokensQuery = makePaginatedResponseQuery(queryKeys.security.oauthTokens, (client) => + client.settings.getOauthTokens(), +); -const revokeOauthTokenMutationOptions = (oauthTokenId: string) => - mutationOptions({ +const useRevokeOauthTokenMutation = (oauthTokenId: string) => { + const client = useClient(); + + return useMutation({ mutationKey: ['security', 'oauthTokens', oauthTokenId], - mutationFn: () => getClient().settings.deleteOauthToken(oauthTokenId), + mutationFn: () => client.settings.deleteOauthToken(oauthTokenId), onSettled: () => { removePageItem( queryKeys.security.oauthTokens, @@ -23,5 +25,6 @@ const revokeOauthTokenMutationOptions = (oauthTokenId: string) => ); }, }); +}; -export { oauthTokensQueryOptions, revokeOauthTokenMutationOptions }; +export { useOauthTokensQuery, useRevokeOauthTokenMutation }; diff --git a/packages/nicolium/src/queries/settings/domain-blocks.ts b/packages/nicolium/src/queries/settings/domain-blocks.ts index f6722c115..9d270aed7 100644 --- a/packages/nicolium/src/queries/settings/domain-blocks.ts +++ b/packages/nicolium/src/queries/settings/domain-blocks.ts @@ -1,47 +1,53 @@ -import { getClient } from '@/api'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { useClient } from '@/hooks/use-client'; import { queryClient } from '../client'; import { queryKeys } from '../keys'; -import { makePaginatedResponseQueryOptions } from '../utils/make-paginated-response-query-options'; -import { mutationOptions } from '../utils/mutation-options'; +import { makePaginatedResponseQuery } from '../utils/make-paginated-response-query'; -import type { RootState, Store } from '@/store'; import type { Account } from 'pl-api'; -let store: Store; -import('@/store').then((value) => (store = value.store)).catch(() => {}); +const useDomainBlocksQuery = makePaginatedResponseQuery(queryKeys.settings.domainBlocks, (client) => + client.filtering.getDomainBlocks(), +); -const domainBlocksQueryOptions = makePaginatedResponseQueryOptions( - queryKeys.settings.domainBlocks, - (client) => client.filtering.getDomainBlocks(), -)(); +const useBlockDomainMutation = () => { + const queryClient = useQueryClient(); + const client = useClient(); -const blockDomainMutationOptions = mutationOptions({ - mutationKey: queryKeys.settings.domainBlocks, - mutationFn: (domain: string) => getClient().filtering.blockDomain(domain), - onSettled: (_, __, domain) => { - queryClient.invalidateQueries(domainBlocksQueryOptions); + return useMutation({ + mutationKey: queryKeys.settings.domainBlocks, + mutationFn: (domain: string) => client.filtering.blockDomain(domain), + onSettled: (_, __, domain) => { + queryClient.invalidateQueries({ queryKey: queryKeys.settings.domainBlocks }); - const accounts = selectAccountsByDomain(store.getState(), domain); - if (!accounts) return; + const accounts = selectAccountsByDomain(domain); + if (!accounts) return; - queryClient.setQueryData(queryKeys.suggestions.all, (suggestions) => - suggestions - ? suggestions.filter((suggestion) => !accounts.includes(suggestion.account_id)) - : undefined, - ); - }, -}); + queryClient.setQueryData(queryKeys.suggestions.all, (suggestions) => + suggestions + ? suggestions.filter((suggestion) => !accounts.includes(suggestion.account_id)) + : undefined, + ); + }, + }); +}; -const unblockDomainMutationOptions = mutationOptions({ - mutationKey: queryKeys.settings.domainBlocks, - mutationFn: (domain: string) => getClient().filtering.unblockDomain(domain), - onSettled: () => { - queryClient.invalidateQueries(domainBlocksQueryOptions); - }, -}); +const useUnblockDomainMutation = () => { + const queryClient = useQueryClient(); + const client = useClient(); -const selectAccountsByDomain = (state: RootState, domain: string): string[] => { + return useMutation({ + mutationKey: queryKeys.settings.domainBlocks, + mutationFn: (domain: string) => client.filtering.unblockDomain(domain), + onSettled: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.settings.domainBlocks }); + }, + }); +}; + +const selectAccountsByDomain = (domain: string): string[] => { const accounts = queryClient .getQueriesData({ queryKey: queryKeys.accounts.root }) .map(([, account]) => account) @@ -51,4 +57,4 @@ const selectAccountsByDomain = (state: RootState, domain: string): string[] => { return accounts ?? []; }; -export { domainBlocksQueryOptions, blockDomainMutationOptions, unblockDomainMutationOptions }; +export { useDomainBlocksQuery, useBlockDomainMutation, useUnblockDomainMutation }; diff --git a/packages/nicolium/src/queries/statuses/scheduled-statuses.ts b/packages/nicolium/src/queries/statuses/scheduled-statuses.ts index dc961b42b..51f101ccc 100644 --- a/packages/nicolium/src/queries/statuses/scheduled-statuses.ts +++ b/packages/nicolium/src/queries/statuses/scheduled-statuses.ts @@ -1,11 +1,10 @@ -import { infiniteQueryOptions } from '@tanstack/react-query'; +import { infiniteQueryOptions, useMutation } from '@tanstack/react-query'; -import { getClient } from '@/api'; +import { useClient } from '@/hooks/use-client'; import { removePageItem } from '@/utils/queries'; import { queryKeys } from '../keys'; import { makePaginatedResponseQueryOptions } from '../utils/make-paginated-response-query-options'; -import { mutationOptions } from '../utils/mutation-options'; const scheduledStatusesQueryOptions = makePaginatedResponseQueryOptions( queryKeys.scheduledStatuses.all, @@ -17,10 +16,12 @@ const scheduledStatusesCountQueryOptions = infiniteQueryOptions({ select: (data) => data.pages.flatMap((page) => page.items).length, }); -const cancelScheduledStatusMutationOptions = (scheduledStatusId: string) => - mutationOptions({ +const useCancelScheduledStatusMutation = (scheduledStatusId: string) => { + const client = useClient(); + + return useMutation({ mutationKey: ['scheduledStatuses', scheduledStatusId], - mutationFn: () => getClient().scheduledStatuses.cancelScheduledStatus(scheduledStatusId), + mutationFn: () => client.scheduledStatuses.cancelScheduledStatus(scheduledStatusId), onSettled: () => { removePageItem( queryKeys.scheduledStatuses.all, @@ -30,9 +31,10 @@ const cancelScheduledStatusMutationOptions = (scheduledStatusId: string) => ); }, }); +}; export { scheduledStatusesQueryOptions, scheduledStatusesCountQueryOptions, - cancelScheduledStatusMutationOptions, + useCancelScheduledStatusMutation, }; diff --git a/packages/nicolium/src/queries/statuses/use-draft-statuses.ts b/packages/nicolium/src/queries/statuses/use-draft-statuses.ts index 6c906511d..4f6a4a991 100644 --- a/packages/nicolium/src/queries/statuses/use-draft-statuses.ts +++ b/packages/nicolium/src/queries/statuses/use-draft-statuses.ts @@ -11,7 +11,7 @@ import * as v from 'valibot'; import { useOwnAccount } from '@/hooks/use-own-account'; import { filteredArray } from '@/schemas/utils'; import KVStore from '@/storage/kv-store'; -import { useComposeStore } from '@/stores/compose'; +import { useComposeActions } from '@/stores/compose'; import { queryKeys } from '../keys'; @@ -85,9 +85,10 @@ const useDraftStatusesCountQuery = () => const usePersistDraftStatus = () => { const { data: account } = useOwnAccount(); const queryClient = useQueryClient(); + const { getCompose } = useComposeActions(); return (composeId: string) => { - const compose = useComposeStore.getState().actions.getCompose(composeId); + const compose = getCompose(composeId); const draft = { ...compose, diff --git a/packages/nicolium/src/queries/statuses/use-status.ts b/packages/nicolium/src/queries/statuses/use-status.ts index 3a0008322..d9042737b 100644 --- a/packages/nicolium/src/queries/statuses/use-status.ts +++ b/packages/nicolium/src/queries/statuses/use-status.ts @@ -31,7 +31,6 @@ type MinifiedContext = ReturnType; type SelectedStatus = NormalizedStatus & { account: Account; - accounts?: Array; reblog: SelectedStatus | null; quote: SelectedStatus | null; }; @@ -66,7 +65,6 @@ const useStatusQuery = (statusId?: string) => { data: { ...statusQuery.data, account: account.data!, - accounts: [account.data!], }, }; }, [statusQuery.data, account.data]) as unknown as UseQueryResult; diff --git a/packages/nicolium/src/queries/timelines/use-events-lists.ts b/packages/nicolium/src/queries/timelines/use-events-lists.ts index e431ef8df..2a8c8e392 100644 --- a/packages/nicolium/src/queries/timelines/use-events-lists.ts +++ b/packages/nicolium/src/queries/timelines/use-events-lists.ts @@ -1,10 +1,8 @@ -import { useInfiniteQuery } from '@tanstack/react-query'; - import { queryKeys } from '../keys'; -import { makePaginatedResponseQueryOptions } from '../utils/make-paginated-response-query-options'; +import { makePaginatedResponseQuery } from '../utils/make-paginated-response-query'; import { minifyStatusList } from '../utils/minify-list'; -const recentEventsQueryOptions = makePaginatedResponseQueryOptions( +const useRecentEventsTimeline = makePaginatedResponseQuery( queryKeys.statusLists.recentEvents, (client) => client.timelines @@ -16,23 +14,17 @@ const recentEventsQueryOptions = makePaginatedResponseQueryOptions( return res; }) .then(minifyStatusList), -)(); + undefined, + 'isLoggedIn', + { staleTime: 5 * 60 * 1000 }, // 5 minutes +); -const useRecentEventsTimeline = () => - useInfiniteQuery({ - ...recentEventsQueryOptions, - staleTime: 5 * 60 * 1000, // 5 minutes - }); - -const joinedEventsQueryOptions = makePaginatedResponseQueryOptions( +const useJoinedEventsTimeline = makePaginatedResponseQuery( queryKeys.statusLists.joinedEvents, (client) => client.events.getJoinedEvents().then(minifyStatusList), -)(); - -const useJoinedEventsTimeline = () => - useInfiniteQuery({ - ...joinedEventsQueryOptions, - staleTime: 5 * 60 * 1000, // 5 minutes - }); + undefined, + 'isLoggedIn', + { staleTime: 5 * 60 * 1000 }, // 5 minutes +); export { useRecentEventsTimeline, useJoinedEventsTimeline }; diff --git a/packages/nicolium/src/queries/utils/make-paginated-response-query.ts b/packages/nicolium/src/queries/utils/make-paginated-response-query.ts index 1ccf1352a..519b43ba9 100644 --- a/packages/nicolium/src/queries/utils/make-paginated-response-query.ts +++ b/packages/nicolium/src/queries/utils/make-paginated-response-query.ts @@ -1,8 +1,9 @@ import { + useInfiniteQuery, type DataTag, type InfiniteData, type QueryKey, - useInfiniteQuery, + type UseInfiniteQueryOptions, } from '@tanstack/react-query'; import { useClient } from '@/hooks/use-client'; @@ -59,15 +60,21 @@ const makePaginatedResponseQuery = queryFn: (client: PlApiClient, params: T1) => Promise>, select?: (data: InfiniteData>) => T3, enabled?: ((...params: T1) => boolean) | 'isLoggedIn' | 'isAdmin', + options?: Omit< + UseInfiniteQueryOptions>, + 'queryKey' | 'queryFn' | 'initialPageParam' | 'getNextPageParam' | 'select' | 'enabled' + >, ) => (...params: T1) => { const client = useClient(); const { data: account } = useOwnAccount(); + type PageParam = { next: (() => Promise>) | null }; + return useInfiniteQuery({ queryKey: typeof queryKey === 'object' ? queryKey : queryKey(...params), - queryFn: ({ pageParam }) => pageParam.next?.() ?? queryFn(client, params), - initialPageParam: { next: null as (() => Promise>) | null }, + queryFn: ({ pageParam }) => (pageParam as PageParam).next?.() ?? queryFn(client, params), + initialPageParam: { next: null } as PageParam, getNextPageParam: (page) => (page.next ? page : undefined), select: select ?? @@ -94,6 +101,7 @@ const makePaginatedResponseQuery = : enabled === 'isAdmin' ? !!(account?.is_admin ?? account?.is_moderator) : (enabled?.(...params) ?? true), + ...options, }); }; diff --git a/packages/nicolium/src/reducers/auth.ts b/packages/nicolium/src/reducers/auth.ts deleted file mode 100644 index 9993da90f..000000000 --- a/packages/nicolium/src/reducers/auth.ts +++ /dev/null @@ -1,467 +0,0 @@ -import trim from 'lodash/trim'; -import { create, type Draft } from 'mutative'; -import { - type Account as AccountEntity, - applicationSchema, - instanceSchema, - PlApiClient, - tokenSchema, - type CredentialAccount, - type CredentialApplication, - type Token, -} from 'pl-api'; -import * as v from 'valibot'; - -import { - AUTH_APP_CREATED, - AUTH_LOGGED_IN, - AUTH_APP_AUTHORIZED, - AUTH_LOGGED_OUT, - SWITCH_ACCOUNT, - VERIFY_CREDENTIALS_SUCCESS, - VERIFY_CREDENTIALS_FAIL, - type AuthAction, -} from '@/actions/auth'; -import { ME_FETCH_SKIP, type MeAction } from '@/actions/me'; -import { - decodeFromMarkup, - MASTODON_PRELOAD_IMPORT, - pleromaDecoder, - type PreloadAction, -} from '@/actions/preload'; -import * as BuildConfig from '@/build-config'; -import { coerceObject } from '@/schemas/utils'; -import KVStore from '@/storage/kv-store'; -import { validId, isURL, parseBaseURL } from '@/utils/auth'; - -import type { NicoliumResponse } from '@/api'; - -const instance = (() => { - try { - const preloadedInstance = decodeFromMarkup('initial-results', pleromaDecoder)[ - '/api/v1/instance' - ]; - const parsedInstance = v.safeParse(instanceSchema, preloadedInstance); - return parsedInstance.success ? parsedInstance.output : undefined; - } catch (e) { - return undefined; - } -})(); - -type Action = AuthAction | MeAction | PreloadAction; - -const backendUrl = isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : ''; - -const mastodonPreloadSchema = coerceObject({ - meta: coerceObject({ - access_token: v.string(), - me: v.string(), - }), - accounts: v.record( - v.string(), - v.object({ - url: v.string(), - }), - ), -}); - -const authUserSchema = v.object({ - access_token: v.string(), - id: v.string(), - url: v.string(), -}); - -const tokenWithAppSchema = v.object({ - ...tokenSchema.entries, - client_id: v.fallback(v.optional(v.string()), undefined), - client_secret: v.fallback(v.optional(v.string()), undefined), -}); - -type TokenWithApp = v.InferOutput; - -interface AuthUser { - access_token: string; - id: string; - url: string; -} - -interface State { - app: CredentialApplication | null; - tokens: Record; - users: Record; - me: string | null; - client: InstanceType; -} - -const buildKey = (parts: string[]) => parts.join(':'); - -// For subdirectory support -const NAMESPACE = trim(BuildConfig.FE_SUBDIRECTORY, '/') - ? `nicolium@${BuildConfig.FE_SUBDIRECTORY}` - : 'nicolium'; - -const STORAGE_KEY = buildKey([NAMESPACE, 'auth']); - -const getLocalState = (): State | undefined => { - const state = JSON.parse(localStorage.getItem(STORAGE_KEY)!); - - if (!state) return undefined; - - return { - app: state.app && v.parse(applicationSchema, state.app), - tokens: Object.fromEntries( - Object.entries(state.tokens).map(([key, value]) => [key, v.parse(tokenWithAppSchema, value)]), - ), - users: Object.fromEntries( - Object.entries(state.users).map(([key, value]) => [key, v.parse(authUserSchema, value)]), - ), - me: state.me, - client: new PlApiClient( - parseBaseURL(state.me) || backendUrl, - state.users[state.me]?.access_token, - { - instance, - }, - ), - }; -}; - -const localState = getLocalState(); - -// Checks if the user has an ID and access token -const validUser = (user?: AuthUser) => { - try { - return !!(user && validId(user.id) && validId(user.access_token)); - } catch (e) { - return false; - } -}; - -// Finds the first valid user in the state -const firstValidUser = (state: State | Draft) => Object.values(state.users).find(validUser); - -// For legacy purposes. IDs get upgraded to URLs further down. -const getUrlOrId = (user?: AuthUser): string | null => { - try { - if (!user) return null; - const { id, url } = user; - return url || id; - } catch { - return null; - } -}; - -// If `me` doesn't match an existing user, attempt to shift it. -const maybeShiftMe = (state: State | Draft) => { - const me = state.me!; - const user = state.users[me]; - - if (validUser(user)) { - return state; - } - - const nextUser = firstValidUser(state); - state.me = getUrlOrId(nextUser); -}; - -// Set the user from the session or localStorage, whichever is valid first -const setSessionUser = (state: State) => { - const user = state.users[state.me!]; - const me = getUrlOrId(validUser(user) ? user : undefined); - - state.me = me; -}; - -const isUpgradingUrlId = (state: State) => { - const me = state.me; - const user = state.users[me!]; - return validId(me) && user && !isURL(me); -}; - -// Checks the state and makes it valid -const sanitizeState = (state: State) => { - // Skip sanitation during ID to URL upgrade - if (isUpgradingUrlId(state)) return state; - - state.users = Object.fromEntries( - Object.entries(state.users).filter(([url, user]) => validUser(user) && user.url === url), - ); - // Remove mismatched tokens - state.tokens = Object.fromEntries( - Object.entries(state.tokens).filter(([id, token]) => validId(id) && token.access_token === id), - ); -}; - -const persistAuth = (state: State) => { - const { client, ...data } = state; - localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); -}; - -const persistState = (state: State) => { - persistAuth(state); -}; - -const initialize = (state: State) => { - maybeShiftMe(state); - setSessionUser(state); - sanitizeState(state); - persistState(state); - - return state; -}; - -const initialState: State = initialize({ - app: null, - tokens: {}, - users: {}, - me: null, - client: new PlApiClient(backendUrl, undefined, { instance }), - ...localState, -}); - -const importToken = (state: State | Draft, token: Token, app?: CredentialApplication) => { - state.tokens[token.access_token] = { - client_id: app?.client_id, - client_secret: app?.client_secret, - ...token, - }; -}; - -// Users are now stored by their ActivityPub ID instead of their -// primary key to support auth against multiple hosts. -const upgradeNonUrlId = (state: State | Draft, account: CredentialAccount) => { - const me = state.me; - if (isURL(me)) return state; - - state.me = state.me === account.id ? account.url : state.me; - delete state.users[account.id]; -}; - -// Returns a predicate function for filtering a mismatched user/token -const userMismatch = - (token: string, account: CredentialAccount) => (user: AuthUser, url: string) => { - const sameToken = user.access_token === token; - const differentUrl = url !== account.url || user.url !== account.url; - const differentId = user.id !== account.id; - - return sameToken && (differentUrl || differentId); - }; - -const importCredentials = ( - state: State | Draft, - token: string, - account: CredentialAccount, -) => { - state.users[account.url] = v.parse(authUserSchema, { - id: account.id, - access_token: token, - url: account.url, - }); - // state.tokens[token].account = account.id; - state.tokens[token].me = account.url; - state.users = Object.fromEntries( - Object.entries(state.users).filter(([url, user]) => !userMismatch(token, account)(user, url)), - ); - state.me = state.me ?? account.url; - upgradeNonUrlId(state, account); -}; - -const deleteToken = (state: State | Draft, token: string) => { - delete state.tokens[token]; - state.users = Object.fromEntries( - Object.entries(state.users).filter(([_, user]) => user.access_token !== token), - ); - maybeShiftMe(state); -}; - -const deleteUser = (state: State | Draft, account: Pick) => { - const accountUrl = account.url; - - delete state.users[accountUrl]; - state.tokens = Object.fromEntries( - Object.entries(state.tokens).filter(([_, token]) => token.me !== accountUrl), - ); - maybeShiftMe(state); -}; - -const importMastodonPreload = (state: State | Draft, data: Record) => { - const parsedData = v.parse(mastodonPreloadSchema, data); - const accountId = parsedData.meta.me; - const accountUrl = parsedData.accounts[accountId]?.url; - const accessToken = parsedData.meta.access_token; - - if (validId(accessToken) && validId(accountId) && isURL(accountUrl)) { - state.tokens[accessToken] = v.parse(tokenSchema, { - access_token: accessToken, - account: accountId, - me: accountUrl, - scope: 'read write follow push', - token_type: 'Bearer', - }); - - state.users[accountUrl] = v.parse(authUserSchema, { - id: accountId, - access_token: accessToken, - url: accountUrl, - }); - } - - maybeShiftMe(state); -}; - -const persistAuthAccount = (account: CredentialAccount) => { - const persistedAccount = { ...account }; - const key = `authAccount:${account.url}`; - - KVStore.getItem(key) - .then((oldAccount: any) => { - const settings = oldAccount?.settings_store ?? {}; - persistedAccount.settings_store ??= settings; - KVStore.setItem(key, persistedAccount); - }) - .catch(console.error); - - return persistedAccount; -}; - -const deleteForbiddenToken = ( - state: State | Draft, - error: { response: NicoliumResponse }, - token: string, -) => { - if (error.response && [401, 403].includes(error.response.status)) { - deleteToken(state, token); - return; - } - - return state; -}; - -const updateState = ( - state: State, - updater: (state: Draft) => void, - clientUpdater?: (state: State) => InstanceType, -) => { - const oldClient = state.client; - - const newState = create(state, updater); - const newClient = clientUpdater?.(state) ?? oldClient; - return { ...newState, client: newClient }; -}; - -const reducer = (state: State, action: Action): State => { - switch (action.type) { - case AUTH_APP_CREATED: - return updateState(state, (draft) => { - draft.app = action.app; - }); - case AUTH_APP_AUTHORIZED: - return updateState(state, (draft) => { - if (draft.app) draft.app = { ...draft.app, ...action.token }; - }); - case AUTH_LOGGED_IN: - return updateState(state, (draft) => { - importToken(draft, action.token, action.app); - }); - case AUTH_LOGGED_OUT: - return updateState(state, (draft) => { - deleteUser(draft, action.account); - }); - case VERIFY_CREDENTIALS_SUCCESS: - return updateState( - state, - (draft) => { - importCredentials(draft, action.token, persistAuthAccount(action.account)); - }, - () => { - if (!state.me) { - if (state.client.baseURL === parseBaseURL(action.account.url)) { - state.client.accessToken = action.token; - return state.client; - } - - return new PlApiClient(parseBaseURL(action.account.url) || backendUrl, action.token); - } - return state.client; - }, - ); - case VERIFY_CREDENTIALS_FAIL: - return updateState(state, (draft) => { - deleteForbiddenToken(draft, action.error as any, action.token); - }); - case SWITCH_ACCOUNT: - return updateState( - state, - (draft) => { - draft.me = action.account.url; - }, - () => { - const accessToken = state.users[action.account.url]?.access_token; - - if (state.client.baseURL === parseBaseURL(action.account.url)) { - state.client.accessToken = accessToken; - return state.client; - } - - return new PlApiClient(parseBaseURL(action.account.url) || backendUrl, accessToken); - }, - ); - case ME_FETCH_SKIP: - return updateState(state, (draft) => { - draft.me = null; - }); - case MASTODON_PRELOAD_IMPORT: - return updateState(state, (draft) => { - importMastodonPreload(draft, action.data); - }); - default: - return state; - } -}; - -const reload = () => { - location.replace('/'); -}; - -// `me` is a user ID string -const validMe = (state: State) => { - const me = state.me; - return typeof me === 'string'; -}; - -// `me` has changed from one valid ID to another -const userSwitched = (oldState: State, state: State) => { - const me = state.me; - const oldMe = oldState.me; - - const stillValid = validMe(oldState) && validMe(state); - const didChange = oldMe !== me; - const userUpgradedUrl = state.users[me!]?.id === oldMe; - - return stillValid && didChange && !userUpgradedUrl; -}; - -const maybeReload = (oldState: State, state: State, action: Action) => { - const loggedOutStandalone = action.type === AUTH_LOGGED_OUT && action.standalone; - const switched = userSwitched(oldState, state); - - if (switched || loggedOutStandalone) { - reload(); - } -}; - -const auth = (oldState: State = initialState, action: Action): State => { - const state = reducer(oldState, action); - - if (state !== oldState) { - // Persist the state in localStorage - persistAuth(state); - - // Reload the page under some conditions - maybeReload(oldState, state, action); - } - - return state; -}; - -export { auth as default }; diff --git a/packages/nicolium/src/reducers/frontend-config.ts b/packages/nicolium/src/reducers/frontend-config.ts deleted file mode 100644 index ea8ce9f91..000000000 --- a/packages/nicolium/src/reducers/frontend-config.ts +++ /dev/null @@ -1,86 +0,0 @@ -import * as v from 'valibot'; - -import { ADMIN_CONFIG_UPDATE_SUCCESS, type AdminActions } from '@/actions/admin'; -import { - FRONTEND_CONFIG_REMEMBER_SUCCESS, - FRONTEND_CONFIG_REQUEST_SUCCESS, - FRONTEND_CONFIG_REQUEST_FAIL, - type FrontendConfigAction, -} from '@/actions/frontend-config'; -import { PLEROMA_PRELOAD_IMPORT, type PreloadAction } from '@/actions/preload'; -import { type PartialFrontendConfig, partialFrontendConfigSchema } from '@/schemas/frontend-config'; -import KVStore from '@/storage/kv-store'; -import ConfigDB from '@/utils/config-db'; - -import type { PleromaConfig } from 'pl-api'; - -const initialState: PartialFrontendConfig = {}; - -const fallbackState: PartialFrontendConfig = { - brandColor: '#d80482', -}; - -const updateFromAdmin = (state: Record, configs: PleromaConfig['configs']) => { - try { - return ConfigDB.find(configs, ':pleroma', ':frontend_configurations')!.value.find( - (value: Record) => value.tuple?.[0] === ':nicolium', - ).tuple?.[1]; - } catch { - return state; - } -}; - -const preloadImport = (state: Record, action: Record) => { - const path = '/api/pleroma/frontend_configurations'; - const feData = action.data[path]; - - if (feData) { - const nicoliumConfig = feData.nicolium || feData.pl_fe; - return nicoliumConfig ? { ...fallbackState, ...nicoliumConfig } : fallbackState; - } else { - return state; - } -}; - -const persistFrontendConfig = (frontendConfig: PartialFrontendConfig, host: string) => { - if (host) { - KVStore.setItem(`frontendConfig:${host}`, frontendConfig).catch(console.error); - } -}; - -const importFrontendConfig = (frontendConfig: unknown, host: string) => { - const parsedFrontendConfig = v.parse(partialFrontendConfigSchema, frontendConfig); - persistFrontendConfig(parsedFrontendConfig, host); - return parsedFrontendConfig; -}; - -const parseFrontendConfig = (frontendConfig: unknown) => { - try { - return v.parse(partialFrontendConfigSchema, frontendConfig); - } catch (e) { - console.error('Failed to parse frontend config', e); - return null; - } -}; - -const frontendConfig = ( - state = initialState, - action: PreloadAction | FrontendConfigAction | AdminActions, -): PartialFrontendConfig => { - switch (action.type) { - case PLEROMA_PRELOAD_IMPORT: - return parseFrontendConfig(preloadImport(state, action)) || state; - case FRONTEND_CONFIG_REMEMBER_SUCCESS: - return parseFrontendConfig(action.frontendConfig) || state; - case FRONTEND_CONFIG_REQUEST_SUCCESS: - return importFrontendConfig(action.frontendConfig ?? {}, action.host || '') || state; - case FRONTEND_CONFIG_REQUEST_FAIL: - return { ...fallbackState, ...state }; - case ADMIN_CONFIG_UPDATE_SUCCESS: - return parseFrontendConfig(updateFromAdmin(state, action.configs)) || state; - default: - return state; - } -}; - -export { frontendConfig as default }; diff --git a/packages/nicolium/src/reducers/index.ts b/packages/nicolium/src/reducers/index.ts deleted file mode 100644 index 80b47a9c0..000000000 --- a/packages/nicolium/src/reducers/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { combineReducers } from '@reduxjs/toolkit'; - -import { AUTH_LOGGED_OUT } from '@/actions/auth'; -import * as BuildConfig from '@/build-config'; - -import auth from './auth'; -import frontendConfig from './frontend-config'; -import instance from './instance'; -import me from './me'; -import meta from './meta'; -import pushNotifications from './push-notifications'; - -const reducers = { - auth, - frontendConfig, - instance, - me, - meta, - pushNotifications, -}; - -const appReducer = combineReducers(reducers); - -type AppState = ReturnType; - -// Clear the state (mostly) when the user logs out -const logOut = (state: AppState): ReturnType => { - if (BuildConfig.NODE_ENV === 'production') { - location.href = '/login'; - } - - const newState = rootReducer(undefined, { type: '' } as any); - - const { instance, frontendConfig, auth } = state; - return { ...newState, instance, frontendConfig, auth }; -}; - -const rootReducer: typeof appReducer = (state, action) => { - switch (action.type) { - case AUTH_LOGGED_OUT: - return appReducer(logOut(state as AppState), action); - default: - return appReducer(state, action); - } -}; - -export default appReducer; diff --git a/packages/nicolium/src/reducers/instance.ts b/packages/nicolium/src/reducers/instance.ts deleted file mode 100644 index df7d92543..000000000 --- a/packages/nicolium/src/reducers/instance.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { create } from 'mutative'; -import { type Instance, instanceSchema, type PleromaConfig } from 'pl-api'; -import * as v from 'valibot'; - -import { ADMIN_CONFIG_UPDATE_SUCCESS, type AdminActions } from '@/actions/admin'; -import { - INSTANCE_FETCH_FAIL, - INSTANCE_FETCH_SUCCESS, - type InstanceAction, -} from '@/actions/instance'; -import { PLEROMA_PRELOAD_IMPORT, type PreloadAction } from '@/actions/preload'; -import KVStore from '@/storage/kv-store'; -import ConfigDB from '@/utils/config-db'; - -const initialState: State = { fetched: false, ...v.parse(instanceSchema, {}) }; - -type State = Instance & { fetched: boolean }; - -const preloadImport = (state: State, action: PreloadAction, path: string): State => { - const instance = action.data[path]; - const parsedInstance = v.safeParse(instanceSchema, instance); - - if (parsedInstance.success) { - return { fetched: true, ...parsedInstance.output }; - } - return state; -}; - -const getConfigValue = (instanceConfig: Array, key: string) => { - const v = instanceConfig.find((value) => value?.tuple?.[0] === key); - - return v ? v?.tuple?.[1] : undefined; -}; - -const importConfigs = (state: State, configs: PleromaConfig['configs']) => { - // FIXME: This is pretty hacked together. Need to make a cleaner map. - const config = ConfigDB.find(configs, ':pleroma', ':instance'); - - if (!config) return state; - - if (config) { - const value = config.value ?? []; - const registrationsOpen = getConfigValue(value, ':registrations_open') as boolean | undefined; - const approvalRequired = getConfigValue(value, ':account_approval_required') as - | boolean - | undefined; - - state.registrations = { - ...state.registrations, - enabled: registrationsOpen ?? state.registrations.enabled, - approval_required: approvalRequired ?? state.registrations.approval_required, - }; - } -}; - -const handleAuthFetch = (state: State) => { - // Authenticated fetch is enabled, so make the instance appear censored - return { - ...state, - title: state.title || '██████', - description: state.description || '████████████', - }; -}; - -const getHost = (instance: { domain: string }) => { - const domain = instance.domain; - try { - return new URL(domain).host; - } catch { - try { - return new URL(`https://${domain}`).host; - } catch { - return null; - } - } -}; - -const persistInstance = (instance: { domain: string }, host: string | null = getHost(instance)) => { - if (host) { - KVStore.setItem(`instance:${host}`, instance).catch(console.error); - } -}; - -const handleInstanceFetchFail = (state: State, error: any) => { - if (error.response?.status === 401) { - return handleAuthFetch(state); - } else { - return state; - } -}; - -const instance = ( - state = initialState, - action: AdminActions | InstanceAction | PreloadAction, -): State => { - switch (action.type) { - case PLEROMA_PRELOAD_IMPORT: - return preloadImport(state, action, '/api/v1/instance'); - case INSTANCE_FETCH_SUCCESS: - persistInstance(action.instance); - return { fetched: true, ...action.instance }; - case INSTANCE_FETCH_FAIL: - return handleInstanceFetchFail(state, action.error); - case ADMIN_CONFIG_UPDATE_SUCCESS: - return create(state, (draft) => importConfigs(draft, action.configs)); - default: - return state; - } -}; -export { instance as default, initialState as instanceInitialState }; diff --git a/packages/nicolium/src/reducers/me.ts b/packages/nicolium/src/reducers/me.ts deleted file mode 100644 index b30697dc6..000000000 --- a/packages/nicolium/src/reducers/me.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - AUTH_LOGGED_OUT, - AUTH_ACCOUNT_REMEMBER_SUCCESS, - VERIFY_CREDENTIALS_SUCCESS, - type AuthAction, - SWITCH_ACCOUNT, -} from '@/actions/auth'; -import { - ME_FETCH_SUCCESS, - ME_FETCH_FAIL, - ME_FETCH_SKIP, - ME_PATCH_SUCCESS, - type MeAction, -} from '@/actions/me'; - -import type { NicoliumResponse } from '@/api'; - -type Me = string | null | false; - -const initialState: Me = null; - -const handleForbidden = (state: Me, error: { response: NicoliumResponse }) => { - if (error.response?.status && [401, 403].includes(error.response.status)) { - return false; - } - return state; -}; - -const me = (state: Me = initialState, action: AuthAction | MeAction): Me => { - switch (action.type) { - case ME_FETCH_SUCCESS: - case ME_PATCH_SUCCESS: - return action.me.id; - case VERIFY_CREDENTIALS_SUCCESS: - case AUTH_ACCOUNT_REMEMBER_SUCCESS: - return state ?? action.account.id; - case ME_FETCH_SKIP: - case AUTH_LOGGED_OUT: - return false; - case ME_FETCH_FAIL: - return handleForbidden(state, action.error as any); - case SWITCH_ACCOUNT: - return action.account.id; - default: - return state; - } -}; - -export { me as default, type Me }; diff --git a/packages/nicolium/src/reducers/meta.ts b/packages/nicolium/src/reducers/meta.ts deleted file mode 100644 index a4ff2b389..000000000 --- a/packages/nicolium/src/reducers/meta.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { STANDALONE_CHECK_SUCCESS, type InstanceAction } from '@/actions/instance'; - -const initialState = { - /** Whether /api/v1/instance 404'd (and we should display the external auth form). */ - instance_fetch_failed: false, -}; - -const meta = (state = initialState, action: InstanceAction): typeof initialState => { - switch (action.type) { - case STANDALONE_CHECK_SUCCESS: - return { instance_fetch_failed: !action.ok }; - default: - return state; - } -}; - -export { meta as default }; diff --git a/packages/nicolium/src/reducers/push-notifications.ts b/packages/nicolium/src/reducers/push-notifications.ts deleted file mode 100644 index 507249a42..000000000 --- a/packages/nicolium/src/reducers/push-notifications.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { create } from 'mutative'; - -import { - SET_BROWSER_SUPPORT, - SET_SUBSCRIPTION, - CLEAR_SUBSCRIPTION, -} from '@/actions/push-notifications/setter'; - -import type { SetterAction } from '@/actions/push-notifications/setter'; - -interface Subscription { - id: string; - endpoint: string; -} - -interface State { - subscription: Subscription | null; - alerts: Record; - isSubscribed: boolean; - browserSupport: boolean; -} - -const initialState: State = { - subscription: null, - alerts: { - follow: true, - follow_request: true, - favourite: true, - reblog: true, - mention: true, - poll: true, - status: true, - }, - isSubscribed: false, - browserSupport: false, -}; - -const pushSubscriptions = (state = initialState, action: SetterAction): State => { - switch (action.type) { - case SET_SUBSCRIPTION: - return create(state, (draft) => { - draft.subscription = { - id: action.subscription.id, - endpoint: action.subscription.endpoint, - }; - draft.alerts = action.subscription.alerts; - draft.isSubscribed = true; - }); - case SET_BROWSER_SUPPORT: - return create(state, (draft) => { - draft.browserSupport = action.value; - }); - case CLEAR_SUBSCRIPTION: - return initialState; - default: - return state; - } -}; - -export { pushSubscriptions as default }; diff --git a/packages/nicolium/src/selectors/index.ts b/packages/nicolium/src/selectors/index.ts index c6667da62..b4838e85d 100644 --- a/packages/nicolium/src/selectors/index.ts +++ b/packages/nicolium/src/selectors/index.ts @@ -1,8 +1,8 @@ import { useQueryClient } from '@tanstack/react-query'; -import { useInstance } from '@/hooks/use-instance'; import { useAdminConfig } from '@/queries/admin/use-config'; import { queryKeys } from '@/queries/keys'; +import { useInstance } from '@/stores/instance'; import { getDomain } from '@/utils/accounts'; import ConfigDB from '@/utils/config-db'; diff --git a/packages/nicolium/src/store.ts b/packages/nicolium/src/store.ts deleted file mode 100644 index c652f13fb..000000000 --- a/packages/nicolium/src/store.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { configureStore, Tuple, type AnyAction } from '@reduxjs/toolkit'; -import { thunk, type ThunkDispatch } from 'redux-thunk'; - -import errorsMiddleware from './middleware/errors'; -import soundsMiddleware from './middleware/sounds'; -import appReducer from './reducers'; - -const untypedStore = configureStore({ - reducer: appReducer, - middleware: () => new Tuple(thunk, errorsMiddleware(), soundsMiddleware()), - devTools: true, -}); - -type Store = typeof untypedStore & { - dispatch: AppDispatch; -}; - -const store: Store = untypedStore as Store; - -// Infer the `RootState` and `AppDispatch` types from the store itself -// https://redux.js.org/usage/usage-with-typescript -type RootState = ReturnType; -type AppDispatch = ThunkDispatch; - -export { store, type Store, type RootState, type AppDispatch }; diff --git a/packages/nicolium/src/stores/auth.ts b/packages/nicolium/src/stores/auth.ts new file mode 100644 index 000000000..f65934c0b --- /dev/null +++ b/packages/nicolium/src/stores/auth.ts @@ -0,0 +1,481 @@ +import trim from 'lodash/trim'; +import { + type Account as AccountEntity, + applicationSchema, + instanceSchema, + PlApiClient, + tokenSchema, + type CredentialAccount, + type CredentialApplication, + type Token, +} from 'pl-api'; +import * as v from 'valibot'; +import { create } from 'zustand'; +import { mutative } from 'zustand-mutative'; + +import * as BuildConfig from '@/build-config'; +import { coerceObject } from '@/schemas/utils'; +import KVStore from '@/storage/kv-store'; +import { validId, isURL, parseBaseURL } from '@/utils/auth'; + +import type { NicoliumResponse } from '@/api'; + +const instance = (() => { + try { + const el = document.getElementById('initial-results'); + if (!el?.textContent) return undefined; + const raw = JSON.parse(el.textContent) as Record; + const decoded: Record = {}; + for (const [key, base64string] of Object.entries(raw)) { + const bytes = Uint8Array.from( + atob(base64string) + .split('') + .map((c) => c.charCodeAt(0)), + ); + decoded[key] = JSON.parse(new TextDecoder().decode(bytes)); + } + const preloadedInstance = decoded['/api/v1/instance']; + const parsedInstance = v.safeParse(instanceSchema, preloadedInstance); + return parsedInstance.success ? parsedInstance.output : undefined; + } catch { + return undefined; + } +})(); + +const backendUrl = isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : ''; + +const mastodonPreloadSchema = coerceObject({ + meta: coerceObject({ + access_token: v.string(), + me: v.string(), + }), + accounts: v.record( + v.string(), + v.object({ + url: v.string(), + }), + ), +}); + +const authUserSchema = v.object({ + access_token: v.string(), + id: v.string(), + url: v.string(), +}); + +const tokenWithAppSchema = v.object({ + ...tokenSchema.entries, + client_id: v.fallback(v.optional(v.string()), undefined), + client_secret: v.fallback(v.optional(v.string()), undefined), +}); + +type TokenWithApp = v.InferOutput; + +interface AuthUser { + access_token: string; + id: string; + url: string; +} + +type Me = string | null | false; + +const buildKey = (parts: string[]) => parts.join(':'); + +const NAMESPACE = trim(BuildConfig.FE_SUBDIRECTORY, '/') + ? `nicolium@${BuildConfig.FE_SUBDIRECTORY}` + : 'nicolium'; + +const STORAGE_KEY = buildKey([NAMESPACE, 'auth']); + +interface AuthData { + app: CredentialApplication | null; + tokens: Record; + users: Record; + // current user URL or id + me: string | null; +} + +interface AuthState extends AuthData { + client: InstanceType; + // string = logged in, null = loading, false = not logged in + currentAccountId: Me; +} + +interface AuthActions { + setApp: (app: CredentialApplication) => void; + setAppToken: (token: Token) => void; + importToken: (token: Token, app?: CredentialApplication) => void; + removeToken: (account: AccountEntity, standalone?: boolean) => void; + importCredentials: (token: string, account: CredentialAccount) => void; + importCredentialsSkip: () => void; + importCredentialsFailed: (error: { response: NicoliumResponse }) => void; + removeFailedToken: (token: string, error: { response: NicoliumResponse }) => void; + switchAccount: (account: AccountEntity) => void; + setCurrentAccount: (account: CredentialAccount) => void; + setCurrentAccountIfUnset: (account: CredentialAccount) => void; + importMastodonPreload: (data: Record) => void; +} + +const validUser = (user?: AuthUser) => { + try { + return !!(user && validId(user.id) && validId(user.access_token)); + } catch { + return false; + } +}; + +const firstValidUser = (state: AuthData) => Object.values(state.users).find(validUser); + +const getUrlOrId = (user?: AuthUser): string | null => { + try { + if (!user) return null; + return user.url || user.id; + } catch { + return null; + } +}; + +const maybeShiftMe = (state: AuthData) => { + const user = state.users[state.me!]; + if (validUser(user)) return; + const nextUser = firstValidUser(state); + state.me = getUrlOrId(nextUser); +}; + +const setSessionUser = (state: AuthData) => { + const user = state.users[state.me!]; + state.me = getUrlOrId(validUser(user) ? user : undefined); +}; + +const isUpgradingUrlId = (state: AuthData) => { + const me = state.me; + const user = state.users[me!]; + return validId(me) && user && !isURL(me); +}; + +const sanitizeState = (state: AuthData) => { + if (isUpgradingUrlId(state)) return; + state.users = Object.fromEntries( + Object.entries(state.users).filter(([url, user]) => validUser(user) && user.url === url), + ); + state.tokens = Object.fromEntries( + Object.entries(state.tokens).filter(([id, token]) => validId(id) && token.access_token === id), + ); +}; + +const persistAuth = (state: AuthData) => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); +}; + +const getLocalState = (): (AuthData & { client: InstanceType }) | undefined => { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return undefined; + const state = JSON.parse(raw); + if (!state) return undefined; + return { + app: state.app && v.parse(applicationSchema, state.app), + tokens: Object.fromEntries( + Object.entries(state.tokens).map(([key, value]) => [key, v.parse(tokenWithAppSchema, value)]), + ), + users: Object.fromEntries( + Object.entries(state.users).map(([key, value]) => [key, v.parse(authUserSchema, value)]), + ), + me: state.me, + client: new PlApiClient( + parseBaseURL(state.me) || backendUrl, + state.users[state.me]?.access_token, + { instance }, + ), + }; +}; + +const localState = getLocalState(); + +const initializeAuthData = (data: AuthData): AuthData => { + maybeShiftMe(data); + setSessionUser(data); + sanitizeState(data); + persistAuth(data); + return data; +}; + +const initialAuthData: AuthData = initializeAuthData({ + app: null, + tokens: {}, + users: {}, + me: null, + ...localState, +}); + +const importToken = (state: AuthData, token: Token, app?: CredentialApplication) => { + state.tokens[token.access_token] = { + client_id: app?.client_id, + client_secret: app?.client_secret, + ...token, + }; +}; + +const upgradeNonUrlId = (state: AuthData, account: CredentialAccount) => { + if (isURL(state.me)) return; + state.me = state.me === account.id ? account.url : state.me; + delete state.users[account.id]; +}; + +const userMismatch = + (token: string, account: CredentialAccount) => (user: AuthUser, url: string) => { + const sameToken = user.access_token === token; + const differentUrl = url !== account.url || user.url !== account.url; + const differentId = user.id !== account.id; + return sameToken && (differentUrl || differentId); + }; + +const importCredentials = (state: AuthData, token: string, account: CredentialAccount) => { + state.users[account.url] = v.parse(authUserSchema, { + id: account.id, + access_token: token, + url: account.url, + }); + state.tokens[token].me = account.url; + state.users = Object.fromEntries( + Object.entries(state.users).filter(([url, user]) => !userMismatch(token, account)(user, url)), + ); + state.me = state.me ?? account.url; + upgradeNonUrlId(state, account); +}; + +const deleteToken = (state: AuthData, token: string) => { + delete state.tokens[token]; + state.users = Object.fromEntries( + Object.entries(state.users).filter(([_, user]) => user.access_token !== token), + ); + maybeShiftMe(state); +}; + +const deleteUser = (state: AuthData, account: Pick) => { + delete state.users[account.url]; + state.tokens = Object.fromEntries( + Object.entries(state.tokens).filter(([_, token]) => token.me !== account.url), + ); + maybeShiftMe(state); +}; + +const importMastodonPreloadData = (state: AuthData, data: Record) => { + const parsedData = v.parse(mastodonPreloadSchema, data); + const accountId = parsedData.meta.me; + const accountUrl = parsedData.accounts[accountId]?.url; + const accessToken = parsedData.meta.access_token; + + if (validId(accessToken) && validId(accountId) && isURL(accountUrl)) { + state.tokens[accessToken] = v.parse(tokenSchema, { + access_token: accessToken, + account: accountId, + me: accountUrl, + scope: 'read write follow push', + token_type: 'Bearer', + }); + + state.users[accountUrl] = v.parse(authUserSchema, { + id: accountId, + access_token: accessToken, + url: accountUrl, + }); + } + maybeShiftMe(state); +}; + +const persistAuthAccount = (account: CredentialAccount) => { + const key = `authAccount:${account.url}`; + KVStore.getItem(key) + .then((oldAccount: any) => { + const settings = oldAccount?.settings_store ?? {}; + account.settings_store ??= settings; + KVStore.setItem(key, account); + }) + .catch(console.error); + return account; +}; + +const deleteForbiddenToken = ( + state: AuthData, + error: { response: NicoliumResponse }, + token: string, +) => { + if (error.response && [401, 403].includes(error.response.status)) { + deleteToken(state, token); + } +}; + +const reload = () => { + location.replace('/'); +}; + +const userSwitched = ( + oldMe: string | null, + newMe: string | null, + users: Record, +) => { + const stillValid = typeof oldMe === 'string' && typeof newMe === 'string'; + const didChange = oldMe !== newMe; + const userUpgradedUrl = users[newMe!]?.id === oldMe; + return stillValid && didChange && !userUpgradedUrl; +}; + +const handleForbiddenMe = (currentAccountId: Me, error: { response: NicoliumResponse }): Me => { + if (error.response?.status && [401, 403].includes(error.response.status)) { + return false; + } + return currentAccountId; +}; + +type AuthStore = AuthState & { actions: AuthActions }; + +const useAuthStore = create()( + mutative((set, get) => ({ + ...initialAuthData, + client: localState?.client ?? new PlApiClient(backendUrl, undefined, { instance }), + currentAccountId: null, + + actions: { + setApp: (app) => { + set((state) => { + state.app = app; + }); + persistAuth(get()); + }, + setAppToken: (token) => { + set((state) => { + if (state.app) state.app = { ...state.app, ...token } as CredentialApplication; + }); + persistAuth(get()); + }, + importToken: (token, app) => { + set((state) => { + importToken(state, token, app); + }); + persistAuth(get()); + }, + removeToken: (account, standalone) => { + const oldMe = get().me; + set((state) => { + deleteUser(state, account); + state.currentAccountId = false; + }); + persistAuth(get()); + + if (BuildConfig.NODE_ENV === 'production') { + location.href = '/login'; + } + + const newMe = get().me; + if (standalone || userSwitched(oldMe, newMe, get().users)) { + reload(); + } + }, + importCredentials: (token, account) => { + const oldMe = get().me; + let newClient = get().client; + + set((state) => { + importCredentials(state, token, persistAuthAccount(account)); + state.currentAccountId = state.currentAccountId ?? account.id; + }); + + const s = get(); + if (!oldMe) { + if (s.client.baseURL === parseBaseURL(account.url)) { + s.client.accessToken = token; + } else { + newClient = new PlApiClient(parseBaseURL(account.url) || backendUrl, token); + set({ client: newClient }); + } + } + + persistAuth(get()); + const newMe = get().me; + if (userSwitched(oldMe, newMe, get().users)) { + reload(); + } + }, + importCredentialsSkip: () => { + const oldMe = get().me; + set((state) => { + state.me = null; + state.currentAccountId = false; + }); + persistAuth(get()); + const newMe = get().me; + if (userSwitched(oldMe, newMe, get().users)) { + reload(); + } + }, + importCredentialsFailed: (error) => { + set((state) => { + state.currentAccountId = handleForbiddenMe(state.currentAccountId, error); + }); + }, + removeFailedToken: (token, error) => { + const oldMe = get().me; + set((state) => { + deleteForbiddenToken(state, error, token); + if (state.currentAccountId === null) state.currentAccountId = false; + }); + persistAuth(get()); + const newMe = get().me; + if (userSwitched(oldMe, newMe, get().users)) { + reload(); + } + }, + switchAccount: (account) => { + const oldMe = get().me; + + set((state) => { + state.me = account.url; + state.currentAccountId = account.id; + }); + + const s = get(); + const accessToken = s.users[account.url]?.access_token; + if (s.client.baseURL === parseBaseURL(account.url)) { + s.client.accessToken = accessToken; + } else { + set({ + client: new PlApiClient(parseBaseURL(account.url) || backendUrl, accessToken), + }); + } + + persistAuth(get()); + const newMe = get().me; + if (userSwitched(oldMe, newMe, get().users)) { + reload(); + } + }, + setCurrentAccount: (account) => { + set((state) => { + state.currentAccountId = account.id; + }); + }, + setCurrentAccountIfUnset: (account) => { + set((state) => { + state.currentAccountId = state.currentAccountId ?? account.id; + }); + }, + importMastodonPreload: (data) => { + const oldMe = get().me; + set((state) => { + importMastodonPreloadData(state, data); + }); + persistAuth(get()); + const newMe = get().me; + if (userSwitched(oldMe, newMe, get().users)) { + reload(); + } + }, + }, + })), +); + +const useMe = () => useAuthStore((state) => state.currentAccountId); + +const useAuthActions = () => useAuthStore((state) => state.actions); + +export { useAuthStore, useAuthActions, useMe, type Me, type AuthUser, type TokenWithApp }; diff --git a/packages/nicolium/src/stores/compose.ts b/packages/nicolium/src/stores/compose.ts index 7bf427b2c..ac911313f 100644 --- a/packages/nicolium/src/stores/compose.ts +++ b/packages/nicolium/src/stores/compose.ts @@ -9,13 +9,13 @@ import { FE_NAME } from '@/actions/settings'; import { createStatus } from '@/actions/statuses'; import { getClient } from '@/api'; import { isNativeEmoji } from '@/features/emoji'; -import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/hooks/use-instance'; import { selectAccount, selectOwnAccount } from '@/queries/accounts/selectors'; import { queryClient } from '@/queries/client'; import { cancelDraftStatus } from '@/queries/statuses/use-draft-statuses'; +import { useAuthStore } from '@/stores/auth'; +import { useInstance } from '@/stores/instance'; import { useModalsActions, useModalsStore } from '@/stores/modals'; import { useSettings, useSettingsStore } from '@/stores/settings'; import toast from '@/toast'; @@ -25,7 +25,6 @@ import { useUiStoreActions } from './ui'; import type { AutoSuggestion } from '@/components/autosuggest-input'; import type { Language } from '@/features/preferences'; import type { NormalizedStatus as Status } from '@/normalizers/status'; -import type { AppDispatch, RootState } from '@/store'; import type { LinkOptions } from '@tanstack/react-router'; import type { Account, @@ -373,9 +372,6 @@ interface ComposeActions { type ComposeStore = ComposeState & { actions: ComposeActions }; -let lazyStore: { dispatch: AppDispatch; getState: () => RootState }; -import('@/store').then(({ store }) => (lazyStore = store)).catch(() => {}); - const useComposeStore = create()( mutative( (set, get) => ({ @@ -414,7 +410,7 @@ const useComposeStore = create()( editorState = null, redacting = false, ) => { - const { features } = getClient(lazyStore.getState); + const { features } = getClient(); const explicitAddressing = features.createStatusExplicitAddressing && !useSettingsStore.getState().settings.forceImplicitAddressing; @@ -473,13 +469,12 @@ const useComposeStore = create()( }, replyCompose: (status, rebloggedBy, approvalRequired) => { - const state = lazyStore.getState(); - const { features } = getClient(lazyStore.getState); + const { features } = getClient(); const { forceImplicitAddressing, preserveSpoilers } = useSettingsStore.getState().settings; const explicitAddressing = features.createStatusExplicitAddressing && !forceImplicitAddressing; - const account = selectOwnAccount(state); + const account = selectOwnAccount(); if (!account) return; @@ -557,7 +552,7 @@ const useComposeStore = create()( }, mentionCompose: (account) => { - if (!lazyStore.getState().me) return; + if (!useAuthStore.getState().currentAccountId) return; get().actions.updateCompose('compose-modal', (compose) => { compose.text = [compose.text.trim(), `@${account.acct} `] @@ -606,8 +601,7 @@ const useComposeStore = create()( }, eventDiscussionCompose: (composeId, status) => { - const state = lazyStore.getState(); - const account = selectOwnAccount(state); + const account = selectOwnAccount(); if (!account) return; @@ -640,7 +634,7 @@ const useComposeStore = create()( startPosition = position - 1; useSettingsStore.getState().actions.rememberEmojiUse(suggestion); - lazyStore.dispatch(saveSettings()); + saveSettings(); } else if (typeof suggestion === 'string' && suggestion[0] === '#') { completion = suggestion; startPosition = position - 1; @@ -708,7 +702,6 @@ const useComposeStore = create()( const useSubmitCompose = (composeId: string) => { const actions = useComposeActions(); const client = useClient(); - const dispatch = useAppDispatch(); const features = useFeatures(); const { openModal, closeModal } = useModalsActions(); const { removeSledzik } = useUiStoreActions(); @@ -782,7 +775,7 @@ const useSubmitCompose = (composeId: string) => { if (compose.language && !editedId) { useSettingsStore.getState().actions.rememberLanguageUse(compose.language); - dispatch(saveSettings()); + saveSettings(); } } @@ -871,19 +864,15 @@ const useSubmitCompose = (composeId: string) => { } try { - const data = await dispatch( - createStatus(params, idempotencyKey, editedId, compose.redacting), - ); + const data = await createStatus(params, idempotencyKey, editedId, compose.redacting); const draftIdToCancel = compose.draftId; actions.resetCompose(composeId); if (draftIdToCancel) { - dispatch((_, getState) => { - const accountUrl = selectOwnAccount(getState())!.url; - cancelDraftStatus(queryClient, accountUrl, draftIdToCancel); - }); + const accountUrl = selectOwnAccount()!.url; + cancelDraftStatus(queryClient, accountUrl, draftIdToCancel); } if (data.scheduled_at === null) { @@ -931,7 +920,6 @@ const useComposeActions = () => useComposeStore((state) => state.actions); const useUploadCompose = (composeId: string) => { const { updateCompose } = useComposeActions(); const instance = useInstance(); - const dispatch = useAppDispatch(); const intl = useIntl(); return useCallback( @@ -957,28 +945,26 @@ const useUploadCompose = (composeId: string) => { Array.from(files).forEach((f, i) => { if (mediaCount + i > attachmentLimit - 1) return; - dispatch( - uploadFile( - f, - intl, - (data) => - updateCompose(composeId, (draft) => { - appendMedia(draft, data); - }), - () => - updateCompose(composeId, (draft) => { - draft.isUploading = false; - }), - ({ loaded }) => { - progress[i] = loaded; - updateCompose(composeId, (draft) => { - draft.progress = Math.round((progress.reduce((a, v) => a + v, 0) / total) * 100); - }); - }, - (value) => { - total += value; - }, - ), + uploadFile( + f, + intl, + (data) => + updateCompose(composeId, (draft) => { + appendMedia(draft, data); + }), + () => + updateCompose(composeId, (draft) => { + draft.isUploading = false; + }), + ({ loaded }) => { + progress[i] = loaded; + updateCompose(composeId, (draft) => { + draft.progress = Math.round((progress.reduce((a, v) => a + v, 0) / total) * 100); + }); + }, + (value) => { + total += value; + }, ); }); }, @@ -988,7 +974,6 @@ const useUploadCompose = (composeId: string) => { const useChangeUploadCompose = (composeId: string) => { const { updateCompose } = useComposeActions(); - const dispatch = useAppDispatch(); return useCallback( async (mediaId: string, params: UpdateMediaParams) => { @@ -1000,7 +985,7 @@ const useChangeUploadCompose = (composeId: string) => { }); try { - const response = await dispatch(updateMedia(mediaId, params)); + const response = await updateMedia(mediaId, params); updateCompose(composeId, (draft) => { draft.isChangingUpload = false; draft.mediaAttachments = draft.mediaAttachments.map((item) => diff --git a/packages/nicolium/src/stores/frontend-config.ts b/packages/nicolium/src/stores/frontend-config.ts new file mode 100644 index 000000000..a6350cfdb --- /dev/null +++ b/packages/nicolium/src/stores/frontend-config.ts @@ -0,0 +1,96 @@ +import * as v from 'valibot'; +import { create } from 'zustand'; + +import { + frontendConfigSchema, + partialFrontendConfigSchema, + type FrontendConfig, + type PartialFrontendConfig, +} from '@/schemas/frontend-config'; +import KVStore from '@/storage/kv-store'; +import ConfigDB from '@/utils/config-db'; + +import type { PleromaConfig } from 'pl-api'; + +const defaultConfig: FrontendConfig = v.parse(frontendConfigSchema, {}); + +const fallbackConfig: PartialFrontendConfig = { + brandColor: '#d80482', +}; + +const parseFrontendConfig = (frontendConfig: unknown): PartialFrontendConfig | null => { + try { + return v.parse(partialFrontendConfigSchema, frontendConfig); + } catch (e) { + console.error('Failed to parse frontend config', e); + return null; + } +}; + +const persistFrontendConfig = (frontendConfig: PartialFrontendConfig, host: string) => { + if (host) { + KVStore.setItem(`frontendConfig:${host}`, frontendConfig).catch(console.error); + } +}; + +type State = { + partialConfig: PartialFrontendConfig; + config: FrontendConfig; + actions: { + importConfig: (config: unknown, host: string) => void; + rememberConfig: (config: unknown) => void; + configFetchFailed: () => void; + importPreload: (data: Record) => void; + importAdminConfigs: (configs: PleromaConfig['configs']) => void; + }; +}; + +const setConfig = ( + partialConfig: PartialFrontendConfig, +): Pick => ({ + partialConfig, + config: { ...defaultConfig, ...partialConfig }, +}); + +const useFrontendConfigStore = create((set) => ({ + partialConfig: {}, + config: defaultConfig, + actions: { + importConfig: (config: unknown, host: string) => { + const parsed = v.parse(partialFrontendConfigSchema, config); + persistFrontendConfig(parsed, host); + set(setConfig(parsed)); + }, + rememberConfig: (config: unknown) => { + const parsed = parseFrontendConfig(config); + if (parsed) set(setConfig(parsed)); + }, + configFetchFailed: () => { + set((state) => setConfig({ ...fallbackConfig, ...state.partialConfig })); + }, + importPreload: (data: Record) => { + const feData = data['/api/pleroma/frontend_configurations']; + if (!feData) return; + + const nicoliumConfig = feData.nicolium || feData.pl_fe; + const merged = nicoliumConfig ? { ...fallbackConfig, ...nicoliumConfig } : fallbackConfig; + const parsed = parseFrontendConfig(merged); + if (parsed) set(setConfig(parsed)); + }, + importAdminConfigs: (configs: PleromaConfig['configs']) => { + try { + const raw = ConfigDB.find(configs, ':pleroma', ':frontend_configurations')!.value.find( + (value: Record) => value.tuple?.[0] === ':nicolium', + ).tuple?.[1]; + const parsed = parseFrontendConfig(raw); + if (parsed) set(setConfig(parsed)); + } catch { + // config not found, ignore + } + }, + }, +})); + +const useFrontendConfigActions = () => useFrontendConfigStore((state) => state.actions); + +export { useFrontendConfigStore, useFrontendConfigActions }; diff --git a/packages/nicolium/src/stores/instance.ts b/packages/nicolium/src/stores/instance.ts new file mode 100644 index 000000000..cbd552a33 --- /dev/null +++ b/packages/nicolium/src/stores/instance.ts @@ -0,0 +1,104 @@ +import { type Instance, instanceSchema, type PleromaConfig } from 'pl-api'; +import * as v from 'valibot'; +import { create } from 'zustand'; +import { mutative } from 'zustand-mutative'; + +import KVStore from '@/storage/kv-store'; +import ConfigDB from '@/utils/config-db'; + +const initialInstance: Instance = v.parse(instanceSchema, {}); + +const getHost = (instance: { domain: string }) => { + const domain = instance.domain; + try { + return new URL(domain).host; + } catch { + try { + return new URL(`https://${domain}`).host; + } catch { + return null; + } + } +}; + +const persistInstance = (instance: { domain: string }, host: string | null = getHost(instance)) => { + if (host) { + KVStore.setItem(`instance:${host}`, instance).catch(console.error); + } +}; + +const getConfigValue = (instanceConfig: Array, key: string) => { + const value = instanceConfig.find((value) => value?.tuple?.[0] === key); + return value ? value?.tuple?.[1] : undefined; +}; + +type State = { + instance: Instance; + fetched: boolean; + /** Whether /api/v1/instance 404'd (and we should display the external auth form). */ + instanceFetchFailed: boolean; + actions: { + loadInstance: (instance: Instance) => void; + importPreload: (data: Record) => void; + importAdminConfigs: (configs: PleromaConfig['configs']) => void; + instanceFetchFailed: (error: unknown) => void; + setInstanceFetchFailed: (failed: boolean) => void; + }; +}; + +const useInstanceStore = create()( + mutative((set) => ({ + instance: initialInstance, + fetched: false, + instanceFetchFailed: false, + actions: { + loadInstance: (instance: Instance) => { + persistInstance(instance); + set({ instance, fetched: true }); + }, + importPreload: (data: Record) => { + const instance = data['/api/v1/instance']; + const parsed = v.safeParse(instanceSchema, instance); + if (parsed.success) { + set({ instance: parsed.output, fetched: true }); + } + }, + importAdminConfigs: (configs: PleromaConfig['configs']) => { + const config = ConfigDB.find(configs, ':pleroma', ':instance'); + if (!config) return; + + const value = config.value ?? []; + const registrationsOpen = getConfigValue(value, ':registrations_open') as + | boolean + | undefined; + const approvalRequired = getConfigValue(value, ':account_approval_required') as + | boolean + | undefined; + + set((state) => { + state.instance.registrations = { + ...state.instance.registrations, + enabled: registrationsOpen ?? state.instance.registrations.enabled, + approval_required: approvalRequired ?? state.instance.registrations.approval_required, + }; + }); + }, + instanceFetchFailed: (error: unknown) => { + if ((error as any)?.response?.status === 401) { + set((state) => { + state.instance.title = state.instance.title || '██████'; + state.instance.description = state.instance.description || '████████████'; + }); + } + }, + setInstanceFetchFailed: (failed: boolean) => { + set({ instanceFetchFailed: failed }); + }, + }, + })), +); + +const useInstance = () => useInstanceStore((state) => state.instance); +const useInstanceActions = () => useInstanceStore((state) => state.actions); + +export { useInstanceStore, useInstance, useInstanceActions }; diff --git a/packages/nicolium/src/stores/push-notifications.ts b/packages/nicolium/src/stores/push-notifications.ts new file mode 100644 index 000000000..365ba9dda --- /dev/null +++ b/packages/nicolium/src/stores/push-notifications.ts @@ -0,0 +1,65 @@ +import { create } from 'zustand'; +import { mutative } from 'zustand-mutative'; + +import type { WebPushSubscription } from 'pl-api'; + +interface Subscription { + id: string; + endpoint: string; +} + +interface PushNotificationState { + subscription: Subscription | null; + alerts: Record; + isSubscribed: boolean; + actions: { + setSubscription: (subscription: WebPushSubscription) => void; + clearSubscription: () => void; + }; +} + +const usePushNotificationsStore = create()( + mutative((set) => ({ + subscription: null, + alerts: { + follow: true, + follow_request: true, + favourite: true, + reblog: true, + mention: true, + poll: true, + status: true, + }, + isSubscribed: false, + + actions: { + setSubscription: (subscription) => { + set((state) => { + state.subscription = { + id: subscription.id, + endpoint: subscription.endpoint, + }; + state.alerts = subscription.alerts; + state.isSubscribed = true; + }); + }, + clearSubscription: () => { + set({ + subscription: null, + alerts: { + follow: true, + follow_request: true, + favourite: true, + reblog: true, + mention: true, + poll: true, + status: true, + }, + isSubscribed: false, + }); + }, + }, + })), +); + +export { usePushNotificationsStore }; diff --git a/packages/nicolium/src/stores/settings.ts b/packages/nicolium/src/stores/settings.ts index d47bf95d5..0eccb52ab 100644 --- a/packages/nicolium/src/stores/settings.ts +++ b/packages/nicolium/src/stores/settings.ts @@ -6,6 +6,7 @@ import { mutative } from 'zustand-mutative'; import { settingsSchema, type Settings } from '@/schemas/frontend-settings'; import KVStore from '@/storage/kv-store'; +import { useAuthStore } from '@/stores/auth'; import toast from '@/toast'; import { type KVStoreRedirectServicesItem, @@ -16,12 +17,8 @@ import { } from '@/utils/url-purify'; import type { Emoji } from '@/features/emoji'; -import type { store } from '@/store'; import type { APIEntity } from '@/types/entities'; -let lazyStore: typeof store; -import('@/store').then(({ store }) => (lazyStore = store)).catch(() => {}); - const messages = defineMessages({ rulesUpdateSuccess: { id: 'url_privacy.update.success', @@ -75,14 +72,14 @@ const changeSetting = (object: APIEntity, path: string[], value: any, root?: Set const mergeSettings = (state: State, updating = false) => { const mergedSettings = { ...state.defaultSettings, ...state.userSettings }; if (updating) { - const me = lazyStore?.getState().me; - if (me) { + const currentAccountId = useAuthStore.getState().currentAccountId; + if (currentAccountId) { if ( mergedSettings.urlPrivacy.rulesUrl && state.settings.urlPrivacy.rulesUrl !== mergedSettings.urlPrivacy.rulesUrl ) { updateRulesFromUrl( - me, + currentAccountId, mergedSettings.urlPrivacy.rulesUrl, mergedSettings.urlPrivacy.hashUrl, ) @@ -96,7 +93,7 @@ const mergeSettings = (state: State, updating = false) => { !mergedSettings.urlPrivacy.rulesUrl && state.settings.urlPrivacy.rulesUrl !== mergedSettings.urlPrivacy.rulesUrl ) { - resetRules(me) + resetRules(currentAccountId) .then(() => { toast.success(messages.rulesUpdateSuccess); }) @@ -110,7 +107,10 @@ const mergeSettings = (state: State, updating = false) => { state.settings.urlPrivacy.redirectServicesUrl !== mergedSettings.urlPrivacy.redirectServicesUrl ) { - updateRedirectServicesFromUrl(me, mergedSettings.urlPrivacy.redirectServicesUrl) + updateRedirectServicesFromUrl( + currentAccountId, + mergedSettings.urlPrivacy.redirectServicesUrl, + ) .then(() => { toast.success(messages.redirectServicesUpdateSuccess); }) @@ -124,7 +124,7 @@ const mergeSettings = (state: State, updating = false) => { mergedSettings.urlPrivacy.redirectServices, ) ) { - setManualRedirectServices(me, mergedSettings.urlPrivacy.redirectServices) + setManualRedirectServices(currentAccountId, mergedSettings.urlPrivacy.redirectServices) .then(() => { toast.success(messages.redirectServicesUpdateSuccess); }) @@ -161,14 +161,14 @@ const useSettingsStore = create()( state.userSettings = v.parse(settingsSchemaPartial, settings); - const me = lazyStore?.getState().me; - if (me) { + const currentAccountId = useAuthStore.getState().currentAccountId; + if (currentAccountId) { KVStore.getItem('url-purify-rules:last') .then((value) => { - if (value !== me) { + if (value !== currentAccountId) { if (state.userSettings.urlPrivacy?.rulesUrl) { updateRulesFromUrl( - me, + currentAccountId, state.userSettings.urlPrivacy.rulesUrl, state.userSettings.urlPrivacy.hashUrl, ) @@ -179,28 +179,28 @@ const useSettingsStore = create()( toast.error(messages.rulesUpdateFail); }); } else { - resetRules(me); + resetRules(currentAccountId); } switch (state.userSettings.urlPrivacy?.redirectLinksMode) { case 'auto': updateRedirectServicesFromUrl( - me, + currentAccountId, state.userSettings.urlPrivacy?.redirectServicesUrl, ); break; case 'manual': setManualRedirectServices( - me, + currentAccountId, state.userSettings.urlPrivacy.redirectServices, ); break; default: - setManualRedirectServices(me, {}); + setManualRedirectServices(currentAccountId, {}); break; } } else { KVStore.getItem( - `url-purify-redirect-services:${me}`, + `url-purify-redirect-services:${currentAccountId}`, ) .then((services) => { if (state.userSettings.urlPrivacy?.redirectLinksMode === 'auto') { @@ -209,13 +209,13 @@ const useSettingsStore = create()( state.userSettings.urlPrivacy?.redirectServicesUrl ) { updateRedirectServicesFromUrl( - me, + currentAccountId, state.userSettings.urlPrivacy?.redirectServicesUrl, ); } } else { setManualRedirectServices( - me, + currentAccountId, state.userSettings.urlPrivacy?.redirectServices ?? {}, ); } diff --git a/packages/nicolium/src/utils/auth.ts b/packages/nicolium/src/utils/auth.ts index 7082c12ef..275a88e69 100644 --- a/packages/nicolium/src/utils/auth.ts +++ b/packages/nicolium/src/utils/auth.ts @@ -1,6 +1,6 @@ import { selectAccount, selectOwnAccount } from '@/queries/accounts/selectors'; - -import type { RootState } from '@/store'; +import { useAuthStore } from '@/stores/auth'; +import { useInstanceStore } from '@/stores/instance'; const validId = (id?: string | null | false) => typeof id === 'string' && id !== 'null' && id !== 'undefined'; @@ -24,39 +24,38 @@ const parseBaseURL = (url?: string) => { } }; -const getLoggedInAccount = (state: RootState) => selectOwnAccount(state); +const getLoggedInAccount = () => selectOwnAccount(); -const isLoggedIn = (getState: () => RootState) => validId(getState().me); +const isLoggedIn = () => validId(useAuthStore.getState().currentAccountId); -const getUserToken = (state: RootState, accountId?: string | false | null) => { +const getUserToken = (accountId?: string | false | null) => { if (!accountId) return; const accountUrl = selectAccount(accountId)?.url; if (!accountUrl) return; - return state.auth.users[accountUrl]?.access_token; + return useAuthStore.getState().users[accountUrl]?.access_token; }; -const getAccessToken = (state: RootState) => { - const me = state.me; - return getUserToken(state, me); +const getAccessToken = () => { + const { currentAccountId } = useAuthStore.getState(); + return getUserToken(currentAccountId); }; -const getAuthUserId = (state: RootState) => { - const me = state.auth.me; - - return [state.auth.users[me!]?.id, me].filter((id) => id).find(validId); +const getAuthUserId = () => { + const { me, users } = useAuthStore.getState(); + return [users[me!]?.id, me].filter((id) => id).find(validId); }; -const getAuthUserUrl = (state: RootState) => { - const me = state.auth.me; - - return [state.auth.users[me!]?.url, me].filter((url) => url).find(isURL); +const getAuthUserUrl = () => { + const { me, users } = useAuthStore.getState(); + return [users[me!]?.url, me].filter((url) => url).find(isURL); }; /** Get the VAPID public key. */ -const getVapidKey = (state: RootState) => - state.auth.app?.vapid_key ?? state.instance.configuration.vapid.public_key; +const getVapidKey = () => + useAuthStore.getState().app?.vapid_key ?? + useInstanceStore.getState().instance.configuration.vapid.public_key; -const getMeUrl = (state: RootState) => selectOwnAccount(state)?.url; +const getMeUrl = () => selectOwnAccount()?.url; export { validId, diff --git a/packages/nicolium/src/utils/scopes.ts b/packages/nicolium/src/utils/scopes.ts index f95e719a9..100378394 100644 --- a/packages/nicolium/src/utils/scopes.ts +++ b/packages/nicolium/src/utils/scopes.ts @@ -1,6 +1,6 @@ import { getFeatures, HOLLO, ICESHRIMP_NET, PLEROMA, TOKI, type Instance } from 'pl-api'; -import type { RootState } from '@/store'; +import { useInstanceStore } from '@/stores/instance'; /** * Get the OAuth scopes to use for login & signup. @@ -44,7 +44,7 @@ const getInstanceScopes = ( }; /** Convenience function to get scopes from instance in store. */ -const getScopes = (state: RootState, admin?: boolean, external?: boolean) => - getInstanceScopes(state.instance, admin, external); +const getScopes = (admin?: boolean, external?: boolean) => + getInstanceScopes(useInstanceStore.getState().instance, admin, external); export { getInstanceScopes, getScopes }; diff --git a/packages/nicolium/src/utils/state.ts b/packages/nicolium/src/utils/state.ts index 15c9a8d4c..000c1cc77 100644 --- a/packages/nicolium/src/utils/state.ts +++ b/packages/nicolium/src/utils/state.ts @@ -1,31 +1,40 @@ /** - * State: general Redux state utility functions. + * State: general state utility functions. * @module @/utils/state */ import * as BuildConfig from '@/build-config'; import { isPrerendered } from '@/precheck'; import { selectOwnAccount } from '@/queries/accounts/selectors'; +import { useFrontendConfigStore } from '@/stores/frontend-config'; +import { useInstanceStore } from '@/stores/instance'; import { isURL } from '@/utils/auth'; -import type { RootState } from '@/store'; - /** Whether to display the fqn instead of the acct. */ -const displayFqn = (state: RootState): boolean => state.frontendConfig.displayFqn ?? true; +const displayFqn = (): boolean => + useFrontendConfigStore.getState().partialConfig.displayFqn ?? true; /** Whether the instance exposes instance blocks through the API. */ -const federationRestrictionsDisclosed = (state: RootState): boolean => - !!state.instance.pleroma.metadata.federation.mrf_policies; +const federationRestrictionsDisclosed = (): boolean => + !!useInstanceStore.getState().instance.pleroma.metadata.federation.mrf_policies; /** * Determine whether Nicolium is running in standalone mode. * Standalone mode runs separately from any backend and can login anywhere. */ -const isStandalone = (state: RootState): boolean => { - const instanceFetchFailed = state.meta.instance_fetch_failed; +const isStandalone = (): boolean => { + const instanceFetchFailed = useInstanceStore.getState().instanceFetchFailed; return isURL(BuildConfig.BACKEND_URL) ? false : !isPrerendered && instanceFetchFailed; }; +const useIsStandalone = () => { + const instanceFetchFailed = useInstanceStore((state) => state.instanceFetchFailed); + return isURL(BuildConfig.BACKEND_URL) ? false : !isPrerendered && instanceFetchFailed; +}; + +const useFederationRestrictionsDisclosed = () => + useInstanceStore((state) => !!state.instance.pleroma.metadata.federation.mrf_policies); + const getHost = (url: string = ''): string => { try { return new URL(url).origin; @@ -35,9 +44,16 @@ const getHost = (url: string = ''): string => { }; /** Get the baseURL of the instance. */ -const getBaseURL = (state: RootState): string => { - const account = selectOwnAccount(state); +const getBaseURL = (): string => { + const account = selectOwnAccount(); return isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : getHost(account?.url); }; -export { displayFqn, federationRestrictionsDisclosed, isStandalone, getBaseURL }; +export { + displayFqn, + federationRestrictionsDisclosed, + isStandalone, + useIsStandalone, + useFederationRestrictionsDisclosed, + getBaseURL, +}; diff --git a/packages/nicolium/src/utils/url-purify.ts b/packages/nicolium/src/utils/url-purify.ts index 26b7ed4e0..d0c77b74e 100644 --- a/packages/nicolium/src/utils/url-purify.ts +++ b/packages/nicolium/src/utils/url-purify.ts @@ -21,7 +21,7 @@ import { import KVStore from '@/storage/kv-store'; -import type { Me } from '@/reducers/me'; +import type { Me } from '@/stores/auth'; interface KVStoreRulesItem { hashUrl?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05872e37c..6e9aa1536 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,9 +85,6 @@ importers: '@react-spring/web': specifier: ^10.0.3 version: 10.0.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@reduxjs/toolkit': - specifier: ^2.11.2 - version: 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4) '@sentry/browser': specifier: ^10.43.0 version: 10.43.0 @@ -259,9 +256,6 @@ importers: react-intl: specifier: ^8.1.3 version: 8.1.3(@types/react@19.2.14)(react@19.2.4)(typescript@5.9.3) - react-redux: - specifier: ^9.2.0 - version: 9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1) react-sparklines: specifier: ^1.7.0 version: 1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -274,12 +268,6 @@ importers: react-virtuoso: specifier: ^4.18.3 version: 4.18.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - redux: - specifier: ^5.0.1 - version: 5.0.1 - redux-thunk: - specifier: ^3.1.0 - version: 3.1.0(redux@5.0.1) reselect: specifier: ^5.1.1 version: 5.1.1 @@ -2856,17 +2844,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@reduxjs/toolkit@2.11.2': - resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - '@rolldown/binding-android-arm64@1.0.0-rc.4': resolution: {integrity: sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3246,9 +3223,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@stylistic/stylelint-plugin@5.0.1': resolution: {integrity: sha512-NaVwCNVZ2LyPA3TnUwvjO9c6P6VUjgRB8UP8SOW+cAOJBVqPPuOIDawsvvtql/LhkuR3JuTdGvr/RM3dUl8l2Q==} engines: {node: '>=20.19.0'} @@ -3511,9 +3485,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/use-sync-external-store@0.0.6': - resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1': resolution: {integrity: sha512-AhPdPuVe4osxWoeImS21jVhc0VJ2QnzLUZtEFMakY0Rf70C0b6il/m7hwRf9wkr9xXZLVOVJ1kYrpvQRuHFE0Q==} cpu: [arm64] @@ -6507,18 +6478,6 @@ packages: react-property@2.0.2: resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==} - react-redux@9.2.0: - resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} - peerDependencies: - '@types/react': ^19.2.14 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} @@ -6600,14 +6559,6 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -10234,18 +10185,6 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@standard-schema/utils': 0.3.0 - immer: 11.1.4 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.1.1 - optionalDependencies: - react: 19.2.4 - react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1) - '@rolldown/binding-android-arm64@1.0.0-rc.4': optional: true @@ -10590,8 +10529,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@standard-schema/utils@0.3.0': {} - '@stylistic/stylelint-plugin@5.0.1(stylelint@17.4.0(typescript@5.9.3))': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -10879,8 +10816,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/use-sync-external-store@0.0.6': {} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1': optional: true @@ -12799,7 +12734,8 @@ snapshots: immediate@3.0.6: {} - immer@11.1.4: {} + immer@11.1.4: + optional: true immutable@5.1.5: {} @@ -14534,15 +14470,6 @@ snapshots: react-property@2.0.2: {} - react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1): - dependencies: - '@types/use-sync-external-store': 0.0.6 - react: 19.2.4 - use-sync-external-store: 1.6.0(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - redux: 5.0.1 - react-refresh@0.18.0: {} react-sparklines@1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -14639,12 +14566,6 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 From 9f3458cf41127f7efcb63c770868633426014748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sat, 14 Mar 2026 14:12:49 +0100 Subject: [PATCH 03/25] nicolium: Migrate reports to hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/reports.ts | 45 ---------------- .../components/statuses/status-action-bar.tsx | 3 +- .../account/components/account-menu.tsx | 8 ++- .../event/components/event-header.tsx | 3 +- .../nicolium/src/modals/block-mute-modal.tsx | 5 +- .../src/modals/report-modal/index.tsx | 54 +++++++++---------- .../src/queries/accounts/use-report.ts | 15 ++++++ 7 files changed, 48 insertions(+), 85 deletions(-) delete mode 100644 packages/nicolium/src/actions/reports.ts create mode 100644 packages/nicolium/src/queries/accounts/use-report.ts diff --git a/packages/nicolium/src/actions/reports.ts b/packages/nicolium/src/actions/reports.ts deleted file mode 100644 index 3eaf92181..000000000 --- a/packages/nicolium/src/actions/reports.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getClient } from '@/api'; -import { useModalsStore } from '@/stores/modals'; - -import type { NormalizedStatus as Status } from '@/normalizers/status'; -import type { Account } from 'pl-api'; - -enum ReportableEntities { - ACCOUNT = 'ACCOUNT', - STATUS = 'STATUS', -} - -type ReportedEntity = { - status?: Pick; - statusId?: string; -}; - -const initReport = ( - entityType: ReportableEntities, - account: Pick, - entities?: ReportedEntity, -) => { - const { status, statusId } = entities ?? {}; - - useModalsStore.getState().actions.openModal('REPORT', { - accountId: account.id, - entityType, - statusIds: [status?.id, statusId].filter((id): id is string => !!id), - }); -}; - -const submitReport = ( - accountId: string, - statusIds: string[], - ruleIds?: string[], - comment?: string, - forward?: boolean, -) => - getClient().accounts.reportAccount(accountId, { - status_ids: statusIds, - rule_ids: ruleIds, - comment: comment, - forward: forward, - }); - -export { ReportableEntities, initReport, submitReport }; diff --git a/packages/nicolium/src/components/statuses/status-action-bar.tsx b/packages/nicolium/src/components/statuses/status-action-bar.tsx index 81ed92849..ab44c7edb 100644 --- a/packages/nicolium/src/components/statuses/status-action-bar.tsx +++ b/packages/nicolium/src/components/statuses/status-action-bar.tsx @@ -5,7 +5,6 @@ import { defineMessages, useIntl } from 'react-intl'; import { redactStatus } from '@/actions/admin'; import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/actions/moderation'; -import { initReport, ReportableEntities } from '@/actions/reports'; import { changeSetting } from '@/actions/settings'; import { deleteStatus, @@ -884,7 +883,7 @@ const MenuButton: React.FC = ({ }; const handleReport: React.EventHandler = () => { - initReport(ReportableEntities.STATUS, status.account, { status }); + openModal('REPORT', { accountId: status.account.id, statusIds: [status.id] }); }; const handleConversationMuteClick: React.EventHandler = () => { diff --git a/packages/nicolium/src/features/account/components/account-menu.tsx b/packages/nicolium/src/features/account/components/account-menu.tsx index 909818317..e54b8341b 100644 --- a/packages/nicolium/src/features/account/components/account-menu.tsx +++ b/packages/nicolium/src/features/account/components/account-menu.tsx @@ -2,7 +2,6 @@ import { GOTOSOCIAL, MASTODON } from 'pl-api'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { initReport, ReportableEntities } from '@/actions/reports'; import DropdownMenu, { type Menu } from '@/components/dropdown-menu'; import IconButton from '@/components/ui/icon-button'; import { useClient } from '@/hooks/use-client'; @@ -113,14 +112,13 @@ const AccountMenu: React.FC = ({ account }) => { const { mutate: unpinAccount } = useUnpinAccountMutation(account?.id!); const { mutate: removeFromFollowers } = useRemoveAccountFromFollowersMutation(account?.id!); const { mutate: updateAccountNote } = useUpdateAccountNoteMutation(account?.id!); + const { mutate: blockDomain } = useBlockDomainMutation(); + const { mutate: unblockDomain } = useUnblockDomainMutation(); const { openModal } = useModalsActions(); const settings = useSettings(); const { software } = features.version; - const { mutate: blockDomain } = useBlockDomainMutation(); - const { mutate: unblockDomain } = useUnblockDomainMutation(); - const onBlock = () => { if (account.relationship?.blocking) { unblockAccount(); @@ -209,7 +207,7 @@ const AccountMenu: React.FC = ({ account }) => { }; const onReport = () => { - initReport(ReportableEntities.ACCOUNT, account); + openModal('REPORT', { accountId: account.id }); }; const onMute = () => { diff --git a/packages/nicolium/src/features/event/components/event-header.tsx b/packages/nicolium/src/features/event/components/event-header.tsx index 3f93dcaad..875f96be0 100644 --- a/packages/nicolium/src/features/event/components/event-header.tsx +++ b/packages/nicolium/src/features/event/components/event-header.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/actions/moderation'; -import { initReport, ReportableEntities } from '@/actions/reports'; import { deleteStatus } from '@/actions/statuses'; import VerificationBadge from '@/components/accounts/verification-badge'; import DropdownMenu, { type Menu as MenuType } from '@/components/dropdown-menu'; @@ -237,7 +236,7 @@ const EventHeader: React.FC = ({ status }) => { }; const handleReport = () => { - initReport(ReportableEntities.STATUS, account, { status }); + openModal('REPORT', { accountId: account.id, statusIds: [status.id] }); }; const handleToggleStatusSensitivity = () => { diff --git a/packages/nicolium/src/modals/block-mute-modal.tsx b/packages/nicolium/src/modals/block-mute-modal.tsx index 201691ad7..5fb5d04b4 100644 --- a/packages/nicolium/src/modals/block-mute-modal.tsx +++ b/packages/nicolium/src/modals/block-mute-modal.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { initReport, ReportableEntities } from '@/actions/reports'; import FormGroup from '@/components/ui/form-group'; import Modal from '@/components/ui/modal'; import Text from '@/components/ui/text'; @@ -15,6 +14,7 @@ import { useMuteAccountMutation, useUpdateAccountNoteMutation, } from '@/queries/accounts/use-relationship'; +import { useModalsActions } from '@/stores/modals'; import toast from '@/toast'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; @@ -46,6 +46,7 @@ const BlockMuteModal: React.FC = ({ const [note, setNote] = useState(undefined); const { notes, blocksDuration, mutesDuration } = useFeatures(); const canSetDuration = action === 'MUTE' ? mutesDuration : blocksDuration; + const { openModal } = useModalsActions(); const currentNote = account?.relationship?.note; @@ -79,7 +80,7 @@ const BlockMuteModal: React.FC = ({ const handleBlockAndReport = () => { handleClick(() => { - initReport(ReportableEntities.STATUS, account, { statusId }); + openModal('REPORT', { accountId: account.id, statusIds: statusId ? [statusId] : undefined }); }); }; diff --git a/packages/nicolium/src/modals/report-modal/index.tsx b/packages/nicolium/src/modals/report-modal/index.tsx index c7aaff909..0d2b6b01f 100644 --- a/packages/nicolium/src/modals/report-modal/index.tsx +++ b/packages/nicolium/src/modals/report-modal/index.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { submitReport, ReportableEntities } from '@/actions/reports'; import AttachmentThumbs from '@/components/media/attachment-thumbs'; import StatusContent from '@/components/statuses/status-content'; import Modal from '@/components/ui/modal'; @@ -10,6 +9,7 @@ import Text from '@/components/ui/text'; import AccountContainer from '@/containers/account-container'; import { useAccount } from '@/queries/accounts/use-account'; import { useBlockAccountMutation } from '@/queries/accounts/use-relationship'; +import { useReportAccountMutation } from '@/queries/accounts/use-report'; import { useMinimalStatus } from '@/queries/statuses/use-status'; import { useAccountTimeline } from '@/queries/timelines/use-timelines'; import { useInstance } from '@/stores/instance'; @@ -58,19 +58,18 @@ const SelectedStatus = ({ statusId }: { statusId: string }) => { interface ReportModalProps { accountId: string; - entityType: ReportableEntities; - statusIds: Array; + statusIds?: Array; } const ReportModal: React.FC = ({ onClose, accountId, - entityType, - statusIds, + statusIds = [], }) => { const { data: account } = useAccount(accountId || undefined); const { mutate: blockAccount } = useBlockAccountMutation(accountId); + const { mutate: reportAccount } = useReportAccountMutation(accountId); const [block, setBlock] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); @@ -82,22 +81,30 @@ const ReportModal: React.FC = ({ const shouldRequireRule = rules.length > 0; - const isReportingAccount = entityType === ReportableEntities.ACCOUNT; - const isReportingStatus = entityType === ReportableEntities.STATUS; + const isReportingAccount = statusIds.length === 0; const [currentStep, setCurrentStep] = useState(Steps.ONE); const handleSubmit = () => { setIsSubmitting(true); - submitReport(accountId, selectedStatusIds, [...ruleIds], comment, forward) - .then(() => { - setIsSubmitting(false); - setCurrentStep(Steps.THREE); - }) - .catch(() => { - setIsSubmitting(false); - }); + reportAccount( + { + status_ids: selectedStatusIds, + comment, + forward, + rule_ids: ruleIds, + }, + { + onSuccess: () => { + setIsSubmitting(false); + setCurrentStep(Steps.THREE); + }, + onError: () => { + setIsSubmitting(false); + }, + }, + ); if (block && account) { blockAccount(undefined); @@ -174,7 +181,7 @@ const ReportModal: React.FC = ({ }; const renderSelectedEntity = () => { - if (entityType === ReportableEntities.STATUS) return renderSelectedStatuses(); + if (!isReportingAccount) return renderSelectedStatuses(); return null; }; @@ -191,19 +198,8 @@ const ReportModal: React.FC = ({ return false; } - return ( - isSubmitting || - (shouldRequireRule && ruleIds.length === 0) || - (isReportingStatus && selectedStatusIds.length === 0) - ); - }, [ - currentStep, - isSubmitting, - shouldRequireRule, - ruleIds.length, - selectedStatusIds.length, - isReportingStatus, - ]); + return isSubmitting || (shouldRequireRule && ruleIds.length === 0); + }, [currentStep, isSubmitting, shouldRequireRule, ruleIds.length, selectedStatusIds.length]); const calculateProgress = useCallback(() => { switch (currentStep) { diff --git a/packages/nicolium/src/queries/accounts/use-report.ts b/packages/nicolium/src/queries/accounts/use-report.ts new file mode 100644 index 000000000..4e422867b --- /dev/null +++ b/packages/nicolium/src/queries/accounts/use-report.ts @@ -0,0 +1,15 @@ +import { useMutation } from '@tanstack/react-query'; + +import { useClient } from '@/hooks/use-client'; + +import type { ReportAccountParams } from 'pl-api'; + +const useReportAccountMutation = (accountId: string) => { + const client = useClient(); + + return useMutation({ + mutationFn: (params: ReportAccountParams) => client.accounts.reportAccount(accountId, params), + }); +}; + +export { useReportAccountMutation }; From a88e070acb788abe780465c0d17ebdc2fef60845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sat, 14 Mar 2026 14:15:27 +0100 Subject: [PATCH 04/25] nicolium: Migrate data exporter to hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/export-data.ts | 19 +++++++------------ .../src/pages/settings/export-data.tsx | 8 +++++--- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/nicolium/src/actions/export-data.ts b/packages/nicolium/src/actions/export-data.ts index 9c60a1894..08f07d102 100644 --- a/packages/nicolium/src/actions/export-data.ts +++ b/packages/nicolium/src/actions/export-data.ts @@ -1,10 +1,8 @@ import { defineMessages } from 'react-intl'; -import { getClient } from '@/api'; -import { useAuthStore } from '@/stores/auth'; import toast from '@/toast'; -import type { Account, PaginatedResponse } from 'pl-api'; +import type { Account, PaginatedResponse, PlApiClient } from 'pl-api'; const messages = defineMessages({ blocksSuccess: { @@ -41,11 +39,8 @@ const listAccounts = async (response: PaginatedResponse) => { return Array.from(new Set(accounts)); }; -const exportFollows = async () => { - const me = useAuthStore.getState().currentAccountId; - if (!me) return; - - const response = await getClient().accounts.getAccountFollowing(me, { limit: 40 }); +const exportFollows = async (client: PlApiClient) => { + const response = await client.accounts.getAccountFollowing(me, { limit: 40 }); const followings = await listAccounts(response); const followingsCsv = followings.map((fqn) => fqn + ',true'); followingsCsv.unshift('Account address,Show boosts'); @@ -54,16 +49,16 @@ const exportFollows = async () => { toast.success(messages.followersSuccess); }; -const exportBlocks = async () => { - const response = await getClient().filtering.getBlocks({ limit: 40 }); +const exportBlocks = async (client: PlApiClient) => { + const response = await client.filtering.getBlocks({ limit: 40 }); const blocks = await listAccounts(response); fileExport(blocks.join('\n'), 'export_block.csv'); toast.success(messages.blocksSuccess); }; -const exportMutes = async () => { - const response = await getClient().filtering.getMutes({ limit: 40 }); +const exportMutes = async (client: PlApiClient) => { + const response = await client.filtering.getMutes({ limit: 40 }); const mutes = await listAccounts(response); fileExport(mutes.join('\n'), 'export_mutes.csv'); diff --git a/packages/nicolium/src/pages/settings/export-data.tsx b/packages/nicolium/src/pages/settings/export-data.tsx index c6da02530..add659e1e 100644 --- a/packages/nicolium/src/pages/settings/export-data.tsx +++ b/packages/nicolium/src/pages/settings/export-data.tsx @@ -7,6 +7,7 @@ import Column from '@/components/ui/column'; import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import Text from '@/components/ui/text'; +import { useClient } from '@/hooks/use-client'; interface ICSVExporter { inputLabel: React.ReactNode; @@ -51,12 +52,13 @@ const messages = defineMessages({ }); const ExportDataPage = () => { + const client = useClient(); const intl = useIntl(); return ( exportFollows(client)} inputLabel={} inputHint={ { } /> exportBlocks(client)} inputLabel={} inputHint={ { } /> exportMutes(client)} inputLabel={} inputHint={ Date: Sat, 14 Mar 2026 14:16:24 +0100 Subject: [PATCH 05/25] nicolium: remove unused MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/circle.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nicolium/src/actions/circle.ts b/packages/nicolium/src/actions/circle.ts index ddd3d9bb4..7acd68732 100644 --- a/packages/nicolium/src/actions/circle.ts +++ b/packages/nicolium/src/actions/circle.ts @@ -1,6 +1,5 @@ // Loosely adapted from twitter-interaction-circles, licensed under MIT License // https://github.com/duiker101/twitter-interaction-circles -import { getClient } from '@/api'; import { useAuthStore } from '@/stores/auth'; import type { PaginatedResponse, PlApiClient, Status } from 'pl-api'; @@ -31,7 +30,6 @@ const processCircle = async () => { setProgress({ state: 'pending', progress: 0 }); - const client = getClient(); const me = useAuthStore.getState().currentAccountId as string; const interactions: Record = {}; From 11aaec185650c31454a9922b1fcb30576cba4fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sat, 14 Mar 2026 22:13:42 +0100 Subject: [PATCH 06/25] nicolium: fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/export-data.ts | 2 +- packages/nicolium/src/pages/settings/export-data.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/nicolium/src/actions/export-data.ts b/packages/nicolium/src/actions/export-data.ts index 08f07d102..b0c5e0081 100644 --- a/packages/nicolium/src/actions/export-data.ts +++ b/packages/nicolium/src/actions/export-data.ts @@ -39,7 +39,7 @@ const listAccounts = async (response: PaginatedResponse) => { return Array.from(new Set(accounts)); }; -const exportFollows = async (client: PlApiClient) => { +const exportFollows = async (client: PlApiClient, me: string) => { const response = await client.accounts.getAccountFollowing(me, { limit: 40 }); const followings = await listAccounts(response); const followingsCsv = followings.map((fqn) => fqn + ',true'); diff --git a/packages/nicolium/src/pages/settings/export-data.tsx b/packages/nicolium/src/pages/settings/export-data.tsx index add659e1e..f424a62fe 100644 --- a/packages/nicolium/src/pages/settings/export-data.tsx +++ b/packages/nicolium/src/pages/settings/export-data.tsx @@ -7,6 +7,7 @@ import Column from '@/components/ui/column'; import Form from '@/components/ui/form'; import FormActions from '@/components/ui/form-actions'; import Text from '@/components/ui/text'; +import { useCurrentAccount } from '@/contexts/current-account-context'; import { useClient } from '@/hooks/use-client'; interface ICSVExporter { @@ -53,12 +54,13 @@ const messages = defineMessages({ const ExportDataPage = () => { const client = useClient(); + const accountId = useCurrentAccount(); const intl = useIntl(); return ( exportFollows(client)} + action={() => exportFollows(client, accountId as string)} inputLabel={} inputHint={ Date: Sun, 15 Mar 2026 10:53:44 +0100 Subject: [PATCH 07/25] nicolium: improve naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/pages/settings/export-data.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nicolium/src/pages/settings/export-data.tsx b/packages/nicolium/src/pages/settings/export-data.tsx index f424a62fe..4a41f876e 100644 --- a/packages/nicolium/src/pages/settings/export-data.tsx +++ b/packages/nicolium/src/pages/settings/export-data.tsx @@ -54,13 +54,13 @@ const messages = defineMessages({ const ExportDataPage = () => { const client = useClient(); - const accountId = useCurrentAccount(); + const currentAccountId = useCurrentAccount(); const intl = useIntl(); return ( exportFollows(client, accountId as string)} + action={() => exportFollows(client, currentAccountId as string)} inputLabel={} inputHint={ Date: Sun, 15 Mar 2026 10:56:54 +0100 Subject: [PATCH 08/25] nicolium: nyaize post-emojified text only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../components/statuses/parsed-content.tsx | 7 +- .../nicolium/src/features/emoji/emojify.tsx | 161 +++++++++--------- 2 files changed, 85 insertions(+), 83 deletions(-) diff --git a/packages/nicolium/src/components/statuses/parsed-content.tsx b/packages/nicolium/src/components/statuses/parsed-content.tsx index d0a29eddc..07443fc73 100644 --- a/packages/nicolium/src/components/statuses/parsed-content.tsx +++ b/packages/nicolium/src/components/statuses/parsed-content.tsx @@ -15,7 +15,6 @@ import { Link } from '@/components/link'; import Emojify from '@/features/emoji/emojify'; import { useSettings } from '@/stores/settings'; import { makeEmojiMap } from '@/utils/normalizers'; -import nyaize from '@/utils/nyaize'; import Purify from '@/utils/url-purify'; import HoverAccountWrapper from '../accounts/hover-account-wrapper'; @@ -197,10 +196,8 @@ function parseContent( let hasSuspiciousUrl = false; - const transformText = (data: string, key?: React.Key) => { - const text = speakAsCat ? nyaize(data) : data; - - return ; + const transformText = (text: string, key?: React.Key) => { + return ; }; const options: HTMLReactParserOptions = { diff --git a/packages/nicolium/src/features/emoji/emojify.tsx b/packages/nicolium/src/features/emoji/emojify.tsx index c0c160c35..9bb8a4365 100644 --- a/packages/nicolium/src/features/emoji/emojify.tsx +++ b/packages/nicolium/src/features/emoji/emojify.tsx @@ -4,6 +4,7 @@ import React from 'react'; import Emoji from '@/components/ui/emoji'; import { useSettings } from '@/stores/settings'; import { makeEmojiMap } from '@/utils/normalizers'; +import nyaize from '@/utils/nyaize'; import { joinPublicPath } from '@/utils/static'; import unicodeMapping from './mapping'; @@ -15,9 +16,10 @@ import type { CustomEmoji } from 'pl-api'; interface IMaybeEmoji { text: string; emojis: Record; + nyaize: boolean; } -const MaybeEmoji: React.FC = ({ text, emojis }) => { +const MaybeEmoji: React.FC = ({ text, emojis, nyaize: shouldNyaize }) => { if (text.length < 3) return text; if (text in emojis) { const emoji = emojis[text]; @@ -28,100 +30,103 @@ const MaybeEmoji: React.FC = ({ text, emojis }) => { } } - return text; + return shouldNyaize ? nyaize(text) : text; }; interface IEmojify { text: string; emojis?: Array | Record; + nyaize?: boolean; } -const Emojify: React.FC = React.memo(({ text, emojis = {} }) => { - const { disableUserProvidedMedia, systemEmojiFont } = useSettings(); +const Emojify: React.FC = React.memo( + ({ text, emojis = {}, nyaize: shouldNyaize = false }) => { + const { disableUserProvidedMedia, systemEmojiFont } = useSettings(); - if (Array.isArray(emojis)) emojis = makeEmojiMap(emojis); + if (Array.isArray(emojis)) emojis = makeEmojiMap(emojis); - const nodes = []; + const nodes = []; - let stack = ''; - let open = false; + let stack = ''; + let open = false; - const clearStack = () => { - if (stack.length) nodes.push(stack); - open = false; - stack = ''; - }; + const clearStack = () => { + if (stack.length) nodes.push(shouldNyaize ? nyaize(stack) : stack); + open = false; + stack = ''; + }; - const splitText = split(text); + const splitText = split(text); - for (const index in splitText) { - let c = splitText[index]; + for (const index in splitText) { + let c = splitText[index]; - // convert FE0E selector to FE0F so it can be found in unimap - if (c.codePointAt(c.length - 1) === 65038) { - c = c.slice(0, -1) + String.fromCodePoint(65039); + // convert FE0E selector to FE0F so it can be found in unimap + if (c.codePointAt(c.length - 1) === 65038) { + c = c.slice(0, -1) + String.fromCodePoint(65039); + } + + // unqualified emojis aren't in emoji-mart's mappings so we just add FEOF + const unqualified = c + String.fromCodePoint(65039); + + if (!systemEmojiFont && c in unicodeMapping) { + clearStack(); + + const { unified, shortcode } = unicodeMapping[c]; + + nodes.push( + {c}, + ); + } else if (!systemEmojiFont && unqualified in unicodeMapping) { + clearStack(); + + const { unified, shortcode } = unicodeMapping[unqualified]; + + nodes.push( + {unqualified}, + ); + } else if (!disableUserProvidedMedia && c === ':') { + if (!open) { + clearStack(); + } + + stack += ':'; + + // we see another : we convert it and clear the stack buffer + if (open) { + nodes.push(); + stack = ''; + } + + open = !open; + } else { + stack += c; + + if (open && !validEmojiChar(c)) { + clearStack(); + } + } } - // unqualified emojis aren't in emoji-mart's mappings so we just add FEOF - const unqualified = c + String.fromCodePoint(65039); + if (stack.length) nodes.push(shouldNyaize ? nyaize(stack) : stack); - if (!systemEmojiFont && c in unicodeMapping) { - clearStack(); - - const { unified, shortcode } = unicodeMapping[c]; - - nodes.push( - {c}, - ); - } else if (!systemEmojiFont && unqualified in unicodeMapping) { - clearStack(); - - const { unified, shortcode } = unicodeMapping[unqualified]; - - nodes.push( - {unqualified}, - ); - } else if (!disableUserProvidedMedia && c === ':') { - if (!open) { - clearStack(); - } - - stack += ':'; - - // we see another : we convert it and clear the stack buffer - if (open) { - nodes.push(); - stack = ''; - } - - open = !open; - } else { - stack += c; - - if (open && !validEmojiChar(c)) { - clearStack(); - } - } - } - - if (stack.length) nodes.push(stack); - - return nodes; -}); + return nodes; + }, +); Emojify.displayName = 'Emojify'; From d937e7c22fc4ca208e724732911e608c181e2b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 10:59:18 +0100 Subject: [PATCH 09/25] nicolium: disable oxfmt for style files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/.oxfmtrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nicolium/.oxfmtrc.json b/packages/nicolium/.oxfmtrc.json index 96daafabf..2e4a52ab8 100644 --- a/packages/nicolium/.oxfmtrc.json +++ b/packages/nicolium/.oxfmtrc.json @@ -1,6 +1,6 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", - "ignorePatterns": [], + "ignorePatterns": ["**/*.{css,scss}"], "printWidth": null, "singleQuote": true, "arrowParens": null, From a166bb5cf7a6743dcd28564327bce0289d40ae0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 11:04:40 +0100 Subject: [PATCH 10/25] nicolium: move some stuff away from actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/admin.ts | 20 +--------------- packages/nicolium/src/actions/statuses.ts | 11 --------- .../src/components/statuses/status.tsx | 8 +++---- packages/nicolium/src/normalizers/status.ts | 1 - .../nicolium/src/pages/dashboard/account.tsx | 13 ++++++----- .../src/queries/admin/use-accounts.ts | 23 +++++++++++++++++++ packages/nicolium/src/stores/status-meta.ts | 9 ++++++++ 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/packages/nicolium/src/actions/admin.ts b/packages/nicolium/src/actions/admin.ts index e564cded8..ca89f003f 100644 --- a/packages/nicolium/src/actions/admin.ts +++ b/packages/nicolium/src/actions/admin.ts @@ -4,24 +4,6 @@ import { queryKeys } from '@/queries/keys'; import { useComposeStore } from '@/stores/compose'; import { useModalsStore } from '@/stores/modals'; -const promoteToAdmin = (accountId: string) => getClient().admin.accounts.promoteToAdmin(accountId); - -const promoteToModerator = (accountId: string) => - getClient().admin.accounts.promoteToModerator(accountId); - -const demoteToUser = (accountId: string) => getClient().admin.accounts.demoteToUser(accountId); - -const setRole = (accountId: string, role: 'user' | 'moderator' | 'admin') => { - switch (role) { - case 'user': - return demoteToUser(accountId); - case 'moderator': - return promoteToModerator(accountId); - case 'admin': - return promoteToAdmin(accountId); - } -}; - const redactStatus = (statusId: string) => { const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); if (!status) return; @@ -40,4 +22,4 @@ const redactStatus = (statusId: string) => { }); }; -export { setRole, redactStatus }; +export { redactStatus }; diff --git a/packages/nicolium/src/actions/statuses.ts b/packages/nicolium/src/actions/statuses.ts index 7542be664..20f812cd2 100644 --- a/packages/nicolium/src/actions/statuses.ts +++ b/packages/nicolium/src/actions/statuses.ts @@ -326,16 +326,6 @@ const toggleMuteStatus = (status: Pick) => // handleTranslateMany(); // }; -const unfilterStatus = (statusId: string) => { - updateStatus( - statusId, - (status) => { - status.showFiltered = true; - }, - queryClient, - ); -}; - export { createStatus, editStatus, @@ -343,5 +333,4 @@ export { deleteStatus, deleteStatusFromGroup, toggleMuteStatus, - unfilterStatus, }; diff --git a/packages/nicolium/src/components/statuses/status.tsx b/packages/nicolium/src/components/statuses/status.tsx index 8ee9cedc7..7c2c43542 100644 --- a/packages/nicolium/src/components/statuses/status.tsx +++ b/packages/nicolium/src/components/statuses/status.tsx @@ -3,7 +3,6 @@ import clsx from 'clsx'; import React, { useEffect, useMemo, useRef } from 'react'; import { defineMessages, useIntl, FormattedList, FormattedMessage } from 'react-intl'; -import { unfilterStatus } from '@/actions/statuses'; import Card from '@/components/ui/card'; import Icon from '@/components/ui/icon'; import Text from '@/components/ui/text'; @@ -24,7 +23,7 @@ import { import { useComposeActions } from '@/stores/compose'; import { useModalsActions } from '@/stores/modals'; import { useSettings } from '@/stores/settings'; -import { useStatusMetaActions } from '@/stores/status-meta'; +import { useStatusMeta, useStatusMetaActions } from '@/stores/status-meta'; import { textForScreenReader } from '@/utils/status'; import HashtagLink from '../hashtag-link'; @@ -206,7 +205,8 @@ const Status: React.FC = React.memo((props) => { const router = useRouter(); const features = useFeatures(); - const { toggleStatusesMediaHidden } = useStatusMetaActions(); + const { toggleStatusesMediaHidden, unfilterStatus } = useStatusMetaActions(); + const { showFiltered } = useStatusMeta(status.id); const { openModal } = useModalsActions(); const { replyCompose, mentionCompose } = useComposeActions(); const { boostModal } = useSettings(); @@ -524,7 +524,7 @@ const Status: React.FC = React.memo((props) => { if (status.deleted) return ; - if (filtered && actualStatus.showFiltered !== true) { + if (filtered && showFiltered !== true) { const body = (
diff --git a/packages/nicolium/src/normalizers/status.ts b/packages/nicolium/src/normalizers/status.ts index f0fdd05b4..d4b73031e 100644 --- a/packages/nicolium/src/normalizers/status.ts +++ b/packages/nicolium/src/normalizers/status.ts @@ -134,7 +134,6 @@ const normalizeStatus = ( poll_id: poll?.id ?? null, group_id: group?.id ?? null, expectsCard: false, - showFiltered: null as null | boolean, deleted: false, ...status, quote_id: status.quote_id ?? null, diff --git a/packages/nicolium/src/pages/dashboard/account.tsx b/packages/nicolium/src/pages/dashboard/account.tsx index a7dc36efe..7305a836e 100644 --- a/packages/nicolium/src/pages/dashboard/account.tsx +++ b/packages/nicolium/src/pages/dashboard/account.tsx @@ -2,7 +2,6 @@ import { PLEROMA } from 'pl-api'; import React, { type ChangeEventHandler, useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, type MessageDescriptor, useIntl } from 'react-intl'; -import { setRole } from '@/actions/admin'; import { useDeactivateUserModal, useDeleteUserModal } from '@/actions/moderation'; import Account from '@/components/accounts/account'; import List, { ListItem } from '@/components/list'; @@ -19,7 +18,7 @@ import { adminAccountRoute } from '@/features/ui/router'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useAccount } from '@/queries/accounts/use-account'; -import { useAdminUpdateTagsMutation } from '@/queries/admin/use-accounts'; +import { useAdminSetRoleMutation, useAdminUpdateTagsMutation } from '@/queries/admin/use-accounts'; import { useAdminSuggestAccountMutation, useAdminUnsuggestAccountMutation, @@ -94,6 +93,8 @@ interface IStaffRolePicker { const StaffRolePicker: React.FC = ({ account }) => { const intl = useIntl(); + const { mutate: adminSetRole } = useAdminSetRoleMutation(account.id); + const roles: Record = useMemo( () => ({ user: intl.formatMessage(messages.roleUser), @@ -106,8 +107,8 @@ const StaffRolePicker: React.FC = ({ account }) => { const handleRoleChange: React.ChangeEventHandler = (e) => { const role = e.target.value as AccountRole; - setRole(account.id, role) - .then(() => { + adminSetRole(role, { + onSuccess: () => { let message: MessageDescriptor | undefined; if (role === 'admin') { @@ -123,8 +124,8 @@ const StaffRolePicker: React.FC = ({ account }) => { if (message) { toast.success(intl.formatMessage(message, { acct: account.acct })); } - }) - .catch(() => {}); + }, + }); }; const accountRole = getRole(account); diff --git a/packages/nicolium/src/queries/admin/use-accounts.ts b/packages/nicolium/src/queries/admin/use-accounts.ts index c49909bae..090b6bec8 100644 --- a/packages/nicolium/src/queries/admin/use-accounts.ts +++ b/packages/nicolium/src/queries/admin/use-accounts.ts @@ -266,6 +266,28 @@ const useAdminUpdateTagsMutation = (accountId: string) => { }); }; +const useAdminSetRoleMutation = (accountId: string) => { + const client = useClient(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ['admin', 'acounts', accountId, 'setRole'], + mutationFn: (role: 'user' | 'moderator' | 'admin') => { + switch (role) { + case 'user': + return client.admin.accounts.demoteToUser(accountId); + case 'moderator': + return client.admin.accounts.promoteToModerator(accountId); + case 'admin': + return client.admin.accounts.promoteToAdmin(accountId); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.accounts.show(accountId) }); + }, + }); +}; + export { useAdminAccount, useAdminAccounts, @@ -281,4 +303,5 @@ export { useAdminTagUserMutation, useAdminUntagUserMutation, useAdminUpdateTagsMutation, + useAdminSetRoleMutation, }; diff --git a/packages/nicolium/src/stores/status-meta.ts b/packages/nicolium/src/stores/status-meta.ts index 9f1aa91b9..8489dba44 100644 --- a/packages/nicolium/src/stores/status-meta.ts +++ b/packages/nicolium/src/stores/status-meta.ts @@ -11,6 +11,7 @@ type State = { targetLanguage?: string; localTargetLanguage?: string; showPollResults?: boolean; + showFiltered?: boolean; } >; actions: { @@ -25,6 +26,7 @@ type State = { hideLocalTranslation: (statusId: string) => void; setStatusLanguage: (statusId: string, language: string) => void; toggleShowPollResults: (statusId: string) => void; + unfilterStatus: (statusId: string) => void; }; }; @@ -117,6 +119,13 @@ const useStatusMetaStore = create()( state.statuses[statusId].showPollResults = !state.statuses[statusId].showPollResults; }); }, + unfilterStatus: (statusId) => { + set((state: State) => { + if (!state.statuses[statusId]) state.statuses[statusId] = {}; + + state.statuses[statusId].showFiltered = true; + }); + }, }, })), ); From 128de0aab9a4faab970c4a2ab254695c3828ab61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 11:05:57 +0100 Subject: [PATCH 11/25] nicolium: batcher made this obsolete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/statuses.ts | 47 ----------------------- 1 file changed, 47 deletions(-) diff --git a/packages/nicolium/src/actions/statuses.ts b/packages/nicolium/src/actions/statuses.ts index 20f812cd2..c019a8eee 100644 --- a/packages/nicolium/src/actions/statuses.ts +++ b/packages/nicolium/src/actions/statuses.ts @@ -279,53 +279,6 @@ const unmuteStatus = (statusId: string) => { const toggleMuteStatus = (status: Pick) => status.muted ? unmuteStatus(status.id) : muteStatus(status.id); -// let TRANSLATIONS_QUEUE: Set = new Set(); -// let TRANSLATIONS_TIMEOUT: NodeJS.Timeout | null = null; - -// const translateStatus = (statusId: string, targetLanguage: string, lazy?: boolean) => { -// const client = getClient(); -// const features = client.features; - -// const handleTranslateMany = () => { -// const copy = [...TRANSLATIONS_QUEUE]; -// TRANSLATIONS_QUEUE = new Set(); -// if (TRANSLATIONS_TIMEOUT) clearTimeout(TRANSLATIONS_TIMEOUT); - -// return client.statuses.translateStatuses(copy, targetLanguage).then((response) => { -// response.forEach((translation) => { -// dispatch({ -// type: STATUS_TRANSLATE_SUCCESS, -// statusId: translation.id, -// translation: translation, -// }); - -// copy -// .filter((statusId) => !response.some(({ id }) => id === statusId)) -// .forEach((statusId) => dispatch({ -// type: STATUS_TRANSLATE_FAIL, -// statusId, -// })); -// }); -// }).catch(error => { -// dispatch({ -// type: STATUS_TRANSLATE_FAIL, -// statusId, -// error, -// }); -// }); -// }; - -// if (features.lazyTranslations && lazy) { -// TRANSLATIONS_QUEUE.add(statusId); - -// if (TRANSLATIONS_TIMEOUT) clearTimeout(TRANSLATIONS_TIMEOUT); -// TRANSLATIONS_TIMEOUT = setTimeout(() => handleTranslateMany(), 3000); -// } else if (features.lazyTranslations && TRANSLATIONS_QUEUE.size) { -// TRANSLATIONS_QUEUE.add(statusId); - -// handleTranslateMany(); -// }; - export { createStatus, editStatus, From 3fb923ed05e1a29d262e715c6d08c85bca547724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:04:52 +0100 Subject: [PATCH 12/25] nicolium: some migrations to mutations, remove unused stuff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/auth.ts | 1 - packages/nicolium/src/actions/preload.ts | 2 +- packages/nicolium/src/actions/statuses.ts | 72 ++------------- packages/nicolium/src/columns/timeline.tsx | 4 +- .../components/statuses/status-action-bar.tsx | 24 ++--- .../src/components/statuses/status.tsx | 4 +- packages/nicolium/src/features/emoji/index.ts | 20 ----- .../event/components/event-header.tsx | 5 +- .../status/components/thread-status.tsx | 5 +- .../src/features/status/components/thread.tsx | 5 +- .../nicolium/src/layouts/landing-layout.tsx | 28 ------ packages/nicolium/src/normalizers/status.ts | 1 - .../src/queries/statuses/use-status.ts | 87 ++++++++++++++++++- .../src/queries/utils/mutation-options.ts | 13 --- packages/nicolium/src/stores/status-meta.ts | 9 ++ packages/nicolium/src/utils/badges.ts | 2 +- 16 files changed, 132 insertions(+), 150 deletions(-) delete mode 100644 packages/nicolium/src/layouts/landing-layout.tsx delete mode 100644 packages/nicolium/src/queries/utils/mutation-options.ts diff --git a/packages/nicolium/src/actions/auth.ts b/packages/nicolium/src/actions/auth.ts index b8db5b83b..103df0062 100644 --- a/packages/nicolium/src/actions/auth.ts +++ b/packages/nicolium/src/actions/auth.ts @@ -351,5 +351,4 @@ export { authLoggedIn, fetchMe, patchMe, - patchMeSuccess, }; diff --git a/packages/nicolium/src/actions/preload.ts b/packages/nicolium/src/actions/preload.ts index 503f8d9bd..2dfc61c5f 100644 --- a/packages/nicolium/src/actions/preload.ts +++ b/packages/nicolium/src/actions/preload.ts @@ -76,4 +76,4 @@ const preload = () => { } }; -export { pleromaDecoder, decodeFromMarkup, preload }; +export { preload }; diff --git a/packages/nicolium/src/actions/statuses.ts b/packages/nicolium/src/actions/statuses.ts index c019a8eee..43d64b687 100644 --- a/packages/nicolium/src/actions/statuses.ts +++ b/packages/nicolium/src/actions/statuses.ts @@ -15,11 +15,13 @@ import { shouldHaveCard } from '@/utils/status'; import { importEntities } from './importer'; import type { NormalizedStatus as Status } from '@/normalizers/status'; +import type { useQueryClient } from '@tanstack/react-query'; import type { CreateStatusParams, Status as BaseStatus } from 'pl-api'; import type { IntlShape } from 'react-intl'; const incrementReplyCount = ( params: Pick, + queryClient: ReturnType, ) => { if (params.in_reply_to_id) { updateStatus( @@ -45,6 +47,7 @@ const incrementReplyCount = ( const decrementReplyCount = ( params: Pick, + queryClient: ReturnType, ) => { if (params.in_reply_to_id) { updateStatus( @@ -77,7 +80,7 @@ const createStatus = ( useContextStore.getState().actions.importPendingStatus(params.in_reply_to_id, idempotencyKey); useTimelinesStore.getState().actions.importPendingStatus(params, idempotencyKey); if (!editedId) { - incrementReplyCount(params); + incrementReplyCount(params, queryClient); } } @@ -145,7 +148,7 @@ const createStatus = ( useTimelinesStore.getState().actions.deletePendingStatus(idempotencyKey); useContextStore.getState().actions.deletePendingStatus(params.in_reply_to_id, idempotencyKey); if (!editedId) { - decrementReplyCount(params); + decrementReplyCount(params, queryClient); } throw error; }); @@ -183,67 +186,6 @@ const fetchStatus = (statusId: string, intl?: IntlShape) => { }); }; -const deleteStatus = (statusId: string, withRedraft = false) => { - if (!isLoggedIn()) return null; - - const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); - if (!status) return null; - - const poll = status.poll_id - ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) - : undefined; - - decrementReplyCount(status); - - return getClient() - .statuses.deleteStatus(statusId) - .then((source) => { - usePendingStatusesStore.getState().actions.deleteStatus(statusId); - useTimelinesStore.getState().actions.deleteStatus(statusId); - updateStatus( - statusId, - (s) => { - s.deleted = true; - }, - queryClient, - ); - - if (withRedraft) { - useComposeStore.getState().actions.setComposeToStatus(status, poll, source, withRedraft); - useModalsStore.getState().actions.openModal('COMPOSE'); - } - }) - .catch(() => { - incrementReplyCount(status); - }); -}; - -const deleteStatusFromGroup = (statusId: string, groupId: string) => { - if (!isLoggedIn()) return null; - - const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); - if (!status) return null; - - decrementReplyCount(status); - - return getClient() - .experimental.groups.deleteGroupStatus(statusId, groupId) - .then(() => { - usePendingStatusesStore.getState().actions.deleteStatus(statusId); - useTimelinesStore.getState().actions.deleteStatus(statusId); - updateStatus( - statusId, - (s) => { - s.deleted = true; - }, - queryClient, - ); - }) - .catch(() => { - incrementReplyCount(status); - }); -}; - const muteStatus = (statusId: string) => { if (!isLoggedIn()) return; @@ -283,7 +225,7 @@ export { createStatus, editStatus, fetchStatus, - deleteStatus, - deleteStatusFromGroup, toggleMuteStatus, + decrementReplyCount, + incrementReplyCount, }; diff --git a/packages/nicolium/src/columns/timeline.tsx b/packages/nicolium/src/columns/timeline.tsx index efd2688e6..292c13f84 100644 --- a/packages/nicolium/src/columns/timeline.tsx +++ b/packages/nicolium/src/columns/timeline.tsx @@ -32,6 +32,7 @@ import { useWrenchedTimeline, } from '@/queries/timelines/use-timelines'; import { useSettings } from '@/stores/settings'; +import { useStatusMeta } from '@/stores/status-meta'; import { selectChild } from '@/utils/scroll-utils'; import { hasActiveFilters, sortFilteredTimeline } from '@/utils/timeline-filter'; @@ -296,9 +297,10 @@ interface ITimelineStatus { const TimelineStatus: React.FC = (props): React.JSX.Element => { const { id, isConnectedTop, isConnectedBottom } = props; + const { deleted } = useStatusMeta(id); const statusQuery = useStatus(id, { withFilteredResults: true }); - if (statusQuery.data?.deleted) { + if (deleted) { return (
diff --git a/packages/nicolium/src/components/statuses/status-action-bar.tsx b/packages/nicolium/src/components/statuses/status-action-bar.tsx index ab44c7edb..1cc47f46c 100644 --- a/packages/nicolium/src/components/statuses/status-action-bar.tsx +++ b/packages/nicolium/src/components/statuses/status-action-bar.tsx @@ -6,12 +6,7 @@ import { defineMessages, useIntl } from 'react-intl'; import { redactStatus } from '@/actions/admin'; import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/actions/moderation'; import { changeSetting } from '@/actions/settings'; -import { - deleteStatus, - deleteStatusFromGroup, - editStatus, - toggleMuteStatus, -} from '@/actions/statuses'; +import { editStatus, toggleMuteStatus } from '@/actions/statuses'; import DropdownMenu from '@/components/dropdown-menu'; import StatusActionButton from '@/components/statuses/status-action-button'; import { useCurrentAccount } from '@/contexts/current-account-context'; @@ -28,6 +23,11 @@ import { useGroupQuery } from '@/queries/groups/use-group'; import { useBlockGroupUserMutation } from '@/queries/groups/use-group-blocks'; import { useCustomEmojis } from '@/queries/instance/use-custom-emojis'; import { useTranslationLanguages } from '@/queries/instance/use-translation-languages'; +import { + useDeleteStatus, + useDeleteStatusFromGroup, + type SelectedStatus, +} from '@/queries/statuses/use-status'; import { useBookmarkStatus, useDislikeStatus, @@ -56,7 +56,6 @@ import Popover from '../ui/popover'; import type { Menu } from '@/components/dropdown-menu'; import type { Emoji as EmojiType } from '@/features/emoji'; import type { UnauthorizedModalAction } from '@/modals/unauthorized-modal'; -import type { SelectedStatus } from '@/queries/statuses/use-status'; import type { Me } from '@/stores/auth'; const messages = defineMessages({ @@ -739,6 +738,11 @@ const MenuButton: React.FC = ({ const { mutate: pinStatus } = usePinStatus(status.id); const { mutate: unpinStatus } = useUnpinStatus(status.id); const { mutate: unblockAccount } = useUnblockAccountMutation(status.account_id); + const { mutate: deleteStatus } = useDeleteStatus(status.id); + const { mutate: deleteStatusFromGroup } = useDeleteStatusFromGroup( + status.id, + status.group_id as string, + ); const deleteStatusModal = useDeleteStatusModal(status.id); const toggleStatusSensitivityModal = useToggleStatusSensitivityModal(status.id); @@ -788,7 +792,7 @@ const MenuButton: React.FC = ({ const doDeleteStatus = (withRedraft = false) => { if (!deleteModal) { - deleteStatus(status.id, withRedraft); + deleteStatus(withRedraft); } else { openModal('CONFIRM', { heading: intl.formatMessage( @@ -800,7 +804,7 @@ const MenuButton: React.FC = ({ confirm: intl.formatMessage( withRedraft ? messages.redraftConfirm : messages.deleteConfirm, ), - onConfirm: () => deleteStatus(status.id, withRedraft), + onConfirm: () => deleteStatus(withRedraft), }); } }; @@ -936,7 +940,7 @@ const MenuButton: React.FC = ({ }), confirm: intl.formatMessage(messages.deleteConfirm), onConfirm: () => { - deleteStatusFromGroup(status.id, group!.id); + deleteStatusFromGroup(); }, }); }; diff --git a/packages/nicolium/src/components/statuses/status.tsx b/packages/nicolium/src/components/statuses/status.tsx index 7c2c43542..252429112 100644 --- a/packages/nicolium/src/components/statuses/status.tsx +++ b/packages/nicolium/src/components/statuses/status.tsx @@ -206,7 +206,7 @@ const Status: React.FC = React.memo((props) => { const features = useFeatures(); const { toggleStatusesMediaHidden, unfilterStatus } = useStatusMetaActions(); - const { showFiltered } = useStatusMeta(status.id); + const { deleted, showFiltered } = useStatusMeta(status.id); const { openModal } = useModalsActions(); const { replyCompose, mentionCompose } = useComposeActions(); const { boostModal } = useSettings(); @@ -521,7 +521,7 @@ const Status: React.FC = React.memo((props) => { if (!status) return null; - if (status.deleted) + if (deleted) return ; if (filtered && showFiltered !== true) { diff --git a/packages/nicolium/src/features/emoji/index.ts b/packages/nicolium/src/features/emoji/index.ts index 956ce1ae9..5f9689766 100644 --- a/packages/nicolium/src/features/emoji/index.ts +++ b/packages/nicolium/src/features/emoji/index.ts @@ -56,25 +56,6 @@ const isAlphaNumeric = (c: string) => { const validEmojiChar = (c: string) => isAlphaNumeric(c) || ['_', '-', '.'].includes(c); -const buildCustomEmojis = (customEmojis: Array) => { - const emojis: EmojiMart[] = []; - - customEmojis.forEach((emoji) => { - const shortcode = emoji.shortcode; - const url = emoji.static_url; - const name = shortcode.replace(':', ''); - - emojis.push({ - id: name, - name, - keywords: [name], - skins: [{ src: url }], - }); - }); - - return emojis; -}; - const buildCustomEmojiCategories = (customEmojis: Array, intl?: IntlShape) => { const emojiCategories: Record[]> = {}; @@ -118,7 +99,6 @@ export { type Emoji, isCustomEmoji, isNativeEmoji, - buildCustomEmojis, buildCustomEmojiCategories, validEmojiChar, }; diff --git a/packages/nicolium/src/features/event/components/event-header.tsx b/packages/nicolium/src/features/event/components/event-header.tsx index 875f96be0..f59eb8e47 100644 --- a/packages/nicolium/src/features/event/components/event-header.tsx +++ b/packages/nicolium/src/features/event/components/event-header.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/actions/moderation'; -import { deleteStatus } from '@/actions/statuses'; import VerificationBadge from '@/components/accounts/verification-badge'; import DropdownMenu, { type Menu as MenuType } from '@/components/dropdown-menu'; import Icon from '@/components/icon'; @@ -17,6 +16,7 @@ import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useAccount } from '@/queries/accounts/use-account'; import { useChats } from '@/queries/chats'; +import { useDeleteStatus } from '@/queries/statuses/use-status'; import { useBookmarkStatus, usePinStatus, @@ -128,6 +128,7 @@ const EventHeader: React.FC = ({ status }) => { const { mutate: unbookmarkStatus } = useUnbookmarkStatus(status?.id!); const { mutate: pinStatus } = usePinStatus(status?.id!); const { mutate: unpinStatus } = useUnpinStatus(status?.id!); + const { mutate: deleteStatus } = useDeleteStatus(status?.id!); const deleteStatusModal = useDeleteStatusModal(status?.id!); const toggleStatusSensitivityModal = useToggleStatusSensitivityModal(status?.id!); @@ -209,7 +210,7 @@ const EventHeader: React.FC = ({ status }) => { /> ), confirm: , - onConfirm: () => deleteStatus(status.id), + onConfirm: () => deleteStatus(undefined), }); }; diff --git a/packages/nicolium/src/features/status/components/thread-status.tsx b/packages/nicolium/src/features/status/components/thread-status.tsx index e42e7834f..431121abf 100644 --- a/packages/nicolium/src/features/status/components/thread-status.tsx +++ b/packages/nicolium/src/features/status/components/thread-status.tsx @@ -6,6 +6,7 @@ import StatusContainer from '@/containers/status-container'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; import { useMinimalStatus } from '@/queries/statuses/use-status'; import { useReplyCount, useReplyToId } from '@/stores/contexts'; +import { useStatusMeta } from '@/stores/status-meta'; import type { FilterContextType } from '@/queries/settings/use-filters'; @@ -26,9 +27,9 @@ const ThreadStatus: React.FC = (props): React.JSX.Element => { const replyCount = useReplyCount(id); const { data: statusData } = useMinimalStatus(id); const isLoaded = Boolean(statusData); - const isDeleted = Boolean(statusData?.deleted); + const { deleted } = useStatusMeta(id); - if (isDeleted) { + if (deleted) { return (
diff --git a/packages/nicolium/src/features/status/components/thread.tsx b/packages/nicolium/src/features/status/components/thread.tsx index 861eb23e5..5895a9bb1 100644 --- a/packages/nicolium/src/features/status/components/thread.tsx +++ b/packages/nicolium/src/features/status/components/thread.tsx @@ -19,7 +19,7 @@ import { useComposeActions } from '@/stores/compose'; import { useThread } from '@/stores/contexts'; import { useModalsActions } from '@/stores/modals'; import { useSettings } from '@/stores/settings'; -import { useStatusMetaActions } from '@/stores/status-meta'; +import { useStatusMeta, useStatusMetaActions } from '@/stores/status-meta'; import { selectChild } from '@/utils/scroll-utils'; import { textForScreenReader } from '@/utils/status'; @@ -50,6 +50,7 @@ const Thread = ({ const intl = useIntl(); const { replyCompose, mentionCompose } = useComposeActions(); + const { deleted } = useStatusMeta(status.id); const { expandStatuses, revealStatusesMedia, toggleStatusesMediaHidden } = useStatusMetaActions(); const { openModal } = useModalsActions(); const { @@ -238,7 +239,7 @@ const Thread = ({ if (id === status.id) return (
- {status.deleted ? ( + {deleted ? ( { - const me = useCurrentAccount(); - const features = useFeatures(); - - return ( - <> - - - - - - {!me && } - {features.trends && } - - - - ); -}; - -export { LandingLayout as default }; diff --git a/packages/nicolium/src/normalizers/status.ts b/packages/nicolium/src/normalizers/status.ts index d4b73031e..889a38204 100644 --- a/packages/nicolium/src/normalizers/status.ts +++ b/packages/nicolium/src/normalizers/status.ts @@ -134,7 +134,6 @@ const normalizeStatus = ( poll_id: poll?.id ?? null, group_id: group?.id ?? null, expectsCard: false, - deleted: false, ...status, quote_id: status.quote_id ?? null, mentions, diff --git a/packages/nicolium/src/queries/statuses/use-status.ts b/packages/nicolium/src/queries/statuses/use-status.ts index d9042737b..2063d62ee 100644 --- a/packages/nicolium/src/queries/statuses/use-status.ts +++ b/packages/nicolium/src/queries/statuses/use-status.ts @@ -1,12 +1,24 @@ -import { useQueries, useQuery, type UseQueryResult } from '@tanstack/react-query'; +import { + useMutation, + useQueries, + useQuery, + useQueryClient, + type UseQueryResult, +} from '@tanstack/react-query'; import { useMemo } from 'react'; import { importEntities } from '@/actions/importer'; +import { decrementReplyCount, incrementReplyCount } from '@/actions/statuses'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { normalizeStatus, type NormalizedStatus } from '@/normalizers/status'; import { useFilters } from '@/queries/settings/use-filters'; +import { useComposeActions } from '@/stores/compose'; import { useContextsActions } from '@/stores/contexts'; +import { useModalsActions } from '@/stores/modals'; +import { usePendingStatusesActions } from '@/stores/pending-statuses'; +import { useStatusMetaActions } from '@/stores/status-meta'; +import { useTimelinesActions } from '@/stores/timelines'; import { checkFiltered } from '@/utils/filters'; import { useAccount } from '../accounts/use-account'; @@ -170,11 +182,84 @@ const findStatuses = ( ) .map(([key, data]) => [key[1], data]); +const useDeleteStatus = (statusId: string) => { + const client = useClient(); + const queryClient = useQueryClient(); + const { markStatusDeleted } = useStatusMetaActions(); + const { deleteStatus: deletePendingStatus } = usePendingStatusesActions(); + const { deleteStatus: deleteStatusFromTimelines } = useTimelinesActions(); + const { setComposeToStatus } = useComposeActions(); + const { openModal } = useModalsActions(); + + return useMutation({ + mutationFn: (_withRedraft?: boolean) => client.statuses.deleteStatus(statusId), + onMutate: () => { + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + if (!status) return; + + decrementReplyCount(status, queryClient); + }, + onSuccess: (source, withRedraft) => { + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + + const poll = status?.poll_id + ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) + : undefined; + + deletePendingStatus(statusId); + deleteStatusFromTimelines(statusId); + markStatusDeleted(statusId); + + if (withRedraft && status) { + setComposeToStatus(status, poll, source, withRedraft); + openModal('COMPOSE'); + } + }, + onError: () => { + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + if (!status) return; + + incrementReplyCount(status, queryClient); + }, + }); +}; + +const useDeleteStatusFromGroup = (statusId: string, groupId: string) => { + const client = useClient(); + const queryClient = useQueryClient(); + const { markStatusDeleted } = useStatusMetaActions(); + const { deleteStatus: deletePendingStatus } = usePendingStatusesActions(); + const { deleteStatus: deleteStatusFromTimelines } = useTimelinesActions(); + + return useMutation({ + mutationFn: () => client.experimental.groups.deleteGroupStatus(statusId, groupId), + onMutate: () => { + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + if (!status) return; + + decrementReplyCount(status, queryClient); + }, + onSuccess: () => { + deletePendingStatus(statusId); + deleteStatusFromTimelines(statusId); + markStatusDeleted(statusId); + }, + onError: () => { + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + if (!status) return; + + incrementReplyCount(status, queryClient); + }, + }); +}; + export { useMinimalStatus, useStatus, useStatusContext, useStatuses, + useDeleteStatus, + useDeleteStatusFromGroup, findStatuses, type MinifiedContext, type SelectedStatus, diff --git a/packages/nicolium/src/queries/utils/mutation-options.ts b/packages/nicolium/src/queries/utils/mutation-options.ts deleted file mode 100644 index ff7f9a591..000000000 --- a/packages/nicolium/src/queries/utils/mutation-options.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { DefaultError, UseMutationOptions } from '@tanstack/react-query'; - -// From https://github.com/TanStack/query/discussions/6096#discussioncomment-9685102 -const mutationOptions = < - TData = unknown, - TError = DefaultError, - TVariables = void, - TContext = unknown, ->( - options: UseMutationOptions, -): UseMutationOptions => options; - -export { mutationOptions }; diff --git a/packages/nicolium/src/stores/status-meta.ts b/packages/nicolium/src/stores/status-meta.ts index 8489dba44..4759117cf 100644 --- a/packages/nicolium/src/stores/status-meta.ts +++ b/packages/nicolium/src/stores/status-meta.ts @@ -12,6 +12,7 @@ type State = { localTargetLanguage?: string; showPollResults?: boolean; showFiltered?: boolean; + deleted?: boolean; } >; actions: { @@ -27,6 +28,7 @@ type State = { setStatusLanguage: (statusId: string, language: string) => void; toggleShowPollResults: (statusId: string) => void; unfilterStatus: (statusId: string) => void; + markStatusDeleted: (statusId: string) => void; }; }; @@ -126,6 +128,13 @@ const useStatusMetaStore = create()( state.statuses[statusId].showFiltered = true; }); }, + markStatusDeleted: (statusId) => { + set((state: State) => { + if (!state.statuses[statusId]) state.statuses[statusId] = {}; + + state.statuses[statusId].deleted = true; + }); + }, }, })), ); diff --git a/packages/nicolium/src/utils/badges.ts b/packages/nicolium/src/utils/badges.ts index 90c9fe306..d17dd9e79 100644 --- a/packages/nicolium/src/utils/badges.ts +++ b/packages/nicolium/src/utils/badges.ts @@ -29,4 +29,4 @@ const getBadges = (account: Pick) => { return filterBadges(tags); }; -export { tagToBadge, badgeToTag, filterBadges, getTagDiff, getBadges }; +export { tagToBadge, badgeToTag, getTagDiff, getBadges }; From c3b2e3954105e12d85c9428ce163aa28f1f3641f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:08:19 +0100 Subject: [PATCH 13/25] update links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- README.md | 8 ++++---- docs/building/nicolium.md | 2 +- docs/contributing/nicolium.md | 4 ++-- docs/contributing/pl-api.md | 4 ++-- packages/nicolium/CHANGELOG.md | 2 +- packages/nicolium/app.json | 2 +- packages/nicolium/package.json | 6 +++--- .../nicolium/src/instance/about.example/index.html | 2 +- packages/nicolium/src/pages/developers/create-app.tsx | 4 ++-- packages/pl-api/README.md | 4 ++-- packages/pl-api/llms.txt | 10 +++++----- packages/pl-api/package.json | 6 +++--- packages/pl-hooks/package.json | 6 +++--- packages/website/package.json | 4 ++-- packages/website/src/components/Footer.astro | 2 +- packages/website/src/components/Header.astro | 2 +- packages/website/src/pages/index.astro | 6 +++--- 17 files changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 530b1f63a..f08a1a560 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Nicolium -[![Codeberg Repo stars](https://img.shields.io/gitea/stars/mkljczk/nicolium?gitea_url=https%3A%2F%2Fcodeberg.org&logo=Codeberg)](https://codeberg.org/mkljczk/nicolium) -[![GitHub Repo stars](https://img.shields.io/github/stars/mkljczk/nicolium)](https://github.com/mkljczk/nicolium) -[![GitHub License](https://img.shields.io/github/license/mkljczk/nicolium)](https://github.com/mkljczk/nicolium?tab=AGPL-3.0-1-ov-file#readme) +[![Codeberg Repo stars](https://img.shields.io/gitea/stars/nicolium/nicolium?gitea_url=https%3A%2F%2Fcodeberg.org&logo=Codeberg)](https://codeberg.org/nicolium/nicolium) +[![GitHub Repo stars](https://img.shields.io/github/stars/nicolium/nicolium)](https://github.com/nicolium/nicolium) +[![GitHub License](https://img.shields.io/github/license/nicolium/nicolium)](https://github.com/nicolium/nicolium?tab=AGPL-3.0-1-ov-file#readme) [![Weblate project translated](https://img.shields.io/weblate/progress/nicolium)](https://hosted.weblate.org/engage/nicolium/) ![trans rights btw](https://img.shields.io/badge/-rights_btw-wtf?style=flat&label=trans&labelColor=5BCEFA&color=F5A9B8) @@ -30,7 +30,7 @@ The repository hosts Nicolium, but also libraries related to the project. Curren ## Contributing -This project is hosted on [Codeberg](https://codeberg.org/mkljczk/nicolium) and [GitHub](https://github.com/mkljczk/nicolium). You can open issues on Codeberg or create pull requests on both platforms. +This project is hosted on [Codeberg](https://codeberg.org/nicolium/nicolium) and [GitHub](https://github.com/nicolium/nicolium). You can open issues on Codeberg or create pull requests on both platforms. You can find more information about setting up the development environment in [the documentation](https://nicolium.app/docs/contributing/nicolium/). diff --git a/docs/building/nicolium.md b/docs/building/nicolium.md index b27a9d1d1..64f60fd0e 100644 --- a/docs/building/nicolium.md +++ b/docs/building/nicolium.md @@ -18,7 +18,7 @@ You can now proceed to fetching Nicolium Git repository, installing dependencies ```bash # Clone the Nicolium repository -git clone https://codeberg.org/mkljczk/nicolium.git +git clone https://codeberg.org/nicolium/nicoliumm.git cd nicolium # Install dependencies pnpm install diff --git a/docs/contributing/nicolium.md b/docs/contributing/nicolium.md index 0fe45ceb9..142211613 100644 --- a/docs/contributing/nicolium.md +++ b/docs/contributing/nicolium.md @@ -29,7 +29,7 @@ You can now proceed to fetching Nicolium Git repository, installing dependencies ```bash # Clone the Nicolium repository -git clone https://codeberg.org/mkljczk/nicolium.git +git clone https://codeberg.org/nicolium/nicoliumm.git cd nicolium # Install dependencies pnpm install @@ -60,7 +60,7 @@ pnpm -F nicolium test:watch ## Contributing guidelines -Nicolium hosts its repository on [Codeberg](https://codeberg.org/mkljczk/nicolium) and [GitHub](https://github.com/mkljczk/nicolium). While issues are only tracked on Codeberg, you can submit pull requests on both platforms. Remember to follow the [Code of Conduct](./code-of-conduct.mdx) when interacting with the community. +Nicolium hosts its repository on [Codeberg](https://codeberg.org/nicolium/nicoliumm) and [GitHub](https://github.comnicolium/nicoliumum). While issues are only tracked on Codeberg, you can submit pull requests on both platforms. Remember to follow the [Code of Conduct](./code-of-conduct.mdx) when interacting with the community. The project uses [ESLint](https://eslint.org/) and [Stylelint](https://stylelint.io/) for code style checking, which is automatically run on every commit using [Husky](https://typicode.github.io/husky). You can run the linters manually using the following command: ```bash diff --git a/docs/contributing/pl-api.md b/docs/contributing/pl-api.md index b4976f105..7c1440f4d 100644 --- a/docs/contributing/pl-api.md +++ b/docs/contributing/pl-api.md @@ -25,7 +25,7 @@ You can now proceed to fetching Nicolium Git repository, installing dependencies ```bash # Clone the Nicolium repository -git clone https://codeberg.org/mkljczk/nicolium.git +git clone https://codeberg.org/nicolium/nicoliumm.git cd nicolium # Install dependencies pnpm install @@ -37,7 +37,7 @@ This will start a Vite development server where you can use `pl-api` client insi ## Contributing guidelines -Nicolium monorepo is hosted on [Codeberg](https://codeberg.org/mkljczk/nicolium) and [GitHub](https://github.com/mkljczk/nicolium). While issues are only tracked on Codeberg, you can submit pull requests on both platforms. Remember to follow the [Code of Conduct](./code-of-conduct.mdx) when interacting with the community. +Nicolium monorepo is hosted on [Codeberg](https://codeberg.org/nicolium/nicoliumm) and [GitHub](https://github.comnicolium/nicoliumum). While issues are only tracked on Codeberg, you can submit pull requests on both platforms. Remember to follow the [Code of Conduct](./code-of-conduct.mdx) when interacting with the community. The project uses [ESLint](https://eslint.org/) for code style checking. You can run the linter using the following command: diff --git a/packages/nicolium/CHANGELOG.md b/packages/nicolium/CHANGELOG.md index a8c1ab547..109e9c145 100644 --- a/packages/nicolium/CHANGELOG.md +++ b/packages/nicolium/CHANGELOG.md @@ -6,7 +6,7 @@ Changes made since the project forked from Soapbox in April 2024. ### Major changes -- Switched to a separate library [`pl-api`](https://codeberg.org/mkljczk/nicolium/src/branch/develop/packages/pl-api) for Mastodon API integration. It is mostly written from scratch, inheriting minor code parts from Soapbox/Mastodon. This also comes with improved compatibility with various Mastodon API extensions and abstracts out the implementation details. +- Switched to a separate library [`pl-api`](https://codeberg.org/nicolium/nicolium/src/branch/develop/packages/pl-api) for Mastodon API integration. It is mostly written from scratch, inheriting minor code parts from Soapbox/Mastodon. This also comes with improved compatibility with various Mastodon API extensions and abstracts out the implementation details. ### Added diff --git a/packages/nicolium/app.json b/packages/nicolium/app.json index 26716204d..26ad84672 100644 --- a/packages/nicolium/app.json +++ b/packages/nicolium/app.json @@ -2,6 +2,6 @@ "name": "Nicolium", "description": "Mastodon-compatible social media front-end.", "keywords": ["fediverse"], - "website": "https://codeberg.org/mkljczk/nicolium", + "website": "https://codeberg.org/nicolium/nicolium", "stack": "container" } diff --git a/packages/nicolium/package.json b/packages/nicolium/package.json index 27a96ef2f..e0efff959 100644 --- a/packages/nicolium/package.json +++ b/packages/nicolium/package.json @@ -7,14 +7,14 @@ "fediverse", "pleroma" ], - "homepage": "https://codeberg.org/mkljczk/nicolium", + "homepage": "https://codeberg.org/nicolium/nicolium", "bugs": { - "url": "https://codeberg.org/mkljczk/nicolium/issues" + "url": "https://codeberg.org/nicolium/nicolium/issues" }, "license": "AGPL-3.0-or-later", "repository": { "type": "git", - "url": "https://codeberg.org/mkljczk/nicolium" + "url": "https://codeberg.org/nicolium/nicolium" }, "type": "module", "scripts": { diff --git a/packages/nicolium/src/instance/about.example/index.html b/packages/nicolium/src/instance/about.example/index.html index 6464eb004..f1ed5830a 100644 --- a/packages/nicolium/src/instance/about.example/index.html +++ b/packages/nicolium/src/instance/about.example/index.html @@ -10,7 +10,7 @@

Nicolium is free and open source (FOSS) software.

The Nicolium repository can be found at - Codeberg

diff --git a/packages/nicolium/src/pages/developers/create-app.tsx b/packages/nicolium/src/pages/developers/create-app.tsx index 9988fe872..e5e434ead 100644 --- a/packages/nicolium/src/pages/developers/create-app.tsx +++ b/packages/nicolium/src/pages/developers/create-app.tsx @@ -172,7 +172,7 @@ const CreateAppPage: React.FC = () => { > @@ -185,7 +185,7 @@ const CreateAppPage: React.FC = () => { > console.log(account.username)); ## Projects using pl-api -- Nicolium - Web client for Mastodon-compatible servers (https://codeberg.org/mkljczk/nicolium) -- **pl-hooks** - React hooks library for Mastodon APIs (https://codeberg.org/mkljczk/nicolium/src/branch/develop/packages/pl-hooks) +- Nicolium - Web client for Mastodon-compatible servers (https://codeberg.org/nicolium/nicolium) +- **pl-hooks** - React hooks library for Mastodon APIs (https://codeberg.org/nicolium/nicolium/src/branch/develop/packages/pl-hooks) ## Development @@ -159,8 +159,8 @@ stream.listen((event) => { ## Links -- Repository: https://codeberg.org/mkljczk/nicolium -- Issues: https://codeberg.org/mkljczk/nicolium/issues +- Repository: https://codeberg.org/nicolium/nicolium +- Issues: https://codeberg.org/nicolium/nicolium/issues - NPM package: https://www.npmjs.com/package/pl-api - API documentation: https://pl.mkljczk.pl/pl-api-docs - Mastodon API docs: https://docs.joinmastodon.org diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index 4d30fe447..81b0704ae 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -1,14 +1,14 @@ { "name": "pl-api", "version": "1.0.0-rc.99", - "homepage": "https://codeberg.org/mkljczk/nicolium/src/branch/develop/packages/pl-api", + "homepage": "https://codeberg.org/nicolium/nicolium/src/branch/develop/packages/pl-api", "bugs": { - "url": "https://codeberg.org/mkljczk/nicolium/issues" + "url": "https://codeberg.org/nicolium/nicolium/issues" }, "license": "AGPL-3.0-or-later", "repository": { "type": "git", - "url": "git+https://codeberg.org/mkljczk/nicolium.git" + "url": "git+https://codeberg.org/nicolium/nicolium.git" }, "files": [ "dist" diff --git a/packages/pl-hooks/package.json b/packages/pl-hooks/package.json index 2420d1177..8dfd0c097 100644 --- a/packages/pl-hooks/package.json +++ b/packages/pl-hooks/package.json @@ -1,14 +1,14 @@ { "name": "pl-hooks", "version": "0.0.15", - "homepage": "https://codeberg.org/mkljczk/nicolium/src/branch/develop/packages/pl-hooks", + "homepage": "https://codeberg.org/nicolium/nicolium/src/branch/develop/packages/pl-hooks", "bugs": { - "url": "https://codeberg.org/mkljczk/nicolium/issues" + "url": "https://codeberg.org/nicolium/nicolium/issues" }, "license": "AGPL-3.0-or-later", "repository": { "type": "git", - "url": "https://codeberg.org/mkljczk/nicolium" + "url": "https://codeberg.org/nicolium/nicolium" }, "files": [ "dist" diff --git a/packages/website/package.json b/packages/website/package.json index 82d568f92..9c80f9a2c 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -4,11 +4,11 @@ "version": "0.0.1", "homepage": "https://nicolium.app", "bugs": { - "url": "https://codeberg.org/mkljczk/nicolium/issues" + "url": "https://codeberg.org/nicolium/nicolium/issues" }, "repository": { "type": "git", - "url": "git+https://codeberg.org/mkljczk/nicolium.git" + "url": "git+https://codeberg.org/nicolium/nicolium.git" }, "scripts": { "dev": "astro dev", diff --git a/packages/website/src/components/Footer.astro b/packages/website/src/components/Footer.astro index e790ea9c9..4e2f2186e 100644 --- a/packages/website/src/components/Footer.astro +++ b/packages/website/src/components/Footer.astro @@ -8,7 +8,7 @@ const footerLinks = [ }, { label: "Source code", - href: "https://codeberg.org/mkljczk/nicolium", + href: "https://codeberg.org/nicolium/nicolium", target: "_blank", }, ]; diff --git a/packages/website/src/components/Header.astro b/packages/website/src/components/Header.astro index 20f77072e..7781132bd 100644 --- a/packages/website/src/components/Header.astro +++ b/packages/website/src/components/Header.astro @@ -18,7 +18,7 @@ const navItems = [ }, { label: "Source code", - href: "https://codeberg.org/mkljczk/nicolium", + href: "https://codeberg.org/nicolium/nicolium", target: "_blank", }, ]; diff --git a/packages/website/src/pages/index.astro b/packages/website/src/pages/index.astro index 68e659346..c74163dc9 100644 --- a/packages/website/src/pages/index.astro +++ b/packages/website/src/pages/index.astro @@ -30,7 +30,7 @@ import Screenshot6 from "../assets/screenshot6.png"; buttonLink="https://web.nicolium.app" buttonTarget="_blank" button2Text="Source code" - button2Link="https://codeberg.org/mkljczk/nicolium" + button2Link="https://codeberg.org/nicolium/nicolium" button2Target="_blank" />
@@ -67,10 +67,10 @@ import Screenshot6 from "../assets/screenshot6.png"; heading="Open source." subheading="Nicolium is free and open source software. You can participate in development, contribute to the project or report bugs." buttonText="Codeberg" - buttonLink="https://codeberg.org/mkljczk/nicolium" + buttonLink="https://codeberg.org/nicolium/nicolium" buttonTarget="_blank" button2Text="GitHub" - button2Link="https://github.com/mkljczk/nicolium" + button2Link="https://github.com/nicolium/nicolium" button2Target="_blank" imageSrc={Screenshot6} imageAlt="Screenshot of a part of pl-api source code" From 37eb842d11795f658a6a078c05ea0cdcc1ceda1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:09:25 +0100 Subject: [PATCH 14/25] update funding.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 37c6d4dbf..f81e90ae4 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: [mkljczk] +github: [mkljczk, pmysl] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username From 0ca6a7ccc19e42c65b3b102b454a5e9adb47ebc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:16:36 +0100 Subject: [PATCH 15/25] pl-api: note that llms.txt will not be maintained MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-api/llms.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/pl-api/llms.txt b/packages/pl-api/llms.txt index 6088205c2..1bfa35f1d 100644 --- a/packages/pl-api/llms.txt +++ b/packages/pl-api/llms.txt @@ -2,6 +2,8 @@ > A JavaScript library for interacting with Mastodon API-compatible servers +! for humans: this file is not actually maintained, i needed it once for a hackathon and didn't touch it since. i prefer writing documentation for humans lol + ## Overview pl-api is a TypeScript/JavaScript library that provides a type-safe client for interacting with Mastodon API-compatible servers. It supports 12 independent Mastodon API implementations and 5 forks, abstracting implementation differences between various backends. From c5b472a466bde4569450914e828b5cadcb88e085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:17:41 +0100 Subject: [PATCH 16/25] Update changelog symlink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- CHANGELOG.md | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 1 deletion(-) mode change 120000 => 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 120000 index 628ba404c..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -packages/pl-fe/CHANGELOG.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..109e9c145 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,165 @@ +# Changelog + +## Unreleased + +Changes made since the project forked from Soapbox in April 2024. + +### Major changes + +- Switched to a separate library [`pl-api`](https://codeberg.org/nicolium/nicolium/src/branch/develop/packages/pl-api) for Mastodon API integration. It is mostly written from scratch, inheriting minor code parts from Soapbox/Mastodon. This also comes with improved compatibility with various Mastodon API extensions and abstracts out the implementation details. + +### Added + +- Cat ears + +**Behavior:** + +- Notifications of the same type and reposts of the same post are grouped client-side. +- Date is displayed for notifications that are not about new posts. +- Replies to your posts are displayed differently to other mentions in notification list. +- Hashtags from the last line of a post are displayed in a separate component. Adapted [from Mastodon](https://github.com/mastodon/mastodon/pull/26499). +- Native grouped notifications are used on Mastodon. +- Likes, reposts and reactions lists are displayed on long press of respective buttons. +- User local time is displayed on profile and in account hover card, if specified in profile fields. +- Poll results can be displayed before voting. + +**Settings:** + +- You can add image description to your avatar/backend, if supported by backend. +- GoToSocial users can manage post interaction policies. +- Users can set interface theme color. +- Users can adjust interface size. +- Users can use system font for emoji rendering. + +**Composing posts:** + +- WYSIWYG text formatting, available if Markdown is supported. +- When writing posts, links to statuses are added as quotes, when supported by backend. +- You can select post language manually, when composing. +- You can write posts with multiple language versions, when supported by backend. +- Language detection is done client-side for composed posts, utilizing `fasttext.wasm.js`. +- Draft posts. They are stored locally only and work with any backend. +- New visibility scopes are supported – local-only and list-only for Pleroma. Local-only is a separate switch on GoToSocial. +- On backends that support explicit mentioning, you can choose to include mentions in your replies body. +- GoToSocial users can set per-post interaction policies. +- When adding a URL with tracking parameters, a suggestion to remove them from the URL is displayed. +- On supported backends, you can see post preview before posting. +- When entering a long, all-lowercase hashtag, a suggestion about hashtag accessibility is displayed. + +**Dashboard:** + +- Dashboard main page displays metrics included in Mastodon admin dashboard, if supported by backend. + +**Features:** + +- The most recent scrobble is displayed on user profile/card. +- Users can generate _interaction circles_ for their profiles. +- You can bite users, if supported by backend. +- You can browse Bubble timeline, if supported by backend. +- Mastodon displays trending articles on Search page. +- Posts can be addressed to lists of users, on Pleroma. +- Support for events with external registration. +- Added a dedicated wrench reaction button. +- Interaction requests are supported. You can review pending requests and you get informed if your backend doesn't let you reply to a post. Supported on GoToSocial. +- Events with external sign up are supported. +- Application name used to post a status is displayed. +- Outgoing follow requests are displayed, if supported by backend. +- It is possible to remove tracking parameters from URLs in displayed posts. +- Displayed media now have a button for alternative text preview. +- Links in displayed posts can be configured to always display target domain, even when it's not a part of their content. +- Users can configure redirects from popular websites to proxy services like Nitter and Piped in displayed posts. +- It is possible to boost a post with specific visibility, if supported by backend. +- Pleroma shoutbox is displayed on chats page. +- Displaying user-provided media can be disabled, media descriptions will be displayed instead. +- MFM can be displayed on compatible backends. +- Lists can be set as exclusive and replies policy can be set up, if supported by backend. +- Threads can be displayed in a linear view, similarly to traditional Pleroma-FE, as an alternative to tree view. You can expand spoilers with one click. + +### Changed + +**Behavior:** + +- Separated favourites from reaction emojis. Limit for one reaction per post is removed. Facebook-like emoji reaction bar is removed. +- Simplified sensitive text/media logic. +- Reposting user is mentioned, when replying to a reposted status. +- Notification types filtering options are reasonably merged. +- Search results are never cleared by just leaving the page. +- Status spoilers are displayed with a collapse/expand button, not in an overlay. +- Mentions and hashtags in bio no longer link to external pages. +- Quotes are counted with reblogs for non-detailed statuses. +- Reactions/favourites/reblogs list modal is displayed on long press. +- Various accessibility changes, focused on screen reader compatibility. + +**Settings:** + +- Moved missing description confirmation option back to Settings page. +- Profile fields can be reordered on the Edit profile page. +- Explicit addressing can be disabled on supported backends. +- Developers options are no longer hidden behind a challenge. + +**Composing posts:** + +- Custom emojis are now split into categories. +- GoToSocial users can post with date in the past. +- Post scopes were renamed to match wording used by Mastodon. + +**UI changes:** + +- Removed header. Search bar and profile dropdown are moved to the sidebar. Mobile sidebar button is moved to the thumb navigation. +- Floating action button for creating new posts is moved to the thumb navigation. +- Mobile sidebar UI is changed to look like a popover. +- Added some animations, improved consistency of the existing ones. +- Max width of the layout is increased. +- Updated Lists UI, to match the overall style. +- RSS button is displayed in account header for local users, when unauthenticated. +- Conversations page is always displayed, even when Chats are supported. +- Made it woke. +- Emojis are zoomed on hover. +- Event create/edit form is now a page, instead of a modal. +- A star is used for favorite icon, instead of a heart. +- Account avatars are squared. +- Background gradients can be disabled. Some other visual behavior depends on this setting. +- Tabler Icons were replaced with Phosphor Icons. +- The entire loading process uses the same animation now. +- Changed status info and notification title design. +- Redesigned audio/video player controls. + +**Internal:** + +- Migrated some local stores from Redux to Zustand. Other stores have been migrated away from `immutable`, before moving them either to Zustand or TanStack Query. +- Posts are now emojified during render, instead of when inserting posts to the state. +- Barrel exports are no longer used. +- Search page uses URL params now. +- Themes use `adoptedStyleSheets` to work with stricter CSP. +- Settings store uses a different key in development environment. +- Styles are being migrated from TailwindCSS to just scss. +- Default max image size is increased to match Mastodon limits. + +**Dependencies:** + +- Replaced `react-popper` and `react-overlays` with `@floating-ui/react`. +- `uuid` package is replaced by the `randomUUID()` method. + +### Removed + +- Removed Truth Social-specific features. +- Removed Nostr-specific stuff. +- Removed Rumble-specific embed handling. +- Removed option that pretends to disable name editing for verified users. +- Removed Call to Action banner. +- Removed links to block explorers for crypto addresses. +- Removed support for custom apps provided during build. +- Removed so called 'GDPR banner'. +- Removed embed page which loads too much for the use case. + +### Fixed + +- When initializing FaviconService, canvas export permission is checked. +- Improved regex for mentions in post composer. +- Post tombstones don't interrupt status navigation with hotkeys. +- Emojis are supported in poll options. +- Unsupported content types are not listed as available, when composing a post. +- Admin dashboard now works on non-Pleroma backends. +- Removed excessive calls to `fetchOwnAccounts`. +- Media modal displays the whole thread correctly. +- BrowsersList is actually being used now. From 8fe215ae31248d05c766af8f30949ae5b5973750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:22:33 +0100 Subject: [PATCH 17/25] update deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/package.json | 10 +- packages/pl-api/package.json | 2 +- packages/pl-hooks/package.json | 6 +- pnpm-lock.yaml | 864 +++++++-------------------------- 4 files changed, 173 insertions(+), 709 deletions(-) diff --git a/packages/nicolium/package.json b/packages/nicolium/package.json index e0efff959..15ff02c99 100644 --- a/packages/nicolium/package.json +++ b/packages/nicolium/package.json @@ -64,7 +64,7 @@ "@tailwindcss/typography": "^0.5.19", "@tanstack/react-pacer": "^0.20.0", "@tanstack/react-query": "^5.90.21", - "@tanstack/react-router": "^1.166.7", + "@tanstack/react-router": "^1.167.0", "@transfem-org/sfm-js": "^0.26.1", "@twemoji/svg": "^15.0.0", "@uidotdev/usehooks": "^2.4.1", @@ -143,20 +143,20 @@ "@types/react-dom": "^19.2.3", "@types/react-sparklines": "^1.7.5", "@types/react-swipeable-views": "^0.13.6", - "@typescript/native-preview": "7.0.0-dev.20260312.1", + "@typescript/native-preview": "7.0.0-dev.20260315.1", "@vitejs/plugin-react": "^5.1.4", "@vitest/coverage-v8": "4.1.0", "eslint-plugin-formatjs": "^6.3.0", "globals": "^17.4.0", - "jsdom": "^28.1.0", + "jsdom": "^29.0.0", "oxfmt": "^0.40.0", "oxlint": "^1.55.0", "oxlint-tsgolint": "^0.16.0", - "rollup-plugin-bundle-stats": "^4.21.10", + "rollup-plugin-bundle-stats": "^4.22.0", "stylelint": "^17.4.0", "stylelint-config-clean-order": "^8.0.1", "stylelint-config-standard-scss": "^17.0.0", - "stylelint-order": "^8.0.0", + "stylelint-order": "^8.1.0", "tailwindcss": "^3.4.19", "tslib": "^2.8.1", "type-fest": "^5.4.4", diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index 81b0704ae..ce29f58dd 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -49,7 +49,7 @@ }, "devDependencies": { "@types/http-link-header": "^1.0.7", - "@types/node": "^25.5.0", + "@types/node": "^25.3.5", "@types/semver": "^7.7.1", "@vitest/coverage-v8": "4.1.0", "oxfmt": "^0.40.0", diff --git a/packages/pl-hooks/package.json b/packages/pl-hooks/package.json index 8dfd0c097..c63a0a5c4 100644 --- a/packages/pl-hooks/package.json +++ b/packages/pl-hooks/package.json @@ -33,10 +33,10 @@ }, "devDependencies": { "@types/lodash": "^4.17.24", - "@types/node": "^25.3.3", + "@types/node": "^25.3.5", "@types/react": "^19.2.14", - "oxfmt": "^0.36.0", - "oxlint": "^1.51.0", + "oxfmt": "^0.40.0", + "oxlint": "^1.55.0", "oxlint-tsgolint": "^0.16.0", "typescript": "^5.9.3", "vite": "^7.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e9aa1536..1b4c788fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,8 +110,8 @@ importers: specifier: ^5.90.21 version: 5.90.21(react@19.2.4) '@tanstack/react-router': - specifier: ^1.166.7 - version: 1.166.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^1.167.0 + version: 1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@transfem-org/sfm-js': specifier: ^0.26.1 version: 0.26.1 @@ -342,14 +342,14 @@ importers: specifier: ^0.13.6 version: 0.13.6 '@typescript/native-preview': - specifier: 7.0.0-dev.20260312.1 - version: 7.0.0-dev.20260312.1 + specifier: 7.0.0-dev.20260315.1 + version: 7.0.0-dev.20260315.1 '@vitejs/plugin-react': specifier: ^5.1.4 version: 5.1.4(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) + version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) eslint-plugin-formatjs: specifier: ^6.3.0 version: 6.3.0(eslint@8.57.1) @@ -357,8 +357,8 @@ importers: specifier: ^17.4.0 version: 17.4.0 jsdom: - specifier: ^28.1.0 - version: 28.1.0 + specifier: ^29.0.0 + version: 29.0.0 oxfmt: specifier: ^0.40.0 version: 0.40.0 @@ -369,20 +369,20 @@ importers: specifier: ^0.16.0 version: 0.16.0 rollup-plugin-bundle-stats: - specifier: ^4.21.10 - version: 4.21.10(core-js@3.48.0)(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^4.22.0 + version: 4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) stylelint: specifier: ^17.4.0 version: 17.4.0(typescript@5.9.3) stylelint-config-clean-order: specifier: ^8.0.1 - version: 8.0.1(stylelint-order@8.0.0(stylelint@17.4.0(typescript@5.9.3)))(stylelint@17.4.0(typescript@5.9.3)) + version: 8.0.1(stylelint-order@8.1.1(stylelint@17.4.0(typescript@5.9.3)))(stylelint@17.4.0(typescript@5.9.3)) stylelint-config-standard-scss: specifier: ^17.0.0 version: 17.0.0(postcss@8.5.8)(stylelint@17.4.0(typescript@5.9.3)) stylelint-order: - specifier: ^8.0.0 - version: 8.0.0(stylelint@17.4.0(typescript@5.9.3)) + specifier: ^8.1.0 + version: 8.1.1(stylelint@17.4.0(typescript@5.9.3)) tailwindcss: specifier: ^3.4.19 version: 3.4.19(yaml@2.8.2) @@ -415,7 +415,7 @@ importers: version: 3.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vitest: specifier: ^4.1.0 - version: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) packages/pl-api: dependencies: @@ -445,14 +445,14 @@ importers: specifier: ^1.0.7 version: 1.0.7 '@types/node': - specifier: ^25.5.0 + specifier: ^25.3.5 version: 25.5.0 '@types/semver': specifier: ^7.7.1 version: 7.7.1 '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) + version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) oxfmt: specifier: ^0.40.0 version: 0.40.0 @@ -479,7 +479,7 @@ importers: version: 4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vitest: specifier: ^4.1.0 - version: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) ws: specifier: ^8.19.0 version: 8.19.0 @@ -506,17 +506,17 @@ importers: specifier: ^4.17.24 version: 4.17.24 '@types/node': - specifier: ^25.3.3 - version: 25.3.3 + specifier: ^25.3.5 + version: 25.5.0 '@types/react': specifier: ^19.2.14 version: 19.2.14 oxfmt: - specifier: ^0.36.0 - version: 0.36.0 + specifier: ^0.40.0 + version: 0.40.0 oxlint: - specifier: ^1.51.0 - version: 1.51.0(oxlint-tsgolint@0.16.0) + specifier: ^1.55.0 + version: 1.55.0(oxlint-tsgolint@0.16.0) oxlint-tsgolint: specifier: ^0.16.0 version: 0.16.0 @@ -525,10 +525,10 @@ importers: version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.3.3)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vite-plugin-dts: specifier: ^4.5.4ls - version: 4.5.4(@types/node@25.3.3)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) packages/website: dependencies: @@ -556,9 +556,6 @@ importers: packages: - '@acemir/cssom@0.9.31': - resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} - '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} @@ -576,8 +573,9 @@ packages: resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@asamuzakjp/dom-selector@6.8.1': - resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + '@asamuzakjp/dom-selector@7.0.3': + resolution: {integrity: sha512-Q6mU0Z6bfj6YvnX2k9n0JxiIwrCFN59x/nWmYQnAqP000ruX/yV+5bp/GRcF5T8ncvfwJQ7fgfP74DlpKExILA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -1132,25 +1130,25 @@ packages: '@bufbuild/protobuf@2.11.0': resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} - '@bundle-stats/cli-utils@4.21.10': - resolution: {integrity: sha512-x+wJvQFLU+82hLyIfzo3IR3oj7Ba+OaBF2Jhkg7i11bUkxR+KjTtdguwKcI5COI1ZT5dnPgngySEiNXd+vTuXA==} + '@bundle-stats/cli-utils@4.22.0': + resolution: {integrity: sha512-Po8/27Xd23MbbGsDyzvStB13PYIs/Y3sngrmyafIdrCUXZycXSq4X8LNYR1YfTxAzs3whOBaF5cVOEZQAT5NPQ==} engines: {node: '>= 14.0'} - '@bundle-stats/html-templates@4.21.10': - resolution: {integrity: sha512-PKOlrbsU7pOE2meid8Pudbi+CriH5nO/CQtVjVxtktgw+kDOMt2fZoasWaZ+pP6Zk07r6chX/F9ntBP9FjYVYg==} + '@bundle-stats/html-templates@4.22.0': + resolution: {integrity: sha512-ZM40lBnNyNCbxqLJ3Sh505Ttv94Y290XtY5MXqZDhP33nWJAZbQCZXEUCXtK6WlL+panoXr4iZlc3tJIQ/Xp1Q==} - '@bundle-stats/plugin-webpack-filter@4.21.10': - resolution: {integrity: sha512-r32p1Y4xNkXPP2vZIJBiKhynog8P2AJDbgw9lxROp3IBe/7M7i+wkxATSGHkuWaYisJDBj7/28acjl7YJYDuzA==} + '@bundle-stats/plugin-webpack-filter@4.22.0': + resolution: {integrity: sha512-JOXFjo7VdSIfyy4SOe3CJuvzP86Rvn8n0VhAiZjLbENRaEn2TETfVqzrAexI65fq+swStAYn0EKouMH7nDas9Q==} engines: {node: '>= 14.0'} peerDependencies: core-js: ^3.0.0 - '@bundle-stats/plugin-webpack-validate@4.21.10': - resolution: {integrity: sha512-izQrd6Bbjcuh+HzYhvcS2STdPRbtnKYQ1yTj1itxm09piVS9UEtbXqzfRsMkiKZ8lXr9IauKOWK7ZOSCIlcgkg==} + '@bundle-stats/plugin-webpack-validate@4.22.0': + resolution: {integrity: sha512-Mt0N40B0eeSgX5AqDouVhvUTmfWH3tbXkc6YrWj3nVkYwmMO5JpLXuDJOFm08yYG8tnd740RPV9lIyBtm/czBw==} engines: {node: '>= 14.0'} - '@bundle-stats/utils@4.21.10': - resolution: {integrity: sha512-eKsrvf+/nP4l1NaJXv7dsot0iSDuadsefBE4oL3+fCsir+kaMYVqknN9oh4EEaDb3ycxxWglE/50xDqFXTWEwg==} + '@bundle-stats/utils@4.22.0': + resolution: {integrity: sha512-2hY3cCvestmFRpKnMkf+bhWdrmBCzOeUN+jbyhAnwkU9FFZKW/5tFiUc8WOiC1UYrGzMRF55MSTha7WWdk36ng==} engines: {node: '>= 14.0'} peerDependencies: core-js: ^3.0.0 @@ -1193,6 +1191,14 @@ packages: '@csstools/css-syntax-patches-for-csstree@1.0.28': resolution: {integrity: sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==} + '@csstools/css-syntax-patches-for-csstree@1.1.1': + resolution: {integrity: sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + '@csstools/css-tokenizer@4.0.0': resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} @@ -1708,8 +1714,8 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@exodus/bytes@1.14.1': - resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==} + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: '@noble/hashes': ^1.8.0 || ^2.0.0 @@ -2149,97 +2155,48 @@ packages: '@oxc-project/types@0.113.0': resolution: {integrity: sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==} - '@oxfmt/binding-android-arm-eabi@0.36.0': - resolution: {integrity: sha512-Z4yVHJWx/swHHjtr0dXrBZb6LxS+qNz1qdza222mWwPTUK4L790+5i3LTgjx3KYGBzcYpjaiZBw4vOx94dH7MQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - '@oxfmt/binding-android-arm-eabi@0.40.0': resolution: {integrity: sha512-S6zd5r1w/HmqR8t0CTnGjFTBLDq2QKORPwriCHxo4xFNuhmOTABGjPaNvCJJVnrKBLsohOeiDX3YqQfJPF+FXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.36.0': - resolution: {integrity: sha512-3ElCJRFNPQl7jexf2CAa9XmAm8eC5JPrIDSjc9jSchkVSFTEqyL0NtZinBB2h1a4i4JgP1oGl/5G5n8YR4FN8Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - '@oxfmt/binding-android-arm64@0.40.0': resolution: {integrity: sha512-/mbS9UUP/5Vbl2D6osIdcYiP0oie63LKMoTyGj5hyMCK/SFkl3EhtyRAfdjPvuvHC0SXdW6ePaTKkBSq1SNcIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.36.0': - resolution: {integrity: sha512-nak4znWCqIExKhYSY/mz/lWsqWIpdsS7o0+SRzXR1Q0m7GrMcG1UrF1pS7TLGZhhkf7nTfEF7q6oZzJiodRDuw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - '@oxfmt/binding-darwin-arm64@0.40.0': resolution: {integrity: sha512-wRt8fRdfLiEhnRMBonlIbKrJWixoEmn6KCjKE9PElnrSDSXETGZfPb8ee+nQNTobXkCVvVLytp2o0obAsxl78Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.36.0': - resolution: {integrity: sha512-V4GP96thDnpKx6ADnMDnhIXNdtV+Ql9D4HUU+a37VTeVbs5qQSF/s6hhUP1b3xUqU7iRcwh72jUU2Y12rtGHAw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - '@oxfmt/binding-darwin-x64@0.40.0': resolution: {integrity: sha512-fzowhqbOE/NRy+AE5ob0+Y4X243WbWzDb00W+pKwD7d9tOqsAFbtWUwIyqqCoCLxj791m2xXIEeLH/3uz7zCCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.36.0': - resolution: {integrity: sha512-/xapWCADfI5wrhxpEUjhI9fnw7MV5BUZizVa8e24n3VSK6A3Y1TB/ClOP1tfxNspykFKXp4NBWl6NtDJP3osqQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - '@oxfmt/binding-freebsd-x64@0.40.0': resolution: {integrity: sha512-agZ9ITaqdBjcerRRFEHB8s0OyVcQW8F9ZxsszjxzeSthQ4fcN2MuOtQFWec1ed8/lDa50jSLHVE2/xPmTgtCfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.36.0': - resolution: {integrity: sha512-1lOmv61XMFIH5uNm27620kRRzWt/RK6tdn250BRDoG9W7OXGOQ5UyI1HVT+SFkoOoKztBiinWgi68+NA1MjBVQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': resolution: {integrity: sha512-ZM2oQ47p28TP1DVIp7HL1QoMUgqlBFHey0ksHct7tMXoU5BqjNvPWw7888azzMt25lnyPODVuye1wvNbvVUFOA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.36.0': - resolution: {integrity: sha512-vMH23AskdR1ujUS9sPck2Df9rBVoZUnCVY86jisILzIQ/QQ/yKUTi7tgnIvydPx7TyB/48wsQ5QMr5Knq5p/aw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.40.0': resolution: {integrity: sha512-RBFPAxRAIsMisKM47Oe6Lwdv6agZYLz02CUhVCD1sOv5ajAcRMrnwCFBPWwGXpazToW2mjnZxFos8TuFjTU15A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.36.0': - resolution: {integrity: sha512-Hy1V+zOBHpBiENRx77qrUTt5aPDHeCASRc8K5KwwAHkX2AKP0nV89eL17hsZrE9GmnXFjsNmd80lyf7aRTXsbw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@oxfmt/binding-linux-arm64-gnu@0.40.0': resolution: {integrity: sha512-Nb2XbQ+wV3W2jSIihXdPj7k83eOxeSgYP3N/SRXvQ6ZYPIk6Q86qEh5Gl/7OitX3bQoQrESqm1yMLvZV8/J7dA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2247,13 +2204,6 @@ packages: os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.36.0': - resolution: {integrity: sha512-SPGLJkOIHSIC6ABUQ5V8NqJpvYhMJueJv26NYqfCnwi/Mn6A61amkpJJ9Suy0Nmvs+OWESJpcebrBUbXPGZyQQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - '@oxfmt/binding-linux-arm64-musl@0.40.0': resolution: {integrity: sha512-tGmWhLD/0YMotCdfezlT6tC/MJG/wKpo4vnQ3Cq+4eBk/BwNv7EmkD0VkD5F/dYkT3b8FNU01X2e8vvJuWoM1w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2261,13 +2211,6 @@ packages: os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.36.0': - resolution: {integrity: sha512-3EuoyB8x9x8ysYJjbEO/M9fkSk72zQKnXCvpZMDHXlnY36/1qMp55Nm0PrCwjGO/1pen5hdOVkz9WmP3nAp2IQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@oxfmt/binding-linux-ppc64-gnu@0.40.0': resolution: {integrity: sha512-rVbFyM3e7YhkVnp0IVYjaSHfrBWcTRWb60LEcdNAJcE2mbhTpbqKufx0FrhWfoxOrW/+7UJonAOShoFFLigDqQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2275,13 +2218,6 @@ packages: os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.36.0': - resolution: {integrity: sha512-MpY3itLwpGh8dnywtrZtaZ604T1m715SydCKy0+qTxetv+IHzuA+aO/AGzrlzUNYZZmtWtmDBrChZGibvZxbRQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.40.0': resolution: {integrity: sha512-3ZqBw14JtWeEoLiioJcXSJz8RQyPE+3jLARnYM1HdPzZG4vk+Ua8CUupt2+d+vSAvMyaQBTN2dZK+kbBS/j5mA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2289,13 +2225,6 @@ packages: os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.36.0': - resolution: {integrity: sha512-mmDhe4Vtx+XwQPRPn/V25+APnkApYgZ23q+6GVsNYY98pf3aU0aI3Me96pbRs/AfJ1jIiGC+/6q71FEu8dHcHw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [musl] - '@oxfmt/binding-linux-riscv64-musl@0.40.0': resolution: {integrity: sha512-JJ4PPSdcbGBjPvb+O7xYm2FmAsKCyuEMYhqatBAHMp/6TA6rVlf9Z/sYPa4/3Bommb+8nndm15SPFRHEPU5qFA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2303,13 +2232,6 @@ packages: os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.36.0': - resolution: {integrity: sha512-AYXhU+DmNWLSnvVwkHM92fuYhogtVHab7UQrPNaDf1sxadugg9gWVmcgJDlIwxJdpk5CVW/TFvwUKwI432zhhA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@oxfmt/binding-linux-s390x-gnu@0.40.0': resolution: {integrity: sha512-Kp0zNJoX9Ik77wUya2tpBY3W9f40VUoMQLWVaob5SgCrblH/t2xr/9B2bWHfs0WCefuGmqXcB+t0Lq77sbBmZw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2317,13 +2239,6 @@ packages: os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.36.0': - resolution: {integrity: sha512-H16QhhQ3usoakMleiAAQ2mg0NsBDAdyE9agUgfC8IHHh3jZEbr0rIKwjEqwbOHK5M0EmfhJmr+aGO/MgZPsneA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.40.0': resolution: {integrity: sha512-7YTCNzleWTaQTqNGUNQ66qVjpoV6DjbCOea+RnpMBly2bpzrI/uu7Rr+2zcgRfNxyjXaFTVQKaRKjqVdeUfeVA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2331,13 +2246,6 @@ packages: os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.36.0': - resolution: {integrity: sha512-EFFGkixA39BcmHiCe2ECdrq02D6FCve5ka6ObbvrheXl4V+R0U/E+/uLyVx1X65LW8TA8QQHdnbdDallRekohw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - '@oxfmt/binding-linux-x64-musl@0.40.0': resolution: {integrity: sha512-hWnSzJ0oegeOwfOEeejYXfBqmnRGHusgtHfCPzmvJvHTwy1s3Neo59UKc1CmpE3zxvrCzJoVHos0rr97GHMNPw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2345,48 +2253,24 @@ packages: os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.36.0': - resolution: {integrity: sha512-zr/t369wZWFOj1qf06Z5gGNjFymfUNDrxKMmr7FKiDRVI1sNsdKRCuRL4XVjtcptKQ+ao3FfxLN1vrynivmCYg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - '@oxfmt/binding-openharmony-arm64@0.40.0': resolution: {integrity: sha512-28sJC1lR4qtBJGzSRRbPnSW3GxU2+4YyQFE6rCmsUYqZ5XYH8jg0/w+CvEzQ8TuAQz5zLkcA25nFQGwoU0PT3Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.36.0': - resolution: {integrity: sha512-FxO7UksTv8h4olzACgrqAXNF6BP329+H322323iDrMB5V/+a1kcAw07fsOsUmqNrb9iJBsCQgH/zqcqp5903ag==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - '@oxfmt/binding-win32-arm64-msvc@0.40.0': resolution: {integrity: sha512-cDkRnyT0dqwF5oIX1Cv59HKCeZQFbWWdUpXa3uvnHFT2iwYSSZspkhgjXjU6iDp5pFPaAEAe9FIbMoTgkTmKPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.36.0': - resolution: {integrity: sha512-OjoMQ89H01M0oLMfr/CPNH1zi48ZIwxAKObUl57oh7ssUBNDp/2Vjf7E1TQ8M4oj4VFQ/byxl2SmcPNaI2YNDg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.40.0': resolution: {integrity: sha512-7rPemBJjqm5Gkv6ZRCPvK8lE6AqQ/2z31DRdWazyx2ZvaSgL7QGofHXHNouRpPvNsT9yxRNQJgigsWkc+0qg4w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.36.0': - resolution: {integrity: sha512-MoyeQ9S36ZTz/4bDhOKJgOBIDROd4dQ5AkT9iezhEaUBxAPdNX9Oq0jD8OSnCj3G4wam/XNxVWKMA52kmzmPtQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.40.0': resolution: {integrity: sha512-/Zmj0yTYSvmha6TG1QnoLqVT7ZMRDqXvFXXBQpIjteEwx9qvUYMBH2xbiOFhDeMUJkGwC3D6fdKsFtaqUvkwNA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2423,97 +2307,48 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.51.0': - resolution: {integrity: sha512-jJYIqbx4sX+suIxWstc4P7SzhEwb4ArWA2KVrmEuu9vH2i0qM6QIHz/ehmbGE4/2fZbpuMuBzTl7UkfNoqiSgw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - '@oxlint/binding-android-arm-eabi@1.55.0': resolution: {integrity: sha512-NhvgAhncTSOhRahQSCnkK/4YIGPjTmhPurQQ2dwt2IvwCMTvZRW5vF2K10UBOxFve4GZDMw6LtXZdC2qeuYIVQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.51.0': - resolution: {integrity: sha512-GtXyBCcH4ti98YdiMNCrpBNGitx87EjEWxevnyhcBK12k/Vu4EzSB45rzSC4fGFUD6sQgeaxItRCEEWeVwPafw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - '@oxlint/binding-android-arm64@1.55.0': resolution: {integrity: sha512-P9iWRh+Ugqhg+D7rkc7boHX8o3H2h7YPcZHQIgvVBgnua5tk4LR2L+IBlreZs58/95cd2x3/004p5VsQM9z4SA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.51.0': - resolution: {integrity: sha512-3QJbeYaMHn6Bh2XeBXuITSsbnIctyTjvHf5nRjKYrT9pPeErNIpp5VDEeAXC0CZSwSVTsc8WOSDwgrAI24JolQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - '@oxlint/binding-darwin-arm64@1.55.0': resolution: {integrity: sha512-esakkJIt7WFAhT30P/Qzn96ehFpzdZ1mNuzpOb8SCW7lI4oB8VsyQnkSHREM671jfpuBb/o2ppzBCx5l0jpgMA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.51.0': - resolution: {integrity: sha512-NzErhMaTEN1cY0E8C5APy74lw5VwsNfJfVPBMWPVQLqAbO0k4FFLjvHURvkUL+Y18Wu+8Vs1kbqPh2hjXYA4pg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - '@oxlint/binding-darwin-x64@1.55.0': resolution: {integrity: sha512-xDMFRCCAEK9fOH6As2z8ELsC+VDGSFRHwIKVSilw+xhgLwTDFu37rtmRbmUlx8rRGS6cWKQPTc47AVxAZEVVPQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.51.0': - resolution: {integrity: sha512-msAIh3vPAoKoHlOE/oe6Q5C/n9umypv/k81lED82ibrJotn+3YG2Qp1kiR8o/Dg5iOEU97c6tl0utxcyFenpFw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - '@oxlint/binding-freebsd-x64@1.55.0': resolution: {integrity: sha512-mYZqnwUD7ALCRxGenyLd1uuG+rHCL+OTT6S8FcAbVm/ZT2AZMGjvibp3F6k1SKOb2aeqFATmwRykrE41Q0GWVw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.51.0': - resolution: {integrity: sha512-CqQPcvqYyMe9ZBot2stjGogEzk1z8gGAngIX7srSzrzexmXixwVxBdFZyxTVM0CjGfDeV+Ru0w25/WNjlMM2Hw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@oxlint/binding-linux-arm-gnueabihf@1.55.0': resolution: {integrity: sha512-LcX6RYcF9vL9ESGwJW3yyIZ/d/ouzdOKXxCdey1q0XJOW1asrHsIg5MmyKdEBR4plQx+shvYeQne7AzW5f3T1w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.51.0': - resolution: {integrity: sha512-dstrlYQgZMnyOssxSbolGCge/sDbko12N/35RBNuqLpoPbft2aeBidBAb0dvQlyBd9RJ6u8D4o4Eh8Un6iTgyQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.55.0': resolution: {integrity: sha512-C+8GS1rPtK+dI7mJFkqoRBkDuqbrNihnyYQsJPS9ez+8zF9JzfvU19lawqt4l/Y23o5uQswE/DORa8aiXUih3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.51.0': - resolution: {integrity: sha512-QEjUpXO7d35rP1/raLGGbAsBLLGZIzV3ZbeSjqWlD3oRnxpRIZ6iL4o51XQHkconn3uKssc+1VKdtHJ81BBhDA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@oxlint/binding-linux-arm64-gnu@1.55.0': resolution: {integrity: sha512-ErLE4XbmcCopA4/CIDiH6J1IAaDOMnf/KSx/aFObs4/OjAAM3sFKWGZ57pNOMxhhyBdcmcXwYymph9GwcpcqgQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2521,13 +2356,6 @@ packages: os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.51.0': - resolution: {integrity: sha512-YSJua5irtG4DoMAjUapDTPhkQLHhBIY0G9JqlZS6/SZPzqDkPku/1GdWs0D6h/wyx0Iz31lNCfIaWKBQhzP0wQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - '@oxlint/binding-linux-arm64-musl@1.55.0': resolution: {integrity: sha512-/kp65avi6zZfqEng56TTuhiy3P/3pgklKIdf38yvYeJ9/PgEeRA2A2AqKAKbZBNAqUzrzHhz9jF6j/PZvhJzTQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2535,13 +2363,6 @@ packages: os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.51.0': - resolution: {integrity: sha512-7L4Wj2IEUNDETKssB9IDYt16T6WlF+X2jgC/hBq3diGHda9vJLpAgb09+D3quFq7TdkFtI7hwz/jmuQmQFPc1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@oxlint/binding-linux-ppc64-gnu@1.55.0': resolution: {integrity: sha512-A6pTdXwcEEwL/nmz0eUJ6WxmxcoIS+97GbH96gikAyre3s5deC7sts38ZVVowjS2QQFuSWkpA4ZmQC0jZSNvJQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2549,13 +2370,6 @@ packages: os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.51.0': - resolution: {integrity: sha512-cBUHqtOXy76G41lOB401qpFoKx1xq17qYkhWrLSM7eEjiHM9sOtYqpr6ZdqCnN9s6ZpzudX4EkeHOFH2E9q0vA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.55.0': resolution: {integrity: sha512-clj0lnIN+V52G9tdtZl0LbdTSurnZ1NZj92Je5X4lC7gP5jiCSW+Y/oiDiSauBAD4wrHt2S7nN3pA0zfKYK/6Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2563,13 +2377,6 @@ packages: os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.51.0': - resolution: {integrity: sha512-WKbg8CysgZcHfZX0ixQFBRSBvFZUHa3SBnEjHY2FVYt2nbNJEjzTxA3ZR5wMU0NOCNKIAFUFvAh5/XJKPRJuJg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [musl] - '@oxlint/binding-linux-riscv64-musl@1.55.0': resolution: {integrity: sha512-NNu08pllN5x/O94/sgR3DA8lbrGBnTHsINZZR0hcav1sj79ksTiKKm1mRzvZvacwQ0hUnGinFo+JO75ok2PxYg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2577,13 +2384,6 @@ packages: os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.51.0': - resolution: {integrity: sha512-N1QRUvJTxqXNSu35YOufdjsAVmKVx5bkrggOWAhTWBc3J4qjcBwr1IfyLh/6YCg8sYRSR1GraldS9jUgJL/U4A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@oxlint/binding-linux-s390x-gnu@1.55.0': resolution: {integrity: sha512-BvfQz3PRlWZRoEZ17dZCqgQsMRdpzGZomJkVATwCIGhHVVeHJMQdmdXPSjcT1DCNUrOjXnVyj1RGDj5+/Je2+Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2591,13 +2391,6 @@ packages: os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.51.0': - resolution: {integrity: sha512-e0Mz0DizsCoqNIjeOg6OUKe8JKJWZ5zZlwsd05Bmr51Jo3AOL4UJnPvwKumr4BBtBrDZkCmOLhCvDGm95nJM2g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.55.0': resolution: {integrity: sha512-ngSOoFCSBMKVQd24H8zkbcBNc7EHhjnF1sv3mC9NNXQ/4rRjI/4Dj9+9XoDZeFEkF1SX1COSBXF1b2Pr9rqdEw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2605,13 +2398,6 @@ packages: os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.51.0': - resolution: {integrity: sha512-wD8HGTWhYBKXvRDvoBVB1y+fEYV01samhWQSy1Zkxq2vpezvMnjaFKRuiP6tBNITLGuffbNDEXOwcAhJ3gI5Ug==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - '@oxlint/binding-linux-x64-musl@1.55.0': resolution: {integrity: sha512-BDpP7W8GlaG7BR6QjGZAleYzxoyKc/D24spZIF2mB3XsfALQJJT/OBmP8YpeTb1rveFSBHzl8T7l0aqwkWNdGA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2619,48 +2405,24 @@ packages: os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.51.0': - resolution: {integrity: sha512-5NSwQ2hDEJ0GPXqikjWtwzgAQCsS7P9aLMNenjjKa+gknN3lTCwwwERsT6lKXSirfU3jLjexA2XQvQALh5h27w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - '@oxlint/binding-openharmony-arm64@1.55.0': resolution: {integrity: sha512-PS6GFvmde/pc3fCA2Srt51glr8Lcxhpf6WIBFfLphndjRrD34NEcses4TSxQrEcxYo6qVywGfylM0ZhSCF2gGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.51.0': - resolution: {integrity: sha512-JEZyah1M0RHMw8d+jjSSJmSmO8sABA1J1RtrHYujGPeCkYg1NeH0TGuClpe2h5QtioRTaF57y/TZfn/2IFV6fA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - '@oxlint/binding-win32-arm64-msvc@1.55.0': resolution: {integrity: sha512-P6JcLJGs/q1UOvDLzN8otd9JsH4tsuuPDv+p7aHqHM3PrKmYdmUvkNj4K327PTd35AYcznOCN+l4ZOaq76QzSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.51.0': - resolution: {integrity: sha512-q3cEoKH6kwjz/WRyHwSf0nlD2F5Qw536kCXvmlSu+kaShzgrA0ojmh45CA81qL+7udfCaZL2SdKCZlLiGBVFlg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.55.0': resolution: {integrity: sha512-gzkk4zE2zsE+WmRxFOiAZHpCpUNDFytEakqNXoNHW+PnYEOTPKDdW6nrzgSeTbGKVPXNAKQnRnMgrh7+n3Xueg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.51.0': - resolution: {integrity: sha512-Q14+fOGb9T28nWF/0EUsYqERiRA7cl1oy4TJrGmLaqhm+aO2cV+JttboHI3CbdeMCAyDI1+NoSlrM7Melhp/cw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@oxlint/binding-win32-x64-msvc@1.55.0': resolution: {integrity: sha512-ZFALNow2/og75gvYzNP7qe+rREQ5xunktwA+lgykoozHZ6hw9bqg4fn5j2UvG4gIn1FXqrZHkOAXuPf5+GOYTQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3274,8 +3036,8 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-router@1.166.7': - resolution: {integrity: sha512-LLcXu2nrCn2WL+w0YAbg3CRZIIO2cYVSC3y+ZYlFBxBs4hh8eoNP1EWFvRLZGCFYpqON7x6qUf1u0W7tH0cJJw==} + '@tanstack/react-router@1.167.0': + resolution: {integrity: sha512-U7CamtXjuC8ixg1c32Rj/4A2OFBnjtMLdbgbyOGHrFHE7ULWS/yhnZLVXff0QSyn6qF92Oecek9mDMHCaTnB2Q==} engines: {node: '>=20.19'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -3293,8 +3055,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.166.7': - resolution: {integrity: sha512-MCc8wYIIcxmbeidM8PL2QeaAjUIHyhEDIZPW6NGfn/uwvyi+K2ucn3AGCxxcXl4JGGm0Mx9+7buYl1v3HdcFrg==} + '@tanstack/router-core@1.167.0': + resolution: {integrity: sha512-pnaaUP+vMQEyL2XjZGe2PXmtzulxvXfGyvEMUs+AEBaNEk77xWA88bl3ujiBRbUxzpK0rxfJf+eSKPdZmBMFdQ==} engines: {node: '>=20.19'} '@tanstack/store@0.8.1': @@ -3431,9 +3193,6 @@ packages: '@types/node@22.19.13': resolution: {integrity: sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==} - '@types/node@25.3.3': - resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} - '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} @@ -3485,43 +3244,43 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-AhPdPuVe4osxWoeImS21jVhc0VJ2QnzLUZtEFMakY0Rf70C0b6il/m7hwRf9wkr9xXZLVOVJ1kYrpvQRuHFE0Q==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-UhRPJWUZMHO1Xuurjr28gR98+nwD+QBJiUWzTLLrvhkGEOA8IG9Q8hqzJ07AQb/V251F/MsEjOSEnHmgGNT6Dg==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-9I0P1/c/mQ6UVcQq7SYY/FJD23IN5T2y4GbSFOKQvzNVASV0tMnX4YV8YNf6b5jcwCzrVcrGNKKgWCj8xEFf8Q==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-7a2P4KyJQF83Zj+3Vi/VCV9I/0lC+2QRgD4JEIh1H0FliRDZILUIbiAqWaksHHl4pBMtlRZr+1hjad5vPUsQmA==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-xwoMywagcvx9F2ocM+ybeg7eH9PHDpx1FBGOrloL1/xkGC4BCrn/RcaAe0AhzXzoJfHHmg7Sz9VzYmTR4N1Kqw==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-DwKY8zVqsQx0+golSKopbzZVIVfnsUVVt7s6Wg5kiAYrFy32TNKqANfbiWLBe5PGSpYu9Mx1XWLpsvi6/58BWw==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-/nAOhSLTxMJfHY+2cKdUxi2wYadf3g1GtC3VzgPfZMNxA28dJ8x75T26aSLaFYluh7cCSAwuGesCImijQDS2Lw==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-n2MhBeDAmdbp6DOtz+I2JeGpNzepzvXxPEDpRE+syAT5mTXstwTk+9w4rF2SLt1YgLuPmon7iZhkUyPTd0dD7A==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-vZs0LLpZw50Ac0TCmF9ND7KphJBhOfp9fxLhC+hFWaUU1iCQRjv1MtvroitF5OJKb21qFPJxkU+kfhlCRxLfqg==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-5kRxtdfqF9X1q/vIg7myq7D0MmF3GywZXS3mmZq6TQEFiu5IClHPaQCuCQqYdtHmHtZllHZz0VaEtnvV9Vamrw==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-4LY/gd9cj1xDY2nEthB7WDW4j/fIYJ9wp9H71nOLd0wNNtkfqRXWSkQEeb+RByhV+dIb/n6kWbQQMeNfk7q4VQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-u0ixXTG/97k2eVRQfwafGckHuK60st5ADYn23KQvZJxeUZ47XWIjzCL1JLcoiRexEAwsEerireyy32l4LxA9BQ==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-EP2JPo9s9EPUwXSX83qTImlDHhgkLeBbJ2MMdj+XrfBltHAvHKktzeSS73UhP77s/TnTkJR6BTWHENKKvLRbGQ==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-iSnVmAZgogCup/5SOF2h+Hwdywa4cGNIw9z8jpTVJof56w5GR9Hx3vN9UszqszjGxPNobYERcOk8QhGZO8uf5g==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260312.1': - resolution: {integrity: sha512-FwhlXG/yG0d7b2UmooBYyszLMpICRYdYGE6v65ZlMnH7cWKQyyFpMFgH9suRf3Np4QCbN+7qisj+F23kQOidVw==} + '@typescript/native-preview@7.0.0-dev.20260315.1': + resolution: {integrity: sha512-t+st0mCz4HpvODTTlj2XxIQtiNT7L7lxP91790MOfA0xTRgwu7ERYV7WB1SbXRyrFDIwuO1bZqT0E0P4qcL4RQ==} hasBin: true '@uidotdev/usehooks@2.4.1': @@ -3711,10 +3470,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -4192,6 +3947,10 @@ packages: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-what@6.2.2: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} @@ -4226,10 +3985,6 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - cssstyle@6.1.0: - resolution: {integrity: sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==} - engines: {node: '>=20'} - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -4958,14 +4713,6 @@ packages: resolution: {integrity: sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==} engines: {node: '>=6.0.0'} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} @@ -5278,9 +5025,9 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - jsdom@28.1.0: - resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + jsdom@29.0.0: + resolution: {integrity: sha512-9FshNB6OepopZ08unmmGpsF7/qCjxGPbo3NbgfJAnPeHXnsODE9WWffXZtRFRFe0ntzaAOcSKNJFz8wiyvF1jQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 peerDependenciesMeta: @@ -5535,6 +5282,10 @@ packages: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} + lru-cache@11.2.7: + resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -5938,11 +5689,6 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - oxfmt@0.36.0: - resolution: {integrity: sha512-/ejJ+KoSW6J9bcNT9a9UtJSJNWhJ3yOLSBLbkoFHJs/8CZjmaZVZAJe4YgO1KMJlKpNQasrn/G9JQUEZI3p0EQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - oxfmt@0.40.0: resolution: {integrity: sha512-g0C3I7xUj4b4DcagevM9kgH6+pUHytikxUcn3/VUkvzTNaaXBeyZqb7IBsHwojeXm4mTBEC/aBjBTMVUkZwWUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5952,16 +5698,6 @@ packages: resolution: {integrity: sha512-4RuJK2jP08XwqtUu+5yhCbxEauCm6tv2MFHKEMsjbosK2+vy5us82oI3VLuHwbNyZG7ekZA26U2LLHnGR4frIA==} hasBin: true - oxlint@1.51.0: - resolution: {integrity: sha512-g6DNPaV9/WI9MoX2XllafxQuxwY1TV++j7hP8fTJByVBuCoVtm3dy9f/2vtH/HU40JztcgWF4G7ua+gkainklQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - oxlint-tsgolint: '>=0.15.0' - peerDependenciesMeta: - oxlint-tsgolint: - optional: true - oxlint@1.55.0: resolution: {integrity: sha512-T+FjepiyWpaZMhekqRpH8Z3I4vNM610p6w+Vjfqgj5TZUxHXl7N8N5IPvmOU8U4XdTRxqtNNTh9Y4hLtr7yvFg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6692,13 +6428,13 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup-plugin-bundle-stats@4.21.10: - resolution: {integrity: sha512-vcUqcAZpXHhBf/Gu7OLcRIVU4Rz6ZXISNCsbuFdpfkGcYDOOcB+pYkTXx73ZHHdD4RkT5SlkrQQwdNKfrTPc5g==} + rollup-plugin-bundle-stats@4.22.0: + resolution: {integrity: sha512-7vTDc6FWf+9yDhjarPDGZ6MnejLb+njsAzO51dR7YJK1phB+HB+RutXaIzYPW/Bh/bpKfPGs3xYaieHdjmFaBA==} engines: {node: '>= 16.0'} peerDependencies: rolldown: ^1.0.0-beta.0 rollup: ^3.0.0 || ^4.0.0 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: rolldown: optional: true @@ -6707,13 +6443,13 @@ packages: vite: optional: true - rollup-plugin-stats@1.5.6: - resolution: {integrity: sha512-uWclISMxZcWfmcnwOJgJ2sLIKsXvDUI7yFfqXFq6g20IeBE+bc+RhXcvMAO2vPN3A66wBvIF6XkGhI7v/KuwGw==} + rollup-plugin-stats@2.1.0: + resolution: {integrity: sha512-mkthlSVb8T2AKfdt/FEJqB/1Nvsr4G+otKZ4dhPKjyWQA6fdL7STRPA4epwVOBsj1yYYv/dcgAmfwfNtMpZ6sw==} engines: {node: '>=18'} peerDependencies: rolldown: ^1.0.0-beta.0 rollup: ^3.0.0 || ^4.0.0 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: rolldown: optional: true @@ -6722,13 +6458,13 @@ packages: vite: optional: true - rollup-plugin-webpack-stats@2.1.11: - resolution: {integrity: sha512-F4R1Lbg9cmOE/C+UjIQ2EghT/bFpe2eF+ei1g1PBUp2EpQgYeED7So/pwC/ROi+4X1LXfi1sQHR5A0zgwUr/Kw==} + rollup-plugin-webpack-stats@3.1.0: + resolution: {integrity: sha512-c8J0EJynWkSpluDs5w1yQj27ST9bp6SUWC3/Ql3eGTGpDKgpBnwmh8S/sveUhppbNppUf1oFtaqQh+X8EM3Hjg==} engines: {node: '>=18'} peerDependencies: rolldown: ^1.0.0-beta.0 rollup: ^3.0.0 || ^4.0.0 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: rolldown: optional: true @@ -7198,8 +6934,8 @@ packages: peerDependencies: stylelint: ^17.0.0 - stylelint-order@8.0.0: - resolution: {integrity: sha512-1oAwPRz6Ba8u9LPjgvdbeMjZszHhjY6DBBK+xMlS3IC89GdTsvAPpGYvW+dkn6pxc4ZaI1S959g4c8CftZbhIg==} + stylelint-order@8.1.1: + resolution: {integrity: sha512-LqsEB6VggJuu5v10RtkrQsBObcdwBE7GuAOlwfc/LR3VL/w8UqKX2BOLIjhyGt0Gne/njo7gRNGiJAKhfmPMNw==} engines: {node: '>=20.19.0'} peerDependencies: stylelint: ^16.18.0 || ^17.0.0 @@ -7372,8 +7108,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} tr46@1.0.1: @@ -7494,8 +7230,8 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici@7.22.0: - resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==} + undici@7.24.3: + resolution: {integrity: sha512-eJdUmK/Wrx2d+mnWWmwwLRyA7OQCkLap60sk3dOK4ViZR7DKwwptwuIvFBg2HaiP9ESaEdhtpSymQPvytpmkCA==} engines: {node: '>=20.18.1'} unicode-canonical-property-names-ecmascript@2.0.1: @@ -8109,8 +7845,6 @@ packages: snapshots: - '@acemir/cssom@0.9.31': {} - '@adobe/css-tools@4.4.4': {} '@alloc/quick-lru@5.2.0': {} @@ -8128,15 +7862,15 @@ snapshots: '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - lru-cache: 11.2.6 + lru-cache: 11.2.7 - '@asamuzakjp/dom-selector@6.8.1': + '@asamuzakjp/dom-selector@7.0.3': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 - css-tree: 3.1.0 + css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.6 + lru-cache: 11.2.7 '@asamuzakjp/nwsapi@2.3.9': {} @@ -8888,15 +8622,15 @@ snapshots: '@bramus/specificity@2.4.2': dependencies: - css-tree: 3.1.0 + css-tree: 3.2.1 '@bufbuild/protobuf@2.11.0': {} - '@bundle-stats/cli-utils@4.21.10(core-js@3.48.0)': + '@bundle-stats/cli-utils@4.22.0(core-js@3.48.0)': dependencies: - '@bundle-stats/html-templates': 4.21.10 - '@bundle-stats/plugin-webpack-filter': 4.21.10(core-js@3.48.0) - '@bundle-stats/utils': 4.21.10(core-js@3.48.0)(lodash@4.17.23) + '@bundle-stats/html-templates': 4.22.0 + '@bundle-stats/plugin-webpack-filter': 4.22.0(core-js@3.48.0) + '@bundle-stats/utils': 4.22.0(core-js@3.48.0)(lodash@4.17.23) find-cache-dir: 3.3.2 lodash: 4.17.23 stream-chain: 3.4.0 @@ -8904,23 +8638,23 @@ snapshots: transitivePeerDependencies: - core-js - '@bundle-stats/html-templates@4.21.10': {} + '@bundle-stats/html-templates@4.22.0': {} - '@bundle-stats/plugin-webpack-filter@4.21.10(core-js@3.48.0)': + '@bundle-stats/plugin-webpack-filter@4.22.0(core-js@3.48.0)': dependencies: core-js: 3.48.0 tslib: 2.8.1 - '@bundle-stats/plugin-webpack-validate@4.21.10': + '@bundle-stats/plugin-webpack-validate@4.22.0': dependencies: lodash: 4.17.23 superstruct: 2.0.2 tslib: 2.8.1 - '@bundle-stats/utils@4.21.10(core-js@3.48.0)(lodash@4.17.23)': + '@bundle-stats/utils@4.22.0(core-js@3.48.0)(lodash@4.17.23)': dependencies: - '@bundle-stats/plugin-webpack-filter': 4.21.10(core-js@3.48.0) - '@bundle-stats/plugin-webpack-validate': 4.21.10 + '@bundle-stats/plugin-webpack-filter': 4.22.0(core-js@3.48.0) + '@bundle-stats/plugin-webpack-validate': 4.22.0 core-js: 3.48.0 lodash: 4.17.23 serialize-query-params: 2.0.4 @@ -8961,6 +8695,10 @@ snapshots: '@csstools/css-syntax-patches-for-csstree@1.0.28': {} + '@csstools/css-syntax-patches-for-csstree@1.1.1(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + '@csstools/css-tokenizer@4.0.0': {} '@csstools/media-query-list-parser@5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -9248,7 +8986,7 @@ snapshots: '@eslint/js@8.57.1': {} - '@exodus/bytes@1.14.1': {} + '@exodus/bytes@1.15.0': {} '@floating-ui/core@1.7.5': dependencies: @@ -9683,14 +9421,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@microsoft/api-extractor-model@7.30.7(@types/node@25.3.3)': - dependencies: - '@microsoft/tsdoc': 0.15.1 - '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.14.0(@types/node@25.3.3) - transitivePeerDependencies: - - '@types/node' - '@microsoft/api-extractor-model@7.30.7(@types/node@25.5.0)': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -9699,24 +9429,6 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.52.10(@types/node@25.3.3)': - dependencies: - '@microsoft/api-extractor-model': 7.30.7(@types/node@25.3.3) - '@microsoft/tsdoc': 0.15.1 - '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.14.0(@types/node@25.3.3) - '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.15.4(@types/node@25.3.3) - '@rushstack/ts-command-line': 5.0.2(@types/node@25.3.3) - lodash: 4.17.23 - minimatch: 10.0.3 - resolve: 1.22.10 - semver: 7.5.4 - source-map: 0.6.1 - typescript: 5.8.2 - transitivePeerDependencies: - - '@types/node' - '@microsoft/api-extractor@7.52.10(@types/node@25.5.0)': dependencies: '@microsoft/api-extractor-model': 7.30.7(@types/node@25.5.0) @@ -9776,117 +9488,60 @@ snapshots: '@oxc-project/types@0.113.0': optional: true - '@oxfmt/binding-android-arm-eabi@0.36.0': - optional: true - '@oxfmt/binding-android-arm-eabi@0.40.0': optional: true - '@oxfmt/binding-android-arm64@0.36.0': - optional: true - '@oxfmt/binding-android-arm64@0.40.0': optional: true - '@oxfmt/binding-darwin-arm64@0.36.0': - optional: true - '@oxfmt/binding-darwin-arm64@0.40.0': optional: true - '@oxfmt/binding-darwin-x64@0.36.0': - optional: true - '@oxfmt/binding-darwin-x64@0.40.0': optional: true - '@oxfmt/binding-freebsd-x64@0.36.0': - optional: true - '@oxfmt/binding-freebsd-x64@0.40.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.36.0': - optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.36.0': - optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.40.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.36.0': - optional: true - '@oxfmt/binding-linux-arm64-gnu@0.40.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.36.0': - optional: true - '@oxfmt/binding-linux-arm64-musl@0.40.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.36.0': - optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.40.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.36.0': - optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.40.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.36.0': - optional: true - '@oxfmt/binding-linux-riscv64-musl@0.40.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.36.0': - optional: true - '@oxfmt/binding-linux-s390x-gnu@0.40.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.36.0': - optional: true - '@oxfmt/binding-linux-x64-gnu@0.40.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.36.0': - optional: true - '@oxfmt/binding-linux-x64-musl@0.40.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.36.0': - optional: true - '@oxfmt/binding-openharmony-arm64@0.40.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.36.0': - optional: true - '@oxfmt/binding-win32-arm64-msvc@0.40.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.36.0': - optional: true - '@oxfmt/binding-win32-ia32-msvc@0.40.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.36.0': - optional: true - '@oxfmt/binding-win32-x64-msvc@0.40.0': optional: true @@ -9908,117 +9563,60 @@ snapshots: '@oxlint-tsgolint/win32-x64@0.16.0': optional: true - '@oxlint/binding-android-arm-eabi@1.51.0': - optional: true - '@oxlint/binding-android-arm-eabi@1.55.0': optional: true - '@oxlint/binding-android-arm64@1.51.0': - optional: true - '@oxlint/binding-android-arm64@1.55.0': optional: true - '@oxlint/binding-darwin-arm64@1.51.0': - optional: true - '@oxlint/binding-darwin-arm64@1.55.0': optional: true - '@oxlint/binding-darwin-x64@1.51.0': - optional: true - '@oxlint/binding-darwin-x64@1.55.0': optional: true - '@oxlint/binding-freebsd-x64@1.51.0': - optional: true - '@oxlint/binding-freebsd-x64@1.55.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.51.0': - optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.55.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.51.0': - optional: true - '@oxlint/binding-linux-arm-musleabihf@1.55.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.51.0': - optional: true - '@oxlint/binding-linux-arm64-gnu@1.55.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.51.0': - optional: true - '@oxlint/binding-linux-arm64-musl@1.55.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.51.0': - optional: true - '@oxlint/binding-linux-ppc64-gnu@1.55.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.51.0': - optional: true - '@oxlint/binding-linux-riscv64-gnu@1.55.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.51.0': - optional: true - '@oxlint/binding-linux-riscv64-musl@1.55.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.51.0': - optional: true - '@oxlint/binding-linux-s390x-gnu@1.55.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.51.0': - optional: true - '@oxlint/binding-linux-x64-gnu@1.55.0': optional: true - '@oxlint/binding-linux-x64-musl@1.51.0': - optional: true - '@oxlint/binding-linux-x64-musl@1.55.0': optional: true - '@oxlint/binding-openharmony-arm64@1.51.0': - optional: true - '@oxlint/binding-openharmony-arm64@1.55.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.51.0': - optional: true - '@oxlint/binding-win32-arm64-msvc@1.55.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.51.0': - optional: true - '@oxlint/binding-win32-ia32-msvc@1.55.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.51.0': - optional: true - '@oxlint/binding-win32-x64-msvc@1.55.0': optional: true @@ -10377,19 +9975,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true - '@rushstack/node-core-library@5.14.0(@types/node@25.3.3)': - dependencies: - ajv: 8.13.0 - ajv-draft-04: 1.0.0(ajv@8.13.0) - ajv-formats: 3.0.1(ajv@8.13.0) - fs-extra: 11.3.1 - import-lazy: 4.0.0 - jju: 1.4.0 - resolve: 1.22.10 - semver: 7.5.4 - optionalDependencies: - '@types/node': 25.3.3 - '@rushstack/node-core-library@5.14.0(@types/node@25.5.0)': dependencies: ajv: 8.13.0 @@ -10408,13 +9993,6 @@ snapshots: resolve: 1.22.10 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.15.4(@types/node@25.3.3)': - dependencies: - '@rushstack/node-core-library': 5.14.0(@types/node@25.3.3) - supports-color: 8.1.1 - optionalDependencies: - '@types/node': 25.3.3 - '@rushstack/terminal@0.15.4(@types/node@25.5.0)': dependencies: '@rushstack/node-core-library': 5.14.0(@types/node@25.5.0) @@ -10422,15 +10000,6 @@ snapshots: optionalDependencies: '@types/node': 25.5.0 - '@rushstack/ts-command-line@5.0.2(@types/node@25.3.3)': - dependencies: - '@rushstack/terminal': 0.15.4(@types/node@25.3.3) - '@types/argparse': 1.0.38 - argparse: 1.0.10 - string-argv: 0.3.2 - transitivePeerDependencies: - - '@types/node' - '@rushstack/ts-command-line@5.0.2(@types/node@25.5.0)': dependencies: '@rushstack/terminal': 0.15.4(@types/node@25.5.0) @@ -10584,11 +10153,11 @@ snapshots: '@tanstack/query-core': 5.90.20 react: 19.2.4 - '@tanstack/react-router@1.166.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/history': 1.161.4 '@tanstack/react-store': 0.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.166.7 + '@tanstack/router-core': 1.167.0 isbot: 5.1.35 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -10609,7 +10178,7 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) - '@tanstack/router-core@1.166.7': + '@tanstack/router-core@1.167.0': dependencies: '@tanstack/history': 1.161.4 '@tanstack/store': 0.9.1 @@ -10765,10 +10334,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@25.3.3': - dependencies: - undici-types: 7.18.2 - '@types/node@25.5.0': dependencies: undici-types: 7.18.2 @@ -10816,36 +10381,36 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260315.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260312.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260315.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260312.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260315.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260312.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260315.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260312.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260315.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260312.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260315.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260312.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260315.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260312.1': + '@typescript/native-preview@7.0.0-dev.20260315.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260312.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260312.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260312.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260312.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260312.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260312.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260312.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260315.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260315.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260315.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260315.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260315.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260315.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260315.1 '@uidotdev/usehooks@2.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: @@ -10875,7 +10440,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': + '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.0 @@ -10887,7 +10452,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.0.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + vitest: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/expect@4.1.0': dependencies: @@ -11105,8 +10670,6 @@ snapshots: acorn@8.16.0: {} - agent-base@7.1.4: {} - ajv-draft-04@1.0.0(ajv@8.13.0): optionalDependencies: ajv: 8.13.0 @@ -11665,6 +11228,11 @@ snapshots: mdn-data: 2.12.2 source-map-js: 1.2.1 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + css-what@6.2.2: {} css.escape@1.5.1: {} @@ -11719,13 +11287,6 @@ snapshots: dependencies: css-tree: 2.2.1 - cssstyle@6.1.0: - dependencies: - '@asamuzakjp/css-color': 5.0.1 - '@csstools/css-syntax-patches-for-csstree': 1.0.28 - css-tree: 3.1.0 - lru-cache: 11.2.6 - csstype@3.2.3: {} data-urls@7.0.0: @@ -12667,7 +12228,7 @@ snapshots: html-encoding-sniffer@6.0.0: dependencies: - '@exodus/bytes': 1.14.1 + '@exodus/bytes': 1.15.0 transitivePeerDependencies: - '@noble/hashes' @@ -12710,20 +12271,6 @@ snapshots: http-link-header@1.1.3: {} - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - husky@9.1.7: {} idb@7.1.1: {} @@ -12997,24 +12544,24 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@28.1.0: + jsdom@29.0.0: dependencies: - '@acemir/cssom': 0.9.31 - '@asamuzakjp/dom-selector': 6.8.1 + '@asamuzakjp/css-color': 5.0.1 + '@asamuzakjp/dom-selector': 7.0.3 '@bramus/specificity': 2.4.2 - '@exodus/bytes': 1.14.1 - cssstyle: 6.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.1.1(css-tree@3.2.1) + '@exodus/bytes': 1.15.0 + css-tree: 3.2.1 data-urls: 7.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 6.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.7 parse5: 8.0.0 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.0 - undici: 7.22.0 + tough-cookie: 6.0.1 + undici: 7.24.3 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 @@ -13022,7 +12569,6 @@ snapshots: xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' - - supports-color jsesc@3.1.0: {} @@ -13232,6 +12778,8 @@ snapshots: lru-cache@11.2.6: {} + lru-cache@11.2.7: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -13897,30 +13445,6 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxfmt@0.36.0: - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.36.0 - '@oxfmt/binding-android-arm64': 0.36.0 - '@oxfmt/binding-darwin-arm64': 0.36.0 - '@oxfmt/binding-darwin-x64': 0.36.0 - '@oxfmt/binding-freebsd-x64': 0.36.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.36.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.36.0 - '@oxfmt/binding-linux-arm64-gnu': 0.36.0 - '@oxfmt/binding-linux-arm64-musl': 0.36.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.36.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.36.0 - '@oxfmt/binding-linux-riscv64-musl': 0.36.0 - '@oxfmt/binding-linux-s390x-gnu': 0.36.0 - '@oxfmt/binding-linux-x64-gnu': 0.36.0 - '@oxfmt/binding-linux-x64-musl': 0.36.0 - '@oxfmt/binding-openharmony-arm64': 0.36.0 - '@oxfmt/binding-win32-arm64-msvc': 0.36.0 - '@oxfmt/binding-win32-ia32-msvc': 0.36.0 - '@oxfmt/binding-win32-x64-msvc': 0.36.0 - oxfmt@0.40.0: dependencies: tinypool: 2.1.0 @@ -13954,29 +13478,6 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 0.16.0 '@oxlint-tsgolint/win32-x64': 0.16.0 - oxlint@1.51.0(oxlint-tsgolint@0.16.0): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.51.0 - '@oxlint/binding-android-arm64': 1.51.0 - '@oxlint/binding-darwin-arm64': 1.51.0 - '@oxlint/binding-darwin-x64': 1.51.0 - '@oxlint/binding-freebsd-x64': 1.51.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.51.0 - '@oxlint/binding-linux-arm-musleabihf': 1.51.0 - '@oxlint/binding-linux-arm64-gnu': 1.51.0 - '@oxlint/binding-linux-arm64-musl': 1.51.0 - '@oxlint/binding-linux-ppc64-gnu': 1.51.0 - '@oxlint/binding-linux-riscv64-gnu': 1.51.0 - '@oxlint/binding-linux-riscv64-musl': 1.51.0 - '@oxlint/binding-linux-s390x-gnu': 1.51.0 - '@oxlint/binding-linux-x64-gnu': 1.51.0 - '@oxlint/binding-linux-x64-musl': 1.51.0 - '@oxlint/binding-openharmony-arm64': 1.51.0 - '@oxlint/binding-win32-arm64-msvc': 1.51.0 - '@oxlint/binding-win32-ia32-msvc': 1.51.0 - '@oxlint/binding-win32-x64-msvc': 1.51.0 - oxlint-tsgolint: 0.16.0 - oxlint@1.55.0(oxlint-tsgolint@0.16.0): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.55.0 @@ -14787,10 +14288,10 @@ snapshots: '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.4 optional: true - rollup-plugin-bundle-stats@4.21.10(core-js@3.48.0)(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-bundle-stats@4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: - '@bundle-stats/cli-utils': 4.21.10(core-js@3.48.0) - rollup-plugin-webpack-stats: 2.1.11(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + '@bundle-stats/cli-utils': 4.22.0(core-js@3.48.0) + rollup-plugin-webpack-stats: 3.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) tslib: 2.8.1 optionalDependencies: rolldown: 1.0.0-rc.4 @@ -14799,15 +14300,15 @@ snapshots: transitivePeerDependencies: - core-js - rollup-plugin-stats@1.5.6(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): optionalDependencies: rolldown: 1.0.0-rc.4 rollup: 2.80.0 vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - rollup-plugin-webpack-stats@2.1.11(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: - rollup-plugin-stats: 1.5.6(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) optionalDependencies: rolldown: 1.0.0-rc.4 rollup: 2.80.0 @@ -15293,10 +14794,10 @@ snapshots: postcss: 8.5.8 postcss-selector-parser: 7.1.1 - stylelint-config-clean-order@8.0.1(stylelint-order@8.0.0(stylelint@17.4.0(typescript@5.9.3)))(stylelint@17.4.0(typescript@5.9.3)): + stylelint-config-clean-order@8.0.1(stylelint-order@8.1.1(stylelint@17.4.0(typescript@5.9.3)))(stylelint@17.4.0(typescript@5.9.3)): dependencies: stylelint: 17.4.0(typescript@5.9.3) - stylelint-order: 8.0.0(stylelint@17.4.0(typescript@5.9.3)) + stylelint-order: 8.1.1(stylelint@17.4.0(typescript@5.9.3)) stylelint-config-recommended-scss@17.0.0(postcss@8.5.8)(stylelint@17.4.0(typescript@5.9.3)): dependencies: @@ -15324,7 +14825,7 @@ snapshots: stylelint: 17.4.0(typescript@5.9.3) stylelint-config-recommended: 18.0.0(stylelint@17.4.0(typescript@5.9.3)) - stylelint-order@8.0.0(stylelint@17.4.0(typescript@5.9.3)): + stylelint-order@8.1.1(stylelint@17.4.0(typescript@5.9.3)): dependencies: postcss: 8.5.8 postcss-sorting: 10.0.0(postcss@8.5.8) @@ -15556,7 +15057,7 @@ snapshots: dependencies: is-number: 7.0.0 - tough-cookie@6.0.0: + tough-cookie@6.0.1: dependencies: tldts: 7.0.23 @@ -15670,7 +15171,7 @@ snapshots: undici-types@7.18.2: {} - undici@7.22.0: {} + undici@7.24.3: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -15837,25 +15338,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-dts@4.5.4(@types/node@25.3.3)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): - dependencies: - '@microsoft/api-extractor': 7.52.10(@types/node@25.3.3) - '@rollup/pluginutils': 5.2.0(rollup@4.59.0) - '@volar/typescript': 2.4.22 - '@vue/language-core': 2.2.0(typescript@5.9.3) - compare-versions: 6.1.1 - debug: 4.4.1 - kolorist: 1.8.0 - local-pkg: 1.1.1 - magic-string: 0.30.17 - typescript: 5.9.3 - optionalDependencies: - vite: 7.3.1(@types/node@25.3.3)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - transitivePeerDependencies: - - '@types/node' - - rollup - - supports-color - vite-plugin-dts@4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@microsoft/api-extractor': 7.52.10(@types/node@25.5.0) @@ -15946,24 +15428,6 @@ snapshots: terser: 5.46.0 yaml: 2.8.2 - vite@7.3.1(@types/node@25.3.3)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): - dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.8 - rollup: 4.59.0 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.3.3 - fsevents: 2.3.3 - jiti: 1.21.7 - lightningcss: 1.31.1 - sass: 1.98.0 - sass-embedded: 1.98.0 - terser: 5.46.0 - yaml: 2.8.2 - vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): dependencies: esbuild: 0.27.3 @@ -15986,7 +15450,7 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.0 '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) @@ -16010,7 +15474,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.0 - jsdom: 28.1.0 + jsdom: 29.0.0 transitivePeerDependencies: - msw @@ -16082,7 +15546,7 @@ snapshots: whatwg-url@16.0.1: dependencies: - '@exodus/bytes': 1.14.1 + '@exodus/bytes': 1.15.0 tr46: 6.0.0 webidl-conversions: 8.0.1 transitivePeerDependencies: From 3cc5e12e38e75a9930542aa6a4ba939aaf5e472a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:26:11 +0100 Subject: [PATCH 18/25] pl-api: update to vite 8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-api/package.json | 2 +- packages/pl-hooks/package.json | 4 +- pnpm-lock.yaml | 497 +++++++++++++++++++++------------ 3 files changed, 319 insertions(+), 184 deletions(-) diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index ce29f58dd..cd0b2a3b8 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -58,7 +58,7 @@ "typedoc-material-theme": "^1.4.1", "typedoc-plugin-valibot": "^1.0.0", "typescript": "^5.9.3", - "vite": "^7.3.1", + "vite": "^8.0.0", "vite-plugin-dts": "^4.5.4", "vitest": "^4.1.0", "ws": "^8.19.0" diff --git a/packages/pl-hooks/package.json b/packages/pl-hooks/package.json index c63a0a5c4..6adbbe1e2 100644 --- a/packages/pl-hooks/package.json +++ b/packages/pl-hooks/package.json @@ -39,8 +39,8 @@ "oxlint": "^1.55.0", "oxlint-tsgolint": "^0.16.0", "typescript": "^5.9.3", - "vite": "^7.3.1", - "vite-plugin-dts": "^4.5.4ls" + "vite": "^8.0.0", + "vite-plugin-dts": "^4.5.4" }, "lint-staged": { "*.{js,cjs,mjs,ts,tsx}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b4c788fe..39157ee15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -346,10 +346,10 @@ importers: version: 7.0.0-dev.20260315.1 '@vitejs/plugin-react': specifier: ^5.1.4 - version: 5.1.4(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 5.1.4(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) + version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) eslint-plugin-formatjs: specifier: ^6.3.0 version: 6.3.0(eslint@8.57.1) @@ -370,7 +370,7 @@ importers: version: 0.16.0 rollup-plugin-bundle-stats: specifier: ^4.22.0 - version: 4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) stylelint: specifier: ^17.4.0 version: 17.4.0(typescript@5.9.3) @@ -397,25 +397,25 @@ importers: version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vite-plugin-compile-time: specifier: ^0.4.6 - version: 0.4.6(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 0.4.6(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 3.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + version: 1.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) vite-plugin-require: specifier: ^1.2.14 - version: 1.2.14(esbuild@0.24.2)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 1.2.14(esbuild@0.24.2)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-static-copy: specifier: ^3.2.0 - version: 3.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 3.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vitest: specifier: ^4.1.0 - version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) packages/pl-api: dependencies: @@ -452,7 +452,7 @@ importers: version: 7.7.1 '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) + version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) oxfmt: specifier: ^0.40.0 version: 0.40.0 @@ -472,14 +472,14 @@ importers: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.3.1 - version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + specifier: ^8.0.0 + version: 8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vitest: specifier: ^4.1.0 - version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) ws: specifier: ^8.19.0 version: 8.19.0 @@ -524,17 +524,17 @@ importers: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.3.1 - version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + specifier: ^8.0.0 + version: 8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vite-plugin-dts: - specifier: ^4.5.4ls - version: 4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^4.5.4 + version: 4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) packages/website: dependencies: '@astrojs/mdx': specifier: ^4.3.13 - version: 4.3.13(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 4.3.13(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2)) '@astrojs/rss': specifier: ^4.0.15 version: 4.0.15 @@ -543,10 +543,10 @@ importers: version: 3.7.0 '@mkljczk/astro-theme': specifier: github:mkljczk/mkljczk-astro-theme - version: https://codeload.github.com/mkljczk/mkljczk-astro-theme/tar.gz/b849fba79b02abce89c823e98ce8549e3819c5f7(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2)) + version: https://codeload.github.com/mkljczk/mkljczk-astro-theme/tar.gz/b849fba79b02abce89c823e98ce8549e3819c5f7(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2)) astro: specifier: ^5.18.0 - version: 5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2) + version: 5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2) rehype-external-links: specifier: ^3.0.0 version: 3.0.0 @@ -2152,8 +2152,12 @@ packages: '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} - '@oxc-project/types@0.113.0': - resolution: {integrity: sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==} + '@oxc-project/runtime@0.115.0': + resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.115.0': + resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} '@oxfmt/binding-android-arm-eabi@0.40.0': resolution: {integrity: sha512-S6zd5r1w/HmqR8t0CTnGjFTBLDq2QKORPwriCHxo4xFNuhmOTABGjPaNvCJJVnrKBLsohOeiDX3YqQfJPF+FXw==} @@ -2606,83 +2610,97 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@rolldown/binding-android-arm64@1.0.0-rc.4': - resolution: {integrity: sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==} + '@rolldown/binding-android-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.4': - resolution: {integrity: sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.4': - resolution: {integrity: sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==} + '@rolldown/binding-darwin-x64@1.0.0-rc.9': + resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.4': - resolution: {integrity: sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': + resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': - resolution: {integrity: sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': + resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': - resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': - resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': + resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': - resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': - resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': + resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': - resolution: {integrity: sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.4': - resolution: {integrity: sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': + resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': - resolution: {integrity: sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': + resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': - resolution: {integrity: sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': + resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2690,8 +2708,8 @@ packages: '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} - '@rolldown/pluginutils@1.0.0-rc.4': - resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==} + '@rolldown/pluginutils@1.0.0-rc.9': + resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -5121,78 +5139,78 @@ packages: lie@3.1.1: resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} - lightningcss-android-arm64@1.31.1: - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.31.1: - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.31.1: - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.31.1: - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.31.1: - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.31.1: - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - lightningcss-linux-arm64-musl@1.31.1: - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] - lightningcss-linux-x64-gnu@1.31.1: - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] - lightningcss-linux-x64-musl@1.31.1: - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.31.1: - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.31.1: - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.31.1: - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} lilconfig@3.1.3: @@ -6423,8 +6441,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rolldown@1.0.0-rc.4: - resolution: {integrity: sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==} + rolldown@1.0.0-rc.9: + resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -7537,6 +7555,49 @@ packages: yaml: optional: true + vite@8.0.0: + resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.0.0-alpha.31 + esbuild: ^0.27.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitefu@1.1.2: resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} peerDependencies: @@ -7904,12 +7965,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.3.13(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astrojs/mdx@4.3.13(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@astrojs/markdown-remark': 6.3.10 '@mdx-js/mdx': 3.1.1 acorn: 8.16.0 - astro: 5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2) + astro: 5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -9456,9 +9517,9 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} - '@mkljczk/astro-theme@https://codeload.github.com/mkljczk/mkljczk-astro-theme/tar.gz/b849fba79b02abce89c823e98ce8549e3819c5f7(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))': + '@mkljczk/astro-theme@https://codeload.github.com/mkljczk/mkljczk-astro-theme/tar.gz/b849fba79b02abce89c823e98ce8549e3819c5f7(astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: - astro: 5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2) + astro: 5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2) '@mkljczk/url-purify@0.0.5': {} @@ -9485,8 +9546,9 @@ snapshots: '@oslojs/encoding@1.1.0': {} - '@oxc-project/types@0.113.0': - optional: true + '@oxc-project/runtime@0.115.0': {} + + '@oxc-project/types@0.115.0': {} '@oxfmt/binding-android-arm-eabi@0.40.0': optional: true @@ -9783,51 +9845,56 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@rolldown/binding-android-arm64@1.0.0-rc.4': + '@rolldown/binding-android-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.4': + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.4': + '@rolldown/binding-darwin-x64@1.0.0-rc.9': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.4': + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.4': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': optional: true '@rolldown/pluginutils@1.0.0-rc.3': {} - '@rolldown/pluginutils@1.0.0-rc.4': - optional: true + '@rolldown/pluginutils@1.0.0-rc.9': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@2.80.0)': dependencies: @@ -10428,7 +10495,7 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.4 - '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': + '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -10436,11 +10503,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': + '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.0 @@ -10452,7 +10519,21 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.0.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + vitest: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + + '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.0 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.0.0 + tinyrainbow: 3.0.3 + vitest: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/expect@4.1.0': dependencies: @@ -10463,13 +10544,21 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + + '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.1.0 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) '@vitest/pretty-format@4.1.0': dependencies: @@ -10787,7 +10876,7 @@ snapshots: astring@1.9.0: {} - astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2): + astro@5.18.0(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.59.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: '@astrojs/compiler': 2.13.1 '@astrojs/internal-helpers': 0.7.5 @@ -10844,8 +10933,8 @@ snapshots: unist-util-visit: 5.1.0 unstorage: 1.17.4 vfile: 6.0.3 - vite: 6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - vitefu: 1.1.2(vite@6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + vite: 6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 @@ -12641,55 +12730,54 @@ snapshots: dependencies: immediate: 3.0.6 - lightningcss-android-arm64@1.31.1: + lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-arm64@1.31.1: + lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.31.1: + lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-freebsd-x64@1.31.1: + lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.31.1: + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.31.1: + lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.31.1: + lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.31.1: + lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-linux-x64-musl@1.31.1: + lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.31.1: + lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.31.1: + lightningcss-win32-x64-msvc@1.32.0: optional: true - lightningcss@1.31.1: + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.31.1 - lightningcss-darwin-arm64: 1.31.1 - lightningcss-darwin-x64: 1.31.1 - lightningcss-freebsd-x64: 1.31.1 - lightningcss-linux-arm-gnueabihf: 1.31.1 - lightningcss-linux-arm64-gnu: 1.31.1 - lightningcss-linux-arm64-musl: 1.31.1 - lightningcss-linux-x64-gnu: 1.31.1 - lightningcss-linux-x64-musl: 1.31.1 - lightningcss-win32-arm64-msvc: 1.31.1 - lightningcss-win32-x64-msvc: 1.31.1 - optional: true + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 lilconfig@3.1.3: {} @@ -14268,51 +14356,52 @@ snapshots: dependencies: glob: 7.2.3 - rolldown@1.0.0-rc.4: + rolldown@1.0.0-rc.9: dependencies: - '@oxc-project/types': 0.113.0 - '@rolldown/pluginutils': 1.0.0-rc.4 + '@oxc-project/types': 0.115.0 + '@rolldown/pluginutils': 1.0.0-rc.9 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.4 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.4 - '@rolldown/binding-darwin-x64': 1.0.0-rc.4 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.4 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.4 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.4 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.4 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.4 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.4 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.4 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.4 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.4 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.4 - optional: true + '@rolldown/binding-android-arm64': 1.0.0-rc.9 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.9 + '@rolldown/binding-darwin-x64': 1.0.0-rc.9 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.9 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.9 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.9 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.9 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.9 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.9 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 - rollup-plugin-bundle-stats@4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-bundle-stats@4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@bundle-stats/cli-utils': 4.22.0(core-js@3.48.0) - rollup-plugin-webpack-stats: 3.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + rollup-plugin-webpack-stats: 3.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) tslib: 2.8.1 optionalDependencies: - rolldown: 1.0.0-rc.4 + rolldown: 1.0.0-rc.9 rollup: 2.80.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - core-js - rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): optionalDependencies: - rolldown: 1.0.0-rc.4 + rolldown: 1.0.0-rc.9 rollup: 2.80.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: - rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.4)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) optionalDependencies: - rolldown: 1.0.0-rc.4 + rolldown: 1.0.0-rc.9 rollup: 2.80.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) rollup@2.80.0: optionalDependencies: @@ -15323,7 +15412,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-compile-time@0.4.6(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-compile-time@0.4.6(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 @@ -15334,11 +15423,11 @@ snapshots: devalue: 5.6.3 esbuild: 0.24.2 magic-string: 0.30.21 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - vite-plugin-dts@4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-dts@4.5.4(@types/node@25.5.0)(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@microsoft/api-extractor': 7.52.10(@types/node@25.5.0) '@rollup/pluginutils': 5.2.0(rollup@4.59.0) @@ -15351,13 +15440,13 @@ snapshots: magic-string: 0.30.17 typescript: 5.9.3 optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-html@3.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-html@3.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -15371,27 +15460,27 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): + vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) workbox-build: 7.3.0(@types/babel__core@7.20.5) workbox-window: 7.3.0 transitivePeerDependencies: - supports-color - vite-plugin-require@1.2.14(esbuild@0.24.2)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-require@1.2.14(esbuild@0.24.2)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 '@vue/compiler-sfc': 3.5.29 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vue-loader: 17.4.2(@vue/compiler-sfc@3.5.29)(webpack@5.105.3(esbuild@0.24.2)) webpack: 5.105.3(esbuild@0.24.2) transitivePeerDependencies: @@ -15402,15 +15491,15 @@ snapshots: - vue - webpack-cli - vite-plugin-static-copy@3.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-static-copy@3.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: chokidar: 3.6.0 p-map: 7.0.4 picocolors: 1.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - vite@6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): + vite@6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -15422,13 +15511,13 @@ snapshots: '@types/node': 25.5.0 fsevents: 2.3.3 jiti: 1.21.7 - lightningcss: 1.31.1 + lightningcss: 1.32.0 sass: 1.98.0 sass-embedded: 1.98.0 terser: 5.46.0 yaml: 2.8.2 - vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): + vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -15440,20 +15529,38 @@ snapshots: '@types/node': 25.5.0 fsevents: 2.3.3 jiti: 1.21.7 - lightningcss: 1.31.1 + lightningcss: 1.32.0 sass: 1.98.0 sass-embedded: 1.98.0 terser: 5.46.0 yaml: 2.8.2 - vitefu@1.1.2(vite@6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): + dependencies: + '@oxc-project/runtime': 0.115.0 + lightningcss: 1.32.0 + picomatch: 4.0.3 + postcss: 8.5.8 + rolldown: 1.0.0-rc.9 + tinyglobby: 0.2.15 optionalDependencies: - vite: 6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + '@types/node': 25.5.0 + esbuild: 0.27.3 + fsevents: 2.3.3 + jiti: 1.21.7 + sass: 1.98.0 + sass-embedded: 1.98.0 + terser: 5.46.0 + yaml: 2.8.2 - vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vitefu@1.1.2(vite@6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + optionalDependencies: + vite: 6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + + vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.1.0 '@vitest/runner': 4.1.0 '@vitest/snapshot': 4.1.0 @@ -15470,7 +15577,35 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.31.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.5.0 + jsdom: 29.0.0 + transitivePeerDependencies: + - msw + + vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + '@vitest/expect': 4.1.0 + '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.1.0 + '@vitest/runner': 4.1.0 + '@vitest/snapshot': 4.1.0 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.0 From f28368bc2d2c84930dc46ffdbc4bcd30d4c4bc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:28:37 +0100 Subject: [PATCH 19/25] nicolium: update to vite 8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/package.json | 6 +- pnpm-lock.yaml | 196 +++++++++++---------------------- 2 files changed, 69 insertions(+), 133 deletions(-) diff --git a/packages/nicolium/package.json b/packages/nicolium/package.json index 15ff02c99..759e8a177 100644 --- a/packages/nicolium/package.json +++ b/packages/nicolium/package.json @@ -144,7 +144,7 @@ "@types/react-sparklines": "^1.7.5", "@types/react-swipeable-views": "^0.13.6", "@typescript/native-preview": "7.0.0-dev.20260315.1", - "@vitejs/plugin-react": "^5.1.4", + "@vitejs/plugin-react": "^6.0.0", "@vitest/coverage-v8": "4.1.0", "eslint-plugin-formatjs": "^6.3.0", "globals": "^17.4.0", @@ -161,12 +161,12 @@ "tslib": "^2.8.1", "type-fest": "^5.4.4", "typescript": "5.9.3", - "vite": "^7.3.1", + "vite": "^8.0.0", "vite-plugin-compile-time": "^0.4.6", "vite-plugin-html": "^3.2.2", "vite-plugin-pwa": "^1.2.0", "vite-plugin-require": "^1.2.14", - "vite-plugin-static-copy": "^3.2.0", + "vite-plugin-static-copy": "^3.3.0", "vitest": "^4.1.0" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39157ee15..ed729d77e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,11 +345,11 @@ importers: specifier: 7.0.0-dev.20260315.1 version: 7.0.0-dev.20260315.1 '@vitejs/plugin-react': - specifier: ^5.1.4 - version: 5.1.4(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^6.0.0 + version: 6.0.1(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) + version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))) eslint-plugin-formatjs: specifier: ^6.3.0 version: 6.3.0(eslint@8.57.1) @@ -370,7 +370,7 @@ importers: version: 0.16.0 rollup-plugin-bundle-stats: specifier: ^4.22.0 - version: 4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) stylelint: specifier: ^17.4.0 version: 17.4.0(typescript@5.9.3) @@ -396,26 +396,26 @@ importers: specifier: 5.9.3 version: 5.9.3 vite: - specifier: ^7.3.1 - version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + specifier: ^8.0.0 + version: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vite-plugin-compile-time: specifier: ^0.4.6 - version: 0.4.6(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 0.4.6(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 3.2.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + version: 1.2.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) vite-plugin-require: specifier: ^1.2.14 - version: 1.2.14(esbuild@0.24.2)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 1.2.14(esbuild@0.24.2)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-static-copy: - specifier: ^3.2.0 - version: 3.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + specifier: ^3.3.0 + version: 3.3.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) vitest: specifier: ^4.1.0 - version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) packages/pl-api: dependencies: @@ -1005,18 +1005,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.29.0': resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} @@ -2705,8 +2693,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.3': - resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} '@rolldown/pluginutils@1.0.0-rc.9': resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} @@ -3322,11 +3310,18 @@ packages: peerDependencies: react: '>= 16.8.0' - '@vitejs/plugin-react@5.1.4': - resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==} + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true '@vitest/coverage-v8@4.1.0': resolution: {integrity: sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==} @@ -6232,10 +6227,6 @@ packages: react-property@2.0.2: resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==} - react-refresh@0.18.0: - resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} - engines: {node: '>=0.10.0'} - react-sparklines@1.7.0: resolution: {integrity: sha512-bJFt9K4c5Z0k44G8KtxIhbG+iyxrKjBZhdW6afP+R7EnIq+iKjbWbEFISrf3WKNFsda+C46XAfnX0StS5fbDcg==} peerDependencies: @@ -7469,11 +7460,11 @@ packages: peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 - vite-plugin-static-copy@3.2.0: - resolution: {integrity: sha512-g2k9z8B/1Bx7D4wnFjPLx9dyYGrqWMLTpwTtPHhcU+ElNZP2O4+4OsyaficiDClus0dzVhdGvoGFYMJxoXZ12Q==} + vite-plugin-static-copy@3.3.0: + resolution: {integrity: sha512-XiAtZcev7nppxNFgKoD55rfL+ukVp/RtrnTJONRwRuzv/B2FK2h2ZRCYjvxhwBV/Oarse83SiyXBSxMTfeEM0Q==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vite@6.4.1: resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} @@ -7515,46 +7506,6 @@ packages: yaml: optional: true - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vite@8.0.0: resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -8490,16 +8441,6 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9892,7 +9833,7 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': optional: true - '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rolldown/pluginutils@1.0.0-rc.7': {} '@rolldown/pluginutils@1.0.0-rc.9': {} @@ -10313,19 +10254,23 @@ snapshots: '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 + optional: true '@types/babel__generator@7.27.0': dependencies: '@babel/types': 7.29.0 + optional: true '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.29.0 '@babel/types': 7.29.0 + optional: true '@types/babel__traverse@7.28.0': dependencies: '@babel/types': 7.29.0 + optional: true '@types/chai@5.2.3': dependencies: @@ -10495,19 +10440,12 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.4 - '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.1(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-rc.3 - '@types/babel__core': 7.20.5 - react-refresh: 0.18.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': + '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.0 @@ -10519,7 +10457,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.0.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + vitest: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)))': dependencies: @@ -10544,13 +10482,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))': dependencies: @@ -14059,8 +13997,6 @@ snapshots: react-property@2.0.2: {} - react-refresh@0.18.0: {} - react-sparklines@1.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: prop-types: 15.8.1 @@ -14377,31 +14313,31 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 - rollup-plugin-bundle-stats@4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-bundle-stats@4.22.0(core-js@3.48.0)(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@bundle-stats/cli-utils': 4.22.0(core-js@3.48.0) - rollup-plugin-webpack-stats: 3.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + rollup-plugin-webpack-stats: 3.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) tslib: 2.8.1 optionalDependencies: rolldown: 1.0.0-rc.9 rollup: 2.80.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - core-js - rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): optionalDependencies: rolldown: 1.0.0-rc.9 rollup: 2.80.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: - rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.9)(rollup@2.80.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) optionalDependencies: rolldown: 1.0.0-rc.9 rollup: 2.80.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) rollup@2.80.0: optionalDependencies: @@ -15412,7 +15348,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-compile-time@0.4.6(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-compile-time@0.4.6(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 @@ -15423,7 +15359,7 @@ snapshots: devalue: 5.6.3 esbuild: 0.24.2 magic-string: 0.30.21 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -15446,7 +15382,7 @@ snapshots: - rollup - supports-color - vite-plugin-html@3.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-html@3.2.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -15460,27 +15396,27 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): + vite-plugin-pwa@1.2.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) workbox-build: 7.3.0(@types/babel__core@7.20.5) workbox-window: 7.3.0 transitivePeerDependencies: - supports-color - vite-plugin-require@1.2.14(esbuild@0.24.2)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-require@1.2.14(esbuild@0.24.2)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 '@vue/compiler-sfc': 3.5.29 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vue-loader: 17.4.2(@vue/compiler-sfc@3.5.29)(webpack@5.105.3(esbuild@0.24.2)) webpack: 5.105.3(esbuild@0.24.2) transitivePeerDependencies: @@ -15491,13 +15427,13 @@ snapshots: - vue - webpack-cli - vite-plugin-static-copy@3.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vite-plugin-static-copy@3.3.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: chokidar: 3.6.0 p-map: 7.0.4 picocolors: 1.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) vite@6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): dependencies: @@ -15517,19 +15453,19 @@ snapshots: terser: 5.46.0 yaml: 2.8.2 - vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): + vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2): dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) + '@oxc-project/runtime': 0.115.0 + lightningcss: 1.32.0 picomatch: 4.0.3 postcss: 8.5.8 - rollup: 4.59.0 + rolldown: 1.0.0-rc.9 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.5.0 + esbuild: 0.24.2 fsevents: 2.3.3 jiti: 1.21.7 - lightningcss: 1.32.0 sass: 1.98.0 sass-embedded: 1.98.0 terser: 5.46.0 @@ -15557,10 +15493,10 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) - vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): + vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) + '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.1.0 '@vitest/runner': 4.1.0 '@vitest/snapshot': 4.1.0 @@ -15577,7 +15513,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.24.2)(jiti@1.21.7)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.0 From 1b357178c6d97782428e69c9fb7f123457390d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:39:36 +0100 Subject: [PATCH 20/25] nicolium: vite 8 stuff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- CHANGELOG.md | 1 - packages/nicolium/CHANGELOG.md | 1 - packages/nicolium/package.json | 10 +- packages/nicolium/postcss.config.cjs | 3 +- packages/nicolium/vite.config.ts | 8 +- pnpm-lock.yaml | 463 --------------------------- 6 files changed, 6 insertions(+), 480 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109e9c145..af05d2949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -162,4 +162,3 @@ Changes made since the project forked from Soapbox in April 2024. - Admin dashboard now works on non-Pleroma backends. - Removed excessive calls to `fetchOwnAccounts`. - Media modal displays the whole thread correctly. -- BrowsersList is actually being used now. diff --git a/packages/nicolium/CHANGELOG.md b/packages/nicolium/CHANGELOG.md index 109e9c145..af05d2949 100644 --- a/packages/nicolium/CHANGELOG.md +++ b/packages/nicolium/CHANGELOG.md @@ -162,4 +162,3 @@ Changes made since the project forked from Soapbox in April 2024. - Admin dashboard now works on non-Pleroma backends. - Removed excessive calls to `fetchOwnAccounts`. - Media modal displays the whole thread correctly. -- BrowsersList is actually being used now. diff --git a/packages/nicolium/package.json b/packages/nicolium/package.json index 759e8a177..1d867e5fa 100644 --- a/packages/nicolium/package.json +++ b/packages/nicolium/package.json @@ -73,12 +73,9 @@ "autoprefixer": "^10.4.27", "blurhash": "^2.0.5", "bowser": "^2.14.1", - "browserslist": "^4.28.1", - "browserslist-to-esbuild": "^2.1.1", "clsx": "^2.1.1", "core-js": "^3.48.0", "cryptocurrency-icons": "^0.18.1", - "cssnano": "^7.1.3", "detect-passive-events": "^2.0.3", "dompurify": "^3.3.3", "emoji-datasource": "15.0.1", @@ -175,10 +172,5 @@ "oxfmt --check" ], "src/styles/**/*.scss": "stylelint" - }, - "browserslist": [ - "> 0.5%", - "last 2 versions", - "not dead" - ] + } } diff --git a/packages/nicolium/postcss.config.cjs b/packages/nicolium/postcss.config.cjs index f8a1a6173..60f9921cb 100644 --- a/packages/nicolium/postcss.config.cjs +++ b/packages/nicolium/postcss.config.cjs @@ -1,9 +1,8 @@ /** @type {import('postcss-load-config').ConfigFn} */ -const config = ({ env }) => ({ +const config = () => ({ plugins: { tailwindcss: {}, autoprefixer: {}, - cssnano: env === 'production' ? {} : false, }, }); diff --git a/packages/nicolium/vite.config.ts b/packages/nicolium/vite.config.ts index 9fd90db97..4fc41b570 100644 --- a/packages/nicolium/vite.config.ts +++ b/packages/nicolium/vite.config.ts @@ -3,7 +3,6 @@ import { fileURLToPath, URL } from 'node:url'; import { tsgoChecker } from '@mkljczk/vite-tsgo-checker'; import react from '@vitejs/plugin-react'; -import browserslistToEsbuild from 'browserslist-to-esbuild'; import { bundleStats } from 'rollup-plugin-bundle-stats'; import { defineConfig } from 'vite'; import compileTime from 'vite-plugin-compile-time'; @@ -16,16 +15,17 @@ const config = defineConfig(() => ({ build: { assetsDir: 'packs', assetsInlineLimit: 0, - rollupOptions: { + rolldownOptions: { output: { assetFileNames: 'packs/assets/[name]-[hash].[ext]', chunkFileNames: 'packs/js/[name]-[hash].js', entryFileNames: 'packs/[name]-[hash].js', - experimentalMinChunkSize: 16 * 1024, + codeSplitting: { + minSize: 16 * 1024, + }, }, }, sourcemap: true, - target: browserslistToEsbuild(), }, assetsInclude: ['**/*.oga'], server: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed729d77e..3b7a1385f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,12 +136,6 @@ importers: bowser: specifier: ^2.14.1 version: 2.14.1 - browserslist: - specifier: ^4.28.1 - version: 4.28.1 - browserslist-to-esbuild: - specifier: ^2.1.1 - version: 2.1.1(browserslist@4.28.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -151,9 +145,6 @@ importers: cryptocurrency-icons: specifier: ^0.18.1 version: 0.18.1 - cssnano: - specifier: ^7.1.3 - version: 7.1.3(postcss@8.5.8) detect-passive-events: specifier: ^2.0.3 version: 2.0.3 @@ -3696,13 +3687,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist-to-esbuild@2.1.1: - resolution: {integrity: sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - browserslist: '*' - browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -3747,9 +3731,6 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001775: resolution: {integrity: sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==} @@ -3936,12 +3917,6 @@ packages: cryptocurrency-icons@0.18.1: resolution: {integrity: sha512-dvR5O8JOmav3559Yb0Igpkia+3vpt/aeNvMu5ZIVUG2Bzpq9wNcOJRIQas49XJrPjtZ98GAEn3aDQO+w7uhS2w==} - css-declaration-sorter@7.3.1: - resolution: {integrity: sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - postcss: ^8.0.9 - css-functions-list@3.3.3: resolution: {integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==} engines: {node: '>=12'} @@ -3976,24 +3951,6 @@ packages: engines: {node: '>=4'} hasBin: true - cssnano-preset-default@7.0.11: - resolution: {integrity: sha512-waWlAMuCakP7//UCY+JPrQS1z0OSLeOXk2sKWJximKWGupVxre50bzPlvpbUwZIDylhf/ptf0Pk+Yf7C+hoa3g==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - cssnano-utils@5.0.1: - resolution: {integrity: sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - cssnano@7.1.3: - resolution: {integrity: sha512-mLFHQAzyapMVFLiJIn7Ef4C2UCEvtlTlbyILR6B5ZsUAV3D/Pa761R5uC1YPhyBkRd3eqaDm2ncaNrD7R4mTRg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -5259,9 +5216,6 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -5271,9 +5225,6 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} @@ -5417,10 +5368,6 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - meow@13.2.0: - resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} - engines: {node: '>=18'} - meow@14.1.0: resolution: {integrity: sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==} engines: {node: '>=20'} @@ -5854,48 +5801,6 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-calc@10.1.1: - resolution: {integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==} - engines: {node: ^18.12 || ^20.9 || >=22.0} - peerDependencies: - postcss: ^8.4.38 - - postcss-colormin@7.0.6: - resolution: {integrity: sha512-oXM2mdx6IBTRm39797QguYzVEWzbdlFiMNfq88fCCN1Wepw3CYmJ/1/Ifa/KjWo+j5ZURDl2NTldLJIw51IeNQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-convert-values@7.0.9: - resolution: {integrity: sha512-l6uATQATZaCa0bckHV+r6dLXfWtUBKXxO3jK+AtxxJJtgMPD+VhhPCCx51I4/5w8U5uHV67g3w7PXj+V3wlMlg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-comments@7.0.6: - resolution: {integrity: sha512-Sq+Fzj1Eg5/CPf1ERb0wS1Im5cvE2gDXCE+si4HCn1sf+jpQZxDI4DXEp8t77B/ImzDceWE2ebJQFXdqZ6GRJw==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-duplicates@7.0.2: - resolution: {integrity: sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-empty@7.0.1: - resolution: {integrity: sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-overridden@7.0.1: - resolution: {integrity: sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -5929,120 +5834,12 @@ packages: postcss-media-query-parser@0.2.3: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - postcss-merge-longhand@7.0.5: - resolution: {integrity: sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-merge-rules@7.0.8: - resolution: {integrity: sha512-BOR1iAM8jnr7zoQSlpeBmCsWV5Uudi/+5j7k05D0O/WP3+OFMPD86c1j/20xiuRtyt45bhxw/7hnhZNhW2mNFA==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-font-values@7.0.1: - resolution: {integrity: sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-gradients@7.0.1: - resolution: {integrity: sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-params@7.0.6: - resolution: {integrity: sha512-YOn02gC68JijlaXVuKvFSCvQOhTpblkcfDre2hb/Aaa58r2BIaK4AtE/cyZf2wV7YKAG+UlP9DT+By0ry1E4VQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-selectors@7.0.6: - resolution: {integrity: sha512-lIbC0jy3AAwDxEgciZlBullDiMBeBCT+fz5G8RcA9MWqh/hfUkpOI3vNDUNEZHgokaoiv0juB9Y8fGcON7rU/A==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - postcss-nested@6.2.0: resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 - postcss-normalize-charset@7.0.1: - resolution: {integrity: sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-display-values@7.0.1: - resolution: {integrity: sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-positions@7.0.1: - resolution: {integrity: sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-repeat-style@7.0.1: - resolution: {integrity: sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-string@7.0.1: - resolution: {integrity: sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-timing-functions@7.0.1: - resolution: {integrity: sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-unicode@7.0.6: - resolution: {integrity: sha512-z6bwTV84YW6ZvvNoaNLuzRW4/uWxDKYI1iIDrzk6D2YTL7hICApy+Q1LP6vBEsljX8FM7YSuV9qI79XESd4ddQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-url@7.0.1: - resolution: {integrity: sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-whitespace@7.0.1: - resolution: {integrity: sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-ordered-values@7.0.2: - resolution: {integrity: sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-reduce-initial@7.0.6: - resolution: {integrity: sha512-G6ZyK68AmrPdMB6wyeA37ejnnRG2S8xinJrZJnOv+IaRKf6koPAVbQsiC7MfkmXaGmF1UO+QCijb27wfpxuRNg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-reduce-transforms@7.0.1: - resolution: {integrity: sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - postcss-resolve-nested-selector@0.1.6: resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} @@ -6075,18 +5872,6 @@ packages: peerDependencies: postcss: ^8.4.20 - postcss-svgo@7.1.1: - resolution: {integrity: sha512-zU9H9oEDrUFKa0JB7w+IYL7Qs9ey1mZyjhbf0KLxwJDdDRtoPvCmaEfknzqfHj44QS9VD6c5sJnBAVYTLRg/Sg==} - engines: {node: ^18.12.0 || ^20.9.0 || >= 18} - peerDependencies: - postcss: ^8.4.32 - - postcss-unique-selectors@7.0.5: - resolution: {integrity: sha512-3QoYmEt4qg/rUWDn6Tc8+ZVPmbp4G1hXDtCNWDx0st8SjtCbRcxRXDDM1QrEiXGG3A45zscSJFb4QH90LViyxg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -6639,10 +6424,6 @@ packages: resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} engines: {node: '>=11.0.0'} - sax@1.5.0: - resolution: {integrity: sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==} - engines: {node: '>=11.0.0'} - saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -6899,12 +6680,6 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - stylehacks@7.0.7: - resolution: {integrity: sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - stylelint-config-clean-order@8.0.1: resolution: {integrity: sha512-zKjp7BiINXRZOG9m0fE/6UKoM6clPekL+LoAiHMCiQU2hgirKL5G0mKc5Z0ygIhQXfb1+DTRDM0mu6Ecdv4q8g==} peerDependencies: @@ -6997,11 +6772,6 @@ packages: engines: {node: '>=16'} hasBin: true - svgo@4.0.1: - resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} - engines: {node: '>=16'} - hasBin: true - symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -11014,11 +10784,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist-to-esbuild@2.1.1(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - meow: 13.2.0 - browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.0 @@ -11070,13 +10835,6 @@ snapshots: camelcase@8.0.0: {} - caniuse-api@3.0.0: - dependencies: - browserslist: 4.28.1 - caniuse-lite: 1.0.30001775 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001775: {} ccount@2.0.1: {} @@ -11223,10 +10981,6 @@ snapshots: cryptocurrency-icons@0.18.1: {} - css-declaration-sorter@7.3.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - css-functions-list@3.3.3: {} css-select@4.3.0: @@ -11266,50 +11020,6 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-default@7.0.11(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - css-declaration-sorter: 7.3.1(postcss@8.5.8) - cssnano-utils: 5.0.1(postcss@8.5.8) - postcss: 8.5.8 - postcss-calc: 10.1.1(postcss@8.5.8) - postcss-colormin: 7.0.6(postcss@8.5.8) - postcss-convert-values: 7.0.9(postcss@8.5.8) - postcss-discard-comments: 7.0.6(postcss@8.5.8) - postcss-discard-duplicates: 7.0.2(postcss@8.5.8) - postcss-discard-empty: 7.0.1(postcss@8.5.8) - postcss-discard-overridden: 7.0.1(postcss@8.5.8) - postcss-merge-longhand: 7.0.5(postcss@8.5.8) - postcss-merge-rules: 7.0.8(postcss@8.5.8) - postcss-minify-font-values: 7.0.1(postcss@8.5.8) - postcss-minify-gradients: 7.0.1(postcss@8.5.8) - postcss-minify-params: 7.0.6(postcss@8.5.8) - postcss-minify-selectors: 7.0.6(postcss@8.5.8) - postcss-normalize-charset: 7.0.1(postcss@8.5.8) - postcss-normalize-display-values: 7.0.1(postcss@8.5.8) - postcss-normalize-positions: 7.0.1(postcss@8.5.8) - postcss-normalize-repeat-style: 7.0.1(postcss@8.5.8) - postcss-normalize-string: 7.0.1(postcss@8.5.8) - postcss-normalize-timing-functions: 7.0.1(postcss@8.5.8) - postcss-normalize-unicode: 7.0.6(postcss@8.5.8) - postcss-normalize-url: 7.0.1(postcss@8.5.8) - postcss-normalize-whitespace: 7.0.1(postcss@8.5.8) - postcss-ordered-values: 7.0.2(postcss@8.5.8) - postcss-reduce-initial: 7.0.6(postcss@8.5.8) - postcss-reduce-transforms: 7.0.1(postcss@8.5.8) - postcss-svgo: 7.1.1(postcss@8.5.8) - postcss-unique-selectors: 7.0.5(postcss@8.5.8) - - cssnano-utils@5.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - cssnano@7.1.3(postcss@8.5.8): - dependencies: - cssnano-preset-default: 7.0.11(postcss@8.5.8) - lilconfig: 3.1.3 - postcss: 8.5.8 - csso@5.0.5: dependencies: css-tree: 2.2.1 @@ -12772,16 +12482,12 @@ snapshots: lodash.debounce@4.0.8: {} - lodash.memoize@4.1.2: {} - lodash.merge@4.6.2: {} lodash.sortby@4.7.0: {} lodash.truncate@4.4.2: {} - lodash.uniq@4.5.0: {} - lodash@4.17.23: {} log-update@6.1.0: @@ -13040,8 +12746,6 @@ snapshots: mdurl@2.0.0: {} - meow@13.2.0: {} - meow@14.1.0: {} merge-stream@2.0.0: {} @@ -13656,43 +13360,6 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-calc@10.1.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - postcss-value-parser: 4.2.0 - - postcss-colormin@7.0.6(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-convert-values@7.0.9(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-discard-comments@7.0.6(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - - postcss-discard-duplicates@7.0.2(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-discard-empty@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-discard-overridden@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-import@15.1.0(postcss@8.5.8): dependencies: postcss: 8.5.8 @@ -13715,112 +13382,11 @@ snapshots: postcss-media-query-parser@0.2.3: {} - postcss-merge-longhand@7.0.5(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - stylehacks: 7.0.7(postcss@8.5.8) - - postcss-merge-rules@7.0.8(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - caniuse-api: 3.0.0 - cssnano-utils: 5.0.1(postcss@8.5.8) - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - - postcss-minify-font-values@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-minify-gradients@7.0.1(postcss@8.5.8): - dependencies: - colord: 2.9.3 - cssnano-utils: 5.0.1(postcss@8.5.8) - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-minify-params@7.0.6(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - cssnano-utils: 5.0.1(postcss@8.5.8) - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-minify-selectors@7.0.6(postcss@8.5.8): - dependencies: - cssesc: 3.0.0 - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - postcss-nested@6.2.0(postcss@8.5.8): dependencies: postcss: 8.5.8 postcss-selector-parser: 6.1.2 - postcss-normalize-charset@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-normalize-display-values@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-positions@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-repeat-style@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-string@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-timing-functions@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-unicode@7.0.6(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-url@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-whitespace@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-ordered-values@7.0.2(postcss@8.5.8): - dependencies: - cssnano-utils: 5.0.1(postcss@8.5.8) - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-reduce-initial@7.0.6(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - caniuse-api: 3.0.0 - postcss: 8.5.8 - - postcss-reduce-transforms@7.0.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - postcss-resolve-nested-selector@0.1.6: {} postcss-safe-parser@7.0.1(postcss@8.5.8): @@ -13850,17 +13416,6 @@ snapshots: dependencies: postcss: 8.5.8 - postcss-svgo@7.1.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - svgo: 4.0.1 - - postcss-unique-selectors@7.0.5(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - postcss-value-parser@4.2.0: {} postcss@8.5.8: @@ -14501,8 +14056,6 @@ snapshots: sax@1.4.4: {} - sax@1.5.0: {} - saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -14813,12 +14366,6 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylehacks@7.0.7(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - stylelint-config-clean-order@8.0.1(stylelint-order@8.1.1(stylelint@17.4.0(typescript@5.9.3)))(stylelint@17.4.0(typescript@5.9.3)): dependencies: stylelint: 17.4.0(typescript@5.9.3) @@ -14952,16 +14499,6 @@ snapshots: picocolors: 1.1.1 sax: 1.4.4 - svgo@4.0.1: - dependencies: - commander: 11.1.0 - css-select: 5.2.2 - css-tree: 3.1.0 - css-what: 6.2.2 - csso: 5.0.5 - picocolors: 1.1.1 - sax: 1.5.0 - symbol-tree@3.2.4: {} sync-child-process@1.0.2: From 03db0391fc0be0aa581d18a74bea9e3ea8b381c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:43:25 +0100 Subject: [PATCH 21/25] nicolium: emoji reaction handling fixes? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../statuses/status-reactions-bar.tsx | 2 +- .../statuses/use-status-interactions.ts | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/nicolium/src/components/statuses/status-reactions-bar.tsx b/packages/nicolium/src/components/statuses/status-reactions-bar.tsx index 30357438d..6cc71760e 100644 --- a/packages/nicolium/src/components/statuses/status-reactions-bar.tsx +++ b/packages/nicolium/src/components/statuses/status-reactions-bar.tsx @@ -75,7 +75,7 @@ const StatusReaction: React.FC = ({ } else if (reaction.me) { emojiUnreact(reaction.name); } else { - emojiReact(reaction.name /*, reaction.url */); + emojiReact(reaction.name); } }; diff --git a/packages/nicolium/src/queries/statuses/use-status-interactions.ts b/packages/nicolium/src/queries/statuses/use-status-interactions.ts index 02923207e..27fc87bb4 100644 --- a/packages/nicolium/src/queries/statuses/use-status-interactions.ts +++ b/packages/nicolium/src/queries/statuses/use-status-interactions.ts @@ -118,11 +118,8 @@ const useEmojiReactMutation = (statusId: string) => { return useMutation({ mutationKey: ['statuses', 'emojiReact', statusId], - mutationFn: async (emoji: string) => { - const status = await client.statuses.createStatusReaction(statusId, emoji); - - importEntities({ statuses: [status] }); - }, + mutationFn: async (emoji: string) => + await client.statuses.createStatusReaction(statusId, emoji), onMutate: (emoji) => { return updateStatus( statusId, @@ -142,6 +139,12 @@ const useEmojiReactMutation = (statusId: string) => { ); }, onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient), + onSettled: (status) => { + importEntities({ statuses: [status] }); + queryClient.invalidateQueries({ + queryKey: queryKeys.accountsLists.statusReactions(statusId), + }); + }, }); }; @@ -156,8 +159,6 @@ const useEmojiUnreactMutation = (statusId: string) => { mutationFn: async (emoji: string) => { const status = await client.statuses.deleteStatusReaction(statusId, emoji); - importEntities({ statuses: [status] }); - if (checkEmojiReactsSupport && !status.account.local) { supportsEmojiReacts(status.account.ap_id ?? status.account.url) .then((result) => { @@ -171,6 +172,8 @@ const useEmojiUnreactMutation = (statusId: string) => { }) .catch(() => {}); } + + return status; }, onMutate: (emoji) => updateStatus( @@ -182,6 +185,12 @@ const useEmojiUnreactMutation = (statusId: string) => { queryClient, ), onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient), + onSettled: (status) => { + importEntities({ statuses: [status] }); + queryClient.invalidateQueries({ + queryKey: queryKeys.accountsLists.statusReactions(statusId), + }); + }, }); }; From 8242a663293f8d26d5880f226d56b2465af7c71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 12:52:38 +0100 Subject: [PATCH 22/25] nicolium: minor style improvement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/components/preview-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nicolium/src/components/preview-card.tsx b/packages/nicolium/src/components/preview-card.tsx index ea2624b6b..9596515cb 100644 --- a/packages/nicolium/src/components/preview-card.tsx +++ b/packages/nicolium/src/components/preview-card.tsx @@ -202,7 +202,7 @@ const PreviewCard: React.FC = ({ ); const description = ( -
+
{trimmedTitle && ( {title} From 642d42f2e3b654d39be93723be2bc497cbc3cc7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 13:19:48 +0100 Subject: [PATCH 23/25] nicolium: move some stuff around MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/accounts.ts | 10 ---------- packages/nicolium/src/actions/auth.ts | 4 ++-- packages/nicolium/src/actions/events.ts | 2 +- packages/nicolium/src/actions/statuses.ts | 2 +- .../src/api/hooks/streaming/use-user-stream.ts | 2 +- packages/nicolium/src/columns/search.tsx | 4 ++-- packages/nicolium/src/columns/trends.tsx | 4 ++-- .../accounts}/account-container.tsx | 0 .../nicolium/src/components/statuses/quoted-status.tsx | 2 +- .../src/components/statuses/status-action-bar.tsx | 2 +- .../statuses}/status-container.tsx | 0 .../src/components/statuses/status-hover-card.tsx | 2 +- .../nicolium/src/components/statuses/status-list.tsx | 2 +- packages/nicolium/src/components/statuses/status.tsx | 2 +- .../admin/components/latest-accounts-panel.tsx | 2 +- .../compose-event/tabs/manage-pending-participants.tsx | 2 +- .../features/compose/components/reply-indicator.tsx | 2 +- .../compose/containers/preview-compose-container.tsx | 2 +- .../features/conversations/components/conversation.tsx | 2 +- .../src/features/event/components/event-header.tsx | 2 +- .../features/notifications/components/notification.tsx | 4 ++-- .../src/features/status/components/thread-status.tsx | 2 +- .../features/ui/components/panels/birthday-panel.tsx | 2 +- .../ui/components/panels/pinned-accounts-panel.tsx | 2 +- .../ui/components/panels/who-to-follow-panel.tsx | 2 +- .../moderation.tsx => hooks/use-admin-modals.tsx} | 2 +- packages/nicolium/src/modals/dislikes-modal.tsx | 2 +- .../nicolium/src/modals/event-participants-modal.tsx | 2 +- .../nicolium/src/modals/familiar-followers-modal.tsx | 2 +- packages/nicolium/src/modals/favourites-modal.tsx | 2 +- .../nicolium/src/modals/list-adder-modal/index.tsx | 2 +- .../modals/list-editor-modal/components/account.tsx | 2 +- packages/nicolium/src/modals/mentions-modal.tsx | 2 +- packages/nicolium/src/modals/reactions-modal.tsx | 2 +- packages/nicolium/src/modals/reblogs-modal.tsx | 2 +- packages/nicolium/src/modals/report-modal/index.tsx | 2 +- .../nicolium/src/pages/account-lists/followers.tsx | 2 +- .../nicolium/src/pages/account-lists/following.tsx | 2 +- .../pages/account-lists/outgoing-follow-requests.tsx | 2 +- .../nicolium/src/pages/account-lists/subscribers.tsx | 2 +- packages/nicolium/src/pages/dashboard/account.tsx | 2 +- packages/nicolium/src/pages/dashboard/report.tsx | 2 +- packages/nicolium/src/pages/dashboard/user-index.tsx | 2 +- packages/nicolium/src/pages/settings/blocks.tsx | 2 +- packages/nicolium/src/pages/settings/mutes.tsx | 2 +- .../src/pages/status-lists/interaction-requests.tsx | 2 +- packages/nicolium/src/queries/admin/use-statuses.ts | 2 +- .../src/queries/conversations/use-conversations.ts | 2 +- .../src/queries/notifications/use-notifications.ts | 2 +- packages/nicolium/src/queries/search/use-search.ts | 2 +- .../src/queries/statuses/use-event-interactions.ts | 2 +- .../src/queries/statuses/use-interaction-requests.ts | 2 +- .../src/queries/statuses/use-status-interactions.ts | 2 +- packages/nicolium/src/queries/statuses/use-status.ts | 2 +- .../nicolium/src/queries/timelines/use-timeline.ts | 2 +- .../src/queries/trends/use-trending-statuses.ts | 2 +- .../importer.ts => queries/utils/import-entities.ts} | 0 packages/nicolium/src/queries/utils/minify-list.ts | 2 +- 58 files changed, 58 insertions(+), 68 deletions(-) delete mode 100644 packages/nicolium/src/actions/accounts.ts rename packages/nicolium/src/{containers => components/accounts}/account-container.tsx (100%) rename packages/nicolium/src/{containers => components/statuses}/status-container.tsx (100%) rename packages/nicolium/src/{actions/moderation.tsx => hooks/use-admin-modals.tsx} (99%) rename packages/nicolium/src/{actions/importer.ts => queries/utils/import-entities.ts} (100%) diff --git a/packages/nicolium/src/actions/accounts.ts b/packages/nicolium/src/actions/accounts.ts deleted file mode 100644 index a576ae119..000000000 --- a/packages/nicolium/src/actions/accounts.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { getClient } from '@/api'; - -import type { CreateAccountParams } from 'pl-api'; - -const createAccount = (params: CreateAccountParams) => - getClient() - .settings.createAccount(params) - .then((response) => ({ params, response })); - -export { createAccount }; diff --git a/packages/nicolium/src/actions/auth.ts b/packages/nicolium/src/actions/auth.ts index 103df0062..fca1d7503 100644 --- a/packages/nicolium/src/actions/auth.ts +++ b/packages/nicolium/src/actions/auth.ts @@ -12,9 +12,9 @@ import { } from 'pl-api'; import { defineMessages } from 'react-intl'; -import { createAccount } from '@/actions/accounts'; import { createApp } from '@/actions/apps'; import { obtainOAuthToken, revokeOAuthToken } from '@/actions/oauth'; +import { getClient } from '@/api'; import * as BuildConfig from '@/build-config'; import { selectAccount, selectOwnAccount } from '@/queries/accounts/selectors'; import { queryClient } from '@/queries/client'; @@ -223,7 +223,7 @@ const register = async (params: CreateAccountParams) => { const { app } = await createAppAndToken(); - const { response } = await createAccount(params); + const response = await getClient().settings.createAccount(params); if ('identifier' in response) { toast.info(response.message); } else { diff --git a/packages/nicolium/src/actions/events.ts b/packages/nicolium/src/actions/events.ts index a91a61d12..973a093c8 100644 --- a/packages/nicolium/src/actions/events.ts +++ b/packages/nicolium/src/actions/events.ts @@ -4,7 +4,7 @@ import { getClient } from '@/api'; import { useComposeStore } from '@/stores/compose'; import toast from '@/toast'; -import { importEntities } from './importer'; +import { importEntities } from '../queries/utils/import-entities'; import type { CreateEventParams, Location, MediaAttachment } from 'pl-api'; diff --git a/packages/nicolium/src/actions/statuses.ts b/packages/nicolium/src/actions/statuses.ts index 43d64b687..8d7364958 100644 --- a/packages/nicolium/src/actions/statuses.ts +++ b/packages/nicolium/src/actions/statuses.ts @@ -12,7 +12,7 @@ import { useTimelinesStore } from '@/stores/timelines'; import { isLoggedIn } from '@/utils/auth'; import { shouldHaveCard } from '@/utils/status'; -import { importEntities } from './importer'; +import { importEntities } from '../queries/utils/import-entities'; import type { NormalizedStatus as Status } from '@/normalizers/status'; import type { useQueryClient } from '@tanstack/react-query'; diff --git a/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts b/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts index c827081a6..6b6db189f 100644 --- a/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts +++ b/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react'; -import { importEntities } from '@/actions/importer'; import { useStatContext } from '@/contexts/stat-context'; import { useLoggedIn } from '@/hooks/use-logged-in'; import { updateReactions } from '@/queries/announcements/use-announcements'; @@ -8,6 +7,7 @@ import { queryClient } from '@/queries/client'; import { updateConversations } from '@/queries/conversations/use-conversations'; import { queryKeys } from '@/queries/keys'; import { useProcessStreamNotification } from '@/queries/notifications/use-notifications'; +import { importEntities } from '@/queries/utils/import-entities'; import { useSettings } from '@/stores/settings'; import { useTimelinesActions } from '@/stores/timelines'; import { getUnreadChatsCount, updateChatListItem } from '@/utils/chats'; diff --git a/packages/nicolium/src/columns/search.tsx b/packages/nicolium/src/columns/search.tsx index 19a6f0fe3..c068246c5 100644 --- a/packages/nicolium/src/columns/search.tsx +++ b/packages/nicolium/src/columns/search.tsx @@ -2,10 +2,10 @@ import clsx from 'clsx'; import React, { useRef } from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import Hashtag from '@/components/hashtag'; import ScrollableList from '@/components/scrollable-list'; -import AccountContainer from '@/containers/account-container'; -import StatusContainer from '@/containers/status-container'; +import StatusContainer from '@/components/statuses/status-container'; import PlaceholderAccount from '@/features/placeholder/components/placeholder-account'; import PlaceholderHashtag from '@/features/placeholder/components/placeholder-hashtag'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; diff --git a/packages/nicolium/src/columns/trends.tsx b/packages/nicolium/src/columns/trends.tsx index 47876c169..bdbcd2ca0 100644 --- a/packages/nicolium/src/columns/trends.tsx +++ b/packages/nicolium/src/columns/trends.tsx @@ -1,11 +1,11 @@ import clsx from 'clsx'; import React from 'react'; +import AccountContainer from '@/components/accounts/account-container'; import Hashtag from '@/components/hashtag'; import ScrollableList from '@/components/scrollable-list'; +import StatusContainer from '@/components/statuses/status-container'; import TrendingLink from '@/components/trending-link'; -import AccountContainer from '@/containers/account-container'; -import StatusContainer from '@/containers/status-container'; import PlaceholderAccount from '@/features/placeholder/components/placeholder-account'; import PlaceholderHashtag from '@/features/placeholder/components/placeholder-hashtag'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; diff --git a/packages/nicolium/src/containers/account-container.tsx b/packages/nicolium/src/components/accounts/account-container.tsx similarity index 100% rename from packages/nicolium/src/containers/account-container.tsx rename to packages/nicolium/src/components/accounts/account-container.tsx diff --git a/packages/nicolium/src/components/statuses/quoted-status.tsx b/packages/nicolium/src/components/statuses/quoted-status.tsx index 9ae219942..41811ee96 100644 --- a/packages/nicolium/src/components/statuses/quoted-status.tsx +++ b/packages/nicolium/src/components/statuses/quoted-status.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx'; import React, { type MouseEventHandler } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import AccountContainer from '@/containers/account-container'; +import AccountContainer from '@/components/accounts/account-container'; import OutlineBox from '../outline-box'; diff --git a/packages/nicolium/src/components/statuses/status-action-bar.tsx b/packages/nicolium/src/components/statuses/status-action-bar.tsx index 1cc47f46c..253f67fc5 100644 --- a/packages/nicolium/src/components/statuses/status-action-bar.tsx +++ b/packages/nicolium/src/components/statuses/status-action-bar.tsx @@ -4,7 +4,6 @@ import React, { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { redactStatus } from '@/actions/admin'; -import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/actions/moderation'; import { changeSetting } from '@/actions/settings'; import { editStatus, toggleMuteStatus } from '@/actions/statuses'; import DropdownMenu from '@/components/dropdown-menu'; @@ -13,6 +12,7 @@ import { useCurrentAccount } from '@/contexts/current-account-context'; import EmojiPickerDropdown from '@/features/emoji/containers/emoji-picker-dropdown-container'; import { languages } from '@/features/preferences'; import { layouts } from '@/features/ui/router'; +import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/hooks/use-admin-modals'; import { useCanInteract } from '@/hooks/use-can-interact'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; diff --git a/packages/nicolium/src/containers/status-container.tsx b/packages/nicolium/src/components/statuses/status-container.tsx similarity index 100% rename from packages/nicolium/src/containers/status-container.tsx rename to packages/nicolium/src/components/statuses/status-container.tsx diff --git a/packages/nicolium/src/components/statuses/status-hover-card.tsx b/packages/nicolium/src/components/statuses/status-hover-card.tsx index b24e98a9e..9507bca02 100644 --- a/packages/nicolium/src/components/statuses/status-hover-card.tsx +++ b/packages/nicolium/src/components/statuses/status-hover-card.tsx @@ -4,7 +4,7 @@ import clsx from 'clsx'; import React, { useEffect } from 'react'; import { showStatusHoverCard } from '@/components/statuses/hover-status-wrapper'; -import StatusContainer from '@/containers/status-container'; +import StatusContainer from '@/components/statuses/status-container'; import { useStatus } from '@/queries/statuses/use-status'; import { useStatusHoverCardActions, useStatusHoverCardStore } from '@/stores/status-hover-card'; diff --git a/packages/nicolium/src/components/statuses/status-list.tsx b/packages/nicolium/src/components/statuses/status-list.tsx index 04d06944b..0d2d76d83 100644 --- a/packages/nicolium/src/components/statuses/status-list.tsx +++ b/packages/nicolium/src/components/statuses/status-list.tsx @@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl'; import LoadGap from '@/components/load-gap'; import ScrollableList, { type IScrollableList } from '@/components/scrollable-list'; -import StatusContainer from '@/containers/status-container'; +import StatusContainer from '@/components/statuses/status-container'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; import PendingStatus from '@/features/ui/components/pending-status'; import { timelineToFilterContextType } from '@/queries/settings/use-filters'; diff --git a/packages/nicolium/src/components/statuses/status.tsx b/packages/nicolium/src/components/statuses/status.tsx index 252429112..441f79f6d 100644 --- a/packages/nicolium/src/components/statuses/status.tsx +++ b/packages/nicolium/src/components/statuses/status.tsx @@ -3,10 +3,10 @@ import clsx from 'clsx'; import React, { useEffect, useMemo, useRef } from 'react'; import { defineMessages, useIntl, FormattedList, FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import Card from '@/components/ui/card'; import Icon from '@/components/ui/icon'; import Text from '@/components/ui/text'; -import AccountContainer from '@/containers/account-container'; import Emojify from '@/features/emoji/emojify'; import StatusTypeIcon from '@/features/status/components/status-type-icon'; import { Hotkeys } from '@/features/ui/components/hotkeys'; diff --git a/packages/nicolium/src/features/admin/components/latest-accounts-panel.tsx b/packages/nicolium/src/features/admin/components/latest-accounts-panel.tsx index 02d4e0f25..42b8414e8 100644 --- a/packages/nicolium/src/features/admin/components/latest-accounts-panel.tsx +++ b/packages/nicolium/src/features/admin/components/latest-accounts-panel.tsx @@ -2,8 +2,8 @@ import { useNavigate } from '@tanstack/react-router'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import Widget from '@/components/ui/widget'; -import AccountContainer from '@/containers/account-container'; import { useAdminAccounts } from '@/queries/admin/use-accounts'; const messages = defineMessages({ diff --git a/packages/nicolium/src/features/compose-event/tabs/manage-pending-participants.tsx b/packages/nicolium/src/features/compose-event/tabs/manage-pending-participants.tsx index 091a924d5..ea2a5398f 100644 --- a/packages/nicolium/src/features/compose-event/tabs/manage-pending-participants.tsx +++ b/packages/nicolium/src/features/compose-event/tabs/manage-pending-participants.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import ScrollableList from '@/components/scrollable-list'; import Button from '@/components/ui/button'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { useAcceptEventParticipationRequestMutation, useEventParticipationRequests, diff --git a/packages/nicolium/src/features/compose/components/reply-indicator.tsx b/packages/nicolium/src/features/compose/components/reply-indicator.tsx index cbcefb6fc..7ebc96c68 100644 --- a/packages/nicolium/src/features/compose/components/reply-indicator.tsx +++ b/packages/nicolium/src/features/compose/components/reply-indicator.tsx @@ -1,11 +1,11 @@ import clsx from 'clsx'; import React from 'react'; +import AccountContainer from '@/components/accounts/account-container'; import Markup from '@/components/markup'; import AttachmentThumbs from '@/components/media/attachment-thumbs'; import { ParsedContent } from '@/components/statuses/parsed-content'; import QuotedStatusIndicator from '@/components/statuses/quoted-status-indicator'; -import AccountContainer from '@/containers/account-container'; import { getTextDirection } from '@/utils/rtl'; import type { NormalizedStatus as Status } from '@/normalizers/status'; diff --git a/packages/nicolium/src/features/compose/containers/preview-compose-container.tsx b/packages/nicolium/src/features/compose/containers/preview-compose-container.tsx index d8b763dd0..639de31ff 100644 --- a/packages/nicolium/src/features/compose/containers/preview-compose-container.tsx +++ b/packages/nicolium/src/features/compose/containers/preview-compose-container.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import OutlineBox from '@/components/outline-box'; import EventPreview from '@/components/statuses/event-preview'; import QuotedStatusIndicator from '@/components/statuses/quoted-status-indicator'; @@ -11,7 +12,6 @@ import StatusReplyMentions from '@/components/statuses/status-reply-mentions'; import Icon from '@/components/ui/icon'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; -import AccountContainer from '@/containers/account-container'; import { useCompose, useComposeActions } from '@/stores/compose'; import type { NormalizedStatus as Status } from '@/normalizers/status'; diff --git a/packages/nicolium/src/features/conversations/components/conversation.tsx b/packages/nicolium/src/features/conversations/components/conversation.tsx index b0b4c8254..45130304f 100644 --- a/packages/nicolium/src/features/conversations/components/conversation.tsx +++ b/packages/nicolium/src/features/conversations/components/conversation.tsx @@ -1,7 +1,7 @@ import { useNavigate } from '@tanstack/react-router'; import React from 'react'; -import StatusContainer from '@/containers/status-container'; +import StatusContainer from '@/components/statuses/status-container'; import { useAccount } from '@/queries/accounts/use-account'; import { useMarkConversationRead, diff --git a/packages/nicolium/src/features/event/components/event-header.tsx b/packages/nicolium/src/features/event/components/event-header.tsx index f59eb8e47..0e5c6eb21 100644 --- a/packages/nicolium/src/features/event/components/event-header.tsx +++ b/packages/nicolium/src/features/event/components/event-header.tsx @@ -2,7 +2,6 @@ import { Link, useNavigate } from '@tanstack/react-router'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/actions/moderation'; import VerificationBadge from '@/components/accounts/verification-badge'; import DropdownMenu, { type Menu as MenuType } from '@/components/dropdown-menu'; import Icon from '@/components/icon'; @@ -11,6 +10,7 @@ import Button from '@/components/ui/button'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; import Emojify from '@/features/emoji/emojify'; +import { useDeleteStatusModal, useToggleStatusSensitivityModal } from '@/hooks/use-admin-modals'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; diff --git a/packages/nicolium/src/features/notifications/components/notification.tsx b/packages/nicolium/src/features/notifications/components/notification.tsx index 3602c61dc..106a86114 100644 --- a/packages/nicolium/src/features/notifications/components/notification.tsx +++ b/packages/nicolium/src/features/notifications/components/notification.tsx @@ -9,16 +9,16 @@ import { type MessageDescriptor, } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import HoverAccountWrapper from '@/components/accounts/hover-account-wrapper'; import Icon from '@/components/icon'; import Markup from '@/components/markup'; import AttachmentThumbs from '@/components/media/attachment-thumbs'; import RelativeTimestamp from '@/components/relative-timestamp'; import { ParsedContent } from '@/components/statuses/parsed-content'; +import StatusContainer from '@/components/statuses/status-container'; import StatusInfo from '@/components/statuses/status-info'; import Emoji from '@/components/ui/emoji'; -import AccountContainer from '@/containers/account-container'; -import StatusContainer from '@/containers/status-container'; import Emojify from '@/features/emoji/emojify'; import { Hotkeys } from '@/features/ui/components/hotkeys'; import { useLoggedIn } from '@/hooks/use-logged-in'; diff --git a/packages/nicolium/src/features/status/components/thread-status.tsx b/packages/nicolium/src/features/status/components/thread-status.tsx index 431121abf..e1f033aa0 100644 --- a/packages/nicolium/src/features/status/components/thread-status.tsx +++ b/packages/nicolium/src/features/status/components/thread-status.tsx @@ -1,8 +1,8 @@ import clsx from 'clsx'; import React from 'react'; +import StatusContainer from '@/components/statuses/status-container'; import Tombstone from '@/components/statuses/tombstone'; -import StatusContainer from '@/containers/status-container'; import PlaceholderStatus from '@/features/placeholder/components/placeholder-status'; import { useMinimalStatus } from '@/queries/statuses/use-status'; import { useReplyCount, useReplyToId } from '@/stores/contexts'; diff --git a/packages/nicolium/src/features/ui/components/panels/birthday-panel.tsx b/packages/nicolium/src/features/ui/components/panels/birthday-panel.tsx index f35c2f381..a641df1db 100644 --- a/packages/nicolium/src/features/ui/components/panels/birthday-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/birthday-panel.tsx @@ -1,8 +1,8 @@ import React, { useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import Widget from '@/components/ui/widget'; -import AccountContainer from '@/containers/account-container'; import { useBirthdayReminders } from '@/queries/accounts/use-birthday-reminders'; const timeToMidnight = () => { diff --git a/packages/nicolium/src/features/ui/components/panels/pinned-accounts-panel.tsx b/packages/nicolium/src/features/ui/components/panels/pinned-accounts-panel.tsx index 2cdbb2974..c5515db7a 100644 --- a/packages/nicolium/src/features/ui/components/panels/pinned-accounts-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/pinned-accounts-panel.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import Widget from '@/components/ui/widget'; -import AccountContainer from '@/containers/account-container'; import Emojify from '@/features/emoji/emojify'; import { WhoToFollowPanel } from '@/features/ui/util/async-components'; import { useEndorsedAccounts } from '@/queries/accounts/use-endorsed-accounts'; diff --git a/packages/nicolium/src/features/ui/components/panels/who-to-follow-panel.tsx b/packages/nicolium/src/features/ui/components/panels/who-to-follow-panel.tsx index 31cc659bc..978f3b94d 100644 --- a/packages/nicolium/src/features/ui/components/panels/who-to-follow-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/who-to-follow-panel.tsx @@ -2,9 +2,9 @@ import { Link } from '@tanstack/react-router'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import Text from '@/components/ui/text'; import Widget from '@/components/ui/widget'; -import AccountContainer from '@/containers/account-container'; import PlaceholderSidebarSuggestions from '@/features/placeholder/components/placeholder-sidebar-suggestions'; import { useFeatures } from '@/hooks/use-features'; import { diff --git a/packages/nicolium/src/actions/moderation.tsx b/packages/nicolium/src/hooks/use-admin-modals.tsx similarity index 99% rename from packages/nicolium/src/actions/moderation.tsx rename to packages/nicolium/src/hooks/use-admin-modals.tsx index 34ca3e2fa..a458c929d 100644 --- a/packages/nicolium/src/actions/moderation.tsx +++ b/packages/nicolium/src/hooks/use-admin-modals.tsx @@ -2,9 +2,9 @@ import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import OutlineBox from '@/components/outline-box'; import Text from '@/components/ui/text'; -import AccountContainer from '@/containers/account-container'; import { selectAccount } from '@/queries/accounts/selectors'; import { useAdminDeleteAccountMutation, diff --git a/packages/nicolium/src/modals/dislikes-modal.tsx b/packages/nicolium/src/modals/dislikes-modal.tsx index cbfd547f7..c72c5e517 100644 --- a/packages/nicolium/src/modals/dislikes-modal.tsx +++ b/packages/nicolium/src/modals/dislikes-modal.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import PullToRefresh from '@/components/pull-to-refresh'; import ScrollableList from '@/components/scrollable-list'; import Modal from '@/components/ui/modal'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { useStatusDislikes } from '@/queries/statuses/use-status-interactions'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; diff --git a/packages/nicolium/src/modals/event-participants-modal.tsx b/packages/nicolium/src/modals/event-participants-modal.tsx index 07a316352..6030e5426 100644 --- a/packages/nicolium/src/modals/event-participants-modal.tsx +++ b/packages/nicolium/src/modals/event-participants-modal.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import PullToRefresh from '@/components/pull-to-refresh'; import ScrollableList from '@/components/scrollable-list'; import Modal from '@/components/ui/modal'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { useEventParticipations } from '@/queries/events/use-event-participations'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; diff --git a/packages/nicolium/src/modals/familiar-followers-modal.tsx b/packages/nicolium/src/modals/familiar-followers-modal.tsx index 86d9e70a4..92ad3cb05 100644 --- a/packages/nicolium/src/modals/familiar-followers-modal.tsx +++ b/packages/nicolium/src/modals/familiar-followers-modal.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import ScrollableList from '@/components/scrollable-list'; import Modal from '@/components/ui/modal'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import Emojify from '@/features/emoji/emojify'; import { useAccount } from '@/queries/accounts/use-account'; import { useFamiliarFollowers } from '@/queries/accounts/use-familiar-followers'; diff --git a/packages/nicolium/src/modals/favourites-modal.tsx b/packages/nicolium/src/modals/favourites-modal.tsx index 61f0613b2..e701f811f 100644 --- a/packages/nicolium/src/modals/favourites-modal.tsx +++ b/packages/nicolium/src/modals/favourites-modal.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import PullToRefresh from '@/components/pull-to-refresh'; import ScrollableList from '@/components/scrollable-list'; import Modal from '@/components/ui/modal'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { useStatusFavourites } from '@/queries/statuses/use-status-interactions'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; diff --git a/packages/nicolium/src/modals/list-adder-modal/index.tsx b/packages/nicolium/src/modals/list-adder-modal/index.tsx index 660c6b023..dd48cce5c 100644 --- a/packages/nicolium/src/modals/list-adder-modal/index.tsx +++ b/packages/nicolium/src/modals/list-adder-modal/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import { CardHeader, CardTitle } from '@/components/ui/card'; import Modal from '@/components/ui/modal'; -import AccountContainer from '@/containers/account-container'; import { NewListForm, getOrderedLists } from '@/pages/account-lists/lists'; import { useLists, useListsForAccount } from '@/queries/accounts/use-lists'; diff --git a/packages/nicolium/src/modals/list-editor-modal/components/account.tsx b/packages/nicolium/src/modals/list-editor-modal/components/account.tsx index 3392d8ce3..fa8a45fe5 100644 --- a/packages/nicolium/src/modals/list-editor-modal/components/account.tsx +++ b/packages/nicolium/src/modals/list-editor-modal/components/account.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import IconButton from '@/components/ui/icon-button'; -import AccountContainer from '@/containers/account-container'; const messages = defineMessages({ remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' }, diff --git a/packages/nicolium/src/modals/mentions-modal.tsx b/packages/nicolium/src/modals/mentions-modal.tsx index bb87f3787..07b28e147 100644 --- a/packages/nicolium/src/modals/mentions-modal.tsx +++ b/packages/nicolium/src/modals/mentions-modal.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import ScrollableList from '@/components/scrollable-list'; import Modal from '@/components/ui/modal'; -import AccountContainer from '@/containers/account-container'; import { useStatus } from '@/queries/statuses/use-status'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; diff --git a/packages/nicolium/src/modals/reactions-modal.tsx b/packages/nicolium/src/modals/reactions-modal.tsx index b2af7fab6..3bdb8e1df 100644 --- a/packages/nicolium/src/modals/reactions-modal.tsx +++ b/packages/nicolium/src/modals/reactions-modal.tsx @@ -2,13 +2,13 @@ import clsx from 'clsx'; import React, { useMemo, useState } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import PullToRefresh from '@/components/pull-to-refresh'; import ScrollableList from '@/components/scrollable-list'; import Emoji from '@/components/ui/emoji'; import Modal from '@/components/ui/modal'; import Spinner from '@/components/ui/spinner'; import Tabs from '@/components/ui/tabs'; -import AccountContainer from '@/containers/account-container'; import { useStatusReactions } from '@/queries/statuses/use-status-interactions'; import type { Item } from '@/components/ui/tabs'; diff --git a/packages/nicolium/src/modals/reblogs-modal.tsx b/packages/nicolium/src/modals/reblogs-modal.tsx index 146b6aca5..67f0dce03 100644 --- a/packages/nicolium/src/modals/reblogs-modal.tsx +++ b/packages/nicolium/src/modals/reblogs-modal.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import PullToRefresh from '@/components/pull-to-refresh'; import ScrollableList from '@/components/scrollable-list'; import Modal from '@/components/ui/modal'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { useStatusReblogs } from '@/queries/statuses/use-status-interactions'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; diff --git a/packages/nicolium/src/modals/report-modal/index.tsx b/packages/nicolium/src/modals/report-modal/index.tsx index 0d2b6b01f..3e721d063 100644 --- a/packages/nicolium/src/modals/report-modal/index.tsx +++ b/packages/nicolium/src/modals/report-modal/index.tsx @@ -1,12 +1,12 @@ import React, { useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import AttachmentThumbs from '@/components/media/attachment-thumbs'; import StatusContent from '@/components/statuses/status-content'; import Modal from '@/components/ui/modal'; import ProgressBar from '@/components/ui/progress-bar'; import Text from '@/components/ui/text'; -import AccountContainer from '@/containers/account-container'; import { useAccount } from '@/queries/accounts/use-account'; import { useBlockAccountMutation } from '@/queries/accounts/use-relationship'; import { useReportAccountMutation } from '@/queries/accounts/use-report'; diff --git a/packages/nicolium/src/pages/account-lists/followers.tsx b/packages/nicolium/src/pages/account-lists/followers.tsx index d867da405..5157d7c37 100644 --- a/packages/nicolium/src/pages/account-lists/followers.tsx +++ b/packages/nicolium/src/pages/account-lists/followers.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import MissingIndicator from '@/components/missing-indicator'; import ScrollableList from '@/components/scrollable-list'; import Column from '@/components/ui/column'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { profileFollowersRoute } from '@/features/ui/router'; import { useFollowers } from '@/queries/account-lists/use-follows'; import { useAccountLookup } from '@/queries/accounts/use-account-lookup'; diff --git a/packages/nicolium/src/pages/account-lists/following.tsx b/packages/nicolium/src/pages/account-lists/following.tsx index 73f02a251..020809eef 100644 --- a/packages/nicolium/src/pages/account-lists/following.tsx +++ b/packages/nicolium/src/pages/account-lists/following.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import MissingIndicator from '@/components/missing-indicator'; import ScrollableList from '@/components/scrollable-list'; import Column from '@/components/ui/column'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { profileFollowingRoute } from '@/features/ui/router'; import { useFollowing } from '@/queries/account-lists/use-follows'; import { useAccountLookup } from '@/queries/accounts/use-account-lookup'; diff --git a/packages/nicolium/src/pages/account-lists/outgoing-follow-requests.tsx b/packages/nicolium/src/pages/account-lists/outgoing-follow-requests.tsx index eb382d41c..50dcdd49a 100644 --- a/packages/nicolium/src/pages/account-lists/outgoing-follow-requests.tsx +++ b/packages/nicolium/src/pages/account-lists/outgoing-follow-requests.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import ScrollableList from '@/components/scrollable-list'; import Column from '@/components/ui/column'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { useOutgoingFollowRequests } from '@/queries/accounts/use-follow-requests'; import { FollowRequestsTabs } from './follow-requests'; diff --git a/packages/nicolium/src/pages/account-lists/subscribers.tsx b/packages/nicolium/src/pages/account-lists/subscribers.tsx index aa880af8e..0c913816c 100644 --- a/packages/nicolium/src/pages/account-lists/subscribers.tsx +++ b/packages/nicolium/src/pages/account-lists/subscribers.tsx @@ -2,13 +2,13 @@ import { useNavigate } from '@tanstack/react-router'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import List, { ListItem } from '@/components/list'; import MissingIndicator from '@/components/missing-indicator'; import ScrollableList from '@/components/scrollable-list'; import Column from '@/components/ui/column'; import Spinner from '@/components/ui/spinner'; import Toggle from '@/components/ui/toggle'; -import AccountContainer from '@/containers/account-container'; import { profileSubscribersRoute } from '@/features/ui/router'; import { useSubscribers } from '@/queries/account-lists/use-follows'; import { useAccountLookup } from '@/queries/accounts/use-account-lookup'; diff --git a/packages/nicolium/src/pages/dashboard/account.tsx b/packages/nicolium/src/pages/dashboard/account.tsx index 7305a836e..2778a8b3c 100644 --- a/packages/nicolium/src/pages/dashboard/account.tsx +++ b/packages/nicolium/src/pages/dashboard/account.tsx @@ -2,7 +2,6 @@ import { PLEROMA } from 'pl-api'; import React, { type ChangeEventHandler, useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, type MessageDescriptor, useIntl } from 'react-intl'; -import { useDeactivateUserModal, useDeleteUserModal } from '@/actions/moderation'; import Account from '@/components/accounts/account'; import List, { ListItem } from '@/components/list'; import MissingIndicator from '@/components/missing-indicator'; @@ -15,6 +14,7 @@ import Toggle from '@/components/ui/toggle'; import { SelectDropdown } from '@/features/forms'; import ColumnLoading from '@/features/ui/components/column-loading'; import { adminAccountRoute } from '@/features/ui/router'; +import { useDeactivateUserModal, useDeleteUserModal } from '@/hooks/use-admin-modals'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; import { useAccount } from '@/queries/accounts/use-account'; diff --git a/packages/nicolium/src/pages/dashboard/report.tsx b/packages/nicolium/src/pages/dashboard/report.tsx index 9936281f2..98c6df5ea 100644 --- a/packages/nicolium/src/pages/dashboard/report.tsx +++ b/packages/nicolium/src/pages/dashboard/report.tsx @@ -5,12 +5,12 @@ import ReactSwipeableViews from 'react-swipeable-views'; import Account from '@/components/accounts/account'; import List, { ListItem } from '@/components/list'; +import StatusContainer from '@/components/statuses/status-container'; import Card from '@/components/ui/card'; import Column from '@/components/ui/column'; import Icon from '@/components/ui/icon'; import IconButton from '@/components/ui/icon-button'; import Text from '@/components/ui/text'; -import StatusContainer from '@/containers/status-container'; import ColumnLoading from '@/features/ui/components/column-loading'; import { adminReportRoute } from '@/features/ui/router'; import { useFeatures } from '@/hooks/use-features'; diff --git a/packages/nicolium/src/pages/dashboard/user-index.tsx b/packages/nicolium/src/pages/dashboard/user-index.tsx index c1dceeb39..fef18d856 100644 --- a/packages/nicolium/src/pages/dashboard/user-index.tsx +++ b/packages/nicolium/src/pages/dashboard/user-index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import ScrollableList from '@/components/scrollable-list'; import Column from '@/components/ui/column'; -import AccountContainer from '@/containers/account-container'; import { adminUsersRoute } from '@/features/ui/router'; import { useAdminAccounts } from '@/queries/admin/use-accounts'; diff --git a/packages/nicolium/src/pages/settings/blocks.tsx b/packages/nicolium/src/pages/settings/blocks.tsx index 62838995c..7d6dcfb9b 100644 --- a/packages/nicolium/src/pages/settings/blocks.tsx +++ b/packages/nicolium/src/pages/settings/blocks.tsx @@ -2,10 +2,10 @@ import clsx from 'clsx'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import ScrollableList from '@/components/scrollable-list'; import Column from '@/components/ui/column'; import Spinner from '@/components/ui/spinner'; -import AccountContainer from '@/containers/account-container'; import { useBlocks } from '@/queries/account-lists/use-blocks'; const messages = defineMessages({ diff --git a/packages/nicolium/src/pages/settings/mutes.tsx b/packages/nicolium/src/pages/settings/mutes.tsx index da24bb191..e2745838b 100644 --- a/packages/nicolium/src/pages/settings/mutes.tsx +++ b/packages/nicolium/src/pages/settings/mutes.tsx @@ -2,9 +2,9 @@ import clsx from 'clsx'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import ScrollableList from '@/components/scrollable-list'; import Column from '@/components/ui/column'; -import AccountContainer from '@/containers/account-container'; import { useMutes } from '@/queries/account-lists/use-blocks'; const messages = defineMessages({ diff --git a/packages/nicolium/src/pages/status-lists/interaction-requests.tsx b/packages/nicolium/src/pages/status-lists/interaction-requests.tsx index ec0c03357..2b4097a29 100644 --- a/packages/nicolium/src/pages/status-lists/interaction-requests.tsx +++ b/packages/nicolium/src/pages/status-lists/interaction-requests.tsx @@ -3,6 +3,7 @@ import clsx from 'clsx'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import AccountContainer from '@/components/accounts/account-container'; import Icon from '@/components/icon'; import AttachmentThumbs from '@/components/media/attachment-thumbs'; import PullToRefresh from '@/components/pull-to-refresh'; @@ -12,7 +13,6 @@ import StatusContent from '@/components/statuses/status-content'; import Button from '@/components/ui/button'; import Column from '@/components/ui/column'; import Text from '@/components/ui/text'; -import AccountContainer from '@/containers/account-container'; import { buildLink } from '@/features/notifications/components/notification'; import { Hotkeys } from '@/features/ui/components/hotkeys'; import { useOwnAccount } from '@/hooks/use-own-account'; diff --git a/packages/nicolium/src/queries/admin/use-statuses.ts b/packages/nicolium/src/queries/admin/use-statuses.ts index 291bfae71..e100c9b1c 100644 --- a/packages/nicolium/src/queries/admin/use-statuses.ts +++ b/packages/nicolium/src/queries/admin/use-statuses.ts @@ -1,7 +1,7 @@ import { useMutation } from '@tanstack/react-query'; -import { importEntities } from '@/actions/importer'; import { useClient } from '@/hooks/use-client'; +import { importEntities } from '@/queries/utils/import-entities'; import { useTimelinesActions } from '@/stores/timelines'; import type { AdminUpdateStatusParams } from 'pl-api'; diff --git a/packages/nicolium/src/queries/conversations/use-conversations.ts b/packages/nicolium/src/queries/conversations/use-conversations.ts index 9953ca5f8..777dc0ed5 100644 --- a/packages/nicolium/src/queries/conversations/use-conversations.ts +++ b/packages/nicolium/src/queries/conversations/use-conversations.ts @@ -2,9 +2,9 @@ import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-q import { create } from 'mutative'; import { useMemo } from 'react'; -import { importEntities } from '@/actions/importer'; import { useClient } from '@/hooks/use-client'; import { useLoggedIn } from '@/hooks/use-logged-in'; +import { importEntities } from '@/queries/utils/import-entities'; import { compareDate } from '@/utils/comparators'; import { queryClient } from '../client'; diff --git a/packages/nicolium/src/queries/notifications/use-notifications.ts b/packages/nicolium/src/queries/notifications/use-notifications.ts index 16febab11..4ff9a0718 100644 --- a/packages/nicolium/src/queries/notifications/use-notifications.ts +++ b/packages/nicolium/src/queries/notifications/use-notifications.ts @@ -10,7 +10,6 @@ import { import { useCallback, useEffect, useMemo } from 'react'; import { useIntl } from 'react-intl'; -import { importEntities } from '@/actions/importer'; import { getNotificationStatusId, notificationMessages, @@ -19,6 +18,7 @@ import { useClient } from '@/hooks/use-client'; import { useLoggedIn } from '@/hooks/use-logged-in'; import { appendFollowRequest } from '@/queries/accounts/use-follow-requests'; import { queryClient } from '@/queries/client'; +import { importEntities } from '@/queries/utils/import-entities'; import { makePaginatedResponseQueryOptions } from '@/queries/utils/make-paginated-response-query-options'; import { useSettingsStore } from '@/stores/settings'; import { compareId } from '@/utils/comparators'; diff --git a/packages/nicolium/src/queries/search/use-search.ts b/packages/nicolium/src/queries/search/use-search.ts index f0dd3f53f..caf64ec7d 100644 --- a/packages/nicolium/src/queries/search/use-search.ts +++ b/packages/nicolium/src/queries/search/use-search.ts @@ -1,7 +1,7 @@ import { notifyManager, useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; -import { importEntities } from '@/actions/importer'; import { useClient } from '@/hooks/use-client'; +import { importEntities } from '@/queries/utils/import-entities'; import { queryKeys } from '../keys'; diff --git a/packages/nicolium/src/queries/statuses/use-event-interactions.ts b/packages/nicolium/src/queries/statuses/use-event-interactions.ts index d3951e9dd..036631f5c 100644 --- a/packages/nicolium/src/queries/statuses/use-event-interactions.ts +++ b/packages/nicolium/src/queries/statuses/use-event-interactions.ts @@ -1,8 +1,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { defineMessages } from 'react-intl'; -import { importEntities } from '@/actions/importer'; import { useClient } from '@/hooks/use-client'; +import { importEntities } from '@/queries/utils/import-entities'; import toast from '@/toast'; import { queryKeys } from '../keys'; diff --git a/packages/nicolium/src/queries/statuses/use-interaction-requests.ts b/packages/nicolium/src/queries/statuses/use-interaction-requests.ts index 4d3176119..52aa17dd8 100644 --- a/packages/nicolium/src/queries/statuses/use-interaction-requests.ts +++ b/packages/nicolium/src/queries/statuses/use-interaction-requests.ts @@ -1,10 +1,10 @@ import { type InfiniteData, useInfiniteQuery, useMutation } from '@tanstack/react-query'; import { type InteractionRequest, PaginatedResponse } from 'pl-api'; -import { importEntities } from '@/actions/importer'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { useLoggedIn } from '@/hooks/use-logged-in'; +import { importEntities } from '@/queries/utils/import-entities'; import { queryKeys } from '../keys'; diff --git a/packages/nicolium/src/queries/statuses/use-status-interactions.ts b/packages/nicolium/src/queries/statuses/use-status-interactions.ts index 27fc87bb4..66d77740c 100644 --- a/packages/nicolium/src/queries/statuses/use-status-interactions.ts +++ b/packages/nicolium/src/queries/statuses/use-status-interactions.ts @@ -8,10 +8,10 @@ import { import { create } from 'mutative'; import { defineMessages, useIntl } from 'react-intl'; -import { importEntities } from '@/actions/importer'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; +import { importEntities } from '@/queries/utils/import-entities'; import { makePaginatedResponseQuery } from '@/queries/utils/make-paginated-response-query'; import { minifyAccountList } from '@/queries/utils/minify-list'; import { useModalsActions } from '@/stores/modals'; diff --git a/packages/nicolium/src/queries/statuses/use-status.ts b/packages/nicolium/src/queries/statuses/use-status.ts index 2063d62ee..bace5ebde 100644 --- a/packages/nicolium/src/queries/statuses/use-status.ts +++ b/packages/nicolium/src/queries/statuses/use-status.ts @@ -7,12 +7,12 @@ import { } from '@tanstack/react-query'; import { useMemo } from 'react'; -import { importEntities } from '@/actions/importer'; import { decrementReplyCount, incrementReplyCount } from '@/actions/statuses'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { normalizeStatus, type NormalizedStatus } from '@/normalizers/status'; import { useFilters } from '@/queries/settings/use-filters'; +import { importEntities } from '@/queries/utils/import-entities'; import { useComposeActions } from '@/stores/compose'; import { useContextsActions } from '@/stores/contexts'; import { useModalsActions } from '@/stores/modals'; diff --git a/packages/nicolium/src/queries/timelines/use-timeline.ts b/packages/nicolium/src/queries/timelines/use-timeline.ts index e4430d2fb..0414c71b4 100644 --- a/packages/nicolium/src/queries/timelines/use-timeline.ts +++ b/packages/nicolium/src/queries/timelines/use-timeline.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { importEntities } from '@/actions/importer'; import { useTimelineStream } from '@/api/hooks/streaming/use-timeline-stream'; +import { importEntities } from '@/queries/utils/import-entities'; import { useTimelinesStore, useTimeline as useStoreTimeline, diff --git a/packages/nicolium/src/queries/trends/use-trending-statuses.ts b/packages/nicolium/src/queries/trends/use-trending-statuses.ts index 4d4a77a82..055c9b71b 100644 --- a/packages/nicolium/src/queries/trends/use-trending-statuses.ts +++ b/packages/nicolium/src/queries/trends/use-trending-statuses.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query'; -import { importEntities } from '@/actions/importer'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; +import { importEntities } from '@/queries/utils/import-entities'; import { queryKeys } from '../keys'; diff --git a/packages/nicolium/src/actions/importer.ts b/packages/nicolium/src/queries/utils/import-entities.ts similarity index 100% rename from packages/nicolium/src/actions/importer.ts rename to packages/nicolium/src/queries/utils/import-entities.ts diff --git a/packages/nicolium/src/queries/utils/minify-list.ts b/packages/nicolium/src/queries/utils/minify-list.ts index e49ff340d..1f7f40064 100644 --- a/packages/nicolium/src/queries/utils/minify-list.ts +++ b/packages/nicolium/src/queries/utils/minify-list.ts @@ -13,7 +13,7 @@ import { type Status, } from 'pl-api'; -import { importEntities } from '@/actions/importer'; +import { importEntities } from '@/queries/utils/import-entities'; import { queryClient } from '../client'; import { queryKeys } from '../keys'; From 39498e2ebe6f3e1f1c0ef2e3250da0c6fa50db76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 13:22:13 +0100 Subject: [PATCH 24/25] nicolium: move more stuff around MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../components/instance-restrictions.tsx | 2 +- .../federation-restrictions/components/restricted-instance.tsx | 2 +- .../src/features/ui/components/panels/instance-info-panel.tsx | 2 +- .../features/ui/components/panels/instance-moderation-panel.tsx | 2 +- packages/nicolium/src/modals/edit-federation-modal.tsx | 2 +- packages/nicolium/src/pages/utils/federation-restrictions.tsx | 2 +- .../index.ts => queries/instance/use-remote-instance.ts} | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename packages/nicolium/src/{selectors/index.ts => queries/instance/use-remote-instance.ts} (100%) diff --git a/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx b/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx index 389aed035..88040cea4 100644 --- a/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx +++ b/packages/nicolium/src/features/federation-restrictions/components/instance-restrictions.tsx @@ -5,7 +5,7 @@ import Icon from '@/components/icon'; import Text from '@/components/ui/text'; import { useInstance } from '@/stores/instance'; -import type { RemoteInstance } from '@/selectors'; +import type { RemoteInstance } from '@/queries/instance/use-remote-instance'; const hasRestrictions = (remoteInstance: RemoteInstance): boolean => { const { accept, reject_deletes, report_removal, ...federation } = remoteInstance.federation; diff --git a/packages/nicolium/src/features/federation-restrictions/components/restricted-instance.tsx b/packages/nicolium/src/features/federation-restrictions/components/restricted-instance.tsx index b4c066ecd..a7a50811b 100644 --- a/packages/nicolium/src/features/federation-restrictions/components/restricted-instance.tsx +++ b/packages/nicolium/src/features/federation-restrictions/components/restricted-instance.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React, { useState } from 'react'; import Icon from '@/components/icon'; -import { useRemoteInstance } from '@/selectors'; +import { useRemoteInstance } from '@/queries/instance/use-remote-instance'; import InstanceRestrictions from './instance-restrictions'; diff --git a/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx b/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx index 32dcdbb93..2b68a5b57 100644 --- a/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx @@ -3,7 +3,7 @@ import { useIntl, defineMessages } from 'react-intl'; import { pinHost, unpinHost } from '@/actions/remote-timeline'; import Widget from '@/components/ui/widget'; -import { useRemoteInstance } from '@/selectors'; +import { useRemoteInstance } from '@/queries/instance/use-remote-instance'; import { useSettings } from '@/stores/settings'; const messages = defineMessages({ diff --git a/packages/nicolium/src/features/ui/components/panels/instance-moderation-panel.tsx b/packages/nicolium/src/features/ui/components/panels/instance-moderation-panel.tsx index 2a2549c88..5ec95f9d9 100644 --- a/packages/nicolium/src/features/ui/components/panels/instance-moderation-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/instance-moderation-panel.tsx @@ -5,7 +5,7 @@ import DropdownMenu from '@/components/dropdown-menu'; import Widget from '@/components/ui/widget'; import InstanceRestrictions from '@/features/federation-restrictions/components/instance-restrictions'; import { useOwnAccount } from '@/hooks/use-own-account'; -import { useRemoteInstance } from '@/selectors'; +import { useRemoteInstance } from '@/queries/instance/use-remote-instance'; import { useModalsActions } from '@/stores/modals'; const messages = defineMessages({ diff --git a/packages/nicolium/src/modals/edit-federation-modal.tsx b/packages/nicolium/src/modals/edit-federation-modal.tsx index f8b5fdc0f..855f55b7b 100644 --- a/packages/nicolium/src/modals/edit-federation-modal.tsx +++ b/packages/nicolium/src/modals/edit-federation-modal.tsx @@ -6,7 +6,7 @@ import List, { ListItem } from '@/components/list'; import Modal from '@/components/ui/modal'; import Toggle from '@/components/ui/toggle'; import { useAdminConfig, useUpdateAdminConfig } from '@/queries/admin/use-config'; -import { useRemoteInstance } from '@/selectors'; +import { useRemoteInstance } from '@/queries/instance/use-remote-instance'; import toast from '@/toast'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; diff --git a/packages/nicolium/src/pages/utils/federation-restrictions.tsx b/packages/nicolium/src/pages/utils/federation-restrictions.tsx index 8cd7a7cfc..34ec32584 100644 --- a/packages/nicolium/src/pages/utils/federation-restrictions.tsx +++ b/packages/nicolium/src/pages/utils/federation-restrictions.tsx @@ -5,7 +5,7 @@ import ScrollableList from '@/components/scrollable-list'; import Accordion from '@/components/ui/accordion'; import Column from '@/components/ui/column'; import RestrictedInstance from '@/features/federation-restrictions/components/restricted-instance'; -import { useHosts } from '@/selectors'; +import { useHosts } from '@/queries/instance/use-remote-instance'; import { useInstance } from '@/stores/instance'; import { useFederationRestrictionsDisclosed } from '@/utils/state'; diff --git a/packages/nicolium/src/selectors/index.ts b/packages/nicolium/src/queries/instance/use-remote-instance.ts similarity index 100% rename from packages/nicolium/src/selectors/index.ts rename to packages/nicolium/src/queries/instance/use-remote-instance.ts From a0e45b895106cfe0c3b78e66a3ba9e272984f97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 15 Mar 2026 13:27:41 +0100 Subject: [PATCH 25/25] nicolium: more moves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/nicolium/src/actions/admin.ts | 25 ------------------- .../nicolium/src/actions/remote-timeline.ts | 22 ---------------- packages/nicolium/src/actions/statuses.ts | 19 ++++++++++++++ .../components/statuses/status-action-bar.tsx | 3 +-- .../components/panels/instance-info-panel.tsx | 22 ++++++++++++---- packages/nicolium/src/features/ui/index.tsx | 2 +- .../hooks/streaming/use-direct-stream.ts | 0 .../hooks/streaming/use-timeline-stream.ts | 0 .../hooks/streaming/use-user-stream.ts | 0 .../src/pages/status-lists/conversations.tsx | 2 +- .../src/queries/timelines/use-timeline.ts | 2 +- 11 files changed, 40 insertions(+), 57 deletions(-) delete mode 100644 packages/nicolium/src/actions/admin.ts delete mode 100644 packages/nicolium/src/actions/remote-timeline.ts rename packages/nicolium/src/{api => }/hooks/streaming/use-direct-stream.ts (100%) rename packages/nicolium/src/{api => }/hooks/streaming/use-timeline-stream.ts (100%) rename packages/nicolium/src/{api => }/hooks/streaming/use-user-stream.ts (100%) diff --git a/packages/nicolium/src/actions/admin.ts b/packages/nicolium/src/actions/admin.ts deleted file mode 100644 index ca89f003f..000000000 --- a/packages/nicolium/src/actions/admin.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getClient } from '@/api'; -import { queryClient } from '@/queries/client'; -import { queryKeys } from '@/queries/keys'; -import { useComposeStore } from '@/stores/compose'; -import { useModalsStore } from '@/stores/modals'; - -const redactStatus = (statusId: string) => { - const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); - if (!status) return; - - const poll = status.poll_id - ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) - : undefined; - - return getClient() - .statuses.getStatusSource(statusId) - .then((source) => { - useComposeStore - .getState() - .actions.setComposeToStatus(status, poll, source, false, null, null, true); - useModalsStore.getState().actions.openModal('COMPOSE'); - }); -}; - -export { redactStatus }; diff --git a/packages/nicolium/src/actions/remote-timeline.ts b/packages/nicolium/src/actions/remote-timeline.ts deleted file mode 100644 index ed3f6fe33..000000000 --- a/packages/nicolium/src/actions/remote-timeline.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { changeSetting } from '@/actions/settings'; -import { useSettingsStore } from '@/stores/settings'; - -const getPinnedHosts = () => { - const { settings } = useSettingsStore.getState(); - return settings.remote_timeline.pinnedHosts; -}; - -const pinHost = (host: string) => { - const pinnedHosts = getPinnedHosts(); - changeSetting(['remote_timeline', 'pinnedHosts'], [...pinnedHosts, host]); -}; - -const unpinHost = (host: string) => { - const pinnedHosts = getPinnedHosts(); - changeSetting( - ['remote_timeline', 'pinnedHosts'], - pinnedHosts.filter((value) => value !== host), - ); -}; - -export { pinHost, unpinHost }; diff --git a/packages/nicolium/src/actions/statuses.ts b/packages/nicolium/src/actions/statuses.ts index 8d7364958..0a4980812 100644 --- a/packages/nicolium/src/actions/statuses.ts +++ b/packages/nicolium/src/actions/statuses.ts @@ -170,6 +170,24 @@ const editStatus = (statusId: string) => { }); }; +const redactStatus = (statusId: string) => { + const status = queryClient.getQueryData(queryKeys.statuses.show(statusId)); + if (!status) return; + + const poll = status.poll_id + ? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id)) + : undefined; + + return getClient() + .statuses.getStatusSource(statusId) + .then((source) => { + useComposeStore + .getState() + .actions.setComposeToStatus(status, poll, source, false, null, null, true); + useModalsStore.getState().actions.openModal('COMPOSE'); + }); +}; + const fetchStatus = (statusId: string, intl?: IntlShape) => { const params = intl && useSettingsStore.getState().settings.autoTranslate @@ -224,6 +242,7 @@ const toggleMuteStatus = (status: Pick) => export { createStatus, editStatus, + redactStatus, fetchStatus, toggleMuteStatus, decrementReplyCount, diff --git a/packages/nicolium/src/components/statuses/status-action-bar.tsx b/packages/nicolium/src/components/statuses/status-action-bar.tsx index 253f67fc5..217560755 100644 --- a/packages/nicolium/src/components/statuses/status-action-bar.tsx +++ b/packages/nicolium/src/components/statuses/status-action-bar.tsx @@ -3,9 +3,8 @@ import { type Account, type CustomEmoji, GroupRoles } from 'pl-api'; import React, { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { redactStatus } from '@/actions/admin'; import { changeSetting } from '@/actions/settings'; -import { editStatus, toggleMuteStatus } from '@/actions/statuses'; +import { editStatus, toggleMuteStatus, redactStatus } from '@/actions/statuses'; import DropdownMenu from '@/components/dropdown-menu'; import StatusActionButton from '@/components/statuses/status-action-button'; import { useCurrentAccount } from '@/contexts/current-account-context'; diff --git a/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx b/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx index 2b68a5b57..415084eb7 100644 --- a/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/instance-info-panel.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useIntl, defineMessages } from 'react-intl'; -import { pinHost, unpinHost } from '@/actions/remote-timeline'; +import { changeSetting } from '@/actions/settings'; import Widget from '@/components/ui/widget'; import { useRemoteInstance } from '@/queries/instance/use-remote-instance'; import { useSettings } from '@/stores/settings'; @@ -22,10 +22,22 @@ const InstanceInfoPanel: React.FC = ({ host }) => { const settings = useSettings(); const remoteInstance = useRemoteInstance(host); - const pinned = settings.remote_timeline.pinnedHosts.includes(host); + const pinnedHosts = settings.remote_timeline.pinnedHosts; + const isPinned = pinnedHosts.includes(host); + + const pinHost = (host: string) => { + changeSetting(['remote_timeline', 'pinnedHosts'], [...pinnedHosts, host]); + }; + + const unpinHost = (host: string) => { + changeSetting( + ['remote_timeline', 'pinnedHosts'], + pinnedHosts.filter((value) => value !== host), + ); + }; const handlePinHost = () => { - if (!pinned) { + if (!isPinned) { pinHost(host); } else { unpinHost(host); @@ -39,11 +51,11 @@ const InstanceInfoPanel: React.FC = ({ host }) => { title={remoteInstance.host} onActionClick={handlePinHost} actionIcon={ - pinned + isPinned ? require('@phosphor-icons/core/regular/push-pin-slash.svg') : require('@phosphor-icons/core/regular/push-pin.svg') } - actionTitle={intl.formatMessage(pinned ? messages.unpinHost : messages.pinHost, { host })} + actionTitle={intl.formatMessage(isPinned ? messages.unpinHost : messages.pinHost, { host })} /> ); }; diff --git a/packages/nicolium/src/features/ui/index.tsx b/packages/nicolium/src/features/ui/index.tsx index fa31eed14..da2fe374c 100644 --- a/packages/nicolium/src/features/ui/index.tsx +++ b/packages/nicolium/src/features/ui/index.tsx @@ -4,11 +4,11 @@ import React, { Suspense, useEffect, useRef } from 'react'; import { Toaster } from 'react-hot-toast'; import { register as registerPushNotifications } from '@/actions/push-notifications/registerer'; -import { useUserStream } from '@/api/hooks/streaming/use-user-stream'; import SidebarNavigation from '@/components/navigation/sidebar-navigation'; import ThumbNavigation from '@/components/navigation/thumb-navigation'; import Layout from '@/components/ui/layout'; import { useCurrentAccount } from '@/contexts/current-account-context'; +import { useUserStream } from '@/hooks/streaming/use-user-stream'; import { useClient } from '@/hooks/use-client'; import { useDraggedFiles } from '@/hooks/use-dragged-files'; import { useFeatures } from '@/hooks/use-features'; diff --git a/packages/nicolium/src/api/hooks/streaming/use-direct-stream.ts b/packages/nicolium/src/hooks/streaming/use-direct-stream.ts similarity index 100% rename from packages/nicolium/src/api/hooks/streaming/use-direct-stream.ts rename to packages/nicolium/src/hooks/streaming/use-direct-stream.ts diff --git a/packages/nicolium/src/api/hooks/streaming/use-timeline-stream.ts b/packages/nicolium/src/hooks/streaming/use-timeline-stream.ts similarity index 100% rename from packages/nicolium/src/api/hooks/streaming/use-timeline-stream.ts rename to packages/nicolium/src/hooks/streaming/use-timeline-stream.ts diff --git a/packages/nicolium/src/api/hooks/streaming/use-user-stream.ts b/packages/nicolium/src/hooks/streaming/use-user-stream.ts similarity index 100% rename from packages/nicolium/src/api/hooks/streaming/use-user-stream.ts rename to packages/nicolium/src/hooks/streaming/use-user-stream.ts diff --git a/packages/nicolium/src/pages/status-lists/conversations.tsx b/packages/nicolium/src/pages/status-lists/conversations.tsx index 3a181fc4e..26f0d6252 100644 --- a/packages/nicolium/src/pages/status-lists/conversations.tsx +++ b/packages/nicolium/src/pages/status-lists/conversations.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDirectStream } from '@/api/hooks/streaming/use-direct-stream'; import Column from '@/components/ui/column'; import ConversationsList from '@/features/conversations/components/conversations-list'; +import { useDirectStream } from '@/hooks/streaming/use-direct-stream'; const messages = defineMessages({ title: { id: 'column.direct', defaultMessage: 'Direct messages' }, diff --git a/packages/nicolium/src/queries/timelines/use-timeline.ts b/packages/nicolium/src/queries/timelines/use-timeline.ts index 0414c71b4..a9d6b5587 100644 --- a/packages/nicolium/src/queries/timelines/use-timeline.ts +++ b/packages/nicolium/src/queries/timelines/use-timeline.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { useTimelineStream } from '@/api/hooks/streaming/use-timeline-stream'; +import { useTimelineStream } from '@/hooks/streaming/use-timeline-stream'; import { importEntities } from '@/queries/utils/import-entities'; import { useTimelinesStore,