Merge remote-tracking branch 'origin/main' into renovate/node-21.x

This commit is contained in:
Alex Gleason 2023-12-20 22:19:08 -06:00
commit c567f8d3a7
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
132 changed files with 3891 additions and 2405 deletions

View File

@ -61,7 +61,7 @@ module.exports = {
'URLSearchParams', // core-js
],
tailwindcss: {
config: 'tailwind.config.cjs',
config: 'tailwind.config.ts',
},
},
@ -268,7 +268,7 @@ module.exports = {
'error',
{
classRegex: '^(base|container|icon|item|list|outer|wrapper)?[c|C]lass(Name)?$',
config: 'tailwind.config.cjs',
config: 'tailwind.config.ts',
},
],
'tailwindcss/migration-from-tailwind-2': 'error',

View File

@ -50,12 +50,12 @@
"@fontsource/roboto-mono": "^5.0.0",
"@fontsource/tajawal": "^5.0.8",
"@gamestdio/websocket": "^0.3.2",
"@lexical/clipboard": "^0.12.2",
"@lexical/hashtag": "^0.12.2",
"@lexical/link": "^0.12.2",
"@lexical/react": "^0.12.2",
"@lexical/selection": "^0.12.2",
"@lexical/utils": "^0.12.2",
"@lexical/clipboard": "^0.12.4",
"@lexical/hashtag": "^0.12.4",
"@lexical/link": "^0.12.4",
"@lexical/react": "^0.12.4",
"@lexical/selection": "^0.12.4",
"@lexical/utils": "^0.12.4",
"@popperjs/core": "^2.11.5",
"@reach/combobox": "^0.18.0",
"@reach/menu-button": "^0.18.0",
@ -63,13 +63,13 @@
"@reach/rect": "^0.18.0",
"@reach/tabs": "^0.18.0",
"@reduxjs/toolkit": "^1.8.1",
"@sentry/browser": "^7.37.2",
"@sentry/react": "^7.37.2",
"@sentry/tracing": "^7.37.2",
"@sentry/browser": "^7.74.1",
"@sentry/react": "^7.74.1",
"@soapbox.pub/wasmboy": "^0.8.0",
"@tabler/icons": "^2.0.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@tanstack/react-query": "^5.0.0",
"@types/escape-html": "^1.0.1",
"@types/http-link-header": "^1.0.3",
@ -82,7 +82,7 @@
"@types/react-datepicker": "^4.4.2",
"@types/react-dom": "^18.0.10",
"@types/react-helmet": "^6.1.5",
"@types/react-motion": "^0.0.35",
"@types/react-motion": "^0.0.39",
"@types/react-router-dom": "^5.3.3",
"@types/react-sparklines": "^1.7.2",
"@types/react-swipeable-views": "^0.13.1",
@ -100,6 +100,7 @@
"bowser": "^2.11.0",
"browserslist": "^4.16.6",
"clsx": "^2.0.0",
"comlink": "^4.4.1",
"core-js": "^3.27.2",
"cryptocurrency-icons": "^0.18.1",
"cssnano": "^6.0.0",
@ -115,10 +116,10 @@
"immer": "^10.0.0",
"immutable": "^4.2.1",
"intersection-observer": "^0.12.2",
"intl-messageformat": "10.5.4",
"intl-messageformat": "10.5.8",
"intl-pluralrules": "^2.0.0",
"leaflet": "^1.8.0",
"lexical": "^0.12.2",
"lexical": "^0.12.4",
"line-awesome": "^1.3.0",
"localforage": "^1.10.0",
"lodash": "^4.7.11",
@ -134,6 +135,7 @@
"react-color": "^2.19.3",
"react-datepicker": "^4.8.0",
"react-dom": "^18.0.0",
"react-error-boundary": "^4.0.11",
"react-helmet": "^6.1.0",
"react-hot-toast": "^2.4.0",
"react-hotkeys": "^1.1.4",
@ -143,7 +145,7 @@
"react-motion": "^0.5.2",
"react-overlays": "^0.9.0",
"react-popper": "^2.3.0",
"react-redux": "^8.0.0",
"react-redux": "^9.0.0",
"react-router-dom": "^5.3.0",
"react-router-dom-v5-compat": "^6.6.2",
"react-router-scroll-4": "^1.0.0-beta.2",
@ -200,16 +202,16 @@
"eslint-plugin-tailwindcss": "^3.13.0",
"fake-indexeddb": "^5.0.0",
"husky": "^8.0.0",
"jsdom": "^22.1.0",
"jsdom": "^23.0.0",
"lint-staged": ">=10",
"react-intl-translations-manager": "^5.0.3",
"react-refresh": "^0.14.0",
"rollup-plugin-visualizer": "^5.9.2",
"stylelint": "^15.10.3",
"stylelint-config-standard-scss": "^11.0.0",
"tailwindcss": "^3.3.3",
"tailwindcss": "^3.4.0",
"vite-plugin-checker": "^0.6.2",
"vite-plugin-pwa": "^0.16.5",
"vite-plugin-pwa": "^0.17.0",
"vitest": "^0.34.4"
},
"resolutions": {

View File

@ -1,6 +1,5 @@
import api from '../api';
import type { AxiosError } from 'axios';
import type { AnyAction } from 'redux';
import type { RootState } from 'soapbox/store';
@ -31,7 +30,7 @@ const submitAccountNoteSuccess = (relationship: any) => ({
relationship,
});
const submitAccountNoteFail = (error: AxiosError) => ({
const submitAccountNoteFail = (error: unknown) => ({
type: ACCOUNT_NOTE_SUBMIT_FAIL,
error,
});

View File

@ -224,7 +224,7 @@ const fetchAccountSuccess = (account: APIEntity) => ({
account,
});
const fetchAccountFail = (id: string | null, error: AxiosError) => ({
const fetchAccountFail = (id: string | null, error: unknown) => ({
type: ACCOUNT_FETCH_FAIL,
id,
error,
@ -272,7 +272,7 @@ const blockAccountSuccess = (relationship: APIEntity, statuses: ImmutableMap<str
statuses,
});
const blockAccountFail = (error: AxiosError) => ({
const blockAccountFail = (error: unknown) => ({
type: ACCOUNT_BLOCK_FAIL,
error,
});
@ -287,7 +287,7 @@ const unblockAccountSuccess = (relationship: APIEntity) => ({
relationship,
});
const unblockAccountFail = (error: AxiosError) => ({
const unblockAccountFail = (error: unknown) => ({
type: ACCOUNT_UNBLOCK_FAIL,
error,
});
@ -350,7 +350,7 @@ const muteAccountSuccess = (relationship: APIEntity, statuses: ImmutableMap<stri
statuses,
});
const muteAccountFail = (error: AxiosError) => ({
const muteAccountFail = (error: unknown) => ({
type: ACCOUNT_MUTE_FAIL,
error,
});
@ -365,7 +365,7 @@ const unmuteAccountSuccess = (relationship: APIEntity) => ({
relationship,
});
const unmuteAccountFail = (error: AxiosError) => ({
const unmuteAccountFail = (error: unknown) => ({
type: ACCOUNT_UNMUTE_FAIL,
error,
});
@ -404,7 +404,7 @@ const subscribeAccountSuccess = (relationship: APIEntity) => ({
relationship,
});
const subscribeAccountFail = (error: AxiosError) => ({
const subscribeAccountFail = (error: unknown) => ({
type: ACCOUNT_SUBSCRIBE_FAIL,
error,
});
@ -419,7 +419,7 @@ const unsubscribeAccountSuccess = (relationship: APIEntity) => ({
relationship,
});
const unsubscribeAccountFail = (error: AxiosError) => ({
const unsubscribeAccountFail = (error: unknown) => ({
type: ACCOUNT_UNSUBSCRIBE_FAIL,
error,
});
@ -446,7 +446,7 @@ const removeFromFollowersSuccess = (relationship: APIEntity) => ({
relationship,
});
const removeFromFollowersFail = (id: string, error: AxiosError) => ({
const removeFromFollowersFail = (id: string, error: unknown) => ({
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL,
id,
error,
@ -482,7 +482,7 @@ const fetchFollowersSuccess = (id: string, accounts: APIEntity[], next: string |
next,
});
const fetchFollowersFail = (id: string, error: AxiosError) => ({
const fetchFollowersFail = (id: string, error: unknown) => ({
type: FOLLOWERS_FETCH_FAIL,
id,
error,
@ -526,7 +526,7 @@ const expandFollowersSuccess = (id: string, accounts: APIEntity[], next: string
next,
});
const expandFollowersFail = (id: string, error: AxiosError) => ({
const expandFollowersFail = (id: string, error: unknown) => ({
type: FOLLOWERS_EXPAND_FAIL,
id,
error,
@ -562,7 +562,7 @@ const fetchFollowingSuccess = (id: string, accounts: APIEntity[], next: string |
next,
});
const fetchFollowingFail = (id: string, error: AxiosError) => ({
const fetchFollowingFail = (id: string, error: unknown) => ({
type: FOLLOWING_FETCH_FAIL,
id,
error,
@ -606,7 +606,7 @@ const expandFollowingSuccess = (id: string, accounts: APIEntity[], next: string
next,
});
const expandFollowingFail = (id: string, error: AxiosError) => ({
const expandFollowingFail = (id: string, error: unknown) => ({
type: FOLLOWING_EXPAND_FAIL,
id,
error,
@ -646,7 +646,7 @@ const fetchRelationshipsSuccess = (relationships: APIEntity[]) => ({
skipLoading: true,
});
const fetchRelationshipsFail = (error: AxiosError) => ({
const fetchRelationshipsFail = (error: unknown) => ({
type: RELATIONSHIPS_FETCH_FAIL,
error,
skipLoading: true,
@ -678,7 +678,7 @@ const fetchFollowRequestsSuccess = (accounts: APIEntity[], next: string | null)
next,
});
const fetchFollowRequestsFail = (error: AxiosError) => ({
const fetchFollowRequestsFail = (error: unknown) => ({
type: FOLLOW_REQUESTS_FETCH_FAIL,
error,
});
@ -715,7 +715,7 @@ const expandFollowRequestsSuccess = (accounts: APIEntity[], next: string | null)
next,
});
const expandFollowRequestsFail = (error: AxiosError) => ({
const expandFollowRequestsFail = (error: unknown) => ({
type: FOLLOW_REQUESTS_EXPAND_FAIL,
error,
});
@ -742,7 +742,7 @@ const authorizeFollowRequestSuccess = (id: string) => ({
id,
});
const authorizeFollowRequestFail = (id: string, error: AxiosError) => ({
const authorizeFollowRequestFail = (id: string, error: unknown) => ({
type: FOLLOW_REQUEST_AUTHORIZE_FAIL,
id,
error,
@ -770,7 +770,7 @@ const rejectFollowRequestSuccess = (id: string) => ({
id,
});
const rejectFollowRequestFail = (id: string, error: AxiosError) => ({
const rejectFollowRequestFail = (id: string, error: unknown) => ({
type: FOLLOW_REQUEST_REJECT_FAIL,
id,
error,
@ -823,7 +823,7 @@ const pinAccountSuccess = (relationship: APIEntity) => ({
relationship,
});
const pinAccountFail = (error: AxiosError) => ({
const pinAccountFail = (error: unknown) => ({
type: ACCOUNT_PIN_FAIL,
error,
});
@ -838,7 +838,7 @@ const unpinAccountSuccess = (relationship: APIEntity) => ({
relationship,
});
const unpinAccountFail = (error: AxiosError) => ({
const unpinAccountFail = (error: unknown) => ({
type: ACCOUNT_UNPIN_FAIL,
error,
});
@ -867,7 +867,7 @@ const fetchPinnedAccountsSuccess = (id: string, accounts: APIEntity[], next: str
next,
});
const fetchPinnedAccountsFail = (id: string, error: AxiosError) => ({
const fetchPinnedAccountsFail = (id: string, error: unknown) => ({
type: PINNED_ACCOUNTS_FETCH_FAIL,
id,
error,

View File

@ -9,7 +9,6 @@ import api from '../api';
import { importFetchedAccounts } from './importer';
import { patchMeSuccess } from './me';
import type { AxiosError } from 'axios';
import type { Account } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
@ -61,7 +60,7 @@ const fetchAliasesSuccess = (aliases: unknown[]) => ({
value: aliases,
});
const fetchAliasesFail = (error: AxiosError) => ({
const fetchAliasesFail = (error: unknown) => ({
type: ALIASES_FETCH_FAIL,
error,
});
@ -143,7 +142,7 @@ const addToAliasesSuccess = () => ({
type: ALIASES_ADD_SUCCESS,
});
const addToAliasesFail = (error: AxiosError) => ({
const addToAliasesFail = (error: unknown) => ({
type: ALIASES_ADD_FAIL,
error,
});
@ -196,7 +195,7 @@ const removeFromAliasesSuccess = () => ({
type: ALIASES_REMOVE_SUCCESS,
});
const removeFromAliasesFail = (error: AxiosError) => ({
const removeFromAliasesFail = (error: unknown) => ({
type: ALIASES_REMOVE_FAIL,
error,
});

View File

@ -3,7 +3,6 @@ import { getFeatures } from 'soapbox/utils/features';
import { importFetchedStatuses } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -61,7 +60,7 @@ export const fetchAnnouncementsSuccess = (announcements: APIEntity) => ({
skipLoading: true,
});
export const fetchAnnouncementsFail = (error: AxiosError) => ({
export const fetchAnnouncementsFail = (error: unknown) => ({
type: ANNOUNCEMENTS_FETCH_FAIL,
error,
skipLoading: true,
@ -94,7 +93,7 @@ export const dismissAnnouncementSuccess = (announcementId: string) => ({
id: announcementId,
});
export const dismissAnnouncementFail = (announcementId: string, error: AxiosError) => ({
export const dismissAnnouncementFail = (announcementId: string, error: unknown) => ({
type: ANNOUNCEMENTS_DISMISS_FAIL,
id: announcementId,
error,
@ -141,7 +140,7 @@ export const addReactionSuccess = (announcementId: string, name: string, already
skipLoading: true,
});
export const addReactionFail = (announcementId: string, name: string, error: AxiosError) => ({
export const addReactionFail = (announcementId: string, name: string, error: unknown) => ({
type: ANNOUNCEMENTS_REACTION_ADD_FAIL,
id: announcementId,
name,
@ -174,7 +173,7 @@ export const removeReactionSuccess = (announcementId: string, name: string) => (
skipLoading: true,
});
export const removeReactionFail = (announcementId: string, name: string, error: AxiosError) => ({
export const removeReactionFail = (announcementId: string, name: string, error: unknown) => ({
type: ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
id: announcementId,
name,

View File

@ -6,7 +6,6 @@ import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
@ -46,7 +45,7 @@ function fetchBlocksSuccess(accounts: any, next: any) {
};
}
function fetchBlocksFail(error: AxiosError) {
function fetchBlocksFail(error: unknown) {
return {
type: BLOCKS_FETCH_FAIL,
error,
@ -90,7 +89,7 @@ function expandBlocksSuccess(accounts: any, next: any) {
};
}
function expandBlocksFail(error: AxiosError) {
function expandBlocksFail(error: unknown) {
return {
type: BLOCKS_EXPAND_FAIL,
error,

View File

@ -2,7 +2,6 @@ import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -43,7 +42,7 @@ const fetchBookmarkedStatusesSuccess = (statuses: APIEntity[], next: string | nu
next,
});
const fetchBookmarkedStatusesFail = (error: AxiosError) => ({
const fetchBookmarkedStatusesFail = (error: unknown) => ({
type: BOOKMARKED_STATUSES_FETCH_FAIL,
error,
});
@ -77,7 +76,7 @@ const expandBookmarkedStatusesSuccess = (statuses: APIEntity[], next: string | n
next,
});
const expandBookmarkedStatusesFail = (error: AxiosError) => ({
const expandBookmarkedStatusesFail = (error: unknown) => ({
type: BOOKMARKED_STATUSES_EXPAND_FAIL,
error,
});

View File

@ -1,4 +1,4 @@
import axios, { AxiosError, Canceler } from 'axios';
import axios, { Canceler } from 'axios';
import { List as ImmutableList } from 'immutable';
import throttle from 'lodash/throttle';
import { defineMessages, IntlShape } from 'react-intl';
@ -388,7 +388,7 @@ const submitComposeSuccess = (composeId: string, status: APIEntity) => ({
status: status,
});
const submitComposeFail = (composeId: string, error: AxiosError) => ({
const submitComposeFail = (composeId: string, error: unknown) => ({
type: COMPOSE_SUBMIT_FAIL,
id: composeId,
error: error,
@ -451,7 +451,7 @@ const uploadComposeSuccess = (composeId: string, media: APIEntity, file: File) =
skipLoading: true,
});
const uploadComposeFail = (composeId: string, error: AxiosError | true) => ({
const uploadComposeFail = (composeId: string, error: unknown) => ({
type: COMPOSE_UPLOAD_FAIL,
id: composeId,
error: error,
@ -484,7 +484,7 @@ const changeUploadComposeSuccess = (composeId: string, media: APIEntity) => ({
skipLoading: true,
});
const changeUploadComposeFail = (composeId: string, id: string, error: AxiosError) => ({
const changeUploadComposeFail = (composeId: string, id: string, error: unknown) => ({
type: COMPOSE_UPLOAD_CHANGE_FAIL,
composeId,
id,

View File

@ -8,7 +8,6 @@ import {
importFetchedStatus,
} from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -76,7 +75,7 @@ const expandConversationsSuccess = (conversations: APIEntity[], next: string | n
isLoadingRecent,
});
const expandConversationsFail = (error: AxiosError) => ({
const expandConversationsFail = (error: unknown) => ({
type: CONVERSATIONS_FETCH_FAIL,
error,
});

View File

@ -1,6 +1,5 @@
import api from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -33,7 +32,7 @@ const fetchCustomEmojisSuccess = (custom_emojis: APIEntity[]) => ({
skipLoading: true,
});
const fetchCustomEmojisFail = (error: AxiosError) => ({
const fetchCustomEmojisFail = (error: unknown) => ({
type: CUSTOM_EMOJIS_FETCH_FAIL,
error,
skipLoading: true,

View File

@ -3,7 +3,6 @@ import api from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -35,7 +34,7 @@ const fetchDirectorySuccess = (accounts: APIEntity[]) => ({
accounts,
});
const fetchDirectoryFail = (error: AxiosError) => ({
const fetchDirectoryFail = (error: unknown) => ({
type: DIRECTORY_FETCH_FAIL,
error,
});
@ -62,7 +61,7 @@ const expandDirectorySuccess = (accounts: APIEntity[]) => ({
accounts,
});
const expandDirectoryFail = (error: AxiosError) => ({
const expandDirectoryFail = (error: unknown) => ({
type: DIRECTORY_EXPAND_FAIL,
error,
});

View File

@ -3,7 +3,6 @@ import { isLoggedIn } from 'soapbox/utils/auth';
import api, { getLinks } from '../api';
import type { AxiosError } from 'axios';
import type { EntityStore } from 'soapbox/entity-store/types';
import type { Account } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
@ -50,7 +49,7 @@ const blockDomainSuccess = (domain: string, accounts: string[]) => ({
accounts,
});
const blockDomainFail = (domain: string, error: AxiosError) => ({
const blockDomainFail = (domain: string, error: unknown) => ({
type: DOMAIN_BLOCK_FAIL,
domain,
error,
@ -88,7 +87,7 @@ const unblockDomainSuccess = (domain: string, accounts: string[]) => ({
accounts,
});
const unblockDomainFail = (domain: string, error: AxiosError) => ({
const unblockDomainFail = (domain: string, error: unknown) => ({
type: DOMAIN_UNBLOCK_FAIL,
domain,
error,
@ -118,7 +117,7 @@ const fetchDomainBlocksSuccess = (domains: string[], next: string | null) => ({
next,
});
const fetchDomainBlocksFail = (error: AxiosError) => ({
const fetchDomainBlocksFail = (error: unknown) => ({
type: DOMAIN_BLOCKS_FETCH_FAIL,
error,
});
@ -162,7 +161,7 @@ const expandDomainBlocksSuccess = (domains: string[], next: string | null) => ({
next,
});
const expandDomainBlocksFail = (error: AxiosError) => ({
const expandDomainBlocksFail = (error: unknown) => ({
type: DOMAIN_BLOCKS_EXPAND_FAIL,
error,
});

View File

@ -1,4 +1,4 @@
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { List as ImmutableList } from 'immutable';
import { isLoggedIn } from 'soapbox/utils/auth';
@ -7,9 +7,8 @@ import api from '../api';
import { importFetchedAccounts, importFetchedStatus } from './importer';
import { favourite, unfavourite } from './interactions';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Status } from 'soapbox/types/entities';
import type { APIEntity, EmojiReaction, Status } from 'soapbox/types/entities';
const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST';
const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS';
@ -27,17 +26,17 @@ const noOp = () => () => new Promise(f => f(undefined));
const simpleEmojiReact = (status: Status, emoji: string, custom?: string) =>
(dispatch: AppDispatch) => {
const emojiReacts: ImmutableList<ImmutableMap<string, any>> = status.pleroma.get('emoji_reactions') || ImmutableList();
const emojiReacts: ImmutableList<EmojiReaction> = status.reactions || ImmutableList();
if (emoji === '👍' && status.favourited) return dispatch(unfavourite(status));
const undo = emojiReacts.filter(e => e.get('me') === true && e.get('name') === emoji).count() > 0;
const undo = emojiReacts.filter(e => e.me === true && e.name === emoji).count() > 0;
if (undo) return dispatch(unEmojiReact(status, emoji));
return Promise.all([
...emojiReacts
.filter((emojiReact) => emojiReact.get('me') === true)
.map(emojiReact => dispatch(unEmojiReact(status, emojiReact.get('name')))).toArray(),
.filter((emojiReact) => emojiReact.me === true)
.map(emojiReact => dispatch(unEmojiReact(status, emojiReact.name))).toArray(),
status.favourited && dispatch(unfavourite(status)),
]).then(() => {
if (emoji === '👍') {
@ -114,7 +113,7 @@ const fetchEmojiReactsSuccess = (id: string, emojiReacts: APIEntity[]) => ({
emojiReacts,
});
const fetchEmojiReactsFail = (id: string, error: AxiosError) => ({
const fetchEmojiReactsFail = (id: string, error: unknown) => ({
type: EMOJI_REACTS_FETCH_FAIL,
id,
error,
@ -135,7 +134,7 @@ const emojiReactSuccess = (status: Status, emoji: string) => ({
skipLoading: true,
});
const emojiReactFail = (status: Status, emoji: string, error: AxiosError) => ({
const emojiReactFail = (status: Status, emoji: string, error: unknown) => ({
type: EMOJI_REACT_FAIL,
status,
emoji,
@ -157,7 +156,7 @@ const unEmojiReactSuccess = (status: Status, emoji: string) => ({
skipLoading: true,
});
const unEmojiReactFail = (status: Status, emoji: string, error: AxiosError) => ({
const unEmojiReactFail = (status: Status, emoji: string, error: unknown) => ({
type: UNEMOJI_REACT_FAIL,
status,
emoji,

View File

@ -12,7 +12,6 @@ import {
STATUS_FETCH_SOURCE_SUCCESS,
} from './statuses';
import type { AxiosError } from 'axios';
import type { ReducerStatus } from 'soapbox/reducers/statuses';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Status as StatusEntity } from 'soapbox/types/entities';
@ -184,7 +183,7 @@ const uploadEventBannerSuccess = (media: APIEntity, file: File) => ({
file,
});
const uploadEventBannerFail = (error: AxiosError | true) => ({
const uploadEventBannerFail = (error: unknown) => ({
type: EVENT_BANNER_UPLOAD_FAIL,
error,
});
@ -253,7 +252,7 @@ const submitEventSuccess = (status: APIEntity) => ({
status,
});
const submitEventFail = (error: AxiosError) => ({
const submitEventFail = (error: unknown) => ({
type: EVENT_SUBMIT_FAIL,
error,
});
@ -295,7 +294,7 @@ const joinEventSuccess = (status: APIEntity) => ({
id: status.id,
});
const joinEventFail = (error: AxiosError, status: StatusEntity, previousState: string | null) => ({
const joinEventFail = (error: unknown, status: StatusEntity, previousState: string | null) => ({
type: EVENT_JOIN_FAIL,
error,
id: status.id,
@ -330,7 +329,7 @@ const leaveEventSuccess = (status: APIEntity) => ({
id: status.id,
});
const leaveEventFail = (error: AxiosError, status: StatusEntity) => ({
const leaveEventFail = (error: unknown, status: StatusEntity) => ({
type: EVENT_LEAVE_FAIL,
id: status.id,
error,
@ -361,7 +360,7 @@ const fetchEventParticipationsSuccess = (id: string, accounts: APIEntity[], next
next,
});
const fetchEventParticipationsFail = (id: string, error: AxiosError) => ({
const fetchEventParticipationsFail = (id: string, error: unknown) => ({
type: EVENT_PARTICIPATIONS_FETCH_FAIL,
id,
error,
@ -398,7 +397,7 @@ const expandEventParticipationsSuccess = (id: string, accounts: APIEntity[], nex
next,
});
const expandEventParticipationsFail = (id: string, error: AxiosError) => ({
const expandEventParticipationsFail = (id: string, error: unknown) => ({
type: EVENT_PARTICIPATIONS_EXPAND_FAIL,
id,
error,
@ -429,7 +428,7 @@ const fetchEventParticipationRequestsSuccess = (id: string, participations: APIE
next,
});
const fetchEventParticipationRequestsFail = (id: string, error: AxiosError) => ({
const fetchEventParticipationRequestsFail = (id: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL,
id,
error,
@ -466,7 +465,7 @@ const expandEventParticipationRequestsSuccess = (id: string, participations: API
next,
});
const expandEventParticipationRequestsFail = (id: string, error: AxiosError) => ({
const expandEventParticipationRequestsFail = (id: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL,
id,
error,
@ -497,7 +496,7 @@ const authorizeEventParticipationRequestSuccess = (id: string, accountId: string
accountId,
});
const authorizeEventParticipationRequestFail = (id: string, accountId: string, error: AxiosError) => ({
const authorizeEventParticipationRequestFail = (id: string, accountId: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL,
id,
accountId,
@ -529,7 +528,7 @@ const rejectEventParticipationRequestSuccess = (id: string, accountId: string) =
accountId,
});
const rejectEventParticipationRequestFail = (id: string, accountId: string, error: AxiosError) => ({
const rejectEventParticipationRequestFail = (id: string, accountId: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUEST_REJECT_FAIL,
id,
accountId,

View File

@ -4,7 +4,6 @@ import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -55,7 +54,7 @@ const fetchFavouritedStatusesSuccess = (statuses: APIEntity[], next: string | nu
skipLoading: true,
});
const fetchFavouritedStatusesFail = (error: AxiosError) => ({
const fetchFavouritedStatusesFail = (error: unknown) => ({
type: FAVOURITED_STATUSES_FETCH_FAIL,
error,
skipLoading: true,
@ -92,7 +91,7 @@ const expandFavouritedStatusesSuccess = (statuses: APIEntity[], next: string | n
next,
});
const expandFavouritedStatusesFail = (error: AxiosError) => ({
const expandFavouritedStatusesFail = (error: unknown) => ({
type: FAVOURITED_STATUSES_EXPAND_FAIL,
error,
});
@ -130,7 +129,7 @@ const fetchAccountFavouritedStatusesSuccess = (accountId: string, statuses: APIE
skipLoading: true,
});
const fetchAccountFavouritedStatusesFail = (accountId: string, error: AxiosError) => ({
const fetchAccountFavouritedStatusesFail = (accountId: string, error: unknown) => ({
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL,
accountId,
error,
@ -170,7 +169,7 @@ const expandAccountFavouritedStatusesSuccess = (accountId: string, statuses: API
next,
});
const expandAccountFavouritedStatusesFail = (accountId: string, error: AxiosError) => ({
const expandAccountFavouritedStatusesFail = (accountId: string, error: unknown) => ({
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL,
accountId,
error,

View File

@ -5,7 +5,6 @@ import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedGroups, importFetchedAccounts } from './importer';
import type { AxiosError } from 'axios';
import type { GroupRole } from 'soapbox/reducers/group-memberships';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -104,7 +103,7 @@ const deleteGroupSuccess = (id: string) => ({
id,
});
const deleteGroupFail = (id: string, error: AxiosError) => ({
const deleteGroupFail = (id: string, error: unknown) => ({
type: GROUP_DELETE_FAIL,
id,
error,
@ -132,7 +131,7 @@ const fetchGroupSuccess = (group: APIEntity) => ({
group,
});
const fetchGroupFail = (id: string, error: AxiosError) => ({
const fetchGroupFail = (id: string, error: unknown) => ({
type: GROUP_FETCH_FAIL,
id,
error,
@ -158,7 +157,7 @@ const fetchGroupsSuccess = (groups: APIEntity[]) => ({
groups,
});
const fetchGroupsFail = (error: AxiosError) => ({
const fetchGroupsFail = (error: unknown) => ({
type: GROUPS_FETCH_FAIL,
error,
});
@ -194,7 +193,7 @@ const fetchGroupRelationshipsSuccess = (relationships: APIEntity[]) => ({
skipLoading: true,
});
const fetchGroupRelationshipsFail = (error: AxiosError) => ({
const fetchGroupRelationshipsFail = (error: unknown) => ({
type: GROUP_RELATIONSHIPS_FETCH_FAIL,
error,
skipLoading: true,
@ -222,7 +221,7 @@ const groupKickSuccess = (groupId: string, accountId: string) => ({
accountId,
});
const groupKickFail = (groupId: string, accountId: string, error: AxiosError) => ({
const groupKickFail = (groupId: string, accountId: string, error: unknown) => ({
type: GROUP_KICK_SUCCESS,
groupId,
accountId,
@ -255,7 +254,7 @@ const fetchGroupBlocksSuccess = (id: string, accounts: APIEntity[], next: string
next,
});
const fetchGroupBlocksFail = (id: string, error: AxiosError) => ({
const fetchGroupBlocksFail = (id: string, error: unknown) => ({
type: GROUP_BLOCKS_FETCH_FAIL,
id,
error,
@ -295,7 +294,7 @@ const expandGroupBlocksSuccess = (id: string, accounts: APIEntity[], next: strin
next,
});
const expandGroupBlocksFail = (id: string, error: AxiosError) => ({
const expandGroupBlocksFail = (id: string, error: unknown) => ({
type: GROUP_BLOCKS_EXPAND_FAIL,
id,
error,
@ -322,7 +321,7 @@ const groupBlockSuccess = (groupId: string, accountId: string) => ({
accountId,
});
const groupBlockFail = (groupId: string, accountId: string, error: AxiosError) => ({
const groupBlockFail = (groupId: string, accountId: string, error: unknown) => ({
type: GROUP_BLOCK_FAIL,
groupId,
accountId,
@ -350,7 +349,7 @@ const groupUnblockSuccess = (groupId: string, accountId: string) => ({
accountId,
});
const groupUnblockFail = (groupId: string, accountId: string, error: AxiosError) => ({
const groupUnblockFail = (groupId: string, accountId: string, error: unknown) => ({
type: GROUP_UNBLOCK_FAIL,
groupId,
accountId,
@ -379,7 +378,7 @@ const groupPromoteAccountSuccess = (groupId: string, accountId: string, membersh
memberships,
});
const groupPromoteAccountFail = (groupId: string, accountId: string, error: AxiosError) => ({
const groupPromoteAccountFail = (groupId: string, accountId: string, error: unknown) => ({
type: GROUP_PROMOTE_FAIL,
groupId,
accountId,
@ -408,7 +407,7 @@ const groupDemoteAccountSuccess = (groupId: string, accountId: string, membershi
memberships,
});
const groupDemoteAccountFail = (groupId: string, accountId: string, error: AxiosError) => ({
const groupDemoteAccountFail = (groupId: string, accountId: string, error: unknown) => ({
type: GROUP_DEMOTE_FAIL,
groupId,
accountId,
@ -443,7 +442,7 @@ const fetchGroupMembershipsSuccess = (id: string, role: GroupRole, memberships:
next,
});
const fetchGroupMembershipsFail = (id: string, role: GroupRole, error: AxiosError) => ({
const fetchGroupMembershipsFail = (id: string, role: GroupRole, error: unknown) => ({
type: GROUP_MEMBERSHIPS_FETCH_FAIL,
id,
role,
@ -486,7 +485,7 @@ const expandGroupMembershipsSuccess = (id: string, role: GroupRole, memberships:
next,
});
const expandGroupMembershipsFail = (id: string, role: GroupRole, error: AxiosError) => ({
const expandGroupMembershipsFail = (id: string, role: GroupRole, error: unknown) => ({
type: GROUP_MEMBERSHIPS_EXPAND_FAIL,
id,
role,
@ -519,7 +518,7 @@ const fetchGroupMembershipRequestsSuccess = (id: string, accounts: APIEntity[],
next,
});
const fetchGroupMembershipRequestsFail = (id: string, error: AxiosError) => ({
const fetchGroupMembershipRequestsFail = (id: string, error: unknown) => ({
type: GROUP_MEMBERSHIP_REQUESTS_FETCH_FAIL,
id,
error,
@ -559,7 +558,7 @@ const expandGroupMembershipRequestsSuccess = (id: string, accounts: APIEntity[],
next,
});
const expandGroupMembershipRequestsFail = (id: string, error: AxiosError) => ({
const expandGroupMembershipRequestsFail = (id: string, error: unknown) => ({
type: GROUP_MEMBERSHIP_REQUESTS_EXPAND_FAIL,
id,
error,
@ -587,7 +586,7 @@ const authorizeGroupMembershipRequestSuccess = (groupId: string, accountId: stri
accountId,
});
const authorizeGroupMembershipRequestFail = (groupId: string, accountId: string, error: AxiosError) => ({
const authorizeGroupMembershipRequestFail = (groupId: string, accountId: string, error: unknown) => ({
type: GROUP_MEMBERSHIP_REQUEST_AUTHORIZE_FAIL,
groupId,
accountId,
@ -616,7 +615,7 @@ const rejectGroupMembershipRequestSuccess = (groupId: string, accountId: string)
accountId,
});
const rejectGroupMembershipRequestFail = (groupId: string, accountId: string, error?: AxiosError) => ({
const rejectGroupMembershipRequestFail = (groupId: string, accountId: string, error?: unknown) => ({
type: GROUP_MEMBERSHIP_REQUEST_REJECT_FAIL,
groupId,
accountId,

View File

@ -2,7 +2,6 @@ import api from 'soapbox/api';
import { importFetchedAccounts } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -37,7 +36,7 @@ const fetchHistorySuccess = (statusId: String, history: APIEntity[]) => ({
history,
});
const fetchHistoryFail = (error: AxiosError) => ({
const fetchHistoryFail = (error: unknown) => ({
type: HISTORY_FETCH_FAIL,
error,
});

View File

@ -9,7 +9,6 @@ import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus } from './importer';
import { expandGroupFeaturedTimeline } from './timelines';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Group, Status as StatusEntity } from 'soapbox/types/entities';
@ -135,7 +134,7 @@ const reblogSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const reblogFail = (status: StatusEntity, error: AxiosError) => ({
const reblogFail = (status: StatusEntity, error: unknown) => ({
type: REBLOG_FAIL,
status: status,
error: error,
@ -154,7 +153,7 @@ const unreblogSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const unreblogFail = (status: StatusEntity, error: AxiosError) => ({
const unreblogFail = (status: StatusEntity, error: unknown) => ({
type: UNREBLOG_FAIL,
status: status,
error: error,
@ -208,7 +207,7 @@ const favouriteSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const favouriteFail = (status: StatusEntity, error: AxiosError) => ({
const favouriteFail = (status: StatusEntity, error: unknown) => ({
type: FAVOURITE_FAIL,
status: status,
error: error,
@ -227,7 +226,7 @@ const unfavouriteSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const unfavouriteFail = (status: StatusEntity, error: AxiosError) => ({
const unfavouriteFail = (status: StatusEntity, error: unknown) => ({
type: UNFAVOURITE_FAIL,
status: status,
error: error,
@ -281,7 +280,7 @@ const dislikeSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const dislikeFail = (status: StatusEntity, error: AxiosError) => ({
const dislikeFail = (status: StatusEntity, error: unknown) => ({
type: DISLIKE_FAIL,
status: status,
error: error,
@ -300,7 +299,7 @@ const undislikeSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const undislikeFail = (status: StatusEntity, error: AxiosError) => ({
const undislikeFail = (status: StatusEntity, error: unknown) => ({
type: UNDISLIKE_FAIL,
status: status,
error: error,
@ -356,7 +355,7 @@ const bookmarkSuccess = (status: StatusEntity, response: APIEntity) => ({
response: response,
});
const bookmarkFail = (status: StatusEntity, error: AxiosError) => ({
const bookmarkFail = (status: StatusEntity, error: unknown) => ({
type: BOOKMARK_FAIL,
status: status,
error: error,
@ -373,7 +372,7 @@ const unbookmarkSuccess = (status: StatusEntity, response: APIEntity) => ({
response: response,
});
const unbookmarkFail = (status: StatusEntity, error: AxiosError) => ({
const unbookmarkFail = (status: StatusEntity, error: unknown) => ({
type: UNBOOKMARK_FAIL,
status: status,
error,
@ -407,7 +406,7 @@ const fetchReblogsSuccess = (id: string, accounts: APIEntity[], next: string | n
next,
});
const fetchReblogsFail = (id: string, error: AxiosError) => ({
const fetchReblogsFail = (id: string, error: unknown) => ({
type: REBLOGS_FETCH_FAIL,
id,
error,
@ -432,7 +431,7 @@ const expandReblogsSuccess = (id: string, accounts: APIEntity[], next: string |
next,
});
const expandReblogsFail = (id: string, error: AxiosError) => ({
const expandReblogsFail = (id: string, error: unknown) => ({
type: REBLOGS_EXPAND_FAIL,
id,
error,
@ -466,7 +465,7 @@ const fetchFavouritesSuccess = (id: string, accounts: APIEntity[], next: string
next,
});
const fetchFavouritesFail = (id: string, error: AxiosError) => ({
const fetchFavouritesFail = (id: string, error: unknown) => ({
type: FAVOURITES_FETCH_FAIL,
id,
error,
@ -491,7 +490,7 @@ const expandFavouritesSuccess = (id: string, accounts: APIEntity[], next: string
next,
});
const expandFavouritesFail = (id: string, error: AxiosError) => ({
const expandFavouritesFail = (id: string, error: unknown) => ({
type: FAVOURITES_EXPAND_FAIL,
id,
error,
@ -523,7 +522,7 @@ const fetchDislikesSuccess = (id: string, accounts: APIEntity[]) => ({
accounts,
});
const fetchDislikesFail = (id: string, error: AxiosError) => ({
const fetchDislikesFail = (id: string, error: unknown) => ({
type: DISLIKES_FETCH_FAIL,
id,
error,
@ -552,7 +551,7 @@ const fetchReactionsSuccess = (id: string, reactions: APIEntity[]) => ({
reactions,
});
const fetchReactionsFail = (id: string, error: AxiosError) => ({
const fetchReactionsFail = (id: string, error: unknown) => ({
type: REACTIONS_FETCH_FAIL,
id,
error,
@ -598,7 +597,7 @@ const pinSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const pinFail = (status: StatusEntity, error: AxiosError) => ({
const pinFail = (status: StatusEntity, error: unknown) => ({
type: PIN_FAIL,
status,
error,
@ -640,7 +639,7 @@ const unpinSuccess = (status: StatusEntity) => ({
skipLoading: true,
});
const unpinFail = (status: StatusEntity, error: AxiosError) => ({
const unpinFail = (status: StatusEntity, error: unknown) => ({
type: UNPIN_FAIL,
status,
error,
@ -676,7 +675,7 @@ const remoteInteractionSuccess = (ap_id: string, profile: string, url: string) =
url,
});
const remoteInteractionFail = (ap_id: string, profile: string, error: AxiosError) => ({
const remoteInteractionFail = (ap_id: string, profile: string, error: unknown) => ({
type: REMOTE_INTERACTION_FAIL,
ap_id,
profile,

View File

@ -6,7 +6,6 @@ import api from '../api';
import { importFetchedAccounts } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -81,7 +80,7 @@ const fetchListSuccess = (list: APIEntity) => ({
list,
});
const fetchListFail = (id: string | number, error: AxiosError) => ({
const fetchListFail = (id: string | number, error: unknown) => ({
type: LIST_FETCH_FAIL,
id,
error,
@ -106,7 +105,7 @@ const fetchListsSuccess = (lists: APIEntity[]) => ({
lists,
});
const fetchListsFail = (error: AxiosError) => ({
const fetchListsFail = (error: unknown) => ({
type: LISTS_FETCH_FAIL,
error,
});
@ -159,7 +158,7 @@ const createListSuccess = (list: APIEntity) => ({
list,
});
const createListFail = (error: AxiosError) => ({
const createListFail = (error: unknown) => ({
type: LIST_CREATE_FAIL,
error,
});
@ -188,7 +187,7 @@ const updateListSuccess = (list: APIEntity) => ({
list,
});
const updateListFail = (id: string | number, error: AxiosError) => ({
const updateListFail = (id: string | number, error: unknown) => ({
type: LIST_UPDATE_FAIL,
id,
error,
@ -218,7 +217,7 @@ const deleteListSuccess = (id: string | number) => ({
id,
});
const deleteListFail = (id: string | number, error: AxiosError) => ({
const deleteListFail = (id: string | number, error: unknown) => ({
type: LIST_DELETE_FAIL,
id,
error,
@ -247,7 +246,7 @@ const fetchListAccountsSuccess = (id: string | number, accounts: APIEntity[], ne
next,
});
const fetchListAccountsFail = (id: string | number, error: AxiosError) => ({
const fetchListAccountsFail = (id: string | number, error: unknown) => ({
type: LIST_ACCOUNTS_FETCH_FAIL,
id,
error,
@ -343,7 +342,7 @@ const removeFromListSuccess = (listId: string | number, accountId: string) => ({
accountId,
});
const removeFromListFail = (listId: string | number, accountId: string, error: AxiosError) => ({
const removeFromListFail = (listId: string | number, accountId: string, error: unknown) => ({
type: LIST_EDITOR_REMOVE_FAIL,
listId,
accountId,
@ -384,7 +383,7 @@ const fetchAccountListsSuccess = (id: string, lists: APIEntity[]) => ({
lists,
});
const fetchAccountListsFail = (id: string, err: AxiosError) => ({
const fetchAccountListsFail = (id: string, err: unknown) => ({
type: LIST_ADDER_LISTS_FETCH_FAIL,
id,
err,

View File

@ -8,7 +8,7 @@ import api from '../api';
import { loadCredentials } from './auth';
import { importFetchedAccount } from './importer';
import type { AxiosError, RawAxiosRequestHeaders } from 'axios';
import type { RawAxiosRequestHeaders } from 'axios';
import type { Account } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -125,7 +125,7 @@ const patchMeSuccess = (me: APIEntity) =>
dispatch(action);
};
const patchMeFail = (error: AxiosError) => ({
const patchMeFail = (error: unknown) => ({
type: ME_PATCH_FAIL,
error,
skipAlert: true,

View File

@ -8,7 +8,6 @@ import resizeImage from 'soapbox/utils/resize-image';
import api from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -59,7 +58,7 @@ const uploadFile = (
file: File,
intl: IntlShape,
onSuccess: (data: APIEntity) => void = () => {},
onFail: (error: AxiosError | true) => void = () => {},
onFail: (error: unknown) => void = () => {},
onProgress: (loaded: number) => void = () => {},
changeTotal: (value: number) => void = () => {},
) =>

View File

@ -1,6 +1,5 @@
import api from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
const MFA_FETCH_REQUEST = 'MFA_FETCH_REQUEST';
@ -50,7 +49,7 @@ const setupMfa = (method: string) =>
return api(getState).get(`/api/pleroma/accounts/mfa/setup/${method}`).then(({ data }) => {
dispatch({ type: MFA_SETUP_SUCCESS, data });
return data;
}).catch((error: AxiosError) => {
}).catch((error: unknown) => {
dispatch({ type: MFA_SETUP_FAIL });
throw error;
});
@ -63,7 +62,7 @@ const confirmMfa = (method: string, code: string, password: string) =>
return api(getState).post(`/api/pleroma/accounts/mfa/confirm/${method}`, params).then(({ data }) => {
dispatch({ type: MFA_CONFIRM_SUCCESS, method, code });
return data;
}).catch((error: AxiosError) => {
}).catch((error: unknown) => {
dispatch({ type: MFA_CONFIRM_FAIL, method, code, error, skipAlert: true });
throw error;
});
@ -75,7 +74,7 @@ const disableMfa = (method: string, password: string) =>
return api(getState).delete(`/api/pleroma/accounts/mfa/${method}`, { data: { password } }).then(({ data }) => {
dispatch({ type: MFA_DISABLE_SUCCESS, method });
return data;
}).catch((error: AxiosError) => {
}).catch((error: unknown) => {
dispatch({ type: MFA_DISABLE_FAIL, method, skipAlert: true });
throw error;
});

View File

@ -9,7 +9,6 @@ import { Stack, Text } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container';
import { selectAccount } from 'soapbox/selectors';
import toast from 'soapbox/toast';
import { isLocal } from 'soapbox/utils/accounts';
import type { AppDispatch, RootState } from 'soapbox/store';
@ -79,7 +78,7 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () =
const account = selectAccount(state, accountId)!;
const acct = account.acct;
const name = account.username;
const local = isLocal(account);
const local = account.local;
const message = (
<Stack space={4}>

View File

@ -1,4 +1,4 @@
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
import { Set as ImmutableSet } from 'immutable';
import ConfigDB from 'soapbox/utils/config-db';
@ -7,9 +7,9 @@ import { fetchConfig, updateConfig } from './admin';
import type { MRFSimple } from 'soapbox/schemas/pleroma';
import type { AppDispatch, RootState } from 'soapbox/store';
const simplePolicyMerge = (simplePolicy: MRFSimple, host: string, restrictions: ImmutableMap<string, any>) => {
const simplePolicyMerge = (simplePolicy: MRFSimple, host: string, restrictions: Record<string, any>) => {
const entries = Object.entries(simplePolicy).map(([key, hosts]) => {
const isRestricted = restrictions.get(key);
const isRestricted = restrictions[key];
if (isRestricted) {
return [key, ImmutableSet(hosts).add(host).toJS()];
@ -21,7 +21,7 @@ const simplePolicyMerge = (simplePolicy: MRFSimple, host: string, restrictions:
return Object.fromEntries(entries);
};
const updateMrf = (host: string, restrictions: ImmutableMap<string, any>) =>
const updateMrf = (host: string, restrictions: Record<string, any>) =>
(dispatch: AppDispatch, getState: () => RootState) =>
dispatch(fetchConfig())
.then(() => {

View File

@ -22,7 +22,6 @@ import {
import { saveMarker } from './markers';
import { getSettings, saveSettings } from './settings';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Status } from 'soapbox/types/entities';
@ -262,7 +261,7 @@ const expandNotificationsSuccess = (notifications: APIEntity[], next: string | n
skipLoading: !isLoadingMore,
});
const expandNotificationsFail = (error: AxiosError, isLoadingMore: boolean) => ({
const expandNotificationsFail = (error: unknown, isLoadingMore: boolean) => ({
type: NOTIFICATIONS_EXPAND_FAIL,
error,
skipLoading: !isLoadingMore,

View File

@ -1,6 +1,5 @@
import api from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -38,7 +37,7 @@ const importFetchedInstance = (instance: APIEntity) => ({
instance,
});
const fetchInstanceFail = (error: AxiosError) => ({
const fetchInstanceFail = (error: unknown) => ({
type: PATRON_INSTANCE_FETCH_FAIL,
error,
skipAlert: true,
@ -49,7 +48,7 @@ const importFetchedAccount = (account: APIEntity) => ({
account,
});
const fetchAccountFail = (error: AxiosError) => ({
const fetchAccountFail = (error: unknown) => ({
type: PATRON_ACCOUNT_FETCH_FAIL,
error,
skipAlert: true,

View File

@ -4,7 +4,6 @@ import api from '../api';
import { importFetchedStatuses } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -37,7 +36,7 @@ const fetchPinnedStatusesSuccess = (statuses: APIEntity[], next: string | null)
next,
});
const fetchPinnedStatusesFail = (error: AxiosError) => ({
const fetchPinnedStatusesFail = (error: unknown) => ({
type: PINNED_STATUSES_FETCH_FAIL,
error,
});

View File

@ -2,7 +2,6 @@ import api from '../api';
import { importFetchedPoll } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -47,7 +46,7 @@ const voteSuccess = (poll: APIEntity) => ({
poll,
});
const voteFail = (error: AxiosError) => ({
const voteFail = (error: unknown) => ({
type: POLL_VOTE_FAIL,
error,
});
@ -61,7 +60,7 @@ const fetchPollSuccess = (poll: APIEntity) => ({
poll,
});
const fetchPollFail = (error: AxiosError) => ({
const fetchPollFail = (error: unknown) => ({
type: POLL_FETCH_FAIL,
error,
});

View File

@ -2,7 +2,6 @@ import api from '../api';
import { openModal } from './modals';
import type { AxiosError } from 'axios';
import type { Account } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { ChatMessage, Group, Status } from 'soapbox/types/entities';
@ -83,7 +82,7 @@ const submitReportSuccess = () => ({
type: REPORT_SUBMIT_SUCCESS,
});
const submitReportFail = (error: AxiosError) => ({
const submitReportFail = (error: unknown) => ({
type: REPORT_SUBMIT_FAIL,
error,
});

View File

@ -2,7 +2,6 @@ import { getFeatures } from 'soapbox/utils/features';
import api, { getLinks } from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -61,7 +60,7 @@ const fetchScheduledStatusesSuccess = (statuses: APIEntity[], next: string | nul
next,
});
const fetchScheduledStatusesFail = (error: AxiosError) => ({
const fetchScheduledStatusesFail = (error: unknown) => ({
type: SCHEDULED_STATUSES_FETCH_FAIL,
error,
});
@ -94,7 +93,7 @@ const expandScheduledStatusesSuccess = (statuses: APIEntity[], next: string | nu
next,
});
const expandScheduledStatusesFail = (error: AxiosError) => ({
const expandScheduledStatusesFail = (error: unknown) => ({
type: SCHEDULED_STATUSES_EXPAND_FAIL,
error,
});

View File

@ -3,7 +3,6 @@ import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatuses } from './importer';
import type { AxiosError } from 'axios';
import type { SearchFilter } from 'soapbox/reducers/search';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -105,7 +104,7 @@ const fetchSearchSuccess = (results: APIEntity[], searchTerm: string, searchType
next,
});
const fetchSearchFail = (error: AxiosError) => ({
const fetchSearchFail = (error: unknown) => ({
type: SEARCH_FETCH_FAIL,
error,
});
@ -178,7 +177,7 @@ const expandSearchSuccess = (results: APIEntity[], searchTerm: string, searchTyp
next,
});
const expandSearchFail = (error: AxiosError) => ({
const expandSearchFail = (error: unknown) => ({
type: SEARCH_EXPAND_FAIL,
error,
});

View File

@ -8,7 +8,6 @@ import { getFeatures } from 'soapbox/utils/features';
import api, { staticClient } from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -106,7 +105,7 @@ const importSoapboxConfig = (soapboxConfig: APIEntity, host: string | null) => {
};
};
const soapboxConfigFail = (error: AxiosError, host: string | null) => ({
const soapboxConfigFail = (error: unknown, host: string | null) => ({
type: SOAPBOX_CONFIG_REQUEST_FAIL,
error,
skipAlert: true,

View File

@ -1,6 +1,5 @@
import api, { getLinks } from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -44,7 +43,7 @@ const fetchHashtagSuccess = (name: string, tag: APIEntity) => ({
tag,
});
const fetchHashtagFail = (error: AxiosError) => ({
const fetchHashtagFail = (error: unknown) => ({
type: HASHTAG_FETCH_FAIL,
error,
});
@ -70,7 +69,7 @@ const followHashtagSuccess = (name: string, tag: APIEntity) => ({
tag,
});
const followHashtagFail = (name: string, error: AxiosError) => ({
const followHashtagFail = (name: string, error: unknown) => ({
type: HASHTAG_FOLLOW_FAIL,
name,
error,
@ -97,7 +96,7 @@ const unfollowHashtagSuccess = (name: string, tag: APIEntity) => ({
tag,
});
const unfollowHashtagFail = (name: string, error: AxiosError) => ({
const unfollowHashtagFail = (name: string, error: unknown) => ({
type: HASHTAG_UNFOLLOW_FAIL,
name,
error,
@ -124,7 +123,7 @@ const fetchFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: string |
next,
});
const fetchFollowedHashtagsFail = (error: AxiosError) => ({
const fetchFollowedHashtagsFail = (error: unknown) => ({
type: FOLLOWED_HASHTAGS_FETCH_FAIL,
error,
});
@ -156,7 +155,7 @@ const expandFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: string
next,
});
const expandFollowedHashtagsFail = (error: AxiosError) => ({
const expandFollowedHashtagsFail = (error: unknown) => ({
type: FOLLOWED_HASHTAGS_EXPAND_FAIL,
error,
});

View File

@ -9,7 +9,6 @@ import api, { getNextLink, getPrevLink } from '../api';
import { fetchGroupRelationships } from './groups';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Status } from 'soapbox/types/entities';
@ -284,7 +283,7 @@ const expandTimelineSuccess = (
skipLoading: !isLoadingMore,
});
const expandTimelineFail = (timeline: string, error: AxiosError, isLoadingMore: boolean) => ({
const expandTimelineFail = (timeline: string, error: unknown, isLoadingMore: boolean) => ({
type: TIMELINE_EXPAND_FAIL,
timeline,
error,

View File

@ -1,6 +1,5 @@
import api from '../api';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
@ -28,7 +27,7 @@ const fetchTrendsSuccess = (tags: APIEntity[]) => ({
skipLoading: true,
});
const fetchTrendsFail = (error: AxiosError) => ({
const fetchTrendsFail = (error: unknown) => ({
type: TRENDS_FETCH_FAIL,
error,
skipLoading: true,

View File

@ -36,7 +36,7 @@ function useSignerStream() {
const respMsg = {
id: reqMsg.data.id,
result: await signEvent(reqMsg.data.params[0]),
result: await signEvent(reqMsg.data.params[0], reqMsg.data.params[1]),
};
const respEvent = await signEvent({

View File

@ -187,7 +187,7 @@ const Account = ({
return (
<div data-testid='account' className='group block w-full shrink-0' ref={overflowRef}>
<HStack alignItems={actionAlignment} justifyContent='between'>
<HStack alignItems={actionAlignment} space={3} justifyContent='between'>
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3} className='overflow-hidden'>
<ProfilePopper
condition={showProfileHoverCard}

View File

@ -12,7 +12,7 @@ const BigCard: React.FC<IBigCard> = ({ title, subtitle, children }) => {
return (
<Card variant='rounded' size='xl'>
<CardBody>
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 sm:-mx-10 sm:pb-10 dark:border-gray-800'>
<Stack space={2}>
<Text size='2xl' align='center' weight='bold'>{title}</Text>
{subtitle && <Text theme='muted' align='center'>{subtitle}</Text>}

View File

@ -1,222 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import * as BuildConfig from 'soapbox/build-config';
import { HStack, Text, Stack } from 'soapbox/components/ui';
import { captureException } from 'soapbox/monitoring';
import KVStore from 'soapbox/storage/kv-store';
import sourceCode from 'soapbox/utils/code';
import { unregisterSW } from 'soapbox/utils/sw';
import SiteLogo from './site-logo';
import type { RootState } from 'soapbox/store';
interface Props extends ReturnType<typeof mapStateToProps> {
children: React.ReactNode;
}
type State = {
hasError: boolean;
error: any;
componentStack: any;
browser?: Bowser.Parser.Parser;
}
class ErrorBoundary extends React.PureComponent<Props, State> {
state: State = {
hasError: false,
error: undefined,
componentStack: undefined,
browser: undefined,
};
textarea: HTMLTextAreaElement | null = null;
componentDidCatch(error: any, info: any): void {
captureException(error, {
tags: {
// Allow page crashes to be easily searched in Sentry.
ErrorBoundary: 'yes',
},
});
this.setState({
hasError: true,
error,
componentStack: info && info.componentStack,
});
import('bowser')
.then(({ default: Bowser }) => {
this.setState({
browser: Bowser.getParser(window.navigator.userAgent),
});
})
.catch(() => {});
}
setTextareaRef: React.RefCallback<HTMLTextAreaElement> = c => {
this.textarea = c;
};
handleCopy: React.MouseEventHandler = () => {
if (!this.textarea) return;
this.textarea.select();
this.textarea.setSelectionRange(0, 99999);
document.execCommand('copy');
};
getErrorText = (): string => {
const { error, componentStack } = this.state;
return error + componentStack;
};
clearCookies: React.MouseEventHandler = (e) => {
localStorage.clear();
sessionStorage.clear();
KVStore.clear();
if ('serviceWorker' in navigator) {
e.preventDefault();
unregisterSW().then(goHome).catch(goHome);
}
};
render() {
const { browser, hasError } = this.state;
const { children, links } = this.props;
if (!hasError) {
return children;
}
const isProduction = BuildConfig.NODE_ENV === 'production';
const errorText = this.getErrorText();
return (
<div className='flex h-screen flex-col bg-white pb-12 pt-16 dark:bg-primary-900'>
<main className='mx-auto flex w-full max-w-7xl grow flex-col justify-center px-4 sm:px-6 lg:px-8'>
<div className='flex shrink-0 justify-center'>
<a href='/' className='inline-flex'>
<SiteLogo alt='Logo' className='h-12 w-auto cursor-pointer' />
</a>
</div>
<div className='py-8'>
<div className='mx-auto max-w-xl space-y-2 text-center'>
<h1 className='text-3xl font-extrabold tracking-tight text-gray-900 dark:text-gray-500 sm:text-4xl'>
<FormattedMessage id='alert.unexpected.message' defaultMessage='Something went wrong.' />
</h1>
<p className='text-lg text-gray-700 dark:text-gray-600'>
<FormattedMessage
id='alert.unexpected.body'
defaultMessage="We're sorry for the interruption. If the problem persists, please reach out to our support team. You may also try to {clearCookies} (this will log you out)."
values={{
clearCookies: (
<a href='/' onClick={this.clearCookies} className='text-primary-600 hover:underline dark:text-accent-blue'>
<FormattedMessage
id='alert.unexpected.clear_cookies'
defaultMessage='clear cookies and browser data'
/>
</a>
),
}}
/>
</p>
<Text theme='muted'>
<Text weight='medium' tag='span' theme='muted'>{sourceCode.displayName}:</Text>
{' '}{sourceCode.version}
</Text>
<div className='mt-10'>
<a href='/' className='text-base font-medium text-primary-600 hover:underline dark:text-accent-blue'>
<FormattedMessage id='alert.unexpected.return_home' defaultMessage='Return Home' />
{' '}
<span className='inline-block rtl:rotate-180' aria-hidden='true'>&rarr;</span>
</a>
</div>
</div>
{!isProduction && (
<div className='mx-auto max-w-lg space-y-4 py-16'>
{errorText && (
<textarea
ref={this.setTextareaRef}
className='block h-48 w-full rounded-md border-gray-300 bg-gray-100 p-4 font-mono text-gray-900 shadow-sm focus:border-primary-500 focus:ring-2 focus:ring-primary-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 sm:text-sm'
value={errorText}
onClick={this.handleCopy}
dir='ltr'
readOnly
/>
)}
{browser && (
<Stack>
<Text weight='semibold'><FormattedMessage id='alert.unexpected.browser' defaultMessage='Browser' /></Text>
<Text theme='muted'>{browser.getBrowserName()} {browser.getBrowserVersion()}</Text>
</Stack>
)}
</div>
)}
</div>
</main>
<footer className='mx-auto w-full max-w-7xl shrink-0 px-4 sm:px-6 lg:px-8'>
<HStack justifyContent='center' space={4} element='nav'>
{links.get('status') && (
<>
<a href={links.get('status')} className='text-sm font-medium text-gray-700 hover:underline dark:text-gray-600'>
<FormattedMessage id='alert.unexpected.links.status' defaultMessage='Status' />
</a>
</>
)}
{links.get('help') && (
<>
<span className='inline-block border-l border-gray-300' aria-hidden='true' />
<a href={links.get('help')} className='text-sm font-medium text-gray-700 hover:underline dark:text-gray-600'>
<FormattedMessage id='alert.unexpected.links.help' defaultMessage='Help Center' />
</a>
</>
)}
{links.get('support') && (
<>
<span className='inline-block border-l border-gray-300' aria-hidden='true' />
<a href={links.get('support')} className='text-sm font-medium text-gray-700 hover:underline dark:text-gray-600'>
<FormattedMessage id='alert.unexpected.links.support' defaultMessage='Support' />
</a>
</>
)}
</HStack>
</footer>
</div>
);
}
}
function goHome() {
location.href = '/';
}
function mapStateToProps(state: RootState) {
const { links, logo } = getSoapboxConfig(state);
return {
siteTitle: state.instance.title,
logo,
links,
};
}
export default connect(mapStateToProps)(ErrorBoundary);

View File

@ -10,7 +10,7 @@ import Icon from './icon';
import { Button, HStack, Stack, Text } from './ui';
import VerificationBadge from './verification-badge';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
import type { Status as StatusEntity } from 'soapbox/types/entities';
const messages = defineMessages({
eventBanner: { id: 'event.banner', defaultMessage: 'Event banner' },
@ -30,7 +30,7 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
const me = useAppSelector((state) => state.me);
const account = status.account as AccountEntity;
const account = status.account;
const event = status.event!;
const banner = event.banner;

192
src/components/gameboy.tsx Normal file
View File

@ -0,0 +1,192 @@
// @ts-ignore No types available
import { WasmBoy } from '@soapbox.pub/wasmboy';
import clsx from 'clsx';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { exitFullscreen, isFullscreen, requestFullscreen } from 'soapbox/features/ui/util/fullscreen';
import { HStack, IconButton } from './ui';
let gainNode: GainNode | undefined;
interface IGameboy extends Pick<React.HTMLAttributes<HTMLDivElement>, 'onFocus' | 'onBlur'> {
/** Classname of the outer `<div>`. */
className?: string;
/** URL to the ROM. */
src: string;
/** Aspect ratio of the canvas. */
aspect?: 'normal' | 'stretched';
}
/** Component to display a playable Gameboy emulator. */
const Gameboy: React.FC<IGameboy> = ({ className, src, aspect = 'normal', onFocus, onBlur, ...rest }) => {
const node = useRef<HTMLDivElement>(null);
const canvas = useRef<HTMLCanvasElement>(null);
const [paused, setPaused] = useState(false);
const [muted, setMuted] = useState(true);
const [fullscreen, setFullscreen] = useState(false);
const [showControls, setShowControls] = useState(true);
async function init() {
await WasmBoy.config(WasmBoyOptions, canvas.current!);
await WasmBoy.loadROM(src);
await play();
if (document.activeElement === canvas.current) {
await WasmBoy.enableDefaultJoypad();
} else {
await WasmBoy.disableDefaultJoypad();
}
}
const handleFocus: React.FocusEventHandler<HTMLDivElement> = useCallback(() => {
WasmBoy.enableDefaultJoypad();
}, []);
const handleBlur: React.FocusEventHandler<HTMLDivElement> = useCallback(() => {
WasmBoy.disableDefaultJoypad();
}, []);
const handleFullscreenChange = useCallback(() => {
setFullscreen(isFullscreen());
}, []);
const handleCanvasClick = useCallback(() => {
setShowControls(!showControls);
}, [showControls]);
const pause = async () => {
await WasmBoy.pause();
setPaused(true);
};
const play = async () => {
await WasmBoy.play();
setPaused(false);
};
const togglePaused = () => paused ? play() : pause();
const toggleMuted = () => setMuted(!muted);
const toggleFullscreen = () => {
if (isFullscreen()) {
exitFullscreen();
} else if (node.current) {
requestFullscreen(node.current);
}
};
const handleDownload = () => {
window.open(src);
};
useEffect(() => {
init();
return () => {
WasmBoy.pause();
WasmBoy.disableDefaultJoypad();
};
}, []);
useEffect(() => {
document.addEventListener('fullscreenchange', handleFullscreenChange, true);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange, true);
};
}, []);
useEffect(() => {
if (fullscreen) {
node.current?.focus();
}
}, [fullscreen]);
useEffect(() => {
if (gainNode) {
gainNode.gain.value = muted ? 0 : 1;
}
}, [gainNode, muted]);
return (
<div
ref={node}
tabIndex={0}
className={clsx(className, 'relative outline-none')}
onFocus={onFocus ?? handleFocus}
onBlur={onBlur ?? handleBlur}
>
<canvas
ref={canvas}
onClick={handleCanvasClick}
className={clsx('h-full w-full bg-black ', {
'object-contain': aspect === 'normal',
'object-cover': aspect === 'stretched',
})}
{...rest}
/>
<HStack
justifyContent='between'
className={clsx('pointer-events-none absolute inset-x-0 bottom-0 w-full bg-gradient-to-t from-black/50 to-transparent p-2 opacity-0 transition-opacity', {
'pointer-events-auto opacity-100': showControls,
})}
>
<HStack space={2}>
<IconButton
theme='transparent'
className='text-white'
onClick={togglePaused}
src={paused ? require('@tabler/icons/player-play.svg') : require('@tabler/icons/player-pause.svg')}
/>
<IconButton
theme='transparent'
className='text-white'
onClick={toggleMuted}
src={muted ? require('@tabler/icons/volume-3.svg') : require('@tabler/icons/volume.svg')}
/>
</HStack>
<HStack space={2}>
<IconButton
theme='transparent'
className='text-white'
src={require('@tabler/icons/download.svg')}
onClick={handleDownload}
/>
<IconButton
theme='transparent'
className='text-white'
onClick={toggleFullscreen}
src={fullscreen ? require('@tabler/icons/arrows-minimize.svg') : require('@tabler/icons/arrows-maximize.svg')}
/>
</HStack>
</HStack>
</div>
);
};
const WasmBoyOptions = {
headless: false,
useGbcWhenOptional: true,
isAudioEnabled: true,
frameSkip: 1,
audioBatchProcessing: true,
timersBatchProcessing: false,
audioAccumulateSamples: true,
graphicsBatchProcessing: false,
graphicsDisableScanlineRendering: false,
tileRendering: true,
tileCaching: true,
gameboyFPSCap: 60,
updateGraphicsCallback: false,
updateAudioCallback: (audioContext: AudioContext, audioBufferSourceNode: AudioBufferSourceNode) => {
gainNode = gainNode ?? audioContext.createGain();
audioBufferSourceNode.connect(gainNode);
return gainNode;
},
saveStateCallback: false,
};
export default Gameboy;

View File

@ -3,7 +3,7 @@ import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Banner, Button, HStack, Stack, Text } from 'soapbox/components/ui';
import { useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks';
import { useInstance, useSoapboxConfig } from 'soapbox/hooks';
const acceptedGdpr = !!localStorage.getItem('soapbox:gdpr');
@ -14,8 +14,7 @@ const GdprBanner: React.FC = () => {
const [slideout, setSlideout] = useState(false);
const instance = useInstance();
const soapbox = useSoapboxConfig();
const isLoggedIn = useAppSelector(state => !!state.me);
const { gdprUrl } = useSoapboxConfig();
const handleAccept = () => {
localStorage.setItem('soapbox:gdpr', 'true');
@ -23,15 +22,13 @@ const GdprBanner: React.FC = () => {
setTimeout(() => setShown(true), 200);
};
const showBanner = soapbox.gdpr && !isLoggedIn && !shown;
if (!showBanner) {
if (shown) {
return null;
}
return (
<Banner theme='opaque' className={clsx('transition-transform', { 'translate-y-full': slideout })}>
<div className='flex flex-col space-y-4 rtl:space-x-reverse lg:flex-row lg:items-center lg:justify-between lg:space-x-4 lg:space-y-0'>
<div className='flex flex-col space-y-4 lg:flex-row lg:items-center lg:justify-between lg:space-x-4 lg:space-y-0 rtl:space-x-reverse'>
<Stack space={2}>
<Text size='xl' weight='bold'>
<FormattedMessage id='gdpr.title' defaultMessage='{siteTitle} uses cookies' values={{ siteTitle: instance.title }} />
@ -47,8 +44,8 @@ const GdprBanner: React.FC = () => {
</Stack>
<HStack space={2} alignItems='center' className='flex-none'>
{soapbox.gdprUrl && (
<a href={soapbox.gdprUrl} tabIndex={-1} className='inline-flex'>
{gdprUrl && (
<a href={gdprUrl} tabIndex={-1} className='inline-flex'>
<Button theme='secondary'>
<FormattedMessage id='gdpr.learn_more' defaultMessage='Learn more' />
</Button>

View File

@ -1,5 +1,5 @@
import clsx from 'clsx';
import React, { useState, useRef, useLayoutEffect } from 'react';
import React, { useState, useRef, useLayoutEffect, Suspense } from 'react';
import Blurhash from 'soapbox/components/blurhash';
import Icon from 'soapbox/components/icon';
@ -15,6 +15,8 @@ import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maxi
import type { Property } from 'csstype';
import type { List as ImmutableList } from 'immutable';
const Gameboy = React.lazy(() => import('./gameboy'));
const ATTACHMENT_LIMIT = 4;
const MAX_FILENAME_LENGTH = 45;
@ -141,8 +143,24 @@ const Item: React.FC<IItem> = ({
}
let thumbnail: React.ReactNode = '';
const ext = attachment.url.split('.').pop()?.toLowerCase();
if (attachment.type === 'unknown') {
if (attachment.type === 'unknown' && ['gb', 'gbc'].includes(ext!)) {
return (
<div
className={clsx('media-gallery__item', {
standalone,
'rounded-md': total > 1,
})}
key={attachment.id}
style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}
>
<Suspense fallback={<div className='media-gallery__item-thumbnail' />}>
<Gameboy className='media-gallery__item-thumbnail cursor-default' src={attachment.url} />
</Suspense>
</div>
);
} else if (attachment.type === 'unknown') {
const filename = truncateFilename(attachment.url, MAX_FILENAME_LENGTH);
const attachmentIcon = (
<Icon
@ -215,7 +233,6 @@ const Item: React.FC<IItem> = ({
</div>
);
} else if (attachment.type === 'audio') {
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={clsx('media-gallery__item-thumbnail')}
@ -225,11 +242,10 @@ const Item: React.FC<IItem> = ({
title={attachment.description}
>
<span className='media-gallery__item__icons'><Icon src={require('@tabler/icons/volume.svg')} /></span>
<span className='media-gallery__file-extension__label'>{ext}</span>
<span className='media-gallery__file-extension__label uppercase'>{ext}</span>
</a>
);
} else if (attachment.type === 'video') {
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={clsx('media-gallery__item-thumbnail')}
@ -246,7 +262,7 @@ const Item: React.FC<IItem> = ({
>
<source src={attachment.url} />
</video>
<span className='media-gallery__file-extension__label'>{ext}</span>
<span className='media-gallery__file-extension__label uppercase'>{ext}</span>
</a>
);
}

View File

@ -122,7 +122,7 @@ const PollOption: React.FC<IPollOption> = (props): JSX.Element | null => {
return (
<div key={option.title}>
{showResults ? (
<div title={voted ? message : undefined}>
<div title={message}>
<HStack
justifyContent='between'
alignItems='center'

View File

@ -14,7 +14,6 @@ import Badge from 'soapbox/components/badge';
import ActionButton from 'soapbox/features/ui/components/action-button';
import { UserPanel } from 'soapbox/features/ui/util/async-components';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import { isLocal } from 'soapbox/utils/accounts';
import { showProfileHoverCard } from './hover-ref-wrapper';
import { dateFormatOptions } from './relative-timestamp';
@ -117,7 +116,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
badges={badges}
/>
{isLocal(account) ? (
{account.local ? (
<HStack alignItems='center' space={0.5}>
<Icon
src={require('@tabler/icons/calendar.svg')}

View File

@ -15,7 +15,7 @@ import StatusContent from './status-content';
import StatusReplyMentions from './status-reply-mentions';
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
import type { Status as StatusEntity } from 'soapbox/types/entities';
const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
@ -51,7 +51,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
const handleExpandClick: MouseEventHandler<HTMLDivElement> = (e) => {
if (!status) return;
const account = status.account as AccountEntity;
const account = status.account;
if (!compose && e.button === 0) {
const statusUrl = `/@${account.acct}/posts/${status.id}`;
@ -79,7 +79,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
return null;
}
const account = status.account as AccountEntity;
const account = status.account;
let actions = {};
if (onCancel) {

View File

@ -0,0 +1,66 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Textarea, Form, Button, FormGroup, FormActions, Text } from 'soapbox/components/ui';
import { useOwnAccount } from 'soapbox/hooks';
import { captureSentryFeedback } from 'soapbox/sentry';
interface ISentryFeedbackForm {
eventId: string;
}
/** Accept feedback for the given Sentry event. */
const SentryFeedbackForm: React.FC<ISentryFeedbackForm> = ({ eventId }) => {
const { account } = useOwnAccount();
const [feedback, setFeedback] = useState<string>();
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
const handleFeedbackChange: React.ChangeEventHandler<HTMLTextAreaElement> = (e) => {
setFeedback(e.target.value);
};
const handleSubmitFeedback: React.FormEventHandler = async (_e) => {
if (!feedback || !eventId) return;
setIsSubmitting(true);
await captureSentryFeedback({
name: account?.acct,
event_id: eventId,
comments: feedback,
}).catch(console.error);
setIsSubmitted(true);
};
if (isSubmitted) {
return (
<Text align='center'>
<FormattedMessage id='alert.unexpected.thanks' defaultMessage='Thanks for your feedback!' />
</Text>
);
}
return (
<Form onSubmit={handleSubmitFeedback}>
<FormGroup>
<Textarea
value={feedback}
onChange={handleFeedbackChange}
placeholder='Anything you can tell us about what happened?'
disabled={isSubmitting}
autoGrow
/>
</FormGroup>
<FormActions>
<Button type='submit' className='mx-auto' disabled={!feedback || isSubmitting}>
<FormattedMessage id='alert.unexpected.submit_feedback' defaultMessage='Submit Feedback' />
</Button>
</FormActions>
</Form>
);
};
export default SentryFeedbackForm;

View File

@ -0,0 +1,199 @@
import React, { type ErrorInfo, useRef, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { FormattedMessage } from 'react-intl';
import { NODE_ENV } from 'soapbox/build-config';
import { HStack, Text, Stack, Textarea } from 'soapbox/components/ui';
import { useSoapboxConfig } from 'soapbox/hooks';
import { captureSentryException } from 'soapbox/sentry';
import KVStore from 'soapbox/storage/kv-store';
import sourceCode from 'soapbox/utils/code';
import { unregisterSW } from 'soapbox/utils/sw';
import SentryFeedbackForm from './sentry-feedback-form';
import SiteLogo from './site-logo';
interface ISiteErrorBoundary {
children: React.ReactNode;
}
/** Application-level error boundary. Fills the whole screen. */
const SiteErrorBoundary: React.FC<ISiteErrorBoundary> = ({ children }) => {
const { links, sentryDsn } = useSoapboxConfig();
const textarea = useRef<HTMLTextAreaElement>(null);
const [error, setError] = useState<unknown>();
const [componentStack, setComponentStack] = useState<string>();
const [browser, setBrowser] = useState<Bowser.Parser.Parser>();
const [sentryEventId, setSentryEventId] = useState<string>();
const sentryEnabled = Boolean(sentryDsn);
const isProduction = NODE_ENV === 'production';
const errorText = String(error) + componentStack;
const clearCookies: React.MouseEventHandler = (e) => {
localStorage.clear();
sessionStorage.clear();
KVStore.clear();
if ('serviceWorker' in navigator) {
e.preventDefault();
unregisterSW().then(goHome).catch(goHome);
}
};
const handleCopy: React.MouseEventHandler = () => {
if (!textarea.current) return;
textarea.current.select();
textarea.current.setSelectionRange(0, 99999);
document.execCommand('copy');
};
function handleError(error: Error, info: ErrorInfo) {
setError(error);
setComponentStack(info.componentStack);
captureSentryException(error, {
tags: {
// Allow page crashes to be easily searched in Sentry.
ErrorBoundary: 'yes',
},
})
.then((eventId) => setSentryEventId(eventId))
.catch(console.error);
import('bowser')
.then(({ default: Bowser }) => setBrowser(Bowser.getParser(window.navigator.userAgent)))
.catch(() => {});
}
function goHome() {
location.href = '/';
}
const fallback = (
<div className='flex h-screen flex-col bg-white pb-12 pt-16 dark:bg-primary-900'>
<main className='mx-auto flex w-full max-w-7xl grow flex-col justify-center px-4 sm:px-6 lg:px-8'>
<div className='flex shrink-0 justify-center'>
<a href='/' className='inline-flex'>
<SiteLogo alt='Logo' className='h-12 w-auto cursor-pointer' />
</a>
</div>
<div className='py-8'>
<div className='mx-auto max-w-xl space-y-2 text-center'>
<h1 className='text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl dark:text-gray-500'>
<FormattedMessage id='alert.unexpected.message' defaultMessage='Something went wrong.' />
</h1>
<p className='text-lg text-gray-700 dark:text-gray-600'>
<FormattedMessage
id='alert.unexpected.body'
defaultMessage="We're sorry for the interruption. If the problem persists, please reach out to our support team. You may also try to {clearCookies} (this will log you out)."
values={{
clearCookies: (
<a href='/' onClick={clearCookies} className='text-primary-600 hover:underline dark:text-accent-blue'>
<FormattedMessage
id='alert.unexpected.clear_cookies'
defaultMessage='clear cookies and browser data'
/>
</a>
),
}}
/>
</p>
<Text theme='muted'>
<Text weight='medium' tag='span' theme='muted'>{sourceCode.displayName}:</Text>
{' '}{sourceCode.version}
</Text>
<div className='mt-10'>
<a href='/' className='text-base font-medium text-primary-600 hover:underline dark:text-accent-blue'>
<FormattedMessage id='alert.unexpected.return_home' defaultMessage='Return Home' />
{' '}
<span className='inline-block rtl:rotate-180' aria-hidden='true'>&rarr;</span>
</a>
</div>
</div>
<div className='mx-auto max-w-lg space-y-4 py-16'>
{(isProduction) ? (
(sentryEnabled && sentryEventId) && (
<SentryFeedbackForm eventId={sentryEventId} />
)
) : (
<>
{errorText && (
<Textarea
ref={textarea}
value={errorText}
onClick={handleCopy}
isCodeEditor
rows={12}
readOnly
/>
)}
{browser && (
<Stack>
<Text weight='semibold'><FormattedMessage id='alert.unexpected.browser' defaultMessage='Browser' /></Text>
<Text theme='muted'>{browser.getBrowserName()} {browser.getBrowserVersion()}</Text>
</Stack>
)}
</>
)}
</div>
</div>
</main>
<footer className='mx-auto w-full max-w-7xl shrink-0 px-4 sm:px-6 lg:px-8'>
<HStack justifyContent='center' space={4} element='nav'>
{links.get('status') && (
<SiteErrorBoundaryLink href={links.get('status')!}>
<FormattedMessage id='alert.unexpected.links.status' defaultMessage='Status' />
</SiteErrorBoundaryLink>
)}
{links.get('help') && (
<SiteErrorBoundaryLink href={links.get('help')!}>
<FormattedMessage id='alert.unexpected.links.help' defaultMessage='Help Center' />
</SiteErrorBoundaryLink>
)}
{links.get('support') && (
<SiteErrorBoundaryLink href={links.get('support')!}>
<FormattedMessage id='alert.unexpected.links.support' defaultMessage='Support' />
</SiteErrorBoundaryLink>
)}
</HStack>
</footer>
</div>
);
return (
<ErrorBoundary fallback={fallback} onError={handleError}>
{children}
</ErrorBoundary>
);
};
interface ISiteErrorBoundaryLink {
href: string;
children: React.ReactNode;
}
function SiteErrorBoundaryLink({ href, children }: ISiteErrorBoundaryLink) {
return (
<>
<span className='inline-block border-l border-gray-300' aria-hidden='true' />
<a href={href} className='text-sm font-medium text-gray-700 hover:underline dark:text-gray-600'>
{children}
</a>
</>
);
}
export default SiteErrorBoundary;

View File

@ -1,4 +1,3 @@
import { List as ImmutableList } from 'immutable';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory, useRouteMatch } from 'react-router-dom';
@ -23,14 +22,13 @@ import { HStack } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks';
import { GroupRoles } from 'soapbox/schemas/group-member';
import toast from 'soapbox/toast';
import { isLocal, isRemote } from 'soapbox/utils/accounts';
import copy from 'soapbox/utils/copy';
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji-reacts';
import GroupPopover from './groups/popover/group-popover';
import type { Menu } from 'soapbox/components/dropdown-menu';
import type { Account, Group, Status } from 'soapbox/types/entities';
import type { Group, Status } from 'soapbox/types/entities';
const messages = defineMessages({
adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' },
@ -132,7 +130,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const unmuteGroup = useUnmuteGroup(group as Group);
const isMutingGroup = !!group?.relationship?.muting;
const deleteGroupStatus = useDeleteGroupStatus(group as Group, status.id);
const blockGroupMember = useBlockGroupMember(group as Group, status?.account as any);
const blockGroupMember = useBlockGroupMember(group as Group, status.account);
const me = useAppSelector(state => state.me);
const { groupRelationship } = useGroupRelationship(status.group?.id);
@ -267,20 +265,20 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(mentionCompose(status.account as Account));
dispatch(mentionCompose(status.account));
};
const handleDirectClick: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(directCompose(status.account as Account));
dispatch(directCompose(status.account));
};
const handleChatClick: React.EventHandler<React.MouseEvent> = (e) => {
const account = status.account as Account;
const account = status.account;
dispatch(launchChat(account.id, history));
};
const handleMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(initMuteModal(status.account as Account));
dispatch(initMuteModal(status.account));
};
const handleMuteGroupClick: React.EventHandler<React.MouseEvent> = () =>
@ -305,7 +303,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleBlockClick: React.EventHandler<React.MouseEvent> = (e) => {
const account = status.account as Account;
const account = status.account;
dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/ban.svg'),
@ -333,7 +331,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(initReport(ReportableEntities.STATUS, status.account as Account, { status }));
dispatch(initReport(ReportableEntities.STATUS, status.account, { status }));
};
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
@ -347,7 +345,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const onModerate: React.MouseEventHandler = (e) => {
const account = status.account as Account;
const account = status.account;
dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id }));
};
@ -360,7 +358,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleDeleteFromGroup: React.EventHandler<React.MouseEvent> = () => {
const account = status.account as Account;
const account = status.account;
dispatch(openModal('CONFIRM', {
heading: intl.formatMessage(messages.deleteHeading),
@ -379,10 +377,10 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const handleBlockFromGroup = () => {
dispatch(openModal('CONFIRM', {
heading: intl.formatMessage(messages.groupBlockFromGroupHeading),
message: intl.formatMessage(messages.groupBlockFromGroupMessage, { name: (status.account as any).username }),
message: intl.formatMessage(messages.groupBlockFromGroupMessage, { name: status.account.username }),
confirm: intl.formatMessage(messages.groupBlockConfirm),
onConfirm: () => {
blockGroupMember({ account_ids: [(status.account as any).id] }, {
blockGroupMember({ account_ids: [status.account.id] }, {
onSuccess() {
toast.success(intl.formatMessage(messages.blocked, { name: account?.acct }));
},
@ -415,7 +413,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
icon: require('@tabler/icons/clipboard-copy.svg'),
});
if (features.embeds && isLocal(account)) {
if (features.embeds && account.local) {
menu.push({
text: intl.formatMessage(messages.embed),
action: handleEmbed,
@ -449,7 +447,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
});
}
if (features.federating && isRemote(account)) {
if (features.federating && !account.local) {
menu.push({
text: intl.formatMessage(messages.external, { domain }),
action: handleExternalClick,
@ -554,7 +552,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
if (isGroupStatus && !!status.group) {
const group = status.group as Group;
const account = status.account as Account;
const account = status.account;
const isGroupOwner = groupRelationship?.role === GroupRoles.OWNER;
const isGroupAdmin = groupRelationship?.role === GroupRoles.ADMIN;
const isStatusFromOwner = group.owner.id === account.id;
@ -627,15 +625,15 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const reblogCount = status.reblogs_count;
const favouriteCount = status.favourites_count;
const emojiReactCount = reduceEmoji(
(status.pleroma.get('emoji_reactions') || ImmutableList()) as ImmutableList<any>,
const emojiReactCount = status.reactions ? reduceEmoji(
status.reactions,
favouriteCount,
status.favourited,
allowedEmoji,
).reduce((acc, cur) => acc + cur.get('count'), 0);
).reduce((acc, cur) => acc + (cur.count || 0), 0) : undefined;
const meEmojiReact = getReactForStatus(status, allowedEmoji);
const meEmojiName = meEmojiReact?.get('name') as keyof typeof reactMessages | undefined;
const meEmojiName = meEmojiReact?.name as keyof typeof reactMessages | undefined;
const reactMessages = {
'👍': messages.reactionLike,

View File

@ -4,7 +4,7 @@ import React from 'react';
import { Text, Icon, Emoji } from 'soapbox/components/ui';
import { shortNumberFormat } from 'soapbox/utils/numbers';
import type { Map as ImmutableMap } from 'immutable';
import type { EmojiReaction } from 'soapbox/schemas';
const COLORS = {
accent: 'accent',
@ -33,7 +33,7 @@ interface IStatusActionButton extends React.ButtonHTMLAttributes<HTMLButtonEleme
active?: boolean;
color?: Color;
filled?: boolean;
emoji?: ImmutableMap<string, any>;
emoji?: EmojiReaction;
text?: React.ReactNode;
theme?: 'default' | 'inverse';
}
@ -45,7 +45,7 @@ const StatusActionButton = React.forwardRef<HTMLButtonElement, IStatusActionButt
if (emoji) {
return (
<span className='flex h-6 w-6 items-center justify-center'>
<Emoji className='h-full w-full p-0.5' emoji={emoji.get('name')} src={emoji.get('url')} />
<Emoji className='h-full w-full p-0.5' emoji={emoji.name} src={emoji.url} />
</span>
);
} else {

View File

@ -71,7 +71,7 @@ const StatusReactionWrapper: React.FC<IStatusReactionWrapper> = ({ statusId, chi
};
const handleClick: React.EventHandler<React.MouseEvent> = e => {
const meEmojiReact = getReactForStatus(status, soapboxConfig.allowedEmoji)?.get('name') || '👍';
const meEmojiReact = getReactForStatus(status, soapboxConfig.allowedEmoji)?.name || '👍';
if (isUserTouching()) {
if (ownAccount) {

View File

@ -8,7 +8,7 @@ import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper';
import { useAppDispatch } from 'soapbox/hooks';
import { isPubkey } from 'soapbox/utils/nostr';
import type { Account, Status } from 'soapbox/types/entities';
import type { Status } from 'soapbox/types/entities';
interface IStatusReplyMentions {
status: Status;
@ -21,7 +21,7 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
const handleOpenMentionsModal: React.MouseEventHandler<HTMLSpanElement> = (e) => {
e.stopPropagation();
const account = status.account as Account;
const account = status.account;
dispatch(openModal('MENTIONS', {
username: account.acct,

View File

@ -24,10 +24,7 @@ import StatusInfo from './statuses/status-info';
import Tombstone from './tombstone';
import { Card, Icon, Stack, Text } from './ui';
import type {
Account as AccountEntity,
Status as StatusEntity,
} from 'soapbox/types/entities';
import type { Status as StatusEntity } from 'soapbox/types/entities';
// Defined in components/scrollable-list
export type ScrollPosition = { height: number; top: number };
@ -168,7 +165,7 @@ const Status: React.FC<IStatus> = (props) => {
const handleHotkeyMention = (e?: KeyboardEvent): void => {
e?.preventDefault();
dispatch(mentionCompose(actualStatus.account as AccountEntity));
dispatch(mentionCompose(actualStatus.account));
};
const handleHotkeyOpen = (): void => {

View File

@ -3,11 +3,10 @@ import { FormattedMessage, useIntl } from 'react-intl';
import { translateStatus, undoStatusTranslation } from 'soapbox/actions/statuses';
import { useAppDispatch, useAppSelector, useFeatures, useInstance } from 'soapbox/hooks';
import { isLocal } from 'soapbox/utils/accounts';
import { Stack, Button, Text } from './ui';
import type { Account, Status } from 'soapbox/types/entities';
import type { Status } from 'soapbox/types/entities';
interface ITranslateButton {
status: Status;
@ -28,7 +27,7 @@ const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
target_languages: targetLanguages,
} = instance.pleroma.metadata.translation;
const renderTranslate = (me || allowUnauthenticated) && (allowRemote || isLocal(status.account as Account)) && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language;
const renderTranslate = (me || allowUnauthenticated) && (allowRemote || status.account.local) && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language;
const supportsLanguages = (!sourceLanguages || sourceLanguages.includes(status.language!)) && (!targetLanguages || targetLanguages.includes(intl.locale));

View File

@ -13,7 +13,7 @@ const Select = React.forwardRef<HTMLSelectElement, ISelect>((props, ref) => {
<select
ref={ref}
className={clsx(
'w-full truncate rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-primary-500 focus:outline-none focus:ring-primary-500 disabled:opacity-50 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:focus:border-primary-500 dark:focus:ring-primary-500 sm:text-sm',
'w-full truncate rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-primary-500 focus:outline-none focus:ring-primary-500 disabled:opacity-50 sm:text-sm dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:focus:border-primary-500 dark:focus:ring-primary-500',
className,
)}
{...filteredProps}

View File

@ -45,7 +45,7 @@ const TagInput: React.FC<ITagInput> = ({ tags, onChange, placeholder }) => {
return (
<div className='relative mt-1 grow shadow-sm'>
<HStack
className='block w-full rounded-md border-gray-400 bg-white p-2 pb-0 text-gray-900 placeholder:text-gray-600 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus:border-primary-500 dark:focus:ring-primary-500 sm:text-sm'
className='block w-full rounded-md border-gray-400 bg-white p-2 pb-0 text-gray-900 placeholder:text-gray-600 focus:border-primary-500 focus:ring-primary-500 sm:text-sm dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus:border-primary-500 dark:focus:ring-primary-500'
space={2}
wrap
>

View File

@ -8,7 +8,7 @@ import { getTextDirection } from 'soapbox/utils/rtl';
import Stack from '../stack/stack';
import Text from '../text/text';
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'id' | 'maxLength' | 'onChange' | 'onKeyDown' | 'onPaste' | 'required' | 'disabled' | 'rows' | 'readOnly'> {
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'id' | 'maxLength' | 'onChange' | 'onClick' | 'onKeyDown' | 'onPaste' | 'required' | 'disabled' | 'rows' | 'readOnly'> {
/** Put the cursor into the input on mount. */
autoFocus?: boolean;
/** Allows the textarea height to grow while typing */
@ -48,13 +48,14 @@ const Textarea = React.forwardRef(({
autoGrow = false,
maxRows = 10,
minRows = 1,
rows: initialRows = 4,
theme = 'default',
maxLength,
value,
...props
}: ITextarea, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
const length = value?.length || 0;
const [rows, setRows] = useState<number>(autoGrow ? 1 : 4);
const [rows, setRows] = useState<number>(autoGrow ? minRows : initialRows);
const locale = useLocale();
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
@ -90,7 +91,7 @@ const Textarea = React.forwardRef(({
ref={ref}
rows={rows}
onChange={handleChange}
className={clsx('block w-full rounded-md text-gray-900 placeholder:text-gray-600 dark:text-gray-100 dark:placeholder:text-gray-600 sm:text-sm', {
className={clsx('block w-full rounded-md text-gray-900 placeholder:text-gray-600 sm:text-sm dark:text-gray-100 dark:placeholder:text-gray-600', {
'bg-white dark:bg-transparent shadow-sm border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
theme === 'default',
'bg-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent',

View File

@ -1,3 +1,4 @@
import { List as ImmutableList } from 'immutable';
import React, { useEffect, useRef } from 'react';
import { FormattedMessage } from 'react-intl';
import { useParams } from 'react-router-dom';
@ -13,7 +14,6 @@ import { getAccountGallery } from 'soapbox/selectors';
import MediaItem from './components/media-item';
import type { List as ImmutableList } from 'immutable';
import type { Attachment, Status } from 'soapbox/types/entities';
interface ILoadMoreMedia {
@ -41,7 +41,7 @@ const AccountGallery = () => {
isUnavailable,
} = useAccountLookup(username, { withRelationship: true });
const attachments: ImmutableList<Attachment> = useAppSelector((state) => getAccountGallery(state, account!.id));
const attachments: ImmutableList<Attachment> = useAppSelector((state) => account ? getAccountGallery(state, account.id) : ImmutableList());
const isLoading = useAppSelector((state) => state.timelines.get(`account:${account?.id}:media`)?.isLoading);
const hasMore = useAppSelector((state) => state.timelines.get(`account:${account?.id}:media`)?.hasMore);
const next = useAppSelector(state => state.timelines.get(`account:${account?.id}:media`)?.next);

View File

@ -28,7 +28,7 @@ import { ChatKeys, useChats } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { Account } from 'soapbox/schemas';
import toast from 'soapbox/toast';
import { isDefaultHeader, isLocal, isRemote } from 'soapbox/utils/accounts';
import { isDefaultHeader } from 'soapbox/utils/accounts';
import copy from 'soapbox/utils/copy';
import { MASTODON, parseVersion } from 'soapbox/utils/features';
@ -110,7 +110,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
return (
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6'>
<div>
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48' />
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' />
</div>
<div className='px-4 sm:px-6'>
@ -287,7 +287,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
return [];
}
if (features.rssFeeds && isLocal(account)) {
if (features.rssFeeds && account.local) {
menu.push({
text: intl.formatMessage(messages.subscribeFeed),
action: handleRssFeedClick,
@ -303,7 +303,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
});
}
if (features.federating && isRemote(account)) {
if (features.federating && !account.local) {
const domain = account.fqn.split('@')[1];
menu.push({
@ -453,7 +453,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
});
}
if (isRemote(account)) {
if (!account.local) {
const domain = account.fqn.split('@')[1];
menu.push(null);
@ -620,7 +620,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
)}
<div>
<div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48'>
<div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50'>
{renderHeader()}
<div className='absolute left-2 top-2'>

View File

@ -44,7 +44,7 @@ const Search: React.FC = () => {
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
<input
className='block w-full rounded-full focus:border-primary-500 focus:ring-primary-500 dark:bg-gray-800 dark:text-white dark:placeholder:text-gray-500 sm:text-sm'
className='block w-full rounded-full focus:border-primary-500 focus:ring-primary-500 sm:text-sm dark:bg-gray-800 dark:text-white dark:placeholder:text-gray-500'
type='text'
value={value}
onChange={handleChange}

View File

@ -16,7 +16,7 @@ const ConsumersList: React.FC<IConsumersList> = () => {
if (providers.length > 0) {
return (
<Card className='bg-gray-50 p-4 dark:bg-primary-800 sm:rounded-xl'>
<Card className='bg-gray-50 p-4 sm:rounded-xl dark:bg-primary-800'>
<Text size='xs' theme='muted'>
<FormattedMessage id='oauth_consumers.title' defaultMessage='Other ways to sign in' />
</Text>

View File

@ -60,7 +60,7 @@ const ChatPage: React.FC<IChatPage> = ({ chatId }) => {
<div
ref={containerRef}
style={{ height }}
className='h-screen overflow-hidden bg-white text-gray-900 shadow-lg dark:bg-primary-900 dark:text-gray-100 dark:shadow-none sm:rounded-t-xl'
className='h-screen overflow-hidden bg-white text-gray-900 shadow-lg sm:rounded-t-xl dark:bg-primary-900 dark:text-gray-100 dark:shadow-none'
>
{isOnboarded ? (
<div
@ -68,7 +68,7 @@ const ChatPage: React.FC<IChatPage> = ({ chatId }) => {
data-testid='chat-page'
>
<Stack
className={clsx('dark:inset col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 dark:bg-gray-900 dark:bg-none sm:col-span-3', {
className={clsx('dark:inset col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 sm:col-span-3 dark:bg-gray-900 dark:bg-none', {
'hidden sm:block': isSidebarHidden,
})}
>

View File

@ -121,7 +121,7 @@ const ChatPageMain = () => {
<HStack alignItems='center'>
<IconButton
src={require('@tabler/icons/arrow-left.svg')}
className='mr-2 h-7 w-7 rtl:rotate-180 sm:mr-0 sm:hidden'
className='mr-2 h-7 w-7 sm:mr-0 sm:hidden rtl:rotate-180'
onClick={() => history.push('/chats')}
/>

View File

@ -24,7 +24,7 @@ const ChatPageNew: React.FC<IChatPageNew> = () => {
<HStack alignItems='center'>
<IconButton
src={require('@tabler/icons/arrow-left.svg')}
className='mr-2 h-7 w-7 rtl:rotate-180 sm:mr-0 sm:hidden'
className='mr-2 h-7 w-7 sm:mr-0 sm:hidden rtl:rotate-180'
onClick={() => history.push('/chats')}
/>

View File

@ -51,7 +51,7 @@ const ChatPageSettings = () => {
<HStack alignItems='center'>
<IconButton
src={require('@tabler/icons/arrow-left.svg')}
className='mr-2 h-7 w-7 rtl:rotate-180 sm:mr-0 sm:hidden'
className='mr-2 h-7 w-7 sm:mr-0 sm:hidden rtl:rotate-180'
onClick={() => history.push('/chats')}
/>

View File

@ -39,9 +39,9 @@ const ChatTextarea: React.FC<IChatTextarea> = React.forwardRef(({
bg-white text-gray-900
shadow-sm placeholder:text-gray-600
focus-within:border-primary-500
focus-within:ring-1 focus-within:ring-primary-500 dark:border-gray-800 dark:bg-gray-800
dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus-within:border-primary-500
dark:focus-within:ring-primary-500 sm:text-sm
focus-within:ring-1 focus-within:ring-primary-500 sm:text-sm dark:border-gray-800
dark:bg-gray-800 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600
dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500
`}
>
{(!!attachments?.length || isUploading) && (

View File

@ -1,5 +1,5 @@
import clsx from 'clsx';
import { CLEAR_EDITOR_COMMAND, TextNode, type LexicalEditor } from 'lexical';
import { CLEAR_EDITOR_COMMAND, TextNode, type LexicalEditor, $getRoot } from 'lexical';
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Link, useHistory } from 'react-router-dom';
@ -18,7 +18,6 @@ import { Button, HStack, Stack } from 'soapbox/components/ui';
import EmojiPickerDropdown from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container';
import { ComposeEditor } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch, useAppSelector, useCompose, useDraggedFiles, useFeatures, useInstance, usePrevious } from 'soapbox/hooks';
import { isMobile } from 'soapbox/is-mobile';
import QuotedStatusContainer from '../containers/quoted-status-container';
import ReplyIndicatorContainer from '../containers/reply-indicator-container';
@ -96,23 +95,25 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const anyMedia = compose.media_attachments.size > 0;
const [composeFocused, setComposeFocused] = useState(false);
const [text, setText] = useState(compose.text);
const firstRender = useRef(true);
const formRef = useRef<HTMLDivElement>(null);
const spoilerTextRef = useRef<AutosuggestInput>(null);
const editorRef = useRef<LexicalEditor>(null);
const { isDraggedOver } = useDraggedFiles(formRef);
const text = editorRef.current?.getEditorState().read(() => $getRoot().getTextContent()) ?? '';
const fulltext = [spoilerText, countableText(text)].join('');
const isEmpty = !(fulltext.trim() || anyMedia);
const condensed = shouldCondense && !isDraggedOver && !composeFocused && isEmpty && !isUploading;
const shouldAutoFocus = autoFocus && !showSearch;
const canSubmit = !!editorRef.current && !isSubmitting && !isUploading && !isChangingUpload && !isEmpty && length(fulltext) <= maxTootChars;
const getClickableArea = () => {
return clickableAreaRef ? clickableAreaRef.current : formRef.current;
};
const isEmpty = () => {
return !(text || spoilerText || anyMedia);
};
const isClickOutside = (e: MouseEvent | React.MouseEvent) => {
return ![
// List of elements that shouldn't collapse the composer when clicked
@ -125,10 +126,10 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
};
const handleClick = useCallback((e: MouseEvent | React.MouseEvent) => {
if (isEmpty() && isClickOutside(e)) {
if (isEmpty && isClickOutside(e)) {
handleClickOutside();
}
}, []);
}, [isEmpty]);
const handleClickOutside = () => {
setComposeFocused(false);
@ -139,20 +140,12 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
};
const handleSubmit = (e?: React.FormEvent<Element>) => {
if (!canSubmit) return;
e?.preventDefault();
dispatch(changeCompose(id, text));
// Submit disabled:
const fulltext = [spoilerText, countableText(text)].join('');
if (e) {
e.preventDefault();
}
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxTootChars || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return;
}
dispatch(submitCompose(id, { history }));
editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
};
@ -215,12 +208,6 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
</HStack>
), [features, id]);
const condensed = shouldCondense && !isDraggedOver && !composeFocused && isEmpty() && !isUploading;
const disabled = isSubmitting;
const countedText = [spoilerText, countableText(text)].join('');
const disabledButton = disabled || isUploading || isChangingUpload || length(countedText) > maxTootChars || (countedText.length !== 0 && countedText.trim().length === 0 && !anyMedia);
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth);
const composeModifiers = !condensed && (
<Stack space={4} className='compose-form__modifiers'>
<UploadForm composeId={id} onSubmit={handleSubmit} />
@ -297,7 +284,6 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
autoFocus={shouldAutoFocus}
hasPoll={hasPoll}
handleSubmit={handleSubmit}
onChange={setText}
onFocus={handleComposeFocus}
onPaste={onPaste}
/>
@ -324,7 +310,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
</HStack>
)}
<Button type='submit' theme='primary' icon={publishIcon} text={publishText} disabled={disabledButton} />
<Button type='submit' theme='primary' icon={publishIcon} text={publishText} disabled={!canSubmit} />
</HStack>
</div>
</Stack>

View File

@ -11,10 +11,6 @@ export const FOCUS_EDITOR_COMMAND: LexicalCommand<void> = createCommand();
const FocusPlugin: React.FC<IFocusPlugin> = ({ autoFocus }) => {
const [editor] = useLexicalComposerContext();
const focus = () => {
editor.dispatchCommand(FOCUS_EDITOR_COMMAND, undefined);
};
useEffect(() => editor.registerCommand(FOCUS_EDITOR_COMMAND, () => {
editor.focus(
() => {
@ -29,8 +25,10 @@ const FocusPlugin: React.FC<IFocusPlugin> = ({ autoFocus }) => {
}, COMMAND_PRIORITY_NORMAL));
useEffect(() => {
if (autoFocus) focus();
}, []);
if (autoFocus) {
editor.dispatchCommand(FOCUS_EDITOR_COMMAND, undefined);
}
}, [autoFocus, editor]);
return null;
};

View File

@ -36,7 +36,7 @@ const HeaderPicker = React.forwardRef<HTMLInputElement, IMediaInput>(({ src, onC
<label
ref={picker}
className={clsx(
'dark:sm:shadow-inset relative h-24 w-full cursor-pointer overflow-hidden rounded-lg bg-primary-100 text-primary-500 dark:bg-gray-800 dark:text-accent-blue sm:h-36 sm:shadow',
'dark:sm:shadow-inset relative h-24 w-full cursor-pointer overflow-hidden rounded-lg bg-primary-100 text-primary-500 sm:h-36 sm:shadow dark:bg-gray-800 dark:text-accent-blue',
{
'border-2 border-primary-600 border-dashed !z-[99]': isDragging,
'ring-2 ring-offset-2 ring-primary-600': isDraggedOver,

View File

@ -19,7 +19,6 @@ import { Button, HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, Me
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import VerificationBadge from 'soapbox/components/verification-badge';
import { useAppDispatch, useFeatures, useOwnAccount, useSettings } from 'soapbox/hooks';
import { isRemote } from 'soapbox/utils/accounts';
import copy from 'soapbox/utils/copy';
import { download } from 'soapbox/utils/download';
import { shortNumberFormat } from 'soapbox/utils/numbers';
@ -29,7 +28,7 @@ import EventActionButton from '../components/event-action-button';
import EventDate from '../components/event-date';
import type { Menu as MenuType } from 'soapbox/components/dropdown-menu';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
import type { Status as StatusEntity } from 'soapbox/types/entities';
const messages = defineMessages({
bannerHeader: { id: 'event.banner', defaultMessage: 'Event banner' },
@ -81,7 +80,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
return (
<>
<div className='-mx-4 -mt-4'>
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48' />
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' />
</div>
<PlaceholderEventHeader />
@ -89,7 +88,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
);
}
const account = status.account as AccountEntity;
const account = status.account;
const event = status.event;
const banner = event.banner;
@ -217,7 +216,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
},
];
if (features.federating && isRemote(account)) {
if (features.federating && !account.local) {
menu.push({
text: intl.formatMessage(messages.external, { domain }),
action: handleExternalClick,
@ -365,7 +364,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
return (
<>
<div className='-mx-4 -mt-4'>
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48'>
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50'>
{banner && (
<a href={banner.url} onClick={handleHeaderClick} target='_blank'>
<StillImage

View File

@ -24,7 +24,7 @@ const SuggestionItem: React.FC<ISuggestionItem> = ({ accountId }) => {
if (!account) return null;
return (
<Stack space={3} className='w-52 shrink-0 rounded-md border border-solid border-gray-300 p-4 dark:border-gray-800 md:w-full md:shrink md:border-transparent md:p-0 dark:md:border-transparent'>
<Stack space={3} className='w-52 shrink-0 rounded-md border border-solid border-gray-300 p-4 md:w-full md:shrink md:border-transparent md:p-0 dark:border-gray-800 dark:md:border-transparent'>
<Link
to={`/@${account.acct}`}
title={account.acct}

View File

@ -36,7 +36,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
return (
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6' data-testid='group-header-missing'>
<div>
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48' />
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' />
</div>
<div className='px-4 sm:px-6'>
@ -92,7 +92,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
<StillImage
src={group.header}
alt={intl.formatMessage(messages.header)}
className='relative h-32 w-full bg-gray-200 object-center dark:bg-gray-900/50 md:rounded-t-xl lg:h-52'
className='relative h-32 w-full bg-gray-200 object-center md:rounded-t-xl lg:h-52 dark:bg-gray-900/50'
onError={() => setIsHeaderMissing(true)}
/>
);
@ -109,7 +109,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
return (
<div
data-testid='group-header-image'
className='flex h-32 w-full items-center justify-center bg-gray-200 dark:bg-gray-800/30 md:rounded-t-xl lg:h-52'
className='flex h-32 w-full items-center justify-center bg-gray-200 md:rounded-t-xl lg:h-52 dark:bg-gray-800/30'
>
{isHeaderMissing ? (
<Icon src={require('@tabler/icons/photo-off.svg')} className='h-6 w-6 text-gray-500 dark:text-gray-700' />

View File

@ -7,7 +7,7 @@ import { useHashtagStream } from 'soapbox/api/hooks';
import List, { ListItem } from 'soapbox/components/list';
import { Column, Toggle } from 'soapbox/components/ui';
import Timeline from 'soapbox/features/ui/components/timeline';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures, useLoggedIn } from 'soapbox/hooks';
interface IHashtagTimeline {
params?: {
@ -22,7 +22,7 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
const dispatch = useAppDispatch();
const tag = useAppSelector((state) => state.tags.get(id));
const next = useAppSelector(state => state.timelines.get(`hashtag:${id}`)?.next);
const { isLoggedIn } = useLoggedIn();
const handleLoadMore = (maxId: string) => {
dispatch(expandHashtagTimeline(id, { url: next, maxId }));
@ -50,7 +50,7 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
return (
<Column label={`#${id}`} transparent>
{features.followHashtags && (
{features.followHashtags && isLoggedIn && (
<List>
<ListItem
label={<FormattedMessage id='hashtag.follow' defaultMessage='Follow hashtag' />}

View File

@ -5,7 +5,7 @@ import React from 'react';
* For testing logging/monitoring & previewing ErrorBoundary design.
*/
const IntentionalError: React.FC = () => {
throw 'This error is intentional.';
throw new Error('This error is intentional.');
};
export default IntentionalError;

View File

@ -7,6 +7,8 @@ import {
nip04 as _nip04,
} from 'nostr-tools';
import { powWorker } from 'soapbox/workers';
/** localStorage key for the Nostr private key (if not using NIP-07). */
const LOCAL_KEY = 'soapbox:nostr:privateKey';
@ -28,9 +30,18 @@ async function getPublicKey(): Promise<string> {
return window.nostr ? window.nostr.getPublicKey() : _getPublicKey(getPrivateKey());
}
interface SignEventOpts {
pow?: number;
}
/** Sign an event with NIP-07, or the locally generated key. */
async function signEvent<K extends number>(event: EventTemplate<K>): Promise<Event<K>> {
return window.nostr ? window.nostr.signEvent(event) as Promise<Event<K>> : finishEvent(event, getPrivateKey()) ;
async function signEvent<K extends number>(template: EventTemplate<K>, opts: SignEventOpts = {}): Promise<Event<K>> {
if (opts.pow) {
const event = await powWorker.mine({ ...template, pubkey: await getPublicKey() }, opts.pow) as Omit<Event<K>, 'sig'>;
return window.nostr ? window.nostr.signEvent(event) as Promise<Event<K>> : finishEvent(event, getPrivateKey()) ;
} else {
return window.nostr ? window.nostr.signEvent(template) as Promise<Event<K>> : finishEvent(template, getPrivateKey()) ;
}
}
/** Crypto function with NIP-07, or the local key. */

View File

@ -26,7 +26,7 @@ const FediverseStep = ({ onNext }: { onNext: () => void }) => {
</Text>
<Stack space={4}>
<div className='border-b border-solid border-gray-200 pb-2 dark:border-gray-800 sm:pb-5'>
<div className='border-b border-solid border-gray-200 pb-2 sm:pb-5 dark:border-gray-800'>
<Stack space={4}>
<Text theme='muted'>
<FormattedMessage

View File

@ -8,7 +8,7 @@ import PlaceholderStatusContent from './placeholder-status-content';
/** Fake notification to display while data is loading. */
const PlaceholderNotification = () => (
<div className='bg-white px-4 py-6 dark:bg-primary-900 sm:p-6'>
<div className='bg-white px-4 py-6 sm:p-6 dark:bg-primary-900'>
<div className='w-full animate-pulse'>
<div className='mb-2'>
<PlaceholderStatusContent minLength={20} maxLength={20} />

View File

@ -13,7 +13,7 @@ import { buildStatus } from '../builder';
import ScheduledStatusActionBar from './scheduled-status-action-bar';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
import type { Status as StatusEntity } from 'soapbox/types/entities';
interface IScheduledStatus {
statusId: string;
@ -28,7 +28,7 @@ const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) =>
if (!status) return null;
const account = status.account as AccountEntity;
const account = status.account;
return (
<div className={clsx('status__wrapper', `status__wrapper-${status.visibility}`, { 'status__wrapper-reply': !!status.in_reply_to_id })} tabIndex={0}>

View File

@ -1,6 +1,4 @@
import clsx from 'clsx';
import { List as ImmutableList } from 'immutable';
import React from 'react';
import clsx from 'clsx';import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
@ -59,7 +57,7 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
const getNormalizedReacts = () => {
return reduceEmoji(
ImmutableList(status.pleroma.get('emoji_reactions') as any),
status.reactions,
status.favourites_count,
status.favourited,
allowedEmoji,
@ -164,20 +162,22 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
const getEmojiReacts = () => {
const emojiReacts = getNormalizedReacts();
const count = emojiReacts.reduce((acc, cur) => (
acc + cur.get('count')
acc + (cur.count || 0)
), 0);
const handleClick = features.emojiReacts ? handleOpenReactionsModal : handleOpenFavouritesModal;
if (count) {
return (
<InteractionCounter count={count} onClick={features.exposableReactions ? handleOpenReactionsModal : undefined}>
<InteractionCounter count={count} onClick={features.exposableReactions ? handleClick : undefined}>
<HStack space={0.5} alignItems='center'>
{emojiReacts.take(3).map((e, i) => {
return (
<Emoji
key={i}
className='h-4.5 w-4.5 flex-none'
emoji={e.get('name')}
src={e.get('url')}
emoji={e.name}
src={e.url}
/>
);
})}
@ -193,7 +193,7 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
<HStack space={3}>
{getReposts()}
{getQuotes()}
{features.emojiReacts ? getEmojiReacts() : getFavourites()}
{(features.emojiReacts || features.emojiReactsMastodon) ? getEmojiReacts() : getFavourites()}
{getDislikes()}
</HStack>
);

View File

@ -0,0 +1,45 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Column, Stack, Text, IconButton } from 'soapbox/components/ui';
import { isNetworkError } from 'soapbox/utils/errors';
const messages = defineMessages({
title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' },
body: { id: 'bundle_column_error.body', defaultMessage: 'Something went wrong while loading this page.' },
retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' },
});
interface IErrorColumn {
error: Error;
onRetry?: () => void;
}
const ErrorColumn: React.FC<IErrorColumn> = ({ error, onRetry = () => location.reload() }) => {
const intl = useIntl();
const handleRetry = () => {
onRetry?.();
};
if (!isNetworkError(error)) {
throw error;
}
return (
<Column label={intl.formatMessage(messages.title)}>
<Stack space={4} alignItems='center' justifyContent='center' className='min-h-[160px] rounded-lg p-10'>
<IconButton
iconClassName='h-10 w-10'
title={intl.formatMessage(messages.retry)}
src={require('@tabler/icons/refresh.svg')}
onClick={handleRetry}
/>
<Text align='center' theme='muted'>{intl.formatMessage(messages.body)}</Text>
</Stack>
</Column>
);
};
export default ErrorColumn;

View File

@ -12,7 +12,6 @@ import OutlineBox from 'soapbox/components/outline-box';
import { Button, Text, HStack, Modal, Stack, Toggle } from 'soapbox/components/ui';
import { useAppDispatch, useFeatures, useOwnAccount } from 'soapbox/hooks';
import toast from 'soapbox/toast';
import { isLocal } from 'soapbox/utils/accounts';
import { getBadges } from 'soapbox/utils/badges';
import BadgeInput from './badge-input';
@ -115,7 +114,7 @@ const AccountModerationModal: React.FC<IAccountModerationModal> = ({ onClose, ac
</OutlineBox>
<List>
{(ownAccount.admin && isLocal(account)) && (
{(ownAccount.admin && account.local) && (
<ListItem label={<FormattedMessage id='account_moderation_modal.fields.account_role' defaultMessage='Staff level' />}>
<div className='w-auto'>
<StaffRolePicker account={account} />

View File

@ -209,7 +209,7 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
<FormGroup
labelText={<FormattedMessage id='compose_event.fields.banner_label' defaultMessage='Event banner' />}
>
<div className='dark:sm:shadow-inset relative flex h-24 items-center justify-center overflow-hidden rounded-lg bg-primary-100 text-primary-500 dark:bg-gray-800 dark:text-white sm:h-32 sm:shadow'>
<div className='dark:sm:shadow-inset relative flex h-24 items-center justify-center overflow-hidden rounded-lg bg-primary-100 text-primary-500 sm:h-32 sm:shadow dark:bg-gray-800 dark:text-white'>
{banner ? (
<>
<img className='h-full w-full object-cover' src={banner.url} alt='' />
@ -234,7 +234,7 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
labelText={<FormattedMessage id='compose_event.fields.description_label' defaultMessage='Event description' />}
>
<ComposeEditor
className='block w-full rounded-md border border-gray-400 bg-white px-3 py-2 text-base text-gray-900 ring-1 placeholder:text-gray-600 focus-within:border-primary-500 focus-within:ring-primary-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500 sm:text-sm'
className='block w-full rounded-md border border-gray-400 bg-white px-3 py-2 text-base text-gray-900 ring-1 placeholder:text-gray-600 focus-within:border-primary-500 focus-within:ring-primary-500 sm:text-sm dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500'
placeholderClassName='pt-2'
composeId='compose-event-modal'
placeholder={intl.formatMessage(messages.eventDescriptionPlaceholder)}

View File

@ -30,24 +30,25 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
const getRemoteInstance = useCallback(makeGetRemoteInstance(), []);
const remoteInstance = useAppSelector(state => getRemoteInstance(state, host));
const [data, setData] = useState({} as any);
const [data, setData] = useState<Record<string, any>>({});
useEffect(() => {
setData(remoteInstance.get('federation'));
setData(remoteInstance.get('federation') as Record<string, any>);
}, [remoteInstance]);
const handleDataChange = (key: string): React.ChangeEventHandler<HTMLInputElement> => {
return ({ target }) => {
setData(data.set(key, target.checked));
setData({ ...data, [key]: target.checked });
};
};
const handleMediaRemoval: React.ChangeEventHandler<HTMLInputElement> = ({ target: { checked } }) => {
const newData = data.merge({
const newData = {
...data,
avatar_removal: checked,
banner_removal: checked,
media_removal: checked,
});
};
setData(newData);
};

View File

@ -42,7 +42,7 @@ const ConfirmationStep: React.FC<IConfirmationStep> = ({ group }) => {
<Stack space={3}>
<Stack>
<label
className='dark:sm:shadow-inset relative h-24 w-full cursor-pointer overflow-hidden rounded-lg bg-primary-100 text-primary-500 dark:bg-gray-800 dark:text-accent-blue sm:h-36 sm:shadow'
className='dark:sm:shadow-inset relative h-24 w-full cursor-pointer overflow-hidden rounded-lg bg-primary-100 text-primary-500 sm:h-36 sm:shadow dark:bg-gray-800 dark:text-accent-blue'
>
{group.header && <img className='h-full w-full object-cover' src={group.header} alt='' />}
</label>

View File

@ -17,8 +17,6 @@ import ConfirmationStep from './steps/confirmation-step';
import OtherActionsStep from './steps/other-actions-step';
import ReasonStep from './steps/reason-step';
import type { AxiosError } from 'axios';
const messages = defineMessages({
blankslate: { id: 'report.reason.blankslate', defaultMessage: 'You have removed all statuses from being selected.' },
done: { id: 'report.done', defaultMessage: 'Done' },
@ -123,7 +121,7 @@ const ReportModal = ({ onClose }: IReportModal) => {
const handleSubmit = () => {
dispatch(submitReport())
.then(() => setCurrentStep(Steps.THREE))
.catch((error: AxiosError) => dispatch(submitReportFail(error)));
.catch((error) => dispatch(submitReportFail(error)));
if (isBlocked && account) {
dispatch(blockAccount(account.id));

View File

@ -7,7 +7,7 @@ import { fetchRules } from 'soapbox/actions/rules';
import { Button, FormGroup, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import StatusCheckBox from 'soapbox/features/report/components/status-check-box';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { isRemote, getDomain } from 'soapbox/utils/accounts';
import { getDomain } from 'soapbox/utils/accounts';
import type { Account } from 'soapbox/schemas';
@ -31,7 +31,7 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
const statusIds = useAppSelector((state) => OrderedSet(state.timelines.get(`account:${account.id}:with_replies`)!.items).union(state.reports.new.status_ids) as OrderedSet<string>);
const isBlocked = useAppSelector((state) => state.reports.new.block);
const isForward = useAppSelector((state) => state.reports.new.forward);
const canForward = isRemote(account) && features.federating;
const canForward = !account.local && features.federating;
const isSubmitting = useAppSelector((state) => state.reports.new.isSubmitting);
const [showAdditionalStatuses, setShowAdditionalStatuses] = useState<boolean>(false);

View File

@ -76,7 +76,7 @@ const Navbar = () => {
<div className='mx-auto max-w-7xl px-2 sm:px-6 lg:px-8'>
<div className='relative flex h-12 justify-between lg:h-16'>
{account && (
<div className='absolute inset-y-0 left-0 flex items-center rtl:left-auto rtl:right-0 lg:hidden'>
<div className='absolute inset-y-0 left-0 flex items-center lg:hidden rtl:left-auto rtl:right-0'>
<button onClick={onOpenSidebar}>
<Avatar src={account.avatar} size={34} />
</button>
@ -125,7 +125,7 @@ const Navbar = () => {
</Button>
</div>
) : (
<Form className='hidden items-center space-x-2 rtl:space-x-reverse xl:flex' onSubmit={handleSubmit}>
<Form className='hidden items-center space-x-2 xl:flex rtl:space-x-reverse' onSubmit={handleSubmit}>
<Input
required
value={username}

View File

@ -14,7 +14,7 @@ import { buildStatus } from '../util/pending-status-builder';
import PollPreview from './poll-preview';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
import type { Status as StatusEntity } from 'soapbox/types/entities';
const shouldHaveCard = (pendingStatus: StatusEntity) => {
return Boolean(pendingStatus.content.match(/https?:\/\/\S*/));
@ -54,7 +54,7 @@ const PendingStatus: React.FC<IPendingStatus> = ({ idempotencyKey, className, mu
if (!status) return null;
if (!status.account) return null;
const account = status.account as AccountEntity;
const account = status.account;
return (
<div className={clsx('opacity-50', className)}>

View File

@ -7,7 +7,6 @@ import Markup from 'soapbox/components/markup';
import { dateFormatOptions } from 'soapbox/components/relative-timestamp';
import { Icon, HStack, Stack, Text } from 'soapbox/components/ui';
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
import { isLocal } from 'soapbox/utils/accounts';
import { badgeToTag, getBadges as getAccountBadges } from 'soapbox/utils/badges';
import { capitalize } from 'soapbox/utils/strings';
@ -176,7 +175,7 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
)}
<div className='flex flex-col items-start gap-2 md:flex-row md:flex-wrap md:items-center'>
{isLocal(account) ? (
{account.local ? (
<HStack alignItems='center' space={0.5}>
<Icon
src={require('@tabler/icons/calendar.svg')}

View File

@ -329,7 +329,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<WrappedRoute path='/developers/timeline' developerOnly page={DefaultPage} component={TestTimeline} content={children} />
<WrappedRoute path='/developers/sw' developerOnly page={DefaultPage} component={ServiceWorkerInfo} content={children} />
<WrappedRoute path='/developers' page={DefaultPage} component={Developers} content={children} />
<WrappedRoute path='/error/network' developerOnly page={EmptyPage} component={lazy(() => Promise.reject())} content={children} />
<WrappedRoute path='/error/network' developerOnly page={EmptyPage} component={lazy(() => Promise.reject(new TypeError('Failed to fetch dynamically imported module: TEST')))} content={children} />
<WrappedRoute path='/error' developerOnly page={EmptyPage} component={IntentionalError} content={children} />
{hasCrypto && <WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />}
@ -494,7 +494,7 @@ const UI: React.FC<IUI> = ({ children }) => {
</Layout>
{(me && !shouldHideFAB()) && (
<div className='fixed bottom-24 right-4 z-40 transition-all rtl:left-4 rtl:right-auto lg:hidden'>
<div className='fixed bottom-24 right-4 z-40 transition-all lg:hidden rtl:left-4 rtl:right-auto'>
<FloatingActionButton />
</div>
)}

View File

@ -1,5 +1,6 @@
import React, { Suspense } from 'react';
import { Redirect, Route, useHistory, RouteProps, RouteComponentProps, match as MatchType } from 'react-router-dom';
import React, { Suspense, useEffect, useRef } from 'react';
import { ErrorBoundary, type FallbackProps } from 'react-error-boundary';
import { Redirect, Route, useHistory, RouteProps, RouteComponentProps, match as MatchType, useLocation } from 'react-router-dom';
import { Layout } from 'soapbox/components/ui';
import { useOwnAccount, useSettings } from 'soapbox/hooks';
@ -7,6 +8,7 @@ import { useOwnAccount, useSettings } from 'soapbox/hooks';
import ColumnForbidden from '../components/column-forbidden';
import ColumnLoading from '../components/column-loading';
import ColumnsArea from '../components/columns-area';
import ErrorColumn from '../components/error-column';
type PageProps = {
params?: MatchType['params'];
@ -46,40 +48,31 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
const renderComponent = ({ match }: RouteComponentProps) => {
if (Page) {
return (
<Suspense fallback={renderLoading()}>
<Page params={match.params} layout={layout} {...componentParams}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</Page>
</Suspense>
<ErrorBoundary FallbackComponent={FallbackError}>
<Suspense fallback={<FallbackLoading />}>
<Page params={match.params} layout={layout} {...componentParams}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</Page>
</Suspense>
</ErrorBoundary>
);
}
return (
<Suspense fallback={renderLoading()}>
<ColumnsArea layout={layout}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</ColumnsArea>
</Suspense>
<ErrorBoundary FallbackComponent={FallbackError}>
<Suspense fallback={<FallbackLoading />}>
<ColumnsArea layout={layout}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</ColumnsArea>
</Suspense>
</ErrorBoundary>
);
};
const renderWithLayout = (children: JSX.Element) => (
<>
<Layout.Main>
{children}
</Layout.Main>
<Layout.Aside />
</>
);
const renderLoading = () => renderWithLayout(<ColumnLoading />);
const renderForbidden = () => renderWithLayout(<ColumnForbidden />);
const loginRedirect = () => {
const actualUrl = encodeURIComponent(`${history.location.pathname}${history.location.search}`);
localStorage.setItem('soapbox:redirect_uri', actualUrl);
@ -97,13 +90,58 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
if (!account) {
return loginRedirect();
} else {
return renderForbidden();
return <FallbackForbidden />;
}
}
return <Route {...rest} render={renderComponent} />;
};
interface IFallbackLayout {
children: JSX.Element;
}
const FallbackLayout: React.FC<IFallbackLayout> = ({ children }) => (
<>
<Layout.Main>
{children}
</Layout.Main>
<Layout.Aside />
</>
);
const FallbackLoading: React.FC = () => (
<FallbackLayout>
<ColumnLoading />
</FallbackLayout>
);
const FallbackForbidden: React.FC = () => (
<FallbackLayout>
<ColumnForbidden />
</FallbackLayout>
);
const FallbackError: React.FC<FallbackProps> = ({ error, resetErrorBoundary }) => {
const location = useLocation();
const firstUpdate = useRef(true);
useEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
} else {
resetErrorBoundary();
}
}, [location]);
return (
<FallbackLayout>
<ErrorColumn error={error} onRetry={resetErrorBoundary} />
</FallbackLayout>
);
};
export {
WrappedRoute,
};

View File

@ -7,19 +7,19 @@ import { ScrollContext } from 'react-router-scroll-4';
import * as BuildConfig from 'soapbox/build-config';
import LoadingScreen from 'soapbox/components/loading-screen';
import SiteErrorBoundary from 'soapbox/components/site-error-boundary';
import {
ModalContainer,
OnboardingWizard,
} from 'soapbox/features/ui/util/async-components';
import {
useAppSelector,
useLoggedIn,
useOwnAccount,
useSoapboxConfig,
} from 'soapbox/hooks';
import { useCachedLocationHandler } from 'soapbox/utils/redirect';
import ErrorBoundary from '../components/error-boundary';
const GdprBanner = React.lazy(() => import('soapbox/components/gdpr-banner'));
const EmbeddedStatus = React.lazy(() => import('soapbox/features/embedded-status'));
const UI = React.lazy(() => import('soapbox/features/ui'));
@ -28,52 +28,29 @@ const UI = React.lazy(() => import('soapbox/features/ui'));
const SoapboxMount = () => {
useCachedLocationHandler();
const me = useAppSelector(state => state.me);
const { isLoggedIn } = useLoggedIn();
const { account } = useOwnAccount();
const soapboxConfig = useSoapboxConfig();
const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding);
const showOnboarding = account && needsOnboarding;
const { redirectRootNoLogin } = soapboxConfig;
const { redirectRootNoLogin, gdpr } = soapboxConfig;
// @ts-ignore: I don't actually know what these should be, lol
const shouldUpdateScroll = (prevRouterProps, { location }) => {
return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey);
};
/** Render the onboarding flow. */
const renderOnboarding = () => (
<Suspense fallback={<LoadingScreen />}>
<OnboardingWizard />
</Suspense>
);
/** Render the auth layout or UI. */
const renderSwitch = () => (
<Switch>
{(!me && redirectRootNoLogin) && (
<Redirect exact from='/' to={redirectRootNoLogin} />
)}
<Route path='/' component={UI} />
</Switch>
);
/** Render the onboarding flow or UI. */
const renderBody = () => {
if (showOnboarding) {
return renderOnboarding();
} else {
return renderSwitch();
}
};
return (
<ErrorBoundary>
<SiteErrorBoundary>
<BrowserRouter basename={BuildConfig.FE_SUBDIRECTORY}>
<CompatRouter>
<ScrollContext shouldUpdateScroll={shouldUpdateScroll}>
<Switch>
{(!isLoggedIn && redirectRootNoLogin) && (
<Redirect exact from='/' to={redirectRootNoLogin} />
)}
<Route
path='/embed/:statusId'
render={(props) => (
@ -82,18 +59,26 @@ const SoapboxMount = () => {
</Suspense>
)}
/>
<Redirect from='/@:username/:statusId/embed' to='/embed/:statusId' />
<Route>
{renderBody()}
<Suspense fallback={<LoadingScreen />}>
{showOnboarding
? <OnboardingWizard />
: <UI />
}
</Suspense>
<Suspense>
<ModalContainer />
</Suspense>
<Suspense>
<GdprBanner />
</Suspense>
{(gdpr && !isLoggedIn) && (
<Suspense>
<GdprBanner />
</Suspense>
)}
<div id='toaster'>
<Toaster
@ -107,7 +92,7 @@ const SoapboxMount = () => {
</ScrollContext>
</CompatRouter>
</BrowserRouter>
</ErrorBoundary>
</SiteErrorBoundary>
);
};

View File

@ -7,7 +7,7 @@
"account.birthday": "ولد في {date}",
"account.birthday_today": "اليوم يوم ميلاد صاحب الحساب!",
"account.block": "حظر @{name}",
"account.block_domain": "إخفاء كل ما يتعلق بالنطاق {domain}",
"account.block_domain": "إخفاء النطاق {domain}",
"account.blocked": "محظور",
"account.chat": "دردشة مع @{name}",
"account.copy": "نسخ رابط الحساب",
@ -164,6 +164,8 @@
"alert.unexpected.links.support": "الدعم",
"alert.unexpected.message": "حدث خطأ ما.",
"alert.unexpected.return_home": "العودة للصفحة الرئيسة",
"alert.unexpected.submit_feedback": "إرسال الملاحظات",
"alert.unexpected.thanks": "شكرا لملاحظاتك!",
"aliases.account.add": "إنشاء اسم مستعار",
"aliases.account_label": "الحساب القديم:",
"aliases.aliases_list_delete": "إلغاء ربط الاسم المستعار",
@ -180,7 +182,7 @@
"app_create.results.explanation_title": "أُنشئ التطبيق بنجاح",
"app_create.results.token_label": "رمز OAuth",
"app_create.scopes_label": "آفاق",
"app_create.scopes_placeholder": "مثلاً: (قراءة كتابة متابعة)",
"app_create.scopes_placeholder": "مثلاً: 'read write follow'",
"app_create.submit": "إنشاء تطبيق",
"app_create.website_label": "الموقع",
"auth.awaiting_approval": "حسابك ينتظر الموافقة",
@ -196,6 +198,9 @@
"birthdays_modal.empty": "ليس لأصدقائك يوم ميلاد اليوم.",
"boost_modal.combo": "يمكنك الضغط على {combo} لتخطي هذا في المرة القادمة",
"boost_modal.title": "إعادة نشر؟",
"bundle_column_error.body": "حدث خطأ ما أثناء تحميل هذه الصفحة.",
"bundle_column_error.retry": "حاول مجددًا",
"bundle_column_error.title": "خطأ في الشبكة",
"card.back.label": "العودة",
"chat.actions.send": "إرسال",
"chat.failed_to_send": "فشل ارسال الرسالة.",
@ -376,7 +381,7 @@
"column_forbidden.body": "ليست لديك الصلاحيات للدخول إلى هذه الصفحة.",
"column_forbidden.title": "محظور",
"common.cancel": "إلغاء",
"compare_history_modal.header": "تعديل السِّجل",
"compare_history_modal.header": "سِجلّ التعديلات",
"compose.character_counter.title": "مُستخدم {chars} حرف من أصل {maxChars} {maxChars, plural, one {حروف} other {حروف}}",
"compose.edit_success": "تم تعديل المنشور",
"compose.invalid_schedule": "يجب عليك جدولة منشور بمدة لا تقل عن 5 دقائق.",
@ -389,7 +394,7 @@
"compose_event.fields.description_label": "وصف الحدث",
"compose_event.fields.description_placeholder": "الوصف",
"compose_event.fields.end_time_label": "الحدث والتاريخ",
"compose_event.fields.end_time_placeholder": "الحدث ينتهي في…",
"compose_event.fields.end_time_placeholder": "ينتهي الحدث في…",
"compose_event.fields.has_end_time": "الحدث له تاريخ إنتهاء",
"compose_event.fields.location_label": "موقع الحدث",
"compose_event.fields.name_label": "عنوان الحدث",
@ -397,10 +402,10 @@
"compose_event.fields.start_time_label": "تاريخ بداية الحدث",
"compose_event.fields.start_time_placeholder": "الحدث يبدأ في…",
"compose_event.participation_requests.authorize": "تفويض",
"compose_event.participation_requests.authorize_success": "تم قبول المستخدم",
"compose_event.participation_requests.authorize_success": "قُبِل المستخدم",
"compose_event.participation_requests.reject": "رفض",
"compose_event.participation_requests.reject_success": "تم رفض المستخدم",
"compose_event.reset_location": "حذف الموقع",
"compose_event.participation_requests.reject_success": "رُفِض المستخدم",
"compose_event.reset_location": "إعادة تعيين الموقع",
"compose_event.submit_success": "تم إنشاء الحدث",
"compose_event.tabs.edit": "تعديل التفاصيل",
"compose_event.tabs.pending": "إدارة الطلبات",
@ -494,10 +499,10 @@
"confirmations.kick_from_group.confirm": "طرد",
"confirmations.kick_from_group.message": "هل أنت متأكد أنك تريد طرد @ {name} من هذه المجموعة؟",
"confirmations.leave_event.confirm": "الخروج من الحدث",
"confirmations.leave_event.message": "إذا كنت تريد إعادة الانضمام إلى الحدث ، فستتم مراجعة الطلب يدويًا مرة أخرى. هل انت متأكد انك تريد المتابعة؟",
"confirmations.leave_group.confirm": "ترك",
"confirmations.leave_event.message": "إذا كنت تريد إعادة الانضمام إلى الحدث ، سيتم مراجعة الطلب يدويًا مرة أخرى. متأكد أنك تريد المتابعة؟",
"confirmations.leave_group.confirm": "مغادرة",
"confirmations.leave_group.heading": "مغادرة المجموعة",
"confirmations.leave_group.message": "أنت على وشك مغادرة المجموعة هل تريد الاستمرار؟?",
"confirmations.leave_group.message": "أنت على وشك مغادرة المجموعة. هل تريد الاستمرار؟",
"confirmations.mute.confirm": "كتم",
"confirmations.mute.heading": "كتم @{name}",
"confirmations.mute.message": "هل تود حقًا كتم {name}؟",
@ -523,11 +528,11 @@
"confirmations.scheduled_status_delete.message": "هل تود حقا حذف هذا المنشور المجدول؟",
"confirmations.unfollow.confirm": "إلغاء المتابعة",
"copy.success": "نسخ إلى الحافظة!",
"crypto_donate.explanation_box.message": "{siteTitle} يقبل العملات الرقمية . بإمكانك التبرع عبر أي من هذه العناوين في الأسفل . شكرا لدعمك!",
"crypto_donate.explanation_box.title": "يتم إرسال العملات الرقمية",
"crypto_donate.explanation_box.message": "يمكنك أن تدعم منصة {siteTitle} من خلال التبرع بالعملات الرقمية على العناوين الظاهرة في الأسفل. شكراً جزيلاً لك على الدعم!",
"crypto_donate.explanation_box.title": "التبرع بالعملات الرقمية",
"crypto_donate_panel.actions.view": "اضغط لعرض {count, plural, one {# محفظة} other {# محفظة}}",
"crypto_donate_panel.heading": "تبرّع بالعملات الرقمية",
"crypto_donate_panel.intro.message": "يقبل {siteTitle} العملات الرقمية لدعم تواجه . شكراً لدعمك!",
"crypto_donate_panel.intro.message": "منصّة {siteTitle} تقبل تبرعاتكم بالعملات الرقمية لدعم تواجدها . شكراً جزيلاً لدعمكم!",
"datepicker.day": "يوم",
"datepicker.hint": "مُجدول للنشر في…",
"datepicker.month": "شهر",
@ -615,7 +620,7 @@
"emoji_button.recent": "المُستخدمة حديثا",
"emoji_button.search": "البحث…",
"emoji_button.search_results": "نتائج البحث",
"emoji_button.skins_1": "الإفتراضي",
"emoji_button.skins_1": "الافتراضي",
"emoji_button.skins_2": "فاتح",
"emoji_button.skins_3": "ضوء المتوسط",
"emoji_button.skins_4": "متوسط",
@ -635,7 +640,7 @@
"empty_column.bookmarks": "ليس لديك أي علامات ، ستظهر هنا عند اضافتها.",
"empty_column.community": "لا توجد منشورات في الخط المحلي بعد. أكتب شيئا ما للعامة كبداية!",
"empty_column.direct": "لم تتلقَ أي رسالة خاصة مباشرة بعد. ستعرض الرسائل المباشرة هنا في حال أرسلت أو تلقيت بعضها.",
"empty_column.dislikes": ا أحد قام بعدم إعجاب هذا المنشور حتى الآن. عندما يفعل شخص ما ، سيظهرون هنا.",
"empty_column.dislikes": م يتفاعل أي شخص بعدم الإعجاب على هذا المنشور بعد. عندما يفعل شخص ما ، سيظهرون هنا.",
"empty_column.domain_blocks": "ليس هناك نطاقات مخفية بعد.",
"empty_column.event_participant_requests": "لا توجد طلبات معلقة للمشاركة في الحدث.",
"empty_column.event_participants": "لم ينضم أحد إلى هذا الحدث حتى الآن. عندما يفعل شخص ما ، سوف يظهر هنا.",
@ -684,7 +689,7 @@
"event.quote": "اقتباس الحدث",
"event.reblog": "إعادة نشر الحدث",
"event.show_on_map": "العرض على الخريطة",
"event.unreblog": "حدث لم يُعَدْ نشره",
"event.unreblog": "إلغاء مشاركة الحدث",
"event.website": "روابط خارجية",
"event_map.navigate": "التنقل",
"events.create_event": "إنشاء حدث",
@ -732,7 +737,7 @@
"filters.filters_list_warn": "ترشيح العرض",
"filters.removed": "حُذف المُرشِّح.",
"followRecommendations.heading": "الحسابات المقترحة",
"follow_request.authorize": "ترخيص بالوصول",
"follow_request.authorize": "السماح بالمتابعة",
"follow_request.reject": "رفض",
"gdpr.accept": "الموافقة",
"gdpr.learn_more": "معرفة المزيد",
@ -1401,7 +1406,7 @@
"status.group_mod_delete": "حذف المشاركة من المجموعة",
"status.interactions.dislikes": "{count, plural, one {عدم اعجاب} other {عدم اعجاب}}",
"status.interactions.favourites": "{count, plural, one {إعجاب واحد} other {إعجاب}}",
"status.interactions.quotes": "{count, plural, one {# صوت} other {# أصوات}}",
"status.interactions.quotes": "{count, plural, one {صوت} other {أصوات}}",
"status.interactions.reblogs": "{count, plural, one {مشاركة} other {مشاركات}}",
"status.load_more": "تحميل المزيد",
"status.mention": "ذِكر @{name}",

View File

@ -164,6 +164,8 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Something went wrong.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.submit_feedback": "Submit Feedback",
"alert.unexpected.thanks": "Thanks for your feedback!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -196,6 +198,9 @@
"birthdays_modal.empty": "None of your friends have birthday today.",
"boost_modal.combo": "You can press {combo} to skip this next time",
"boost_modal.title": "Repost?",
"bundle_column_error.body": "Something went wrong while loading this page.",
"bundle_column_error.retry": "Try again",
"bundle_column_error.title": "Network error",
"card.back.label": "Back",
"chat.actions.send": "Send",
"chat.failed_to_send": "Message failed to send.",

View File

@ -28,7 +28,7 @@
"account.follows_you": "במעקב אחריך",
"account.header.alt": "Profile header",
"account.hide_reblogs": "להסתיר הידהודים מאת @{name}",
"account.last_status": "פעיל לאחרונה",
"account.last_status": "פעיל הלפנה",
"account.link_verified_on": "בעלות על הקישור הזה נבדקה לאחרונה ב{date}",
"account.locked_info": "החשבון הוגדר כנעול. בעל החשבון בודק ידנית מי יכול לעקוב אחריו.",
"account.login": "התחברות",
@ -197,7 +197,7 @@
"boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
"boost_modal.title": "Repost?",
"card.back.label": "Back",
"chat.actions.send": "שליחה",
"chat.actions.send": "שלח",
"chat.failed_to_send": "שליחת ההודעה נכשלה.",
"chat.input.placeholder": "הקלד הודעה",
"chat.new_message.title": "הודעה חדשה",
@ -213,15 +213,18 @@
"chat_composer.unblock": "הסרת חסימה",
"chat_list_item.blocked_you": "משתמש זה חסם אותך",
"chat_list_item.blocking": "אתה חסמת את המשתמש זה",
"chat_message_list.blocked": "אתה חסמת את המשתמש זה",
"chat_message_list.blocked": "משתמש זה נחסם על ידיך",
"chat_message_list.blockedBy": "אתה חסום על ידי",
"chat_message_list.network_failure.action": "לנסות שוב",
"chat_message_list.network_failure.subtitle": "נתקלנו בכשל ברשת.",
"chat_message_list.network_failure.title": "אופס!",
"chat_message_list_intro.actions.leave_chat": "עזוב את השיחה",
"chat_message_list_intro.actions.message_lifespan": "הודעות ישנות יותר מ {day, plural, one {# יום} other {# ימים}} נמחקות.",
"chat_message_list_intro.actions.report": "דווח",
"chat_message_list_intro.leave_chat.confirm": "עזוב את השיחה",
"chat_message_list_intro.leave_chat.heading": "עזוב את השיחה",
"chat_message_list_intro.intro": "רוצה להתחיל לשוחח איתך",
"chat_message_list_intro.leave_chat.confirm": "צא מהשיחה",
"chat_message_list_intro.leave_chat.heading": "צא מהשיחה",
"chat_message_list_intro.leave_chat.message": "אתה בטוח שאתה רוצה לעזוב את הצ'אט הזה? הודעות יימחקו עבורך והצ'אט הזה יוסר מרשימת הצ'אט שלך.",
"chat_pane.blankslate.action": "הודעה למישהו",
"chat_pane.blankslate.body": "חפשו מישהו לשוחח איתו.",
"chat_pane.blankslate.title": "עדיין אין הודעות",
@ -239,17 +242,22 @@
"chat_settings.auto_delete.days": "{day, plural, one {# יום} other {# ימים}}",
"chat_settings.block.confirm": "חסימה",
"chat_settings.block.heading": "חסימת @{acct}",
"chat_settings.leave.confirm": "עזוב את השיחה",
"chat_settings.leave.heading": "עזוב את השיחה",
"chat_settings.leave.confirm": "צא מהשיחה",
"chat_settings.leave.heading": "צא מהשיחה",
"chat_settings.options.block_user": "חסימת @{acct}",
"chat_settings.options.leave_chat": "עזוב את השיחה",
"chat_settings.options.leave_chat": "צא מהשיחה",
"chat_settings.options.report_user": "דיווח על @{acct}",
"chat_settings.options.unblock_user": "הסרת חסימה מעל @{acct}",
"chat_settings.title": "פרטים הצ'אט",
"chat_settings.unblock.confirm": "הסרת חסימה",
"chat_settings.unblock.heading": "הסרת חסימה מעל @{acct}",
"chat_window.auto_delete_tooltip": "הודעות צ'אט מוגדרות למחיקה אוטומטית לאחר {day, plural, one {# יום} other {# ימים}} לאחר שליחה.",
"chats.actions.delete": "מחק לכולם",
"chats.actions.deleteForMe": "למחוק עבורי",
"chats.actions.more": "עוד",
"chats.actions.report": "דווח",
"chats.dividers.today": "היום",
"chats.main.blankslate.title": "עדיין אין הודעות",
"chats.search_placeholder": "התחל שיחה עם…",
"colum.filters.expiration.1800": "30 דקות",
"colum.filters.expiration.21600": "6 שעות",
@ -284,6 +292,7 @@
"column.developers.service_worker": "Service Worker",
"column.direct": "הודעות ישירות",
"column.directory": "דפדף בין פרופילים",
"column.dislikes": "לא אהבתי",
"column.domain_blocks": "דומיינים מוסתרים",
"column.edit_profile": "ערוך פרופיל",
"column.export_data": "יצא נתונים",
@ -604,9 +613,19 @@
"empty_column.search.hashtags": "אין תוצאות האשטאג עבור \"{term}\"",
"empty_column.search.statuses": "אין תוצאות פוסטים עבור \"{term}\"",
"empty_column.test": "The test timeline is empty.",
"event.discussion.empty": "אף אחד עדיין לא הגיב על האירוע הזה. כשמישהו יגיב, התגובה תופיע כאן.",
"event.export_ics": "ייצוא לוח השנה שלך",
"event.external": "צפו באירוע על {Domain}",
"event.join_state.accept": "הולך",
"event.join_state.empty": "השתתפות",
"event.join_state.pending": "בהמתנה",
"event.join_state.rejected": "הולך",
"event.location": "מיקום",
"event.manage": "ניהול",
"event.organized_by": "מאורגן על ידי {name}",
"event.reblog": "פרסום אירוע מחדש",
"event.unreblog": "בטל פרסום מחדש של האירוע",
"event.website": "קישורים חיצוניים",
"export_data.actions.export": "ייצא נתונים",
"export_data.actions.export_blocks": "ייצוא רשימת חסימות",
"export_data.actions.export_follows": "ייצא מעקבים",
@ -714,6 +733,7 @@
"group.update.success": "הקבוצה נשמרה בהצלחה",
"group.upload_banner": "העלה תמונה",
"group.upload_banner.title": "העלה תמונת רקע",
"groups.discover.popular.show_more": "להציג עוד",
"groups.discover.popular.title": "קבוצות פופולריות",
"groups.discover.search.error.subtitle": "נא לנסות שוב מאוחר יותר.",
"groups.discover.search.error.title": "התרחשה שגיאה",
@ -725,6 +745,9 @@
"groups.discover.search.recent_searches.clear_all": "נקה הכל",
"groups.discover.search.recent_searches.title": "חיפושים אחרונים",
"groups.discover.search.results.groups": "קבוצות",
"groups.discover.suggested.show_more": "להציג עוד",
"groups.discover.suggested.title": "הצעות בשבילך",
"groups.discover.tags.show_more": "להציג עוד",
"header.login.label": "התחברות",
"header.register.label": "Register",
"home.column_settings.show_reblogs": "הצגת הדהודים",
@ -879,6 +902,7 @@
"navigation_bar.favourites": "חיבובים",
"navigation_bar.filters": "מילים מושתקות",
"navigation_bar.follow_requests": "בקשות מעקב",
"navigation_bar.followed_tags": "האשטגים עקבתי אחריהם",
"navigation_bar.import_data": "יבא נתונים",
"navigation_bar.in_reply_to": "בתשובה ל",
"navigation_bar.invites": "הזמנות",
@ -887,6 +911,12 @@
"navigation_bar.preferences": "העדפות",
"navigation_bar.profile_directory": "ספריית פרופילים",
"navigation_bar.soapbox_config": "תצורת סבוניה",
"new_event_panel.action": "צור אירוע",
"new_event_panel.subtitle": "לא מוצא משהו בשבילך? תיצר אירוע משלך.",
"new_event_panel.title": "צור אירוע חדש",
"new_group_panel.action": "צור קבוצה",
"new_group_panel.subtitle": "לא מוצא משהו בשבילך? תקים קבוצה פרטית או ציבורית משלך.",
"new_group_panel.title": "הקמת קבוצה חדשה",
"notification.favourite": "הפוסט שלך חובב על ידי {name}",
"notification.follow": "{name} במעקב אחרייך",
"notification.follow_request": "{name} ביקש לעקוב אחריך",

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
"account.block_domain": "{domain}全体を非表示",
"account.blocked": "ブロック済み",
"account.chat": "@{name}さんとチャット",
"account.copy": "プロフィールへのリンクをコピー",
"account.deactivated": "非アクティブ化",
"account.direct": "@{name}さんにダイレクトメッセージ",
"account.domain_blocked": "ドメイン非表示",
@ -82,6 +83,11 @@
"account_search.placeholder": "アカウントを検索",
"actualStatus.edited": "{date}に編集済",
"actualStatuses.quote_tombstone": "Post is unavailable.",
"admin.announcements.action": "アナウンスを作成",
"admin.announcements.delete": "削除",
"admin.announcements.edit": "編集",
"admin.announcements.ends_at": "終了日時:",
"admin.announcements.starts_at": "開始日時:",
"admin.awaiting_approval.empty_message": "There is nobody waiting for approval. When a new user signs up, you can review them here.",
"admin.dashboard.registration_mode.approval_hint": "Users can sign up, but their account only gets activated when an admin approves it.",
"admin.dashboard.registration_mode.approval_label": "Approval Required",
@ -98,6 +104,12 @@
"admin.dashcounters.user_count_label": "total users",
"admin.dashwidgets.email_list_header": "Email list",
"admin.dashwidgets.software_header": "Software",
"admin.edit_announcement.created": "アナウンスが作成されました",
"admin.edit_announcement.deleted": "アナウンスを削除しました",
"admin.edit_announcement.fields.all_day_label": "終日",
"admin.edit_announcement.fields.content_label": "内容",
"admin.edit_announcement.fields.content_placeholder": "アナウンスの内容",
"admin.edit_announcement.fields.end_time_label": "終了日",
"admin.latest_accounts_panel.more": "クリックして {count} 人のおすすめユーザーを表示",
"admin.latest_accounts_panel.title": "Latest Accounts",
"admin.moderation_log.empty_message": "You have not performed any moderation actions yet. When you do, a history will be shown here.",

1
src/locales/jv.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -84,7 +84,7 @@
"account_note.placeholder": "Nie wprowadzono opisu",
"account_search.placeholder": "Szukaj konta",
"actualStatus.edited": "Edytowano {date}",
"actualStatuses.quote_tombstone": "Wpis jest niedostępny",
"actualStatuses.quote_tombstone": "Wpis jest niedostępny.",
"admin.announcements.action": "Utwórz ogłoszenie",
"admin.announcements.all_day": "Cały dzień",
"admin.announcements.delete": "Usuń",
@ -164,6 +164,8 @@
"alert.unexpected.links.support": "Wsparcie techniczne",
"alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
"alert.unexpected.return_home": "Wróć na stronę główną",
"alert.unexpected.submit_feedback": "Prześlij opinię",
"alert.unexpected.thanks": "Dziękujemy za twoją opinię!",
"aliases.account.add": "Utwórz alias",
"aliases.account_label": "Stare konto:",
"aliases.aliases_list_delete": "Odłącz alias",
@ -196,6 +198,9 @@
"birthdays_modal.empty": "Żaden z Twoich znajomych nie ma dziś urodzin.",
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
"boost_modal.title": "Repost?",
"bundle_column_error.body": "Coś poszło nie tak podczas ładowania tej strony.",
"bundle_column_error.retry": "Spróbuj ponownie",
"bundle_column_error.title": "Błąd sieci",
"card.back.label": "Wstecz",
"chat.actions.send": "Wyślij",
"chat.failed_to_send": "Nie udało się wysłać wiadomości.",
@ -229,6 +234,9 @@
"chat_message_list_intro.leave_chat.confirm": "Opuść czat",
"chat_message_list_intro.leave_chat.heading": "Opuść czat",
"chat_message_list_intro.leave_chat.message": "Czy na pewno chcesz opuścić ten czat? Wiadomości zostaną dla Ciebie usunięte, a czat zniknie z Twojej skrzynki.",
"chat_pane.blankslate.action": "Napisz do kogoś",
"chat_pane.blankslate.body": "Poszukaj kogoś do rozpoczęcia rozmowy.",
"chat_pane.blankslate.title": "Brak wiadomości",
"chat_search.blankslate.body": "Szukaj kogoś do rozpoczęcia rozmowy.",
"chat_search.blankslate.title": "Rozpocznij rozmowę",
"chat_search.empty_results_blankslate.body": "Spróbuj znaleźć inną nazwę.",
@ -260,10 +268,10 @@
"chat_window.auto_delete_label": "Usuwaj automatycznie po {day, plural, one {# dniu} other {# dniach}}",
"chat_window.auto_delete_tooltip": "Wiadomości będą usuwane po {day, plural, one {# dniu} other {# dniach}} od wysłania.",
"chats.actions.copy": "Kopiuj",
"chats.actions.delete": "Usuń wiadomość",
"chats.actions.delete": "Usuń dla wszystkich",
"chats.actions.deleteForMe": "Usuń dla mnie",
"chats.actions.more": "Więcej",
"chats.actions.report": "Zgłoś użytkownika",
"chats.actions.report": "Zgłoś",
"chats.dividers.today": "Dzisiaj",
"chats.main.blankslate.new_chat": "Napisz do kogoś",
"chats.main.blankslate.subtitle": "Szukaj kogoś do rozpoczęcia rozmowy",
@ -305,6 +313,7 @@
"column.developers.service_worker": "Service Worker",
"column.direct": "Wiadomości bezpośrednie",
"column.directory": "Przeglądaj profile",
"column.dislikes": "Nie lubi",
"column.domain_blocks": "Ukryte domeny",
"column.edit_profile": "Edytuj profil",
"column.event_map": "Lokalizacja wydarzenia",
@ -324,7 +333,7 @@
"column.filters.delete_error": "Błąd usuwania filtru",
"column.filters.drop_header": "Usuwaj zamiast ukrywać",
"column.filters.drop_hint": "Filtrowane wpisy znikną bezpowrotnie, nawet jeżeli filtr zostanie później usunięty",
"column.filters.edit": "Edytuj",
"column.filters.edit": "Edytuj filtr",
"column.filters.expires": "Wygasaj po",
"column.filters.hide_header": "Całkowicie ukryj",
"column.filters.hide_hint": "Całkowicie ukrywaj filtrowaną zawartość, zamiast wyświetlać ostrzeżenie",
@ -359,10 +368,11 @@
"column.notifications": "Powiadomienia",
"column.pins": "Przypięte wpisy",
"column.preferences": "Preferencje",
"column.public": "Globalna oś czasu",
"column.public": "Sfederowana oś czasu",
"column.quotes": "Cytatu wpisu",
"column.reactions": "Reakcje",
"column.reblogs": "Podbicia",
"column.registration": "Zarejestruj się",
"column.scheduled_statuses": "Zaplanowane wpisy",
"column.search": "Szukaj",
"column.settings_store": "Settings store",
@ -589,7 +599,7 @@
"edit_profile.hints.discoverable": "Wyświetlaj konto w katalogu profilów i pozwalaj na indeksowanie przez zewnętrzne usługi",
"edit_profile.hints.hide_network": "To, kogo obserwujesz i kto Cię obserwuje nie będzie wyświetlane na Twoim profilu",
"edit_profile.hints.locked": "Wymaga ręcznego zatwierdzania obserwacji",
"edit_profile.hints.meta_fields": "Możesz ustawić {count, plural, one {# niestandardowe pole wyświetlane} few {# niestandardowe pola wyświetlane} many {# niestandardowych pól wyświetlanych}} na Twoim profilu.",
"edit_profile.hints.meta_fields": "Możesz ustawić {count, plural, one {# niestandardowe pole wyświetlane} few {# niestandardowe pola wyświetlane} other {# niestandardowych pól wyświetlanych}} na Twoim profilu.",
"edit_profile.hints.stranger_notifications": "Wyświetlaj tylko powiadomienia od osób, które obserwujesz",
"edit_profile.save": "Zapisz",
"edit_profile.success": "Zapisano profil!",
@ -1060,7 +1070,7 @@
"notification.mentioned": "{name} wspomniał(a) o tobie",
"notification.move": "{name} przeniósł(-osła) się na {targetName}",
"notification.name": "{link}{others}",
"notification.others": " + {count} więcej",
"notification.others": "+ {count} więcej",
"notification.pleroma:chat_mention": "{name} wysłał(a) Ci wiadomośść",
"notification.pleroma:emoji_reaction": "{name} zareagował(a) na Twój wpis",
"notification.pleroma:event_reminder": "Wydarzenie w którym bierzesz udział wkrótce się zaczyna",
@ -1355,13 +1365,15 @@
"soapbox_config.promo_panel.meta_fields.icon_placeholder": "Ikona",
"soapbox_config.promo_panel.meta_fields.label_placeholder": "Podpis",
"soapbox_config.promo_panel.meta_fields.url_placeholder": "Adres URL",
"soapbox_config.raw_json_hint": "Edytuj ustawienia bezpośrednio. Zmiany dokonane w pliku JSON zastąpią powyższe ustawienia. Naciśnij Zapisz, aby zastosować zmiany.",
"soapbox_config.raw_json_hint": "Edytuj ustawienia bezpośrednio. Zmiany dokonane w pliku JSON zastąpią powyższe ustawienia. Naciśnij Zapisz, aby zastosować zmiany.",
"soapbox_config.raw_json_invalid": "jest nieprawidłowe",
"soapbox_config.raw_json_label": "Zaawansowane: Edytuj surowe dane JSON",
"soapbox_config.redirect_root_no_login_hint": "Cel przekierowania, jeśli użytkownik jest niezalogowany.",
"soapbox_config.redirect_root_no_login_label": "Przekieruj stronę główną",
"soapbox_config.save": "Zapisz",
"soapbox_config.saved": "Zapisano konfigurację Soapbox!",
"soapbox_config.sentry_dsn_hint": "Adres URL DSN do zgłaszania błędów. Działa z Sentry i GlitchTip.",
"soapbox_config.sentry_dsn_label": "DSN Sentry",
"soapbox_config.tile_server_attribution_label": "Atrybucja kafelków map",
"soapbox_config.tile_server_label": "Serwer kafelków map",
"soapbox_config.verified_can_edit_name_label": "Pozwól zweryfikowanym użytkownikom na zmianę swojej nazwy wyświetlanej.",
@ -1388,7 +1400,7 @@
"status.group": "Napisano w {group}",
"status.group_mod_delete": "Usuń wpis z grupy",
"status.interactions.favourites": "{count, plural, one {Polubienie} few {Polubienia} other {Polubień}}",
"status.interactions.quotes": "{count, plural, one {cytat} few {cytaty} many {cytatów}}",
"status.interactions.quotes": "{count, plural, one {Cytat} few {Cytaty} other {Cytatów}}",
"status.interactions.reblogs": "{count, plural, one {Podanie dalej} few {Podania dalej} other {Podań dalej}}",
"status.load_more": "Załaduj więcej",
"status.mention": "Wspomnij o @{name}",

Some files were not shown because too many files have changed in this diff Show More