Merge commit '0caf334891e0a5f4e995f1d667eb83d3aad7e41e' into glitch-soc/merge-upstream
Conflicts: - `lib/mastodon/version.rb`: Upstream updated the API version, while we also have a custom glitch API version. Updated as upstream did.
This commit is contained in:
commit
ae2ff31f32
114 files changed
+1147
-740
No files matched your search
@@ -3,6 +3,7 @@ import { createAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
apiRemoveAccountFromFollowers,
|
||||
apiGetEndorsedAccounts,
|
||||
apiGetAccounts,
|
||||
} from 'mastodon/api/accounts';
|
||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
|
||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||
@@ -113,3 +114,12 @@ export const fetchEndorsedAccounts = createDataLoadingThunk(
|
||||
return data;
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchAccounts = createDataLoadingThunk(
|
||||
'accounts/multi_accounts',
|
||||
({ accountIds }: { accountIds: string[] }) => apiGetAccounts(accountIds),
|
||||
(data, { dispatch }) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
return data;
|
||||
},
|
||||
);
|
||||
@@ -36,9 +36,18 @@ function notificationTypeForFilter(type: NotificationType) {
|
||||
}
|
||||
|
||||
function notificationTypeForQuickFilter(type: NotificationType) {
|
||||
if (type === 'quoted_update') return 'update';
|
||||
else if (type === 'quote') return 'mention';
|
||||
else return type;
|
||||
switch (type) {
|
||||
case 'quoted_update':
|
||||
return 'update';
|
||||
case 'quote':
|
||||
return 'mention';
|
||||
case 'collection_update':
|
||||
return 'collection';
|
||||
case 'added_to_collection':
|
||||
return 'collection';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function excludeAllTypesExcept(filter: string) {
|
||||
|
||||
@@ -86,6 +86,9 @@ export function setupBrowserNotifications() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(NotificationPermission) => void} callback
|
||||
*/
|
||||
export function requestBrowserPermission(callback = noOp) {
|
||||
return dispatch => {
|
||||
requestNotificationPermission((permission) => {
|
||||
|
||||
@@ -19,6 +19,11 @@ import type {
|
||||
ApiProfileUpdateParams,
|
||||
} from '../api_types/profile';
|
||||
|
||||
export const apiGetAccounts = (ids: string[]) =>
|
||||
apiRequestGet<ApiAccountJSON[]>('v1/accounts', {
|
||||
id: ids,
|
||||
});
|
||||
|
||||
export const apiSubmitAccountNote = (id: string, value: string) =>
|
||||
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
|
||||
comment: value,
|
||||
|
||||
@@ -15,48 +15,40 @@ import type {
|
||||
} from '../api_types/collections';
|
||||
|
||||
export const apiCreateCollection = (collection: ApiCreateCollectionPayload) =>
|
||||
apiRequestPost<ApiWrappedCollectionJSON>('v1_alpha/collections', collection);
|
||||
apiRequestPost<ApiWrappedCollectionJSON>('v1/collections', collection);
|
||||
|
||||
export const apiUpdateCollection = ({
|
||||
id,
|
||||
...collection
|
||||
}: ApiUpdateCollectionPayload) =>
|
||||
apiRequestPut<ApiWrappedCollectionJSON>(
|
||||
`v1_alpha/collections/${id}`,
|
||||
collection,
|
||||
);
|
||||
apiRequestPut<ApiWrappedCollectionJSON>(`v1/collections/${id}`, collection);
|
||||
|
||||
export const apiDeleteCollection = (collectionId: string) =>
|
||||
apiRequestDelete(`v1_alpha/collections/${collectionId}`);
|
||||
apiRequestDelete(`v1/collections/${collectionId}`);
|
||||
|
||||
export const apiGetCollection = (collectionId: string) =>
|
||||
apiRequestGet<ApiCollectionWithAccountsJSON>(
|
||||
`v1_alpha/collections/${collectionId}`,
|
||||
`v1/collections/${collectionId}`,
|
||||
);
|
||||
|
||||
export const apiGetCollectionsCreatedByAccount = (accountId: string) =>
|
||||
apiRequestGet<ApiCollectionsJSON>(
|
||||
`v1_alpha/accounts/${accountId}/collections`,
|
||||
);
|
||||
apiRequestGet<ApiCollectionsJSON>(`v1/accounts/${accountId}/collections`);
|
||||
|
||||
export const apiGetCollectionsFeaturingAccount = (accountId: string) =>
|
||||
apiRequestGet<ApiCollectionsJSON>(
|
||||
`v1_alpha/accounts/${accountId}/in_collections`,
|
||||
);
|
||||
apiRequestGet<ApiCollectionsJSON>(`v1/accounts/${accountId}/in_collections`);
|
||||
|
||||
export const apiAddCollectionItem = (collectionId: string, accountId: string) =>
|
||||
apiRequestPost<WrappedCollectionAccountItem>(
|
||||
`v1_alpha/collections/${collectionId}/items`,
|
||||
`v1/collections/${collectionId}/items`,
|
||||
{ account_id: accountId },
|
||||
);
|
||||
|
||||
export const apiRemoveCollectionItem = (collectionId: string, itemId: string) =>
|
||||
apiRequestDelete<WrappedCollectionAccountItem>(
|
||||
`v1_alpha/collections/${collectionId}/items/${itemId}`,
|
||||
`v1/collections/${collectionId}/items/${itemId}`,
|
||||
);
|
||||
|
||||
export const apiRevokeCollectionInclusion = (
|
||||
collectionId: string,
|
||||
itemId: string,
|
||||
) =>
|
||||
apiRequestPost(`v1_alpha/collections/${collectionId}/items/${itemId}/revoke`);
|
||||
) => apiRequestPost(`v1/collections/${collectionId}/items/${itemId}/revoke`);
|
||||
@@ -15,7 +15,7 @@ export const apiUpdate = (list: Partial<ApiListJSON>) =>
|
||||
|
||||
export const apiGetLists = () => apiRequestGet<ApiListJSON[]>('v1/lists');
|
||||
|
||||
export const apiGetAccounts = (listId: string) =>
|
||||
export const apiGetListAccounts = (listId: string) =>
|
||||
apiRequestGet<ApiAccountJSON[]>(`v1/lists/${listId}/accounts`, {
|
||||
limit: 0,
|
||||
});
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
word-break: break-all;
|
||||
text-align: left;
|
||||
|
||||
/* Allow the handle text to be selected */
|
||||
user-select: text;
|
||||
|
||||
> svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
@@ -100,6 +100,10 @@ interface ComboboxProps<
|
||||
* Icon to be displayed in the text input
|
||||
*/
|
||||
icon?: TextInputProps['icon'] | null;
|
||||
/**
|
||||
* Set to true to open as soon as there is focus
|
||||
*/
|
||||
openOnFocus?: boolean;
|
||||
/**
|
||||
* Set to false to keep the menu open when an item is selected
|
||||
*/
|
||||
@@ -217,8 +221,10 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
|
||||
renderGroupTitle,
|
||||
renderItem,
|
||||
onSelectItem,
|
||||
onFocus,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
openOnFocus = false,
|
||||
closeOnSelect = true,
|
||||
suppressMenu = false,
|
||||
icon = SearchIcon,
|
||||
@@ -288,6 +294,16 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleFocus: React.FocusEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
if (openOnFocus) {
|
||||
setShouldMenuOpen(true);
|
||||
}
|
||||
onFocus?.(e);
|
||||
},
|
||||
[onFocus, openOnFocus],
|
||||
);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e);
|
||||
@@ -487,6 +503,7 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
|
||||
autoComplete='off'
|
||||
spellCheck='false'
|
||||
value={value}
|
||||
onFocus={handleFocus}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
icon={icon ?? undefined}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Button } from '@/mastodon/components/button';
|
||||
import { DisplayName } from '@/mastodon/components/display_name';
|
||||
import { EmptyState } from '@/mastodon/components/empty_state';
|
||||
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
|
||||
import { areCollectionsEnabled } from '@/mastodon/features/collections/utils';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
@@ -50,56 +49,39 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
let title: React.ReactNode = null;
|
||||
let message: React.ReactNode = null;
|
||||
|
||||
const hasCollections = areCollectionsEnabled();
|
||||
|
||||
if (me === accountId) {
|
||||
if (hasCollections) {
|
||||
// Return only here to insert the "Create a collection" button as the action for the empty state.
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
// Return only here to insert the "Create a collection" button as the action for the empty state.
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!withoutAddCollectionButton && (
|
||||
<Link to='/collections/new' className='button'>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!withoutAddCollectionButton && (
|
||||
<Link to='/collections/new' className='button'>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
<Button secondary onClick={confirmHideFeaturedTab}>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_hide_tab'
|
||||
defaultMessage='Hide this tab instead'
|
||||
/>
|
||||
</Button>
|
||||
</EmptyState>
|
||||
);
|
||||
} else {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.pre_collections'
|
||||
defaultMessage='Stay tuned for Collections'
|
||||
/>
|
||||
);
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.pre_collections_desc'
|
||||
defaultMessage='Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
</Link>
|
||||
)}
|
||||
<Button secondary onClick={confirmHideFeaturedTab}>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_hide_tab'
|
||||
defaultMessage='Hide this tab instead'
|
||||
/>
|
||||
</Button>
|
||||
</EmptyState>
|
||||
);
|
||||
} else if (suspended) {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
|
||||
@@ -30,13 +30,10 @@ import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_you';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
|
||||
import { EmptyMessage } from './components/empty_message';
|
||||
import { Subheading, SubheadingLink } from './components/subheading';
|
||||
|
||||
const collectionsEnabled = areCollectionsEnabled();
|
||||
|
||||
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
multiColumn,
|
||||
}) => {
|
||||
@@ -98,14 +95,11 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
);
|
||||
|
||||
const hasCollections =
|
||||
collectionsEnabled &&
|
||||
collectionsLoadStatus === 'idle' &&
|
||||
listedCollections.length > 0;
|
||||
collectionsLoadStatus === 'idle' && listedCollections.length > 0;
|
||||
|
||||
const hasFeaturedAccounts = !featuredAccountIds.isEmpty();
|
||||
|
||||
const isLoading =
|
||||
!accountId || (collectionsEnabled && collectionsLoadStatus !== 'idle');
|
||||
const isLoading = !accountId || collectionsLoadStatus !== 'idle';
|
||||
|
||||
if (accountId === null) {
|
||||
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
||||
@@ -165,57 +159,53 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
</ItemList>
|
||||
</>
|
||||
)}
|
||||
{collectionsEnabled && (
|
||||
<>
|
||||
<Subheading as='header'>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h2>
|
||||
{accountId === me && (
|
||||
<SubheadingLink to='/collections/new' icon={AddIcon}>
|
||||
<FormattedMessage
|
||||
id='account.featured.new_collection'
|
||||
defaultMessage='New collection'
|
||||
/>
|
||||
</SubheadingLink>
|
||||
)}
|
||||
</Subheading>
|
||||
{hasCollections ? (
|
||||
<ItemList>
|
||||
<TruncatedListItems
|
||||
visibleItems={listedCollections}
|
||||
truncatedItems={unlistedCollections}
|
||||
toggleButton={{
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_with_count'
|
||||
defaultMessage='Unlisted collections ({count})'
|
||||
values={{ count: unlistedCollections.length }}
|
||||
/>
|
||||
),
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_description'
|
||||
defaultMessage='These don’t appear on your profile to others. Anyone with the link can discover them.'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
renderListItem={renderListItem}
|
||||
/>
|
||||
</ItemList>
|
||||
) : (
|
||||
<EmptyMessage
|
||||
withoutAddCollectionButton
|
||||
blockedBy={blockedBy}
|
||||
hidden={hidden}
|
||||
suspended={suspended}
|
||||
accountId={accountId}
|
||||
<Subheading as='header'>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h2>
|
||||
{accountId === me && (
|
||||
<SubheadingLink to='/collections/new' icon={AddIcon}>
|
||||
<FormattedMessage
|
||||
id='account.featured.new_collection'
|
||||
defaultMessage='New collection'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</SubheadingLink>
|
||||
)}
|
||||
</Subheading>
|
||||
{hasCollections ? (
|
||||
<ItemList>
|
||||
<TruncatedListItems
|
||||
visibleItems={listedCollections}
|
||||
truncatedItems={unlistedCollections}
|
||||
toggleButton={{
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_with_count'
|
||||
defaultMessage='Unlisted collections ({count})'
|
||||
values={{ count: unlistedCollections.length }}
|
||||
/>
|
||||
),
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_description'
|
||||
defaultMessage='These don’t appear on your profile to others. Anyone with the link can discover them.'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
renderListItem={renderListItem}
|
||||
/>
|
||||
</ItemList>
|
||||
) : (
|
||||
<EmptyMessage
|
||||
withoutAddCollectionButton
|
||||
blockedBy={blockedBy}
|
||||
hidden={hidden}
|
||||
suspended={suspended}
|
||||
accountId={accountId}
|
||||
/>
|
||||
)}
|
||||
<RemoteHint accountId={accountId} />
|
||||
</Scrollable>
|
||||
|
||||
@@ -215,6 +215,7 @@ export const CollectionAccounts: React.FC<{
|
||||
resetAccounts,
|
||||
} = useSearchAccounts({
|
||||
withRelationships: true,
|
||||
withDefaultFollows: searchValue === '',
|
||||
// Don't suggest accounts that were already added
|
||||
filterResults: (account) =>
|
||||
!editorItems.find((item) => item.account_id === account.id),
|
||||
@@ -363,6 +364,7 @@ export const CollectionAccounts: React.FC<{
|
||||
)}
|
||||
{hasPendingItems && <PendingNote />}
|
||||
<ComboboxField
|
||||
openOnFocus
|
||||
id={inputId}
|
||||
label={intl.formatMessage({
|
||||
id: 'collections.search_accounts_label',
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
userCollectionLimit,
|
||||
} from '../editor';
|
||||
import classes from '../styles.module.scss';
|
||||
import { areCollectionsEnabled } from '../utils';
|
||||
|
||||
const CreateButton: React.FC = () => (
|
||||
<Link to='/collections/new' className='button button--compact'>
|
||||
@@ -49,7 +48,7 @@ export function useCollectionsCreatedBy(accountId: string | null | undefined) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && areCollectionsEnabled()) {
|
||||
if (accountId) {
|
||||
void dispatch(fetchCollectionsCreatedByAccount({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
@@ -15,7 +15,6 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { CollectionListItem } from '../components/collection_list_item';
|
||||
import classes from '../styles.module.scss';
|
||||
import { areCollectionsEnabled } from '../utils';
|
||||
|
||||
import { CollectionListError } from './created_by_you';
|
||||
|
||||
@@ -23,7 +22,7 @@ function useCollectionsFeaturing(accountId: string | null | undefined) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && areCollectionsEnabled()) {
|
||||
if (accountId) {
|
||||
void dispatch(fetchCollectionsFeaturingAccount({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import type { ApiMutedAccountJSON } from '@/mastodon/api_types/accounts';
|
||||
import type { ApiAccountJSON } from '@/mastodon/api_types/accounts';
|
||||
import type { Account } from '@/mastodon/models/account';
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
export function areCollectionsEnabled() {
|
||||
return isServerFeatureEnabled('collections');
|
||||
}
|
||||
|
||||
export const getCollectionPath = (id: string) => `/collections/${id}`;
|
||||
|
||||
export const canAccountBeAdded = (account: ApiMutedAccountJSON | Account) =>
|
||||
export const canAccountBeAdded = (account: ApiAccountJSON | Account) =>
|
||||
['automatic', 'manual'].includes(account.feature_approval.current_user);
|
||||
|
||||
export const canAccountBeAddedByFollowers = (
|
||||
account: ApiMutedAccountJSON | Account,
|
||||
account: ApiAccountJSON | Account,
|
||||
) =>
|
||||
account.feature_approval.automatic.includes('followers') ||
|
||||
account.feature_approval.manual.includes('followers');
|
||||
@@ -15,7 +15,7 @@ import { fetchList } from 'mastodon/actions/lists';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { apiFollowAccount } from 'mastodon/api/accounts';
|
||||
import {
|
||||
apiGetAccounts,
|
||||
apiGetListAccounts,
|
||||
apiAddAccountToList,
|
||||
apiRemoveAccountFromList,
|
||||
} from 'mastodon/api/lists';
|
||||
@@ -184,7 +184,7 @@ const ListMembers: React.FC<{
|
||||
if (id) {
|
||||
dispatch(fetchList(id));
|
||||
|
||||
void apiGetAccounts(id)
|
||||
void apiGetListAccounts(id)
|
||||
.then((data) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
setAccountIds(data.map((a) => a.id));
|
||||
|
||||
@@ -12,7 +12,7 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { fetchList } from 'mastodon/actions/lists';
|
||||
import { createList, updateList } from 'mastodon/actions/lists_typed';
|
||||
import { apiGetAccounts } from 'mastodon/api/lists';
|
||||
import { apiGetListAccounts } from 'mastodon/api/lists';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type { RepliesPolicyType } from 'mastodon/api_types/lists';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
@@ -44,7 +44,7 @@ const MembersLink: React.FC<{
|
||||
const [avatarAccounts, setAvatarAccounts] = useState<ApiAccountJSON[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
void apiGetAccounts(id)
|
||||
void apiGetListAccounts(id)
|
||||
.then((data) => {
|
||||
setAvatarCount(data.length);
|
||||
setAvatarAccounts(data.slice(0, 3));
|
||||
|
||||
@@ -51,7 +51,6 @@ import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifica
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { AnnualReportNavItem } from '../annual_report/nav_item';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
|
||||
import { DisabledAccountBanner } from './components/disabled_account_banner';
|
||||
import { FollowedTagsPanel } from './components/followed_tags_panel';
|
||||
@@ -361,18 +360,16 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
text={intl.formatMessage(messages.bookmarks)}
|
||||
/>
|
||||
</li>
|
||||
{areCollectionsEnabled() && (
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
text={intl.formatMessage(messages.collections)}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
text={intl.formatMessage(messages.collections)}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
|
||||
@@ -12,6 +12,7 @@ import ClearColumnButton from './clear_column_button';
|
||||
import GrantPermissionButton from './grant_permission_button';
|
||||
import { PolicyControls } from './policy_controls';
|
||||
import SettingToggle from './setting_toggle';
|
||||
import { ColumnSettingsGroup } from './column_settings_group';
|
||||
|
||||
class ColumnSettings extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -187,6 +188,11 @@ class ColumnSettings extends PureComponent {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ColumnSettingsGroup
|
||||
type="collections"
|
||||
label={<FormattedMessage id='notifications.column_settings.collections' defaultMessage='Collections:' />}
|
||||
/>
|
||||
|
||||
{((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
|
||||
<section role='group' aria-labelledby='notifications-admin-sign-up'>
|
||||
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3>
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { showAlert } from '@/mastodon/actions/alerts';
|
||||
import { requestBrowserPermission } from '@/mastodon/actions/notifications';
|
||||
import { changeAlerts } from '@/mastodon/actions/push_notifications';
|
||||
import { changeSetting } from '@/mastodon/actions/settings';
|
||||
import {
|
||||
createAppSelector,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '@/mastodon/store';
|
||||
|
||||
import SettingToggle from './setting_toggle';
|
||||
|
||||
const selectNotificationSettings = createAppSelector(
|
||||
[
|
||||
(state) =>
|
||||
(state.settings as Immutable.Map<string, unknown>).get(
|
||||
'notifications',
|
||||
) as Immutable.Map<string, Immutable.Map<string, unknown> | boolean>,
|
||||
(state) => state.notifications.get('browserPermission') as string,
|
||||
(state) => state.push_notifications,
|
||||
],
|
||||
(settings, browserPermission, pushSettings) => ({
|
||||
settings,
|
||||
browserPermission: browserPermission !== 'denied',
|
||||
pushSettings,
|
||||
showPushSettings:
|
||||
pushSettings.get('supported') && pushSettings.get('enabled'),
|
||||
}),
|
||||
);
|
||||
|
||||
const messages = defineMessages({
|
||||
permissionDenied: {
|
||||
id: 'notifications.permission_denied_alert',
|
||||
defaultMessage:
|
||||
"Desktop notifications can't be enabled, as browser permission has been denied before",
|
||||
},
|
||||
});
|
||||
|
||||
type SettingsType = 'alerts' | 'shows' | 'sounds';
|
||||
|
||||
export const ColumnSettingsGroup: FC<{ label: ReactNode; type: string }> = ({
|
||||
label,
|
||||
type,
|
||||
}) => {
|
||||
const { settings, browserPermission, pushSettings, showPushSettings } =
|
||||
useAppSelector(selectNotificationSettings);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleChange = useCallback(
|
||||
(path: [SettingsType, string], checked: boolean) => {
|
||||
if (
|
||||
path[0] === 'alerts' &&
|
||||
checked &&
|
||||
typeof window.Notification !== 'undefined' &&
|
||||
Notification.permission !== 'granted'
|
||||
) {
|
||||
dispatch(
|
||||
requestBrowserPermission((permission) => {
|
||||
if (permission === 'granted') {
|
||||
dispatch(changeSetting(['notifications', ...path], checked));
|
||||
} else {
|
||||
dispatch(showAlert({ message: messages.permissionDenied }));
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(changeSetting(['notifications', ...path], checked));
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
const handlePushChange = useCallback(
|
||||
(path: string[], checked: boolean) => {
|
||||
if (
|
||||
checked &&
|
||||
typeof window.Notification !== 'undefined' &&
|
||||
Notification.permission !== 'granted'
|
||||
) {
|
||||
dispatch(
|
||||
requestBrowserPermission((permission: NotificationPermission) => {
|
||||
if (permission === 'granted') {
|
||||
dispatch(changeAlerts(path, checked));
|
||||
} else {
|
||||
dispatch(showAlert({ message: messages.permissionDenied }));
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(changeAlerts(path, checked));
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
return (
|
||||
<section role='group' aria-labelledby={`notifications-${type}`}>
|
||||
<h3 id={`notifications-${type}`}>{label}</h3>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle
|
||||
disabled={!browserPermission}
|
||||
prefix='notifications_desktop'
|
||||
settings={settings}
|
||||
settingPath={['alerts', type]}
|
||||
onChange={handleChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.alert'
|
||||
defaultMessage='Desktop notifications'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{showPushSettings && (
|
||||
<SettingToggle
|
||||
prefix='notifications_push'
|
||||
settings={pushSettings}
|
||||
settingPath={['alerts', type]}
|
||||
onChange={handlePushChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.push'
|
||||
defaultMessage='Push notifications'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SettingToggle
|
||||
prefix='notifications'
|
||||
settings={settings}
|
||||
settingPath={['shows', type]}
|
||||
onChange={handleChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.show'
|
||||
defaultMessage='Show in column'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingToggle
|
||||
prefix='notifications'
|
||||
settings={settings}
|
||||
settingPath={['sounds', type]}
|
||||
onChange={handleChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.sound'
|
||||
defaultMessage='Play sound'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import CollectionsIcon from '@/material-icons/400-24px/category.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
@@ -26,6 +27,10 @@ const tooltips = defineMessages({
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
collections: {
|
||||
id: 'notifications.filter.collections',
|
||||
defaultMessage: 'Collections',
|
||||
},
|
||||
statuses: {
|
||||
id: 'notifications.filter.statuses',
|
||||
defaultMessage: 'Updates from people you follow',
|
||||
@@ -124,6 +129,14 @@ export const FilterBar: React.FC = () => {
|
||||
>
|
||||
<Icon id='user-plus' icon={PersonAddIcon} />
|
||||
</BarButton>
|
||||
<BarButton
|
||||
selectedFilter={selectedFilter}
|
||||
type='collection'
|
||||
key='collection'
|
||||
title={intl.formatMessage(tooltips.collections)}
|
||||
>
|
||||
<Icon id='collections' icon={CollectionsIcon} />
|
||||
</BarButton>
|
||||
</div>
|
||||
);
|
||||
else
|
||||
|
||||
@@ -17,7 +17,6 @@ import { blockAccount } from 'mastodon/actions/accounts';
|
||||
import { closeModal } from 'mastodon/actions/modal';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { areCollectionsEnabled } from '../../collections/utils';
|
||||
|
||||
export const BlockModal = ({ accountId, acct }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -73,12 +72,10 @@ export const BlockModal = ({ accountId, acct }) => {
|
||||
<div><FormattedMessage id='block_modal.they_cant_mention' defaultMessage="You can't mention, follow, or quote each other." /></div>
|
||||
</li>
|
||||
|
||||
{areCollectionsEnabled() &&
|
||||
<li>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={CollectionsIcon} /></div>
|
||||
<div><FormattedMessage id='block_modal.no_collections' defaultMessage="Neither of you can add each other to collections. You'll be automatically removed from each others' existing collections, if applicable." /></div>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={CollectionsIcon} /></div>
|
||||
<div><FormattedMessage id='block_modal.no_collections' defaultMessage="Neither of you can add each other to collections. You'll be automatically removed from each others' existing collections, if applicable." /></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ import { CustomHomepage } from 'mastodon/features/custom_homepage';
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
import '../../components/status';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
import { getNavigationSkipLinkId, SkipLinks } from './components/skip_links';
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -235,13 +234,9 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
|
||||
<WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/featured', '/accounts/:id/featured']} component={AccountFeatured} content={children} />
|
||||
{areCollectionsEnabled() &&
|
||||
[
|
||||
<WrappedRoute path={['/@:acct/collections']} component={Collections} content={children} key='collections-list' />,
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />,
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />,
|
||||
]
|
||||
}
|
||||
<WrappedRoute path={['/@:acct/collections']} component={Collections} content={children} key='collections-list' />
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />
|
||||
<WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
@@ -8,16 +8,20 @@ import { apiRequest } from 'mastodon/api';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { useCurrentAccountId } from './useAccountId';
|
||||
|
||||
export function useSearchAccounts({
|
||||
onSettled,
|
||||
filterResults,
|
||||
resetOnInputClear = true,
|
||||
withRelationships = false,
|
||||
withDefaultFollows = false,
|
||||
}: {
|
||||
onSettled?: (value: string) => void;
|
||||
filterResults?: (account: ApiAccountJSON) => boolean;
|
||||
resetOnInputClear?: boolean;
|
||||
withRelationships?: boolean;
|
||||
withDefaultFollows?: boolean;
|
||||
} = {}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -29,7 +33,7 @@ export function useSearchAccounts({
|
||||
const searchRequestRef = useRef<AbortController | null>(null);
|
||||
|
||||
const searchAccounts = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
async (value: string) => {
|
||||
if (searchRequestRef.current) {
|
||||
searchRequestRef.current.abort();
|
||||
}
|
||||
@@ -46,41 +50,100 @@ export function useSearchAccounts({
|
||||
|
||||
searchRequestRef.current = new AbortController();
|
||||
|
||||
void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
|
||||
signal: searchRequestRef.current.signal,
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setAccounts(accounts);
|
||||
setLoadingState('idle');
|
||||
onSettled?.(value);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingState('error');
|
||||
onSettled?.(value);
|
||||
});
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
'v1/accounts/search',
|
||||
{
|
||||
signal: searchRequestRef.current.signal,
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setAccounts(accounts);
|
||||
setLoadingState('idle');
|
||||
onSettled?.(value);
|
||||
} catch {
|
||||
setLoadingState('error');
|
||||
onSettled?.(value);
|
||||
}
|
||||
},
|
||||
500,
|
||||
{ leading: true, trailing: true },
|
||||
);
|
||||
|
||||
const startSearch = useCallback(
|
||||
(value: string) => {
|
||||
void searchAccounts(value);
|
||||
},
|
||||
[searchAccounts],
|
||||
);
|
||||
|
||||
const resetAccounts = useCallback(() => {
|
||||
setAccounts([]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
searchAccounts,
|
||||
resetAccounts,
|
||||
const currentUserId = useCurrentAccountId();
|
||||
const [defaultAccounts, setDefaultAccounts] = useState<
|
||||
ApiAccountJSON[] | null
|
||||
>(null);
|
||||
useEffect(() => {
|
||||
if (
|
||||
!currentUserId ||
|
||||
loadingState !== 'idle' ||
|
||||
defaultAccounts !== null ||
|
||||
!withDefaultFollows
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function doRequest() {
|
||||
setLoadingState('loading');
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
`v1/accounts/${currentUserId}/following`,
|
||||
{ params: { limit: 40 } },
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setDefaultAccounts(accounts);
|
||||
setLoadingState('idle');
|
||||
} catch {
|
||||
setLoadingState('error');
|
||||
}
|
||||
}
|
||||
void doRequest();
|
||||
}, [
|
||||
currentUserId,
|
||||
accounts,
|
||||
dispatch,
|
||||
filterResults,
|
||||
loadingState,
|
||||
withRelationships,
|
||||
defaultAccounts,
|
||||
withDefaultFollows,
|
||||
]);
|
||||
|
||||
return {
|
||||
searchAccounts: startSearch,
|
||||
resetAccounts,
|
||||
accounts:
|
||||
accounts.length === 0 && withDefaultFollows
|
||||
? (defaultAccounts ?? [])
|
||||
: accounts,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Новыя скаргі:",
|
||||
"notifications.column_settings.admin.sign_up": "Новыя рэгістрацыі:",
|
||||
"notifications.column_settings.alert": "Апавяшчэнні на працоўным стале",
|
||||
"notifications.column_settings.collections": "Калекцыі:",
|
||||
"notifications.column_settings.favourite": "Упадабанае:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Паказаць усе катэгорыі",
|
||||
"notifications.column_settings.filter_bar.category": "Панэль хуткай фільтрацыі",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Праўкі:",
|
||||
"notifications.filter.all": "Усе",
|
||||
"notifications.filter.boosts": "Пашырэнні",
|
||||
"notifications.filter.collections": "Калекцыі",
|
||||
"notifications.filter.favourites": "Упадабанае",
|
||||
"notifications.filter.follows": "Падпісаны на",
|
||||
"notifications.filter.mentions": "Згадванні",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nye rapporteringer:",
|
||||
"notifications.column_settings.admin.sign_up": "Nye tilmeldinger:",
|
||||
"notifications.column_settings.alert": "Computernotifikationer",
|
||||
"notifications.column_settings.collections": "Samlinger:",
|
||||
"notifications.column_settings.favourite": "Favoritter:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Vis alle kategorier",
|
||||
"notifications.column_settings.filter_bar.category": "Hurtigfiltreringsbjælke",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Redigeringer:",
|
||||
"notifications.filter.all": "Alle",
|
||||
"notifications.filter.boosts": "Fremhævelser",
|
||||
"notifications.filter.collections": "Samlinger",
|
||||
"notifications.filter.favourites": "Favoritter",
|
||||
"notifications.filter.follows": "Følger",
|
||||
"notifications.filter.mentions": "Omtaler",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Neue Meldungen:",
|
||||
"notifications.column_settings.admin.sign_up": "Neue Registrierungen:",
|
||||
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
|
||||
"notifications.column_settings.collections": "Sammlungen:",
|
||||
"notifications.column_settings.favourite": "Favoriten:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Alle Filterkategorien anzeigen",
|
||||
"notifications.column_settings.filter_bar.category": "Filterleiste",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Überarbeitete Beiträge:",
|
||||
"notifications.filter.all": "Alles",
|
||||
"notifications.filter.boosts": "Geteilte Beiträge",
|
||||
"notifications.filter.collections": "Sammlungen",
|
||||
"notifications.filter.favourites": "Favoriten",
|
||||
"notifications.filter.follows": "Neue Follower",
|
||||
"notifications.filter.mentions": "Erwähnungen",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Νέες αναφορές:",
|
||||
"notifications.column_settings.admin.sign_up": "Νέες εγγραφές:",
|
||||
"notifications.column_settings.alert": "Ειδοποιήσεις για υπολογιστή",
|
||||
"notifications.column_settings.collections": "Συλλογές:",
|
||||
"notifications.column_settings.favourite": "Αγαπημένα:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Εμφάνιση όλων των κατηγοριών",
|
||||
"notifications.column_settings.filter_bar.category": "Μπάρα γρήγορου φίλτρου",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Επεξεργασίες:",
|
||||
"notifications.filter.all": "Όλες",
|
||||
"notifications.filter.boosts": "Προωθήσεις",
|
||||
"notifications.filter.collections": "Συλλογές",
|
||||
"notifications.filter.favourites": "Αγαπημένα",
|
||||
"notifications.filter.follows": "Ακολουθείς",
|
||||
"notifications.filter.mentions": "Επισημάνσεις",
|
||||
|
||||
@@ -637,8 +637,6 @@
|
||||
"empty_column.account_featured.other": "{acct} has not featured anything yet.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Create a collection",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Hide this tab instead",
|
||||
"empty_column.account_featured_self.pre_collections": "Stay tuned for Collections",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Showcase your favorite accounts",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Collections are curated lists of accounts to help others discover more of the Fediverse.",
|
||||
"empty_column.account_featured_unknown.other": "This account hasn’t featured anything yet.",
|
||||
@@ -1000,6 +998,7 @@
|
||||
"notifications.column_settings.admin.report": "New reports:",
|
||||
"notifications.column_settings.admin.sign_up": "New sign-ups:",
|
||||
"notifications.column_settings.alert": "Desktop notifications",
|
||||
"notifications.column_settings.collections": "Collections:",
|
||||
"notifications.column_settings.favourite": "Favorites:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||
@@ -1019,6 +1018,7 @@
|
||||
"notifications.column_settings.update": "Edits:",
|
||||
"notifications.filter.all": "All",
|
||||
"notifications.filter.boosts": "Boosts",
|
||||
"notifications.filter.collections": "Collections",
|
||||
"notifications.filter.favourites": "Favorites",
|
||||
"notifications.filter.follows": "Follows",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nuevas denuncias:",
|
||||
"notifications.column_settings.admin.sign_up": "Nuevos registros:",
|
||||
"notifications.column_settings.alert": "Notificaciones de escritorio",
|
||||
"notifications.column_settings.collections": "Colecciones:",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar todas las categorías",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Ediciones:",
|
||||
"notifications.filter.all": "Todas",
|
||||
"notifications.filter.boosts": "Adhesiones",
|
||||
"notifications.filter.collections": "Colecciones",
|
||||
"notifications.filter.favourites": "Favoritos",
|
||||
"notifications.filter.follows": "Seguidores",
|
||||
"notifications.filter.mentions": "Menciones",
|
||||
|
||||
@@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Copiar enlace al portapapeles",
|
||||
"copypaste.copied": "Copiado",
|
||||
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
||||
"custom_homepage.about": "Acerca de",
|
||||
"custom_homepage.about_this_server": "Acerca de este servidor",
|
||||
"custom_homepage.administered_by": "Administrado por",
|
||||
"custom_homepage.contact": "Contacto:",
|
||||
"custom_homepage.latest_activity": "Actividad más reciente",
|
||||
"custom_homepage.these_are_the_latest_posts": "Estas son las últimas 40 publicaciones de las cuentas de este servidor.",
|
||||
"directory.federated": "Desde el fediverso conocido",
|
||||
"directory.local": "Sólo de {domain}",
|
||||
"directory.new_arrivals": "Recién llegados",
|
||||
|
||||
@@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Copiar enlace al portapapeles",
|
||||
"copypaste.copied": "Copiado",
|
||||
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
||||
"custom_homepage.about": "Acerca de",
|
||||
"custom_homepage.about_this_server": "Acerca de este servidor",
|
||||
"custom_homepage.administered_by": "Administrado por",
|
||||
"custom_homepage.contact": "Contacto:",
|
||||
"custom_homepage.latest_activity": "Actividad más reciente",
|
||||
"custom_homepage.these_are_the_latest_posts": "Estas son las últimas 40 publicaciones de las cuentas de este servidor.",
|
||||
"directory.federated": "Desde el fediverso conocido",
|
||||
"directory.local": "Solo desde {domain}",
|
||||
"directory.new_arrivals": "Recién llegados",
|
||||
|
||||
@@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Kopeeri link lõikelauale",
|
||||
"copypaste.copied": "Kopeeritud",
|
||||
"copypaste.copy_to_clipboard": "Kopeeri vahemällu",
|
||||
"custom_homepage.about": "Teave",
|
||||
"custom_homepage.about_this_server": "Teave koduserveri kohta",
|
||||
"custom_homepage.administered_by": "Seda haldab",
|
||||
"custom_homepage.contact": "Kontakt:",
|
||||
"custom_homepage.latest_activity": "Viimane tegevus",
|
||||
"custom_homepage.these_are_the_latest_posts": "Need on selle serveri kasutajate viimased 40 postitust.",
|
||||
"directory.federated": "Tuntud födiversumist",
|
||||
"directory.local": "Ainult domeenilt {domain}",
|
||||
"directory.new_arrivals": "Uustulijad",
|
||||
@@ -775,17 +781,17 @@
|
||||
"home.pending_critical_update.link": "Vaata uuendusi",
|
||||
"home.pending_critical_update.title": "Saadaval kriitiline turvauuendus!",
|
||||
"home.show_announcements": "Kuva teadaandeid",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon ei saa teavitada kasutajaid, et ignoreerisid nende teavitusi. Teavituste ignoreerimine ei peata sõnumite endi saatmist.",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon ei saa teavitada kasutajaid, et eirasid nende teavitusi. Teavituste eiramine ei peata sõnumite endi saatmist.",
|
||||
"ignore_notifications_modal.filter_instead": "Selle asemel filtreeri",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Saad endiselt kasutajaid vastu võtta, tagasi lükata või neist teatada",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Filtreerimine aitab vältida võimalikke segaminiajamisi",
|
||||
"ignore_notifications_modal.filter_to_review_separately": "Saad filtreeritud teateid eraldi vaadata",
|
||||
"ignore_notifications_modal.ignore": "Ignoreeri teavitusi",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Ignoreeri modereeritud kontode teavitusi?",
|
||||
"ignore_notifications_modal.new_accounts_title": "Ignoreeri uute kontode teavitusi?",
|
||||
"ignore_notifications_modal.not_followers_title": "Ignoreeri inimeste teavitusi, kes sind ei jälgi?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignoreeri inimeste teavitusi, keda sa ei jälgi?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignoreeri soovimatute eraviisiliste mainimiste teateid?",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Kas eirad modereeritud kontode teavitusi?",
|
||||
"ignore_notifications_modal.new_accounts_title": "Kas eirad uute kontode teavitusi?",
|
||||
"ignore_notifications_modal.not_followers_title": "Kas eirad teavitusi kasutajatelt, kes sind ei jälgi?",
|
||||
"ignore_notifications_modal.not_following_title": "Kas eirad teavitusi kasutajatelt, keda sa ei jälgi?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Kas eirad soovimatute eraviisiliste mainimiste teavitusi?",
|
||||
"info_button.label": "Abi",
|
||||
"info_button.what_is_alt_text": "<h1>Mis on alt-tekst?</h1> <p>Alt-tekst pakub pildi kirjeldust nägemispuudega inimeste jaoks või neile, kel on aeglane internet või neile, kes otsivad lisaselgitust</p> <p>Saad parandada ligipääsetavust ja mõistmist kõigi jaoks, kirjutades selge, lühida ja objektiivse alt-teksti.</p> <ul> <li>Lisa tähtsad elemendid</li> <li>Tee pildil olevast tekstist kokkuvõte</li> <li>Kasuta reeglipärast lausestruktuuri</li> <li>Väldi ebaolulist infot</li> <li>Keskendu keerukate vaadete puhul (näiteks diagrammid ja kaardid) puhul trendidele ja põhiseostele</li> </ul>",
|
||||
"interaction_modal.action": "Suhestumaks kasutaja {name} postitusega palun logi sisse oma Mastodoni kasutajakontoga sõltumata serverist, mida kasutad.",
|
||||
@@ -879,7 +885,7 @@
|
||||
"loading_indicator.label": "Laadimine…",
|
||||
"media_gallery.hide": "Peida",
|
||||
"moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Peida teavituste hulgast",
|
||||
"mute_modal.hide_from_notifications": "Peida teavituste seast",
|
||||
"mute_modal.hide_options": "Peida valikud",
|
||||
"mute_modal.indefinite": "Kuni eemaldan neilt summutamise",
|
||||
"mute_modal.show_options": "Kuva valikud",
|
||||
@@ -908,6 +914,7 @@
|
||||
"navigation_bar.live_feed_local": "Ajajoon reaalajas (sinu server)",
|
||||
"navigation_bar.live_feed_public": "Ajajoon reaalajas (Födiversum)",
|
||||
"navigation_bar.logout": "Logi välja",
|
||||
"navigation_bar.main": "Esileht",
|
||||
"navigation_bar.moderation": "Modereerimine",
|
||||
"navigation_bar.more": "Lisavalikud",
|
||||
"navigation_bar.mutes": "Summutatud kasutajad",
|
||||
@@ -980,8 +987,8 @@
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Loobuda # taotlusest…} other {Loobuda # taotlusest…}}",
|
||||
"notification_requests.edit_selection": "Muuda",
|
||||
"notification_requests.exit_selection": "Valmis",
|
||||
"notification_requests.explainer_for_limited_account": "Sellelt kontolt tulevad teavitused on filtreeritud, sest moderaator on seda kontot piiranud.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Sellelt kontolt tulevad teavitused on filtreeritud, sest moderaator on seda kontot või serverit piiranud.",
|
||||
"notification_requests.explainer_for_limited_account": "Selle kasutajakontoga seotud teavitused on filtreeritud, sest moderaator on selle konto tegevust piiranud.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Selle kasutajakontoga seotud teavitused on filtreeritud, sest moderaator on selle konto tegevust või suhtlust andtu koduserveriga piiranud.",
|
||||
"notification_requests.maximize": "Maksimeeri",
|
||||
"notification_requests.minimize_banner": "Minimeeri filtreeritud teavituste bänner",
|
||||
"notification_requests.notifications_from": "Teavitus kasutajalt {name}",
|
||||
@@ -1305,6 +1312,7 @@
|
||||
"tabs_bar.menu": "Menüü",
|
||||
"tabs_bar.notifications": "Teated",
|
||||
"tabs_bar.publish": "Uus postitus",
|
||||
"tabs_bar.quick_links": "Kiirlingid",
|
||||
"tabs_bar.search": "Otsing",
|
||||
"tag.remove": "Eemalda",
|
||||
"terms_of_service.effective_as_of": "Kehtib alates {date}",
|
||||
|
||||
@@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Cóipeáil nasc chuig an ghearrthaisce",
|
||||
"copypaste.copied": "Cóipeáilte",
|
||||
"copypaste.copy_to_clipboard": "Cóipeáil chuig an ngearrthaisce",
|
||||
"custom_homepage.about": "Maidir",
|
||||
"custom_homepage.about_this_server": "Maidir leis an bhfreastalaí seo",
|
||||
"custom_homepage.administered_by": "Arna riar ag",
|
||||
"custom_homepage.contact": "Déan teagmháil le:",
|
||||
"custom_homepage.latest_activity": "An ghníomhaíocht is déanaí",
|
||||
"custom_homepage.these_are_the_latest_posts": "Seo iad na 40 postáil is déanaí ó chuntais ar an bhfreastalaí seo.",
|
||||
"directory.federated": "Ó chomhchruinne aitheanta",
|
||||
"directory.local": "Ó {domain} amháin",
|
||||
"directory.new_arrivals": "Daoine atá tar éis teacht",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Novas denuncias:",
|
||||
"notifications.column_settings.admin.sign_up": "Novas usuarias:",
|
||||
"notifications.column_settings.alert": "Notificacións de escritorio",
|
||||
"notifications.column_settings.collections": "Coleccións:",
|
||||
"notifications.column_settings.favourite": "Favoritas:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar todas as categorías",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Edicións:",
|
||||
"notifications.filter.all": "Todo",
|
||||
"notifications.filter.boosts": "Compartidos",
|
||||
"notifications.filter.collections": "Coleccións",
|
||||
"notifications.filter.favourites": "Favoritas",
|
||||
"notifications.filter.follows": "Seguimentos",
|
||||
"notifications.filter.mentions": "Mencións",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nuove segnalazioni:",
|
||||
"notifications.column_settings.admin.sign_up": "Nuove iscrizioni:",
|
||||
"notifications.column_settings.alert": "Notifiche desktop",
|
||||
"notifications.column_settings.collections": "Collezioni:",
|
||||
"notifications.column_settings.favourite": "Preferiti:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostra tutte le categorie",
|
||||
"notifications.column_settings.filter_bar.category": "Barra del filtro veloce",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Modifiche:",
|
||||
"notifications.filter.all": "Tutti",
|
||||
"notifications.filter.boosts": "Condivisioni",
|
||||
"notifications.filter.collections": "Collezioni",
|
||||
"notifications.filter.favourites": "Preferiti",
|
||||
"notifications.filter.follows": "Seguaci",
|
||||
"notifications.filter.mentions": "Menzioni",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nieuwe rapportages:",
|
||||
"notifications.column_settings.admin.sign_up": "Nieuwe registraties:",
|
||||
"notifications.column_settings.alert": "Desktopmeldingen",
|
||||
"notifications.column_settings.collections": "Verzamelingen:",
|
||||
"notifications.column_settings.favourite": "Favorieten:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen",
|
||||
"notifications.column_settings.filter_bar.category": "Snelle filterbalk",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Bewerkingen:",
|
||||
"notifications.filter.all": "Alles",
|
||||
"notifications.filter.boosts": "Boosts",
|
||||
"notifications.filter.collections": "Verzamelingen",
|
||||
"notifications.filter.favourites": "Favorieten",
|
||||
"notifications.filter.follows": "Nieuwe volgers",
|
||||
"notifications.filter.mentions": "Vermeldingen",
|
||||
|
||||
@@ -715,22 +715,22 @@
|
||||
"follow_suggestions.curated_suggestion": "Escolha da equipe",
|
||||
"follow_suggestions.dismiss": "Não mostrar novamente",
|
||||
"follow_suggestions.featured_longer": "Escolhido à mão pela equipe de {domain}",
|
||||
"follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que você segue",
|
||||
"follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as pessoas que você segue.",
|
||||
"follow_suggestions.friends_of_friends_longer": "Em alta entre as pessoas que você segue",
|
||||
"follow_suggestions.hints.featured": "Este perfil foi escolhido à mão pela equipe de {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Este perfil está em alta entre as pessoas que você segue.",
|
||||
"follow_suggestions.hints.most_followed": "Este perfil é um dos mais seguidos em {domain}.",
|
||||
"follow_suggestions.hints.most_interactions": "Este perfil tem recebido recentemente muita atenção em {domain}.",
|
||||
"follow_suggestions.hints.most_interactions": "Este perfil recentemente ganhou bastante atenção em {domain}.",
|
||||
"follow_suggestions.hints.similar_to_recently_followed": "Este perfil é semelhante aos perfis que você seguiu recentemente.",
|
||||
"follow_suggestions.personalized_suggestion": "Sugestão personalizada",
|
||||
"follow_suggestions.popular_suggestion": "Sugestão popular",
|
||||
"follow_suggestions.popular_suggestion_longer": "Popular em {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Similar a perfis que você seguiu recentemente",
|
||||
"follow_suggestions.view_all": "Visualizar tudo",
|
||||
"follow_suggestions.popular_suggestion": "Sugestão em alta",
|
||||
"follow_suggestions.popular_suggestion_longer": "Em alta em {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Semelhante aos perfis que você seguiu recentemente",
|
||||
"follow_suggestions.view_all": "Ver tudo",
|
||||
"follow_suggestions.who_to_follow": "Quem seguir",
|
||||
"followed_tags": "Hashtags seguidas",
|
||||
"followers.hide_other_followers": "Este usuário escolheu não deixar visíveis seus seguidores",
|
||||
"followers.title": "Seguinte {name}",
|
||||
"following.hide_other_following": "Este usuário escolheu não deixar visíveis aqueles a quem segue",
|
||||
"followers.hide_other_followers": "O usuário decidiu não deixar seus seguidores visíveis",
|
||||
"followers.title": "Seguindo {name}",
|
||||
"following.hide_other_following": "O usuário decidiu não deixar quem eles seguem visível",
|
||||
"following.title": "Seguido por {name}",
|
||||
"footer.about": "Sobre",
|
||||
"footer.about_mastodon": "Sobre o Mastodon",
|
||||
@@ -740,13 +740,13 @@
|
||||
"footer.get_app": "Baixe o app",
|
||||
"footer.keyboard_shortcuts": "Atalhos de teclado",
|
||||
"footer.privacy_policy": "Política de privacidade",
|
||||
"footer.source_code": "Exibir código-fonte",
|
||||
"footer.source_code": "Ver código-fonte",
|
||||
"footer.status": "Status",
|
||||
"footer.terms_of_service": "Termos de serviço",
|
||||
"form_error.blank": "O espaço não pode estar em branco.",
|
||||
"form_error.blank": "O campo não pode estar vazio.",
|
||||
"form_field.optional": "(opcional)",
|
||||
"getting_started.heading": "Primeiros passos",
|
||||
"hashtag.admin_moderation": "Abrir interface de moderação para #{name}",
|
||||
"hashtag.admin_moderation": "Abrir menu de moderação para #{name}",
|
||||
"hashtag.browse": "Buscar publicações em #{hashtag}",
|
||||
"hashtag.browse_from_account": "Buscar publicações de @{name} em #{hashtag}",
|
||||
"hashtag.column_header.tag_mode.all": "e {additional}",
|
||||
@@ -764,27 +764,27 @@
|
||||
"hashtag.feature": "Destacar no perfil",
|
||||
"hashtag.follow": "Seguir hashtag",
|
||||
"hashtag.mute": "Silenciar #{hashtag}",
|
||||
"hashtag.unfeature": "Não destacar no perfil",
|
||||
"hashtag.unfeature": "Remover destaque",
|
||||
"hashtag.unfollow": "Parar de seguir hashtag",
|
||||
"hashtags.and_other": "…e {count, plural, one {}other {outros #}}",
|
||||
"hints.profiles.followers_may_be_missing": "Pode haver seguidores deste perfil faltando.",
|
||||
"hints.profiles.follows_may_be_missing": "Pode haver seguidos por este perfil faltando.",
|
||||
"hints.profiles.posts_may_be_missing": "É possível que algumas publicações deste perfil estejam faltando.",
|
||||
"hints.profiles.see_more_followers": "Ver mais seguidores no {domain}",
|
||||
"hints.profiles.see_more_follows": "Ver mais seguidos no {domain}",
|
||||
"hashtags.and_other": "…e {count, plural, other {mais #}}",
|
||||
"hints.profiles.followers_may_be_missing": "Alguns seguidores deste perfil podem estar faltando.",
|
||||
"hints.profiles.follows_may_be_missing": "Alguns seguidos deste perfil podem estar faltando.",
|
||||
"hints.profiles.posts_may_be_missing": "Algumas publicações deste perfil podem estar faltando.",
|
||||
"hints.profiles.see_more_followers": "Ver mais seguidores em {domain}",
|
||||
"hints.profiles.see_more_follows": "Ver mais perfis seguidos em {domain}",
|
||||
"hints.profiles.see_more_posts": "Ver mais publicações em {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar citações",
|
||||
"home.column_settings.show_quotes": "Exibir citações",
|
||||
"home.column_settings.show_reblogs": "Mostrar impulsos",
|
||||
"home.column_settings.show_replies": "Mostrar respostas",
|
||||
"home.hide_announcements": "Ocultar anúncios",
|
||||
"home.pending_critical_update.body": "Por favor, atualize o seu servidor Mastodon o mais rápido possível!",
|
||||
"home.pending_critical_update.body": "Por favor, atualize o servidor do Mastodon o mais rápido possível!",
|
||||
"home.pending_critical_update.link": "Ver atualizações",
|
||||
"home.pending_critical_update.title": "Atualização de segurança crítica disponível!",
|
||||
"home.show_announcements": "Mostrar anúncios",
|
||||
"ignore_notifications_modal.disclaimer": "O Mastodon não pode informar aos usuários que você ignorou suas notificações. Ignorar notificações não impedirá que as próprias mensagens sejam enviadas.",
|
||||
"ignore_notifications_modal.disclaimer": "O Mastodon não informa os usuários se você ignorar as notificações deles. Ignorar notificações não impedirá as mensagens de serem enviadas.",
|
||||
"ignore_notifications_modal.filter_instead": "Filtrar em vez disso",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Você ainda conseguirá aceitar, rejeitar ou denunciar usuários",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "A filtragem ajuda a evitar confusão potencial",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Você ainda poderá aceitar, rejeitar ou denunciar",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Os filtros ajudam a evitar potenciais confusões",
|
||||
"ignore_notifications_modal.filter_to_review_separately": "Você pode rever notificações filtradas separadamente",
|
||||
"ignore_notifications_modal.ignore": "Ignorar notificações",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Ignorar notificações de contas moderadas?",
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Novas denúncias:",
|
||||
"notifications.column_settings.admin.sign_up": "Novas inscrições:",
|
||||
"notifications.column_settings.alert": "Notificações no computador",
|
||||
"notifications.column_settings.collections": "Coleções:",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Exibir todas as categorias",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtro rápido",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Editar:",
|
||||
"notifications.filter.all": "Tudo",
|
||||
"notifications.filter.boosts": "Impulsos",
|
||||
"notifications.filter.collections": "Coleções",
|
||||
"notifications.filter.favourites": "Favoritos",
|
||||
"notifications.filter.follows": "Seguidores",
|
||||
"notifications.filter.mentions": "Menções",
|
||||
|
||||
@@ -999,6 +999,7 @@
|
||||
"notifications.column_settings.admin.report": "Báo cáo mới:",
|
||||
"notifications.column_settings.admin.sign_up": "Người mới tham gia:",
|
||||
"notifications.column_settings.alert": "Báo trên máy tính",
|
||||
"notifications.column_settings.collections": "Gói khởi đầu:",
|
||||
"notifications.column_settings.favourite": "Lượt thích:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Xếp theo từng loại thông báo",
|
||||
"notifications.column_settings.filter_bar.category": "Phân loại thông báo",
|
||||
@@ -1018,6 +1019,7 @@
|
||||
"notifications.column_settings.update": "Sửa tút:",
|
||||
"notifications.filter.all": "Tất cả",
|
||||
"notifications.filter.boosts": "Đăng lại",
|
||||
"notifications.filter.collections": "Gói khởi đầu",
|
||||
"notifications.filter.favourites": "Lượt thích",
|
||||
"notifications.filter.follows": "Người theo dõi mới",
|
||||
"notifications.filter.mentions": "Lượt nhắc đến",
|
||||
|
||||
@@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "复制链接到剪贴板",
|
||||
"copypaste.copied": "已复制",
|
||||
"copypaste.copy_to_clipboard": "复制到剪贴板",
|
||||
"custom_homepage.about": "关于",
|
||||
"custom_homepage.about_this_server": "关于此服务器",
|
||||
"custom_homepage.administered_by": "管理者为",
|
||||
"custom_homepage.contact": "联系方式:",
|
||||
"custom_homepage.latest_activity": "最新动态",
|
||||
"custom_homepage.these_are_the_latest_posts": "以下是来自此服务器账号的最新 40 条嘟文。",
|
||||
"directory.federated": "来自已知联邦宇宙",
|
||||
"directory.local": "仅来自 {domain}",
|
||||
"directory.new_arrivals": "新来者",
|
||||
@@ -908,7 +914,7 @@
|
||||
"navigation_bar.live_feed_local": "实时动态(本站)",
|
||||
"navigation_bar.live_feed_public": "实时动态(公开)",
|
||||
"navigation_bar.logout": "退出登录",
|
||||
"navigation_bar.main": "首页",
|
||||
"navigation_bar.main": "主要",
|
||||
"navigation_bar.moderation": "审核",
|
||||
"navigation_bar.more": "更多",
|
||||
"navigation_bar.mutes": "已隐藏的用户",
|
||||
@@ -994,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "新举报:",
|
||||
"notifications.column_settings.admin.sign_up": "新注册:",
|
||||
"notifications.column_settings.alert": "桌面通知",
|
||||
"notifications.column_settings.collections": "收藏列表:",
|
||||
"notifications.column_settings.favourite": "喜欢:",
|
||||
"notifications.column_settings.filter_bar.advanced": "显示全部类别",
|
||||
"notifications.column_settings.filter_bar.category": "快速筛选栏",
|
||||
@@ -1013,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "编辑:",
|
||||
"notifications.filter.all": "全部",
|
||||
"notifications.filter.boosts": "转嘟",
|
||||
"notifications.filter.collections": "收藏列表",
|
||||
"notifications.filter.favourites": "喜欢",
|
||||
"notifications.filter.follows": "关注",
|
||||
"notifications.filter.mentions": "提及",
|
||||
|
||||
@@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "新檢舉報告:",
|
||||
"notifications.column_settings.admin.sign_up": "新註冊帳號:",
|
||||
"notifications.column_settings.alert": "桌面通知",
|
||||
"notifications.column_settings.collections": "收藏名單:",
|
||||
"notifications.column_settings.favourite": "最愛:",
|
||||
"notifications.column_settings.filter_bar.advanced": "顯示所有分類",
|
||||
"notifications.column_settings.filter_bar.category": "快速過濾器",
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "編輯:",
|
||||
"notifications.filter.all": "全部",
|
||||
"notifications.filter.boosts": "轉嘟",
|
||||
"notifications.filter.collections": "收藏名單",
|
||||
"notifications.filter.favourites": "最愛",
|
||||
"notifications.filter.follows": "跟隨的使用者",
|
||||
"notifications.filter.mentions": "提及",
|
||||
|
||||
@@ -41,6 +41,7 @@ const initialState = ImmutableMap({
|
||||
poll: false,
|
||||
status: false,
|
||||
update: false,
|
||||
collections: false,
|
||||
'admin.sign_up': false,
|
||||
'admin.report': false,
|
||||
}),
|
||||
@@ -65,6 +66,7 @@ const initialState = ImmutableMap({
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
collections: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true,
|
||||
}),
|
||||
@@ -79,6 +81,7 @@ const initialState = ImmutableMap({
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
collections: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true,
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { fetchAccounts } from '@/mastodon/actions/accounts_typed';
|
||||
import { importFetchedAccounts } from '@/mastodon/actions/importer';
|
||||
import {
|
||||
apiCreateCollection,
|
||||
@@ -20,6 +21,7 @@ import type {
|
||||
CollectionAccountItem,
|
||||
} from '@/mastodon/api_types/collections';
|
||||
import { initialState, me } from '@/mastodon/initial_state';
|
||||
import type { AppDispatch } from '@/mastodon/store';
|
||||
import {
|
||||
createAppAsyncThunk,
|
||||
createAppSelector,
|
||||
@@ -322,16 +324,42 @@ const collectionSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Prefetch accounts whose avatars will be displayed in the collection list
|
||||
*/
|
||||
async function importAccountsForPreviewCard(
|
||||
collections: ApiCollectionJSON[],
|
||||
dispatch: AppDispatch,
|
||||
) {
|
||||
const previewAccountIds = collections
|
||||
.flatMap((collection) =>
|
||||
collection.items.slice(0, 3).map((item) => item.account_id),
|
||||
)
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
await dispatch(
|
||||
fetchAccounts({
|
||||
accountIds: previewAccountIds,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export const fetchCollectionsCreatedByAccount = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/fetchCollectionsCreatedByAccount`,
|
||||
({ accountId }: { accountId: string }) =>
|
||||
apiGetCollectionsCreatedByAccount(accountId),
|
||||
async ({ collections }, { dispatch }) => {
|
||||
await importAccountsForPreviewCard(collections, dispatch);
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchCollectionsFeaturingAccount = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/fetchCollectionsFeaturingAccount`,
|
||||
({ accountId }: { accountId: string }) =>
|
||||
apiGetCollectionsFeaturingAccount(accountId),
|
||||
async ({ collections }, { dispatch }) => {
|
||||
await importAccountsForPreviewCard(collections, dispatch);
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchCollection = createDataLoadingThunk(
|
||||
|
||||
@@ -29,7 +29,10 @@ const filterNotificationsByAllowedTypes = (
|
||||
(item) =>
|
||||
item.type === 'gap' ||
|
||||
allowedType === item.type ||
|
||||
(allowedType === 'mention' && item.type === 'quote'),
|
||||
(allowedType === 'mention' && item.type === 'quote') ||
|
||||
(allowedType === 'collection' &&
|
||||
(item.type === 'collection_update' ||
|
||||
item.type === 'added_to_collection')),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,10 +17,19 @@ export const selectSettingsNotificationsShows = createSelector(
|
||||
|
||||
export const selectSettingsNotificationsExcludedTypes = createSelector(
|
||||
[selectSettingsNotificationsShows],
|
||||
(shows) =>
|
||||
Object.entries(shows)
|
||||
.filter(([_type, enabled]) => !enabled)
|
||||
.map(([type, _enabled]) => type),
|
||||
(shows) => {
|
||||
const excludedTypes: string[] = [];
|
||||
for (const key in shows) {
|
||||
if (!shows[key]) {
|
||||
if (key === 'collections') {
|
||||
excludedTypes.push('collection_update', 'added_to_collection');
|
||||
} else {
|
||||
excludedTypes.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return excludedTypes;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectSettingsNotificationsQuickFilterShow = (state: RootState) =>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function isProduction() {
|
||||
else return import.meta.env.PROD;
|
||||
}
|
||||
|
||||
export type ServerFeatures = 'fasp' | 'collections';
|
||||
export type ServerFeatures = 'fasp';
|
||||
|
||||
export function isServerFeatureEnabled(feature: ServerFeatures) {
|
||||
return initialState?.features.includes(feature) ?? false;
|
||||
|
||||
Reference in new issue
Block a user