Merge commit 'c6facd27ed191177afb003640b8ee7d0742c7f63' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2026-04-30 19:00:48 +02:00
227 changed files with 2723 additions and 1926 deletions
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="black">
<path d="M13.2502 8.00029C13.2502 5.10089 10.8996 2.75044 8.00024 2.75029C5.10075 2.75029 2.75024 5.10079 2.75024 8.00029C2.7504 10.8997 5.10084 13.2503 8.00024 13.2503C10.8995 13.2501 13.2501 10.8996 13.2502 8.00029ZM7.41663 4.50029C7.41663 4.17812 7.67808 3.91667 8.00024 3.91667C8.32228 3.91683 8.58301 4.17822 8.58301 4.50029V7.63884L10.5945 8.64458C10.8825 8.78865 10.999 9.13922 10.8551 9.42729C10.711 9.71541 10.3605 9.83276 10.0724 9.68877L7.73877 8.52153C7.54143 8.4227 7.41673 8.221 7.41663 8.00029V4.50029ZM14.4166 8.00029C14.4165 11.5439 11.5438 14.4165 8.00024 14.4167C4.45651 14.4167 1.58316 11.544 1.58301 8.00029C1.58301 4.45646 4.45642 1.58305 8.00024 1.58305C11.5439 1.58321 14.4166 4.45656 14.4166 8.00029Z" />
</svg>

After

Width:  |  Height:  |  Size: 799 B

+9 -7
View File
@@ -8,6 +8,7 @@ import { browserHistory } from 'mastodon/components/router';
import { countableText } from 'mastodon/features/compose/util/counter';
import { search as emojiSearch } from 'mastodon/features/emoji/emoji_mart_search_light';
import { tagHistory } from 'mastodon/settings';
import { fetchCustomEmojiData } from '@/mastodon/features/emoji/picker';
import { showAlert, showAlertForError } from './alerts';
import { useEmoji } from './emojis';
@@ -565,7 +566,7 @@ export function clearComposeSuggestions() {
};
}
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
const fetchComposeSuggestionsAccounts = throttle((dispatch, token) => {
if (fetchComposeSuggestionsAccountsController) {
fetchComposeSuggestionsAccountsController.abort();
}
@@ -592,12 +593,13 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) =>
});
}, 200, { leading: true, trailing: true });
const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 });
const fetchComposeSuggestionsEmojis = async (dispatch, token) => {
const custom = await fetchCustomEmojiData();
const results = emojiSearch(token.replace(':', ''), { maxResults: 5, custom });
dispatch(readyComposeSuggestionsEmojis(token, results));
};
const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
const fetchComposeSuggestionsTags = throttle((dispatch, token) => {
if (fetchComposeSuggestionsTagsController) {
fetchComposeSuggestionsTagsController.abort();
}
@@ -631,14 +633,14 @@ export function fetchComposeSuggestions(token) {
return (dispatch, getState) => {
switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, token);
void fetchComposeSuggestionsEmojis(dispatch, token);
break;
case '#':
case '#':
fetchComposeSuggestionsTags(dispatch, getState, token);
fetchComposeSuggestionsTags(dispatch, token);
break;
default:
fetchComposeSuggestionsAccounts(dispatch, getState, token);
fetchComposeSuggestionsAccounts(dispatch, token);
break;
}
};
@@ -1,40 +0,0 @@
import api from '../api';
export const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST';
export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS';
export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL';
export function fetchCustomEmojis() {
return (dispatch) => {
dispatch(fetchCustomEmojisRequest());
api().get('/api/v1/custom_emojis').then(response => {
dispatch(fetchCustomEmojisSuccess(response.data));
}).catch(error => {
dispatch(fetchCustomEmojisFail(error));
});
};
}
export function fetchCustomEmojisRequest() {
return {
type: CUSTOM_EMOJIS_FETCH_REQUEST,
skipLoading: true,
};
}
export function fetchCustomEmojisSuccess(custom_emojis) {
return {
type: CUSTOM_EMOJIS_FETCH_SUCCESS,
custom_emojis,
skipLoading: true,
};
}
export function fetchCustomEmojisFail(error) {
return {
type: CUSTOM_EMOJIS_FETCH_FAIL,
error,
skipLoading: true,
};
}
+6 -1
View File
@@ -34,11 +34,16 @@ export const apiGetCollection = (collectionId: string) =>
`v1_alpha/collections/${collectionId}`,
);
export const apiGetAccountCollections = (accountId: string) =>
export const apiGetCollectionsCreatedByAccount = (accountId: string) =>
apiRequestGet<ApiCollectionsJSON>(
`v1_alpha/accounts/${accountId}/collections`,
);
export const apiGetCollectionsFeaturingAccount = (accountId: string) =>
apiRequestGet<ApiCollectionsJSON>(
`v1_alpha/accounts/${accountId}/in_collections`,
);
export const apiAddCollectionItem = (collectionId: string, accountId: string) =>
apiRequestPost<WrappedCollectionAccountItem>(
`v1_alpha/collections/${collectionId}/items`,
@@ -4,6 +4,10 @@ import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import { fetchRelationships } from '@/mastodon/actions/accounts';
import { useAccount } from '@/mastodon/hooks/useAccount';
import type { AccountRole } from '@/mastodon/models/account';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import {
AdminBadge,
AutomatedBadge,
@@ -11,10 +15,7 @@ import {
BlockedBadge,
GroupBadge,
MutedBadge,
} from '@/mastodon/components/badge';
import { useAccount } from '@/mastodon/hooks/useAccount';
import type { AccountRole } from '@/mastodon/models/account';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
} from '../badge';
import classes from './styles.module.scss';
@@ -6,9 +6,6 @@ import { defineMessages, useIntl } from 'react-intl';
import classNames from 'classnames';
import { followAccount } from '@/mastodon/actions/accounts';
import { CopyIconButton } from '@/mastodon/components/copy_icon_button';
import { FollowButton } from '@/mastodon/components/follow_button';
import { IconButton } from '@/mastodon/components/icon_button';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { getAccountHidden } from '@/mastodon/selectors/accounts';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
@@ -16,6 +13,10 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react';
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
import { CopyIconButton } from '../copy_icon_button';
import { FollowButton } from '../follow_button';
import { IconButton } from '../icon_button';
import { AccountMenu } from './menu';
const messages = defineMessages({
@@ -7,21 +7,21 @@ import classNames from 'classnames';
import IconVerified from '@/images/icons/icon_verified.svg?react';
import { openModal } from '@/mastodon/actions/modal';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import type { EmojiHTMLProps } from '@/mastodon/components/emoji/html';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { Icon } from '@/mastodon/components/icon';
import { IconButton } from '@/mastodon/components/icon_button';
import { MiniCard } from '@/mastodon/components/mini_card';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import { useFieldHtml } from '@/mastodon/features/account_timeline/hooks/useFieldHtml';
import { cleanExtraEmojis } from '@/mastodon/features/emoji/normalize';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useResizeObserver } from '@/mastodon/hooks/useObserver';
import type { AccountFieldShape } from '@/mastodon/models/account';
import { useAppDispatch } from '@/mastodon/store';
import MoreIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { cleanExtraEmojis } from '../../emoji/normalize';
import type { AccountField } from '../common';
import { useFieldHtml } from '../hooks/useFieldHtml';
import { CustomEmojiProvider } from '../emoji/context';
import type { EmojiHTMLProps } from '../emoji/html';
import { EmojiHTML } from '../emoji/html';
import { Icon } from '../icon';
import { IconButton } from '../icon_button';
import { MiniCard } from '../mini_card';
import { useElementHandledLink } from '../status/handled_link';
import classes from './styles.module.scss';
@@ -37,6 +37,12 @@ const dateFormatOptions: Intl.DateTimeFormatOptions = {
minute: '2-digit',
};
export interface AccountField extends AccountFieldShape {
nameHasEmojis: boolean;
value_plain: string;
valueHasEmojis: boolean;
}
export const AccountHeaderFields: FC<{ accountId: string }> = ({
accountId,
}) => {
@@ -4,9 +4,6 @@ import classNames from 'classnames';
import { Helmet } from 'react-helmet';
import { openModal } from '@/mastodon/actions/modal';
import { AccountBio } from '@/mastodon/components/account_bio';
import { Avatar } from '@/mastodon/components/avatar';
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
import FollowRequestNoteContainer from '@/mastodon/features/account/containers/follow_request_note_container';
import { useLayout } from '@/mastodon/hooks/useLayout';
import { useVisibility } from '@/mastodon/hooks/useVisibility';
@@ -19,17 +16,20 @@ import type { Account } from '@/mastodon/models/account';
import { getAccountHidden } from '@/mastodon/selectors/accounts';
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
import { FamiliarFollowers } from '../../../components/familiar_followers';
import { AccountBio } from '../account_bio';
import { Avatar } from '../avatar';
import { AnimateEmojiProvider } from '../emoji/context';
import { FamiliarFollowers } from '../familiar_followers';
import { AccountName } from './account_name';
import { AccountSubscriptionForm } from './account_subscription_form';
import { AccountButtons } from './buttons';
import { AccountHeaderFields } from './fields';
import { MemorialNote } from './memorial_note';
import { MovedNote } from './moved_note';
import { AccountName } from './name';
import { AccountNote } from './note';
import { AccountNumberFields } from './number_fields';
import classes from './styles.module.scss';
import { AccountSubscriptionForm } from './subscription_form';
import { AccountTabs } from './tabs';
const titleFromAccount = (account: Account) => {
@@ -21,7 +21,6 @@ import {
import { openModal } from '@/mastodon/actions/modal';
import { initMuteModal } from '@/mastodon/actions/mutes';
import { initReport } from '@/mastodon/actions/reports';
import { Dropdown } from '@/mastodon/components/dropdown_menu';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useIdentity } from '@/mastodon/identity_context';
import type { Account } from '@/mastodon/models/account';
@@ -40,6 +39,8 @@ import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react'
import ReportIcon from '@/material-icons/400-24px/report.svg?react';
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
import { Dropdown } from '../dropdown_menu';
import classes from './styles.module.scss';
export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
@@ -2,9 +2,10 @@ import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { DisplayName } from '@/mastodon/components/display_name';
import { AvatarOverlay } from 'mastodon/components/avatar_overlay';
import { useAppSelector } from 'mastodon/store';
import { useAppSelector } from '@/mastodon/store';
import { AvatarOverlay } from '../avatar_overlay';
import { DisplayName } from '../display_name';
export const MovedNote: React.FC<{
accountId: string;
@@ -7,12 +7,7 @@ import classNames from 'classnames';
import Overlay from 'react-overlays/esm/Overlay';
import FollowerIcon from '@/images/icons/icon_follower.svg?react';
import { showAlert } from '@/mastodon/actions/alerts';
import { Badge } from '@/mastodon/components/badge';
import { Button } from '@/mastodon/components/button';
import { DisplayName } from '@/mastodon/components/display_name';
import { Icon } from '@/mastodon/components/icon';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useRelationship } from '@/mastodon/hooks/useRelationship';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
@@ -21,6 +16,11 @@ import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
import DomainIcon from '@/material-icons/400-24px/language.svg?react';
import { FollowsYouBadge } from '../badge';
import { Button } from '../button';
import { DisplayName } from '../display_name';
import { Icon } from '../icon';
import { AccountBadges } from './badges';
import classes from './styles.module.scss';
@@ -60,17 +60,7 @@ export const AccountName: FC<{ accountId: string }> = ({ accountId }) => {
<h1>
<DisplayName account={account} variant='simple' />
</h1>
{relationship?.followed_by && (
<Badge
icon={<FollowerIcon className={classes.followerBadgeIcon} />}
label={
<FormattedMessage
id='account.follows_you'
defaultMessage='Follows you'
/>
}
/>
)}
{relationship?.followed_by && <FollowsYouBadge />}
</div>
<AccountNameHelp
@@ -122,7 +112,6 @@ const AccountNameHelp: FC<{
aria-controls={accessibilityId}
>
{handle}
<Icon
id='help'
icon={HelpIcon}
@@ -189,7 +178,7 @@ const AccountNameHelp: FC<{
</ol>
<FormattedMessage
id='account.name.help.footer'
defaultMessage='Just like you can send emails to people using different email providers, you can interact with people on other Mastodon servers, and with anyone on other Mastodon-compatible social apps.'
defaultMessage='Just like you can send emails to people using different email providers, you can interact with people on other Mastodon servers, and with anyone on other Fediverse apps.'
tagName='p'
/>
@@ -5,11 +5,12 @@ import { defineMessages, useIntl } from 'react-intl';
import { fetchRelationships } from '@/mastodon/actions/accounts';
import { openModal } from '@/mastodon/actions/modal';
import { Callout } from '@/mastodon/components/callout';
import { IconButton } from '@/mastodon/components/icon_button';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import EditIcon from '@/material-icons/400-24px/edit_square.svg?react';
import { Callout } from '../callout';
import { IconButton } from '../icon_button';
import classes from './styles.module.scss';
const messages = defineMessages({
@@ -4,15 +4,13 @@ import type { FC } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { openModal } from '@/mastodon/actions/modal';
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
import {
NumberFields,
NumberFieldsItem,
} from '@/mastodon/components/number_fields';
import { ShortNumber } from '@/mastodon/components/short_number';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useAppDispatch } from '@/mastodon/store';
import { FormattedDateWrapper } from '../formatted_date';
import { NumberFields, NumberFieldsItem } from '../number_fields';
import { ShortNumber } from '../short_number';
export const AccountNumberFields: FC<{ accountId: string }> = ({
accountId,
}) => {
@@ -37,10 +37,6 @@
}
}
.followerBadgeIcon {
color: var(--color-text-secondary);
}
.badges {
margin-top: 8px;
}
@@ -50,13 +46,14 @@
gap: 2px;
padding: 0;
margin-top: 4px;
align-items: center;
appearance: none;
background: none;
border: none;
color: var(--color-text-secondary);
font-size: 13px;
transition: color 0.2s ease-in-out;
word-break: break-all;
text-align: left;
> svg {
width: 16px;
@@ -203,6 +200,9 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
.noteContent {
white-space-collapse: preserve-breaks;
overflow-wrap: break-word;
word-break: break-all;
hyphens: auto;
}
.noteEditButton {
@@ -275,10 +275,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
&.fieldItem {
border-color: var(--color-border-success-soft);
}
dt {
padding-right: 24px;
padding-right: 32px; // 8px padding + 16px for the icon + 8px gap
}
}
@@ -355,48 +352,10 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
}
}
.tabs,
.noTabs {
border-bottom: 1px solid var(--color-border-primary);
}
.tabs {
display: flex;
gap: 16px;
padding: 0 16px;
@container (width < 500px) {
a {
flex: 1 1 0px;
text-align: center;
}
}
a {
display: block;
font-size: 15px;
font-weight: 500;
padding: 18px 0;
text-decoration: none;
color: var(--color-text-primary);
border-radius: 0;
transition: color 0.2s ease-in-out;
&:not([aria-current='page']):is(:hover, :focus) {
color: var(--color-text-brand-soft);
}
}
:global(.active) {
color: var(--color-text-brand);
border-bottom: 4px solid var(--color-border-brand);
padding-bottom: 14px;
}
}
.noTabs {
width: 100%;
border-width: 0 0 1px;
border-bottom: 1px solid var(--color-border-primary);
}
.bannerBase {
@@ -8,16 +8,17 @@ import { Link } from 'react-router-dom';
import { AxiosError } from 'axios';
import { apiSubscribeByEmail } from 'mastodon/api/accounts';
import { apiSubscribeByEmail } from '@/mastodon/api/accounts';
import type {
ValidationErrorResponse,
ValidationError,
} from 'mastodon/api_types/errors';
import { Button } from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name';
import type { FieldStatus } from 'mastodon/components/form_fields';
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
import { useAppSelector } from 'mastodon/store';
} from '@/mastodon/api_types/errors';
import { useAppSelector } from '@/mastodon/store';
import { Button } from '../button';
import { DisplayName } from '../display_name';
import type { FieldStatus } from '../form_fields';
import { TextInputField } from '../form_fields/text_input_field';
import classes from './styles.module.scss';
@@ -3,11 +3,12 @@ import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import type { NavLinkProps } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useAccountId } from '@/mastodon/hooks/useAccountId';
import { TabLink, TabList } from '../tab_list';
import classes from './styles.module.scss';
const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
@@ -28,20 +29,20 @@ export const AccountTabs: FC = () => {
}
return (
<div className={classes.tabs}>
<NavLink isActive={isActive} to={`/@${acct}`}>
<TabList>
<TabLink isActive={isActive} to={`/@${acct}`}>
<FormattedMessage id='account.activity' defaultMessage='Activity' />
</NavLink>
</TabLink>
{show_media && (
<NavLink exact to={`/@${acct}/media`}>
<TabLink exact to={`/@${acct}/media`}>
<FormattedMessage id='account.media' defaultMessage='Media' />
</NavLink>
</TabLink>
)}
{show_featured && (
<NavLink exact to={`/@${acct}/featured`}>
<TabLink exact to={`/@${acct}/featured`}>
<FormattedMessage id='account.featured' defaultMessage='Featured' />
</NavLink>
</TabLink>
)}
</div>
</TabList>
);
};
@@ -1,6 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { accountFactoryState } from '@/testing/factories';
import { accountFactoryState, relationshipsFactory } from '@/testing/factories';
import { PendingBadge } from '../badge';
import { AccountListItem } from './index';
@@ -26,12 +28,30 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const FollowsYou: Story = {
parameters: {
state: {
relationships: {
'1': relationshipsFactory({
followed_by: true,
}),
},
},
},
};
export const WithCustomStats: Story = {
args: {
stats: ['posts', 'last-active'],
},
};
export const WithCustomBadge: Story = {
args: {
badge: <PendingBadge />,
},
};
export const WithBorder: Story = {
args: {
withBorder: true,
@@ -1,18 +1,23 @@
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
import { Account } from 'mastodon/components/account';
import { VerifiedBadge } from 'mastodon/components/badge';
import { FollowsYouBadge, VerifiedBadge } from 'mastodon/components/badge';
import { useAccount } from 'mastodon/hooks/useAccount';
import { useRelationship } from 'mastodon/hooks/useRelationship';
import { domain } from 'mastodon/initial_state';
import type { Relationship } from 'mastodon/models/relationship';
import { Avatar } from '../avatar';
import { useAccountHandle } from '../display_name/default';
import { DisplayNameSimple } from '../display_name/simple';
import { EmojiHTML } from '../emoji/html';
import { FollowButton } from '../follow_button';
import { FormattedDateWrapper } from '../formatted_date';
import { ListItemLink, ListItemWrapper } from '../list_item';
import { NumberFields, NumberFieldsItem } from '../number_fields';
import { RelativeTimestamp } from '../relative_timestamp';
import { ShortNumber } from '../short_number';
@@ -29,9 +34,10 @@ type Stat = 'followers' | 'following' | 'posts' | 'joined' | 'last-active';
interface Props {
accountId: string | undefined;
stats?: Stat[];
renderButton?: (options: RenderButtonOptions) => React.ReactNode;
withBio?: boolean;
withBorder?: boolean;
badge?: ReactNode;
renderButton?: (options: RenderButtonOptions) => React.ReactNode;
}
const DEFAULT_STATS: Stat[] = ['followers', 'posts', 'last-active'];
@@ -48,10 +54,12 @@ export const AccountListItem: React.FC<Props> = ({
stats = DEFAULT_STATS,
withBio = true,
withBorder = true,
badge: badgeProp,
renderButton = defaultRenderButton,
}) => {
const intl = useIntl();
const account = useAccount(accountId);
const handle = useAccountHandle(account, domain);
const relationship = useRelationship(accountId);
const createdThisYear = useMemo(
@@ -63,22 +71,34 @@ export const AccountListItem: React.FC<Props> = ({
return null;
}
const badge =
badgeProp ?? (relationship?.followed_by ? <FollowsYouBadge /> : null);
const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
return (
<div className={classes.wrapper} data-with-border={withBorder}>
<div className={classes.header}>
<Account
id={accountId}
minimal
size={40}
withMenu={false}
withBorder={false}
className={classes.account}
/>
{renderButton({ accountId, relationship })}
</div>
<ListItemWrapper
className={classes.main}
icon={<Avatar account={account} size={40} />}
sideContent={
<span className={classes.button}>
{renderButton({ accountId, relationship })}
</span>
}
>
<ListItemLink
to={`/@${account.acct}`}
data-hover-card-account={accountId}
subtitle={handle}
>
<DisplayNameSimple
account={account}
className={classes.displayName}
/>
{badge && <span className={classes.badge}>{badge}</span>}
</ListItemLink>
</ListItemWrapper>
<NumberFields>
{stats.includes('followers') && (
@@ -10,22 +10,22 @@
}
}
.header {
display: flex;
align-items: start;
width: 100%;
.main {
--list-item-padding: 0;
}
.account {
--account-name-size: 15px;
--account-handle-color: var(--color-text-secondary);
--account-handle-size: 13px;
--account-bio-color: var(--color-text-primary);
--account-bio-size: 13px;
--account-outer-spacing: 0;
.displayName {
// Spacing for badge
margin-inline-end: 6px;
}
flex-grow: 1;
margin-inline-end: 16px;
.badge {
// Sort out vertical alignment next to name
vertical-align: -4px;
}
.button {
align-self: start;
}
.verifiedBadge {
@@ -5,6 +5,8 @@ import { FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
import AdminIcon from '@/images/icons/icon_admin.svg?react';
import ClockIcon from '@/images/icons/icon_clock.svg?react';
import FollowerIcon from '@/images/icons/icon_follower.svg?react';
import IconVerified from '@/images/icons/icon_verified.svg?react';
import type { OnAttributeHandler } from '@/mastodon/utils/html';
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
@@ -32,6 +34,11 @@ interface BadgeProps extends React.ComponentPropsWithoutRef<'div'> {
| 'danger';
}
type PresetBadgeProps = Omit<
BadgeProps,
'label' | 'icon' | 'domain' | 'roleId'
>;
export const Badge: FC<BadgeProps> = ({
icon = <PersonIcon />,
variant = 'default',
@@ -83,13 +90,32 @@ export const GroupBadge: FC<Partial<BadgeProps>> = ({ label, ...props }) => (
/>
);
export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
export const AutomatedBadge: FC<PresetBadgeProps> = (props) => (
<Badge
icon={<SmartToyIcon />}
label={
<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />
}
className={className}
{...props}
/>
);
export const FollowsYouBadge: FC<PresetBadgeProps> = (props) => (
<Badge
icon={<FollowerIcon />}
label={
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
}
{...props}
/>
);
export const PendingBadge: FC<PresetBadgeProps> = (props) => (
<Badge
variant='warning'
icon={<ClockIcon />}
label={<FormattedMessage id='account.pending' defaultMessage='Pending' />}
{...props}
/>
);
@@ -1,6 +1,7 @@
.badge {
color: var(--color-text-primary);
font-size: 13px;
font-weight: 400;
display: inline-flex;
max-width: 100%;
padding: 4px;
@@ -61,7 +62,6 @@
.warning {
background-color: var(--color-bg-warning-softest);
color: var(--color-text-warning);
}
.danger {
@@ -87,7 +87,7 @@
}
.variantSubtle {
border: 1px solid var(--color-bg-brand-softest);
border: 1px solid var(--color-border-brand-soft);
background-color: var(--color-bg-primary);
.icon {
@@ -276,9 +276,11 @@ export const ColumnHeader: React.FC<Props> = ({
</>
);
const HeadingElement = hasTitle ? 'h1' : 'div';
const component = (
<div className={wrapperClassName}>
<h1 className={buttonClassName}>
<HeadingElement className={buttonClassName}>
{hasTitle && (
<>
{backButton}
@@ -311,7 +313,7 @@ export const ColumnHeader: React.FC<Props> = ({
{extraButton}
{collapseButton}
</div>
</h1>
</HeadingElement>
<div
className={collapsibleClassName}
@@ -2,6 +2,7 @@
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 600px;
padding: 24px;
gap: 16px;
@@ -1,5 +1,7 @@
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import ElephantImage from '@/images/elephant_ui.svg?react';
import classes from './empty_state.module.scss';
@@ -19,6 +21,7 @@ export const EmptyState: React.FC<{
title?: React.ReactNode;
message?: React.ReactNode;
children?: React.ReactNode;
className?: string;
}> = ({
image = 'default',
title = (
@@ -26,11 +29,12 @@ export const EmptyState: React.FC<{
),
message,
children,
className,
}) => {
const imageToRender = typeof image === 'string' ? images[image] : image;
return (
<div className={classes.wrapper}>
<div className={classNames(classes.wrapper, className)}>
{(title || message || imageToRender) && (
<div className={classes.content}>
{imageToRender}
@@ -1,7 +1,6 @@
.input {
box-sizing: border-box;
display: block;
resize: vertical;
width: 100%;
padding: 10px 16px;
font-family: inherit;
@@ -50,6 +49,10 @@
}
}
textarea.input {
resize: vertical;
}
.iconWrapper {
position: relative;
}
@@ -2,10 +2,11 @@ import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import { revealAccount } from 'mastodon/actions/accounts_typed';
import { Button } from 'mastodon/components/button';
import { domain } from 'mastodon/initial_state';
import { useAppDispatch } from 'mastodon/store';
import { revealAccount } from '@/mastodon/actions/accounts_typed';
import { domain } from '@/mastodon/initial_state';
import { useAppDispatch } from '@/mastodon/store';
import { Button } from './button';
export const LimitedAccountHint: React.FC<{ accountId: string }> = ({
accountId,
@@ -1,16 +1,17 @@
import classNames from 'classnames';
import { polymorphicForwardRef } from '@/types/polymorphic';
interface ModalShellProps {
className?: string;
children?: React.ReactNode;
}
export const ModalShell: React.FC<ModalShellProps> = ({
children,
className,
}) => {
return (
<div
export const ModalShell = polymorphicForwardRef<'form', ModalShellProps>(
({ as: Comp = 'form', children, className, ...restProps }, ref) => (
<Comp
{...restProps}
ref={ref}
className={classNames(
'modal-root__modal',
'safety-action-modal',
@@ -18,9 +19,10 @@ export const ModalShell: React.FC<ModalShellProps> = ({
)}
>
{children}
</div>
);
};
</Comp>
),
);
ModalShell.displayName = 'ModalShell';
export const ModalShellBody: React.FC<ModalShellProps> = ({
children,
@@ -1,8 +1,10 @@
import type { ComponentPropsWithoutRef } from 'react';
import { Children, forwardRef } from 'react';
import { forwardRef } from 'react';
import classNames from 'classnames';
import { hasReactChildren } from '@/mastodon/utils/has_react_children';
import { LoadingIndicator } from '../loading_indicator';
export const Scrollable = forwardRef<
@@ -36,7 +38,7 @@ export const ItemList = forwardRef<
emptyMessage?: React.ReactNode;
}
>(({ isLoading, emptyMessage, className, children, ...otherProps }, ref) => {
if (!isLoading && Children.count(children) === 0 && emptyMessage) {
if (!isLoading && !hasReactChildren(children) && emptyMessage) {
return (
<div className='empty-column-indicator'>
<span>{emptyMessage}</span>
@@ -8,7 +8,7 @@ import { debounce } from 'lodash';
import { TIMELINE_GAP, TIMELINE_PINNED_VIEW_ALL, TIMELINE_SUGGESTIONS } from 'mastodon/actions/timelines';
import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator';
import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions';
import { PinnedShowAllButton } from '@/mastodon/features/account_timeline/v2/pinned_statuses';
import { PinnedShowAllButton } from '@/mastodon/features/account_timeline/components/pinned_statuses';
import { StatusQuoteManager } from '../components/status_quoted';
@@ -0,0 +1,51 @@
import type { ComponentPropsWithoutRef, FC } from 'react';
import classNames from 'classnames';
import type { NavLinkProps } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import classes from './styles.module.scss';
interface TabListProps {
/**
* Setting this will remove the default border and
* horizontal padding from the component
*/
plain?: boolean;
}
/**
* Display a simple row of links as tabs.
* The current page will be highlighted automatically based on the link destination.
*/
export const TabList: FC<TabListProps & ComponentPropsWithoutRef<'div'>> = ({
plain,
className,
children,
...otherProps
}) => {
return (
<div
{...otherProps}
className={classNames(
className,
classes.tabList,
!plain && classes.withSpaceAndBorder,
)}
>
{children}
</div>
);
};
export const TabLink: FC<NavLinkProps> = ({
className,
children,
...otherProps
}) => {
return (
<NavLink className={classNames(classes.tab, className)} {...otherProps}>
{children}
</NavLink>
);
};
@@ -0,0 +1,45 @@
.tabList {
display: flex;
gap: 16px;
}
.withSpaceAndBorder {
padding-inline: 16px;
border-bottom: 1px solid var(--color-border-primary);
}
.tab {
display: block;
padding: 18px 0;
color: var(--color-text-primary);
font-size: 15px;
font-weight: 500;
text-decoration: none;
border-radius: 0;
transition: color 0.2s ease-in-out;
@container (width < 500px) {
flex: 1 1 0px;
text-align: center;
}
&:hover {
text-decoration: none;
}
&:focus {
// Override silly global border radius on focused links
border-radius: 0;
outline-offset: 2px;
}
&:not(:global(.active)):is(:hover, :focus) {
color: var(--color-text-brand-soft);
}
&:global(.active) {
color: var(--color-text-brand);
border-bottom: 4px solid var(--color-border-brand);
padding-bottom: 14px;
}
}
@@ -0,0 +1,33 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { TabList, TabLink } from './index';
const meta = {
title: 'Components/TabList',
component: TabList,
subcomponents: { TabLink },
} satisfies Meta<typeof TabList>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<TabList>
<TabLink to='/'>Activity</TabLink>
<TabLink to='/media'>Media</TabLink>
<TabLink to='/featured'>Featured</TabLink>
</TabList>
),
};
export const Plain: Story = {
render: () => (
<TabList plain>
<TabLink to='/'>Activity</TabLink>
<TabLink to='/media'>Media</TabLink>
<TabLink to='/featured'>Featured</TabLink>
</TabList>
),
};
@@ -1,6 +1,5 @@
import { Provider } from 'react-redux';
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { fetchServer } from 'mastodon/actions/server';
import { hydrateStore } from 'mastodon/actions/store';
import { Router } from 'mastodon/components/router';
@@ -13,7 +12,6 @@ if (initialState) {
store.dispatch(hydrateStore(initialState));
}
store.dispatch(fetchCustomEmojis());
store.dispatch(fetchServer());
const ComposeContainer = () => (
@@ -5,8 +5,6 @@ import { Route } from 'react-router-dom';
import { Provider as ReduxProvider } from 'react-redux';
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { hydrateStore } from 'mastodon/actions/store';
import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
@@ -26,9 +24,6 @@ const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction);
if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis());
}
export default class Mastodon extends PureComponent {
identity = createIdentityContext(initialState);
@@ -107,11 +107,11 @@ export const messages = defineMessages({
},
profileTabTitle: {
id: 'account_edit.profile_tab.title',
defaultMessage: 'Profile tab settings',
defaultMessage: 'Profile display settings',
},
profileTabSubtitle: {
id: 'account_edit.profile_tab.subtitle',
defaultMessage: 'Customize the tabs on your profile and what they display.',
defaultMessage: 'Customize how your profile is displayed.',
},
advancedSettingsTitle: {
id: 'account_edit.advanced_settings.title',
@@ -27,7 +27,8 @@ export const ProfileDisplayModal: FC<DialogModalProps> = ({ onClose }) => {
const handleToggleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(event) => {
const { name, checked } = event.target;
void dispatch(patchProfile({ [name]: checked }));
const targetChecked = name === 'hide_collections' ? !checked : checked;
void dispatch(patchProfile({ [name]: targetChecked }));
},
[dispatch],
);
@@ -101,6 +102,25 @@ export const ProfileDisplayModal: FC<DialogModalProps> = ({ onClose }) => {
/>
}
/>
<ToggleField
checked={!profile.hideCollections}
onChange={handleToggleChange}
disabled={isPending}
name='hide_collections'
label={
<FormattedMessage
id='account_edit.profile_tab.show_relations.title'
defaultMessage='Show ‘Followers’ and ‘Following’'
/>
}
hint={
<FormattedMessage
id='account_edit.profile_tab.show_relations.description'
defaultMessage='Shows accounts you follow and follows you to other users in your profile. People will still be able to see if you are following them.'
/>
}
/>
</div>
<Callout
@@ -8,7 +8,7 @@ import { Link } from 'react-router-dom';
import { openModal } from '@/mastodon/actions/modal';
import { Button } from '@/mastodon/components/button';
import { EmptyState } from '@/mastodon/components/empty_state';
import { LimitedAccountHint } from '@/mastodon/features/account_timeline/components/limited_account_hint';
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
import { areCollectionsEnabled } from '@/mastodon/features/collections/utils';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { useAppDispatch } from '@/mastodon/store';
@@ -6,29 +6,29 @@ import { useHistory } from 'react-router';
import { List as ImmutableList } from 'immutable';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import { fetchEndorsedAccounts } from 'mastodon/actions/accounts';
import { AccountListItem } from 'mastodon/components/account_list_item';
import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RemoteHint } from 'mastodon/components/remote_hint';
import { fetchEndorsedAccounts } from '@/mastodon/actions/accounts';
import { AccountHeader } from '@/mastodon/components/account_header';
import { AccountListItem } from '@/mastodon/components/account_list_item';
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { RemoteHint } from '@/mastodon/components/remote_hint';
import {
Article,
ItemList,
Scrollable,
} from 'mastodon/components/scrollable_list/components';
import type { TruncatedListItemInfo } from 'mastodon/components/truncated_list';
import { TruncatedListItems } from 'mastodon/components/truncated_list';
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import Column from 'mastodon/features/ui/components/column';
import { useAccount } from 'mastodon/hooks/useAccount';
import { useAccountId } from 'mastodon/hooks/useAccountId';
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
} from '@/mastodon/components/scrollable_list/components';
import type { TruncatedListItemInfo } from '@/mastodon/components/truncated_list';
import { TruncatedListItems } from '@/mastodon/components/truncated_list';
import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
import Column from '@/mastodon/features/ui/components/column';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useAccountId } from '@/mastodon/hooks/useAccountId';
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import { useAccountCollections } from '../collections';
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';
@@ -67,7 +67,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
) as ImmutableList<string>,
);
const { collections, status: collectionsLoadStatus } =
useAccountCollections(accountId);
useCollectionsCreatedBy(accountId);
const { listedCollections = [], unlistedCollections = [] } = Object.groupBy(
collections,
@@ -4,23 +4,23 @@ import { FormattedMessage } from 'react-intl';
import { List as ImmutableList, isList } from 'immutable';
import { openModal } from 'mastodon/actions/modal';
import { expandAccountMediaTimeline } from 'mastodon/actions/timelines';
import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { RemoteHint } from 'mastodon/components/remote_hint';
import ScrollableList from 'mastodon/components/scrollable_list';
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import Column from 'mastodon/features/ui/components/column';
import { useAccountId } from 'mastodon/hooks/useAccountId';
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { openModal } from '@/mastodon/actions/modal';
import { expandAccountMediaTimeline } from '@/mastodon/actions/timelines';
import { AccountHeader } from '@/mastodon/components/account_header';
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
import { RemoteHint } from '@/mastodon/components/remote_hint';
import ScrollableList from '@/mastodon/components/scrollable_list';
import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
import Column from '@/mastodon/features/ui/components/column';
import { useAccountId } from '@/mastodon/hooks/useAccountId';
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
import type { MediaAttachment } from '@/mastodon/models/media_attachment';
import {
useAppSelector,
useAppDispatch,
createAppSelector,
} from 'mastodon/store';
} from '@/mastodon/store';
import { MediaItem } from './components/media_item';
@@ -1,7 +0,0 @@
import type { AccountFieldShape } from '@/mastodon/models/account';
export interface AccountField extends AccountFieldShape {
nameHasEmojis: boolean;
value_plain: string;
valueHasEmojis: boolean;
}
@@ -13,8 +13,8 @@ import { useOverflowButton } from '@/mastodon/hooks/useOverflow';
import { selectAccountFeaturedTags } from '@/mastodon/selectors/accounts';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { useAccountContext } from './context';
import classes from './styles.module.scss';
import { useAccountContext } from '../hooks/useAccountContext';
import classes from '../styles.module.scss';
export const FeaturedTags: FC<{ accountId: string }> = ({ accountId }) => {
// Fetch tags.
@@ -7,14 +7,13 @@ import { useParams } from 'react-router';
import Overlay from 'react-overlays/esm/Overlay';
import { AccountTabs } from '@/mastodon/components/account_header/tabs';
import { Toggle } from '@/mastodon/components/form_fields';
import { Icon } from '@/mastodon/components/icon';
import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react';
import { AccountTabs } from '../components/tabs';
import { useAccountContext } from './context';
import classes from './styles.module.scss';
import { useAccountContext } from '../hooks/useAccountContext';
import classes from '../styles.module.scss';
export const AccountFilters: FC = () => {
const { acct } = useParams<{ acct: string }>();
@@ -1,68 +0,0 @@
import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import type { Relationship } from '@/mastodon/models/relationship';
export const AccountInfo: FC<{ relationship?: Relationship }> = ({
relationship,
}) => {
if (!relationship) {
return null;
}
return (
<div className='account__header__info'>
{(relationship.followed_by || relationship.requested_by) && (
<span className='relationship-tag'>
<AccountInfoFollower relationship={relationship} />
</span>
)}
{relationship.blocking && (
<span className='relationship-tag'>
<FormattedMessage id='account.blocking' defaultMessage='Blocking' />
</span>
)}
{relationship.muting && (
<span key='muting' className='relationship-tag'>
<FormattedMessage id='account.muting' defaultMessage='Muting' />
</span>
)}
{relationship.domain_blocking && (
<span key='domain_blocking' className='relationship-tag'>
<FormattedMessage
id='account.domain_blocking'
defaultMessage='Blocking domain'
/>
</span>
)}
</div>
);
};
const AccountInfoFollower: FC<{ relationship: Relationship }> = ({
relationship,
}) => {
if (
relationship.followed_by &&
(relationship.following || relationship.requested)
) {
return (
<FormattedMessage
id='account.mutual'
defaultMessage='You follow each other'
/>
);
} else if (relationship.followed_by) {
return (
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
);
} else if (relationship.requested_by) {
return (
<FormattedMessage
id='account.requests_to_follow_you'
defaultMessage='Requests to follow you'
/>
);
}
return null;
};
@@ -0,0 +1,55 @@
import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import IconPinned from '@/images/icons/icon_pinned.svg?react';
import { Badge } from '@/mastodon/components/badge';
import { Button } from '@/mastodon/components/button';
import { Icon } from '@/mastodon/components/icon';
import { StatusHeader } from '@/mastodon/components/status/header';
import type { StatusHeaderRenderFn } from '@/mastodon/components/status/header';
import { useAccountContext } from '../hooks/useAccountContext';
import classes from '../styles.module.scss';
export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({
featured,
...args
}) => {
if (!featured) {
return <StatusHeader {...args} />;
}
return (
<StatusHeader {...args} className={classes.pinnedStatusHeader}>
<Badge
className={classes.pinnedBadge}
icon={<Icon id='pinned' icon={IconPinned} />}
label={
<FormattedMessage
id='account.timeline.pinned'
defaultMessage='Pinned'
/>
}
/>
</StatusHeader>
);
};
export const PinnedShowAllButton: FC = () => {
const { onShowAllPinned } = useAccountContext();
return (
<Button
onClick={onShowAllPinned}
className={classNames(classes.pinnedViewAllButton, 'focusable')}
>
<Icon id='pinned' icon={IconPinned} />
<FormattedMessage
id='account.timeline.pinned.view_all'
defaultMessage='View all pinned posts'
/>
</Button>
);
};
@@ -20,7 +20,7 @@ import {
createAppSelector,
} from '@/mastodon/store';
import classes from './styles.module.scss';
import classes from '../styles.module.scss';
const MAX_SUGGESTED_TAGS = 3;
@@ -1,4 +1,3 @@
import type { FC, ReactNode } from 'react';
import {
createContext,
useCallback,
@@ -10,7 +9,7 @@ import {
import { useStorageState } from '@/mastodon/hooks/useStorage';
interface AccountTimelineContextValue {
accountId: string;
accountId: string | null;
boosts: boolean;
replies: boolean;
showAllPinned: boolean;
@@ -19,13 +18,20 @@ interface AccountTimelineContextValue {
onShowAllPinned: () => void;
}
const AccountTimelineContext =
export const AccountTimelineContext =
createContext<AccountTimelineContextValue | null>(null);
export const AccountTimelineProvider: FC<{
accountId: string;
children: ReactNode;
}> = ({ accountId, children }) => {
export function useAccountContext() {
const values = useContext(AccountTimelineContext);
if (!values) {
throw new Error(
'useAccountFilters must be used within an AccountTimelineProvider',
);
}
return values;
}
export const useAccountContextValue = (accountId?: string | null) => {
const storageOptions = {
type: 'local',
prefix: 'account-filters',
@@ -62,9 +68,9 @@ export const AccountTimelineProvider: FC<{
}, []);
// Memoize the context value to avoid unnecessary re-renders.
const value = useMemo(
return useMemo(
() => ({
accountId,
accountId: accountId ?? null,
boosts,
replies,
showAllPinned,
@@ -82,20 +88,4 @@ export const AccountTimelineProvider: FC<{
showAllPinned,
],
);
return (
<AccountTimelineContext.Provider value={value}>
{children}
</AccountTimelineContext.Provider>
);
};
export function useAccountContext() {
const values = useContext(AccountTimelineContext);
if (!values) {
throw new Error(
'useAccountFilters must be used within an AccountTimelineProvider',
);
}
return values;
}
@@ -0,0 +1,58 @@
import { useEffect, useMemo } from 'react';
import { TIMELINE_PINNED_VIEW_ALL } from '@/mastodon/actions/timelines';
import {
expandTimelineByKey,
timelineKey,
} from '@/mastodon/actions/timelines_typed';
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { useAccountContext } from './useAccountContext';
export function usePinnedStatusIds({
accountId,
tagged,
forceEmptyState = false,
}: {
accountId: string;
tagged?: string;
forceEmptyState?: boolean;
}) {
const pinnedKey = timelineKey({
type: 'account',
userId: accountId,
tagged,
pinned: true,
replies: true,
});
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(expandTimelineByKey({ key: pinnedKey }));
}, [dispatch, pinnedKey]);
const pinnedTimeline = useAppSelector((state) =>
selectTimelineByKey(state, pinnedKey),
);
const { showAllPinned } = useAccountContext();
const pinnedTimelineItems = pinnedTimeline?.items; // Make a const to avoid the React Compiler complaining.
const pinnedStatusIds = useMemo(() => {
if (!pinnedTimelineItems || forceEmptyState) {
return undefined;
}
if (pinnedTimelineItems.size <= 1 || showAllPinned) {
return pinnedTimelineItems;
}
return pinnedTimelineItems.slice(0, 1).push(TIMELINE_PINNED_VIEW_ALL);
}, [forceEmptyState, pinnedTimelineItems, showAllPinned]);
return {
statusIds: pinnedStatusIds,
isLoading: !!pinnedTimeline?.isLoading,
showAllPinned,
};
}
@@ -1,197 +0,0 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { me } from 'mastodon/initial_state';
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
import { getAccountHidden } from 'mastodon/selectors/accounts';
import { lookupAccount, fetchAccount } from '../../actions/accounts';
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
import { ColumnBackButton } from '../../components/column_back_button';
import { LoadingIndicator } from '../../components/loading_indicator';
import StatusList from '../../components/status_list';
import Column from '../ui/components/column';
import { RemoteHint } from 'mastodon/components/remote_hint';
import { AccountHeader } from './components/account_header';
import { LimitedAccountHint } from './components/limited_account_hint';
import { FeaturedCarousel } from '@/mastodon/components/featured_carousel';
const emptyList = ImmutableList();
const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = false }) => {
const accountId = id || state.accounts_map[normalizeForLookup(acct)];
if (accountId === null) {
return {
isLoading: false,
isAccount: false,
statusIds: emptyList,
};
} else if (!accountId) {
return {
isLoading: true,
statusIds: emptyList,
};
}
const path = withReplies ? `${accountId}:with_replies` : `${accountId}${tagged ? `:${tagged}` : ''}`;
return {
accountId,
isAccount: !!state.getIn(['accounts', accountId]),
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
hidden: getAccountHidden(state, accountId),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
};
};
class AccountTimeline extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.shape({
acct: PropTypes.string,
id: PropTypes.string,
tagged: PropTypes.string,
}).isRequired,
accountId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
withReplies: PropTypes.bool,
blockedBy: PropTypes.bool,
isAccount: PropTypes.bool,
suspended: PropTypes.bool,
hidden: PropTypes.bool,
multiColumn: PropTypes.bool,
};
_load () {
const { accountId, withReplies, params: { tagged }, dispatch } = this.props;
dispatch(fetchAccount(accountId));
if (!withReplies) {
dispatch(expandAccountFeaturedTimeline(accountId, { tagged }));
}
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
if (accountId === me) {
dispatch(connectTimeline(`account:${me}`));
}
}
componentDidMount () {
const { params: { acct }, accountId, dispatch } = this.props;
if (accountId) {
this._load();
} else {
dispatch(lookupAccount(acct));
}
}
componentDidUpdate (prevProps) {
const { params: { acct, tagged }, accountId, withReplies, dispatch } = this.props;
if (prevProps.accountId !== accountId && accountId) {
this._load();
} else if (prevProps.params.acct !== acct) {
dispatch(lookupAccount(acct));
} else if (prevProps.params.tagged !== tagged) {
if (!withReplies) {
dispatch(expandAccountFeaturedTimeline(accountId, { tagged }));
}
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
}
if (prevProps.accountId === me && accountId !== me) {
dispatch(disconnectTimeline({ timeline: `account:${me}` }));
}
}
componentWillUnmount () {
const { dispatch, accountId } = this.props;
if (accountId === me) {
dispatch(disconnectTimeline({ timeline: `account:${me}` }));
}
}
handleLoadMore = maxId => {
this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies, tagged: this.props.params.tagged }));
};
render () {
const { accountId, statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl, params: { tagged } } = this.props;
if (isLoading && statusIds.isEmpty()) {
return (
<Column>
<LoadingIndicator />
</Column>
);
} else if (!isLoading && !isAccount) {
return (
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
);
}
let emptyMessage;
const forceEmptyState = suspended || blockedBy || hidden;
if (suspended) {
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
} else if (hidden) {
emptyMessage = <LimitedAccountHint accountId={accountId} />;
} else if (blockedBy) {
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
} else if (remote && statusIds.isEmpty()) {
emptyMessage = <RemoteHint accountId={accountId} url={remoteUrl} />;
} else {
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts found' />;
}
return (
<Column>
<ColumnBackButton />
<StatusList
prepend={
<>
<AccountHeader accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={tagged} />
{!forceEmptyState && <FeaturedCarousel accountId={this.props.accountId} tagged={tagged} />}
</>
}
alwaysPrepend
append={<RemoteHint accountId={accountId} />}
scrollKey='account_timeline'
statusIds={forceEmptyState ? emptyList : statusIds}
isLoading={isLoading}
hasMore={!forceEmptyState && hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
timelineId='account'
withCounters
/>
</Column>
);
}
}
export default connect(mapStateToProps)(AccountTimeline);
@@ -12,8 +12,10 @@ import {
expandTimelineByKey,
timelineKey,
} from '@/mastodon/actions/timelines_typed';
import { AccountHeader } from '@/mastodon/components/account_header';
import { Column } from '@/mastodon/components/column';
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { RemoteHint } from '@/mastodon/components/remote_hint';
import StatusList from '@/mastodon/components/status_list';
@@ -26,23 +28,23 @@ import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { AccountHeader } from '../components/account_header';
import { LimitedAccountHint } from '../components/limited_account_hint';
import { AccountTimelineProvider, useAccountContext } from './context';
import { FeaturedTags } from './featured_tags';
import { AccountFilters } from './filters';
import { FeaturedTags } from './components/featured_tags';
import { AccountFilters } from './components/filters';
import { renderPinnedStatusHeader } from './components/pinned_statuses';
import { TagSuggestions } from './components/tags_suggestions';
import {
renderPinnedStatusHeader,
usePinnedStatusIds,
} from './pinned_statuses';
AccountTimelineContext,
useAccountContext,
useAccountContextValue,
} from './hooks/useAccountContext';
import { usePinnedStatusIds } from './hooks/usePinned';
import classes from './styles.module.scss';
import { TagSuggestions } from './tags_suggestions';
const emptyList = ImmutableList<string>();
const AccountTimelineV2: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
const AccountTimeline: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
const accountId = useAccountId();
const accountContext = useAccountContextValue(accountId);
// Null means accountId does not exist (e.g. invalid acct). Undefined means loading.
if (accountId === null) {
@@ -59,13 +61,13 @@ const AccountTimelineV2: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
// Add this key to remount the timeline when accountId changes.
return (
<AccountTimelineProvider accountId={accountId}>
<AccountTimelineContext.Provider value={accountContext}>
<InnerTimeline
accountId={accountId}
key={accountId}
multiColumn={multiColumn}
/>
</AccountTimelineProvider>
</AccountTimelineContext.Provider>
);
};
@@ -183,4 +185,4 @@ const EmptyMessage: FC<{ accountId: string }> = ({ accountId }) => {
};
// eslint-disable-next-line import/no-default-export
export default AccountTimelineV2;
export default AccountTimeline;
@@ -2,6 +2,7 @@ import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import type { AccountField } from '@/mastodon/components/account_header/fields';
import { Button } from '@/mastodon/components/button';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import {
@@ -10,7 +11,6 @@ import {
ModalShellBody,
} from '@/mastodon/components/modal_shell';
import type { AccountField } from '../common';
import { useFieldHtml } from '../hooks/useFieldHtml';
import classes from './styles.module.scss';
@@ -97,7 +97,7 @@ export const AccountJoinModal: FC<{
}, [anniversary, handle, dispatch, isMe]);
return (
<ModalShell className={classes.joinShell}>
<ModalShell as='div' className={classes.joinShell}>
<ModalShellBody className={classes.joinWrapper}>
<AccountAnniversaryImage anniversary={anniversary} />
@@ -1,110 +0,0 @@
import type { FC } from 'react';
import { useEffect, useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import IconPinned from '@/images/icons/icon_pinned.svg?react';
import { TIMELINE_PINNED_VIEW_ALL } from '@/mastodon/actions/timelines';
import {
expandTimelineByKey,
timelineKey,
} from '@/mastodon/actions/timelines_typed';
import { Badge } from '@/mastodon/components/badge';
import { Button } from '@/mastodon/components/button';
import { Icon } from '@/mastodon/components/icon';
import { StatusHeader } from '@/mastodon/components/status/header';
import type { StatusHeaderRenderFn } from '@/mastodon/components/status/header';
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { useAccountContext } from './context';
import classes from './styles.module.scss';
export function usePinnedStatusIds({
accountId,
tagged,
forceEmptyState = false,
}: {
accountId: string;
tagged?: string;
forceEmptyState?: boolean;
}) {
const pinnedKey = timelineKey({
type: 'account',
userId: accountId,
tagged,
pinned: true,
replies: true,
});
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(expandTimelineByKey({ key: pinnedKey }));
}, [dispatch, pinnedKey]);
const pinnedTimeline = useAppSelector((state) =>
selectTimelineByKey(state, pinnedKey),
);
const { showAllPinned } = useAccountContext();
const pinnedTimelineItems = pinnedTimeline?.items; // Make a const to avoid the React Compiler complaining.
const pinnedStatusIds = useMemo(() => {
if (!pinnedTimelineItems || forceEmptyState) {
return undefined;
}
if (pinnedTimelineItems.size <= 1 || showAllPinned) {
return pinnedTimelineItems;
}
return pinnedTimelineItems.slice(0, 1).push(TIMELINE_PINNED_VIEW_ALL);
}, [forceEmptyState, pinnedTimelineItems, showAllPinned]);
return {
statusIds: pinnedStatusIds,
isLoading: !!pinnedTimeline?.isLoading,
showAllPinned,
};
}
export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({
featured,
...args
}) => {
if (!featured) {
return <StatusHeader {...args} />;
}
return (
<StatusHeader {...args} className={classes.pinnedStatusHeader}>
<Badge
className={classes.pinnedBadge}
icon={<Icon id='pinned' icon={IconPinned} />}
label={
<FormattedMessage
id='account.timeline.pinned'
defaultMessage='Pinned'
/>
}
/>
</StatusHeader>
);
};
export const PinnedShowAllButton: FC = () => {
const { onShowAllPinned } = useAccountContext();
return (
<Button
onClick={onShowAllPinned}
className={classNames(classes.pinnedViewAllButton, 'focusable')}
>
<Icon id='pinned' icon={IconPinned} />
<FormattedMessage
id='account.timeline.pinned.view_all'
defaultMessage='View all pinned posts'
/>
</Button>
);
};
@@ -1,52 +0,0 @@
import type { FC } from 'react';
import { Link } from 'react-router-dom';
import { RelativeTimestamp } from '@/mastodon/components/relative_timestamp';
import type { StatusHeaderProps } from '@/mastodon/components/status/header';
import {
StatusDisplayName,
StatusEditedAt,
StatusVisibility,
} from '@/mastodon/components/status/header';
import type { Account } from '@/mastodon/models/account';
export const AccountStatusHeader: FC<StatusHeaderProps> = ({
status,
account,
children,
avatarSize = 48,
wrapperProps,
onHeaderClick,
}) => {
const statusAccount = status.get('account') as Account | undefined;
const editedAt = status.get('edited_at') as string;
return (
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
<div
onClick={onHeaderClick}
onAuxClick={onHeaderClick}
{...wrapperProps}
className='status__info'
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
>
<Link
to={`/@${statusAccount?.acct}/${status.get('id') as string}`}
className='status__relative-time'
>
<StatusVisibility visibility={status.get('visibility')} />
<RelativeTimestamp timestamp={status.get('created_at') as string} />
{editedAt && <StatusEditedAt editedAt={editedAt} />}
</Link>
<StatusDisplayName
statusAccount={statusAccount}
friendAccount={account}
avatarSize={avatarSize}
/>
{children}
</div>
);
};
@@ -1,6 +1,5 @@
.wrapper {
--list-item-padding: 16px;
--list-item-padding-block: 12px;
--list-item-gap: 16px;
&:not(.wrapperWithoutBorder) {
@@ -111,7 +111,7 @@ export const CollectionLockup: React.FC<CollectionLockupProps> = ({
sideContent={sideContent}
>
<ListItemLink
as='h2'
as='h3'
to={getCollectionPath(id)}
subtitle={collectionInfo}
>
@@ -2,8 +2,12 @@ import { useCallback, useMemo, useRef, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { PendingBadge } from '@/mastodon/components/badge';
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
import type {
ApiCollectionJSON,
CollectionAccountItem,
} from 'mastodon/api_types/collections';
import type { RenderButtonOptions } from 'mastodon/components/account_list_item';
import {
AccountListItem,
@@ -75,47 +79,53 @@ const SensitiveScreen: React.FC<{
);
};
const getCollectionAccounts = createAppSelector(
type CollectionItemWithAccount = CollectionAccountItem & {
account?: Account | null;
};
const getCollectionItems = createAppSelector(
[
(state) => state.accounts,
(state, collectionId?: string) =>
state.collections.collections[collectionId ?? '']?.items,
],
(accounts, collectionAccountItems) =>
(collectionAccountItems ?? []).map(({ account_id }) =>
account_id ? accounts.get(account_id) : null,
(collectionAccountItems ?? []).map(
(item): CollectionItemWithAccount => ({
...item,
account: item.account_id ? accounts.get(item.account_id) : null,
}),
),
);
export const CollectionAccountsList: React.FC<{
collection?: ApiCollectionJSON;
isLoading: boolean;
}> = ({ collection, isLoading }) => {
collection: ApiCollectionJSON;
}> = ({ collection }) => {
const intl = useIntl();
const confirmRevoke = useConfirmRevoke(collection);
const listHeadingRef = useRef<HTMLHeadingElement>(null);
const isOwnCollection = collection?.account_id === me;
const { account_id: collectionOwnerId, id } = collection ?? {};
const isOwnCollection = collection.account_id === me;
const { account_id: collectionOwnerId, id } = collection;
const relationships = useAppSelector((state) => state.relationships);
const collectionAccounts = useAppSelector((state) =>
getCollectionAccounts(state, id),
getCollectionItems(state, id),
);
const { visibleAccounts, hiddenAccounts } = useMemo(() => {
const visibleAccounts: Account[] = [];
const hiddenAccounts: Account[] = [];
const visibleAccounts: CollectionItemWithAccount[] = [];
const hiddenAccounts: CollectionItemWithAccount[] = [];
collectionAccounts.forEach((item) => {
if (!item) {
// We currently simply hide unavailable accounts, this includes
// accounts that are pending inclusion; at least for the collection
// owner we should display an indication of pending users
const { account, account_id } = item;
if (!isOwnCollection && !account) {
// Hide unavailable accounts unless you own this collection
return;
}
const relationship = relationships.get(item.id);
const relationship = account_id ? relationships.get(account_id) : null;
if (relationship?.blocking || relationship?.muting) {
hiddenAccounts.push(item);
} else {
@@ -124,7 +134,7 @@ export const CollectionAccountsList: React.FC<{
});
return { visibleAccounts, hiddenAccounts };
}, [collectionAccounts, relationships]);
}, [collectionAccounts, isOwnCollection, relationships]);
const renderAccountItemButton = useCallback(
({ relationship, accountId }: RenderButtonOptions) => {
@@ -159,15 +169,16 @@ export const CollectionAccountsList: React.FC<{
index,
totalListLength,
isLastElement,
}: TruncatedListItemInfo<Account>) => (
}: TruncatedListItemInfo<CollectionItemWithAccount>) => (
<Article
key={item.id}
aria-posinset={index + 1}
aria-setsize={totalListLength}
>
<AccountListItem
accountId={item.id}
accountId={item.account_id}
withBorder={!isLastElement}
badge={item.state === 'pending' ? <PendingBadge /> : null}
renderButton={renderAccountItemButton}
/>
</Article>
@@ -182,53 +193,41 @@ export const CollectionAccountsList: React.FC<{
tabIndex={-1}
ref={listHeadingRef}
>
{collection ? (
<FormattedMessage
id='collections.account_count'
defaultMessage='{count, plural, one {# account} other {# accounts}}'
values={{ count: collection.item_count }}
/>
) : (
<FormattedMessage
id='collections.detail.accounts_heading'
defaultMessage='Accounts'
/>
)}
<FormattedMessage
id='collections.account_count'
defaultMessage='{count, plural, one {# account} other {# accounts}}'
values={{ count: collection.item_count }}
/>
</h3>
{collection && (
<SensitiveScreen
sensitive={!isOwnCollection && collection.sensitive}
focusTargetRef={listHeadingRef}
>
<ItemList
isLoading={isLoading}
emptyMessage={intl.formatMessage(messages.empty)}
>
<TruncatedListItems
visibleItems={visibleAccounts}
truncatedItems={hiddenAccounts}
toggleButton={{
icon: VisibilityOffIcon,
title: (
<FormattedMessage
id='collections.hidden_accounts_link'
defaultMessage='{count, plural, one {# hidden account} other {# hidden accounts}}'
values={{ count: hiddenAccounts.length }}
/>
),
subtitle: (
<FormattedMessage
id='collections.hidden_accounts_description'
defaultMessage='You’ve blocked or muted {count, plural, one {this user} other {these users}}'
values={{ count: hiddenAccounts.length }}
/>
),
}}
renderListItem={renderListItem}
/>
</ItemList>
</SensitiveScreen>
)}
<SensitiveScreen
sensitive={!isOwnCollection && collection.sensitive}
focusTargetRef={listHeadingRef}
>
<ItemList emptyMessage={intl.formatMessage(messages.empty)}>
<TruncatedListItems
visibleItems={visibleAccounts}
truncatedItems={hiddenAccounts}
toggleButton={{
icon: VisibilityOffIcon,
title: (
<FormattedMessage
id='collections.hidden_accounts_link'
defaultMessage='{count, plural, one {# hidden account} other {# hidden accounts}}'
values={{ count: hiddenAccounts.length }}
/>
),
subtitle: (
<FormattedMessage
id='collections.hidden_accounts_description'
defaultMessage='You’ve blocked or muted {count, plural, one {this user} other {these users}}'
values={{ count: hiddenAccounts.length }}
/>
),
}}
renderListItem={renderListItem}
/>
</ItemList>
</SensitiveScreen>
</>
);
};
@@ -6,17 +6,24 @@ import { Helmet } from 'react-helmet';
import { useHistory, useLocation, useParams } from 'react-router';
import { Link } from 'react-router-dom';
import { openModal } from '@/mastodon/actions/modal';
import { useAccountHandle } from '@/mastodon/components/display_name/default';
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
import StarIcon from '@/material-icons/400-24px/star.svg?react';
import { openModal } from 'mastodon/actions/modal';
import type {
ApiCollectionJSON,
CollectionAccountItem,
} from 'mastodon/api_types/collections';
import { Badge } from 'mastodon/components/badge';
import { Callout } from 'mastodon/components/callout';
import { Column } from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header';
import { DisplayName } from 'mastodon/components/display_name';
import { useAccountHandle } from 'mastodon/components/display_name/default';
import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
import { IconButton } from 'mastodon/components/icon_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { Scrollable } from 'mastodon/components/scrollable_list/components';
import { useAccount } from 'mastodon/hooks/useAccount';
import { domain, me } from 'mastodon/initial_state';
@@ -68,13 +75,15 @@ export const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
};
const RevokeControls: React.FC<{
currentUserCollectionItem: CollectionAccountItem;
collection: ApiCollectionJSON;
}> = ({ collection }) => {
}> = ({ currentUserCollectionItem, collection }) => {
const authorAccount = useAccount(collection.account_id);
const confirmRevoke = useConfirmRevoke(collection);
return (
<Callout
icon={StarIcon}
title={
<FormattedMessage
id='collections.detail.you_are_in_this_collection'
@@ -94,13 +103,40 @@ const RevokeControls: React.FC<{
defaultMessage='{author} added you on {date}'
values={{
author: <DisplayName account={authorAccount} variant='simple' />,
date: '{date}', // TODO: Data not yet provided by API
date: (
<FormattedDateWrapper
value={currentUserCollectionItem.created_at}
day='2-digit'
month='short'
year='numeric'
/>
),
}}
/>
</Callout>
);
};
export const PendingNote: React.FC = () => {
return (
<Callout
variant='subtle'
icon={HelpIcon}
title={
<FormattedMessage
id='collections.pending_accounts.title'
defaultMessage='Why am I seeing pending accounts?'
/>
}
>
<FormattedMessage
id='collections.pending_accounts.message'
defaultMessage='Accounts may appear as pending when we’re awaiting a response from the user or their server. Only you can see pending accounts.'
/>
</Callout>
);
};
const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
collection,
}) => {
@@ -110,10 +146,11 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
const history = useHistory();
const isOwnCollection = account_id === me;
const currentUserIndex = items.findIndex(
const currentUserCollectionItem = items.find(
(account) => account.account_id === me,
);
const isCurrentUserInCollection = !isOwnCollection && currentUserIndex > -1;
const isCurrentUserInCollection =
!isOwnCollection && !!currentUserCollectionItem;
const openShareModal = useCallback(() => {
dispatch(
@@ -136,6 +173,8 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
}
}, [history, openShareModal, isNewCollection, location.pathname]);
const hasPendingAccounts = items.some((item) => item.state === 'pending');
return (
<header className={classes.header}>
<div className={classes.titleWithMenu}>
@@ -160,7 +199,13 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
</div>
</div>
{description && <p className={classes.description}>{description}</p>}
{isCurrentUserInCollection && <RevokeControls collection={collection} />}
{hasPendingAccounts && <PendingNote />}
{isCurrentUserInCollection && (
<RevokeControls
currentUserCollectionItem={currentUserCollectionItem}
collection={collection}
/>
)}
</header>
);
};
@@ -174,7 +219,6 @@ export const CollectionDetailPage: React.FC<{
const collection = useAppSelector((state) =>
id ? state.collections.collections[id] : undefined,
);
const isLoading = !!id && !collection;
useEffect(() => {
if (id) {
@@ -195,8 +239,14 @@ export const CollectionDetailPage: React.FC<{
/>
<Scrollable>
{collection && <CollectionHeader collection={collection} />}
<CollectionAccountsList collection={collection} isLoading={isLoading} />
{collection ? (
<>
<CollectionHeader collection={collection} />
<CollectionAccountsList collection={collection} />
</>
) : (
<LoadingIndicator />
)}
</Scrollable>
<Helmet>
@@ -27,7 +27,7 @@ const messages = defineMessages({
},
});
export function useConfirmRevoke(collection?: ApiCollectionJSON) {
export function useConfirmRevoke(collection?: ApiCollectionJSON | null) {
const dispatch = useAppDispatch();
const { id, items = [] } = collection ?? {};
const ownCollectionItemId = items.find((item) => item.account_id === me)?.id;
@@ -6,16 +6,17 @@ import { useHistory } from 'react-router-dom';
import type { Map as ImmutableMap } from 'immutable';
import { useComboboxItemProps } from '@/mastodon/components/form_fields/combobox_field';
import type { ApiMutedAccountJSON } from 'mastodon/api_types/accounts';
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
import { AccountListItem } from 'mastodon/components/account_list_item';
import { Avatar } from 'mastodon/components/avatar';
import { PendingBadge } from 'mastodon/components/badge';
import { Button } from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name';
import { useAccountHandle } from 'mastodon/components/display_name/default';
import { EmptyState } from 'mastodon/components/empty_state';
import { FormStack, ComboboxField } from 'mastodon/components/form_fields';
import { useComboboxItemProps } from 'mastodon/components/form_fields/combobox_field';
import {
ListItemContent,
ListItemWrapper,
@@ -31,12 +32,14 @@ import { domain } from 'mastodon/initial_state';
import type { Relationship } from 'mastodon/models/relationship';
import {
addCollectionItem,
getCollectionItemIds,
getEditorCollectionItems,
removeCollectionItem,
updateCollectionEditorField,
} from 'mastodon/reducers/slices/collections';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { PendingNote } from '../detail';
import classes from './styles.module.scss';
import { WizardStepTitle } from './wizard_step_title';
@@ -44,8 +47,9 @@ const MAX_ACCOUNT_COUNT = 25;
const AddedAccountItem: React.FC<{
accountId: string;
pending?: boolean;
onRemove: (id: string) => void;
}> = ({ accountId, onRemove }) => {
}> = ({ accountId, pending, onRemove }) => {
const handleRemoveAccount = useCallback(() => {
onRemove(accountId);
}, [accountId, onRemove]);
@@ -62,7 +66,13 @@ const AddedAccountItem: React.FC<{
[handleRemoveAccount],
);
return <AccountListItem accountId={accountId} renderButton={renderButton} />;
return (
<AccountListItem
accountId={accountId}
badge={pending && <PendingBadge />}
renderButton={renderButton}
/>
);
};
const SuggestedAccountItem: React.FC<{ id: string }> = ({ id }) => {
@@ -183,22 +193,26 @@ export const CollectionAccounts: React.FC<{
const { id, items: collectionItems } = collection ?? {};
const isEditMode = !!id;
const addedAccountIds = useAppSelector(
(state) => state.collections.editor.accountIds,
const editorItemsFromState = useAppSelector(
(state) => state.collections.editor.items,
);
// In edit mode, we're bypassing state and just return collection items directly,
// since they're edited "live", saving after each addition/deletion
const accountIds = useMemo(
// In edit mode, we're bypassing our Redux state and just work on the
// collection items directly since they're edited "live", saving right
// after each addition/deletion
const editorItems = useMemo(
() =>
isEditMode ? getCollectionItemIds(collectionItems) : addedAccountIds,
[isEditMode, collectionItems, addedAccountIds],
isEditMode
? getEditorCollectionItems(collectionItems)
: editorItemsFromState,
[isEditMode, collectionItems, editorItemsFromState],
);
const hasPendingItems = editorItems.some((item) => item.state === 'pending');
const [searchValue, setSearchValue] = useState('');
const hasAccounts = accountIds.length > 0;
const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT;
const hasItems = editorItems.length > 0;
const hasMaxItems = editorItems.length === MAX_ACCOUNT_COUNT;
const {
accounts: suggestedAccounts,
@@ -208,7 +222,8 @@ export const CollectionAccounts: React.FC<{
} = useSearchAccounts({
withRelationships: true,
// Don't suggest accounts that were already added
filterResults: (account) => !accountIds.includes(account.id),
filterResults: (account) =>
!editorItems.find((item) => item.account_id === account.id),
});
const relationships = useAppSelector((state) => state.relationships);
@@ -236,24 +251,33 @@ export const CollectionAccounts: React.FC<{
(accountId: string) => {
dispatch(
updateCollectionEditorField({
field: 'accountIds',
value: accountIds.filter((id) => id !== accountId),
field: 'items',
value: editorItems.filter((item) => item.account_id !== accountId),
}),
);
},
[accountIds, dispatch],
[editorItems, dispatch],
);
const addAccountItem = useCallback(
(item: ApiMutedAccountJSON) => {
dispatch(
updateCollectionEditorField({
field: 'accountIds',
value: [...accountIds, item.id],
field: 'items',
value: [
...editorItems,
{
account_id: item.id,
state:
item.feature_approval.current_user === 'manual'
? 'pending'
: 'accepted',
},
],
}),
);
},
[accountIds, dispatch],
[editorItems, dispatch],
);
const instantRemoveAccountItem = useCallback(
@@ -319,12 +343,10 @@ export const CollectionAccounts: React.FC<{
e.preventDefault();
if (!id) {
history.push(`/collections/new/details`, {
account_ids: accountIds,
});
history.push('/collections/new/details');
}
},
[id, history, accountIds],
[id, history],
);
const inputId = useId();
@@ -345,16 +367,17 @@ export const CollectionAccounts: React.FC<{
}
/>
)}
{hasPendingItems && <PendingNote />}
<ComboboxField
id={inputId}
label={intl.formatMessage({
id: 'collections.search_accounts_label',
defaultMessage: 'Search for an account to add',
})}
value={hasMaxAccounts ? '' : searchValue}
value={hasMaxItems ? '' : searchValue}
onChange={handleSearchValueChange}
onKeyDown={handleSearchKeyDown}
disabled={hasMaxAccounts}
disabled={hasMaxItems}
isLoading={isLoadingSuggestions}
items={groupedItems}
getItemId={getItemId}
@@ -363,7 +386,7 @@ export const CollectionAccounts: React.FC<{
renderGroupTitle={renderGroupTitle}
onSelectItem={handleSelectItem}
status={
hasMaxAccounts
hasMaxItems
? {
variant: 'warning',
message: intl.formatMessage({
@@ -378,12 +401,12 @@ export const CollectionAccounts: React.FC<{
</header>
<div>
{hasAccounts && (
{hasItems && (
<AccountsHeadingElement className={classes.listHeading}>
<FormattedMessage
id='collections.hints.accounts_counter'
defaultMessage='{count}/{max} accounts'
values={{ count: accountIds.length, max: MAX_ACCOUNT_COUNT }}
values={{ count: editorItems.length, max: MAX_ACCOUNT_COUNT }}
/>
</AccountsHeadingElement>
)}
@@ -410,14 +433,15 @@ export const CollectionAccounts: React.FC<{
/>
}
>
{accountIds.map((accountId, index) => (
{editorItems.map(({ account_id, state }, index) => (
<Article
key={accountId}
key={account_id}
aria-posinset={index}
aria-setsize={accountIds.length}
aria-setsize={editorItems.length}
>
<AddedAccountItem
accountId={accountId}
accountId={account_id}
pending={state === 'pending'}
onRemove={handleRemoveAccountItem}
/>
</Article>
@@ -426,7 +450,7 @@ export const CollectionAccounts: React.FC<{
</Scrollable>
</div>
</FormStack>
{!isEditMode && hasAccounts && (
{!isEditMode && hasItems && (
<div className={classes.stickyFooter}>
<Button type='submit'>
{id ? (
@@ -46,7 +46,7 @@ import { WizardStepTitle } from './wizard_step_title';
export const CollectionDetails: React.FC = () => {
const dispatch = useAppDispatch();
const history = useHistory();
const { id, name, description, topic, discoverable, sensitive, accountIds } =
const { id, name, description, topic, discoverable, sensitive, items } =
useAppSelector((state) => state.collections.editor);
const handleNameChange = useCallback(
@@ -123,7 +123,7 @@ export const CollectionDetails: React.FC = () => {
description,
discoverable,
sensitive,
account_ids: accountIds,
account_ids: items.map((item) => item.account_id),
};
if (topic) {
payload.tag_name = topic;
@@ -152,7 +152,7 @@ export const CollectionDetails: React.FC = () => {
sensitive,
dispatch,
history,
accountIds,
items,
currentUserName,
],
);
@@ -25,7 +25,7 @@ import {
} from 'mastodon/reducers/slices/collections';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { useAccountCollections } from '..';
import { useCollectionsCreatedBy } from '../overview/created_by_you';
import { CollectionAccounts } from './accounts';
import { CollectionDetails } from './details';
@@ -86,7 +86,7 @@ export const CollectionEditorPage: React.FC<{
// When creating a new collection, we load the current account's collections
// to determine if they're allowed to create more.
const { collections: collectionList, status: collectionListStatus } =
useAccountCollections(isEditMode ? null : accountId);
useCollectionsCreatedBy(isEditMode ? null : accountId);
const isLoading =
(isEditMode && !collection) ||
@@ -144,7 +144,7 @@ export const CollectionEditorPage: React.FC<{
/>
</Switch>
) : (
<MaxCollectionsCallout />
<MaxCollectionsCallout className={classes.maxCollectionsError} />
)}
</div>
@@ -156,9 +156,11 @@ export const CollectionEditorPage: React.FC<{
);
};
export const MaxCollectionsCallout: React.FC = () => (
export const MaxCollectionsCallout: React.FC<{ className?: string }> = ({
className,
}) => (
<Callout
className={classes.maxCollectionsError}
className={className}
title={
<FormattedMessage
id='collections.maximum_collection_count_reached'
@@ -1,57 +1,43 @@
import { useEffect } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { defineMessages, useIntl } from 'react-intl';
import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom';
import { Route, Switch, useRouteMatch } from 'react-router-dom';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import CollectionsFilledIcon from '@/material-icons/400-24px/category-fill.svg?react';
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
import { TabLink, TabList } from '@/mastodon/components/tab_list';
import { Column } from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header';
import { DisplayNameSimple } from 'mastodon/components/display_name/simple';
import { Icon } from 'mastodon/components/icon';
import {
ItemList,
Scrollable,
} from 'mastodon/components/scrollable_list/components';
import { Scrollable } from 'mastodon/components/scrollable_list/components';
import { useAccount } from 'mastodon/hooks/useAccount';
import { useAccountId, useCurrentAccountId } from 'mastodon/hooks/useAccountId';
import {
fetchAccountCollections,
selectAccountCollections,
} from 'mastodon/reducers/slices/collections';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { CollectionListItem } from './components/collection_list_item';
import {
messages as editorMessages,
MaxCollectionsCallout,
userCollectionLimit,
} from './editor';
import { areCollectionsEnabled } from './utils';
import { CollectionsCreatedByYou } from './overview/created_by_you';
import { CollectionsFeaturingYou } from './overview/featuring_you';
import classes from './styles.module.scss';
const messages = defineMessages({
headingMe: { id: 'column.my_collections', defaultMessage: 'My collections' },
headingMe: {
id: 'column.your_collections',
defaultMessage: 'Your Collections',
},
headingOther: {
id: 'column.other_collections',
defaultMessage: 'Collections by {name}',
defaultMessage: "{name}'s Collections",
},
createdByYou: {
id: 'collections.list.created_by_you',
defaultMessage: 'Created by you',
},
createdByAuthor: {
id: 'collections.list.created_by_author',
defaultMessage: 'Created by {name}',
},
featuringYou: {
id: 'collections.list.featuring_you',
defaultMessage: 'Featuring you',
},
});
export function useAccountCollections(accountId: string | null | undefined) {
const dispatch = useAppDispatch();
useEffect(() => {
if (accountId && areCollectionsEnabled()) {
void dispatch(fetchAccountCollections({ accountId }));
}
}, [dispatch, accountId]);
return useAppSelector((state) => selectAccountCollections(state, accountId));
}
export const Collections: React.FC<{
multiColumn?: boolean;
}> = ({ multiColumn }) => {
@@ -59,37 +45,11 @@ export const Collections: React.FC<{
const me = useCurrentAccountId();
const accountId = useAccountId();
const account = useAccount(accountId);
const { path } = useRouteMatch();
const { collections, status } = useAccountCollections(accountId);
const isOwnCollectionsPage = accountId === me;
const emptyMessage =
status === 'error' || !accountId ? (
<FormattedMessage
id='collections.error_loading_collections'
defaultMessage='There was an error when trying to load your collections.'
tagName='span'
/>
) : (
<>
<span>
<FormattedMessage
id='collections.no_collections_yet'
defaultMessage='No collections yet.'
/>
<br />
<FormattedMessage
id='collections.create_a_collection_hint'
defaultMessage='Create a collection to recommend or share your favourite accounts with others.'
/>
</span>
<SquigglyArrow className='empty-column-indicator__arrow' />
</>
);
const canCreateMoreCollections = collections.length < userCollectionLimit;
const isOwnCollection = accountId === me;
const titleMessage = isOwnCollection
const titleMessage = isOwnCollectionsPage
? messages.headingMe
: messages.headingOther;
@@ -100,45 +60,41 @@ export const Collections: React.FC<{
name: <DisplayNameSimple account={account} />,
});
const createdByTabMessage = isOwnCollectionsPage
? messages.createdByYou
: messages.createdByAuthor;
return (
<Column bindToDocument={!multiColumn} label={pageTitle}>
<ColumnHeader
title={pageTitleHtml}
icon='collections'
iconComponent={CollectionsFilledIcon}
multiColumn={multiColumn}
extraButton={
isOwnCollection &&
status === 'idle' &&
canCreateMoreCollections && (
<Link
to='/collections/new'
className='column-header__button'
title={intl.formatMessage(editorMessages.create)}
aria-label={intl.formatMessage(editorMessages.create)}
>
<Icon id='plus' icon={AddIcon} />
</Link>
)
}
/>
<ColumnHeader showBackButton multiColumn={multiColumn} />
<Scrollable>
{status === 'idle' && !canCreateMoreCollections && (
<MaxCollectionsCallout />
)}
<ItemList emptyMessage={emptyMessage} isLoading={status === 'loading'}>
{collections.map((item, index) => (
<CollectionListItem
withTimestamp
withAuthorHandle={false}
key={item.id}
collection={item}
positionInList={index + 1}
listSize={collections.length}
/>
))}
</ItemList>
<header className={classes.header}>
<h1 className={classes.heading}>{pageTitleHtml}</h1>
<TabList plain>
<TabLink exact to={`/@${account?.acct}/collections`}>
{intl.formatMessage(createdByTabMessage, {
name: <DisplayNameSimple account={account} />,
})}
</TabLink>
{isOwnCollectionsPage && (
<TabLink
exact
to={`/@${account?.acct}/collections/featuring-you`}
>
{intl.formatMessage(messages.featuringYou)}
</TabLink>
)}
</TabList>
</header>
<Switch>
<Route exact path={path} component={CollectionsCreatedByYou} />
<Route
exact
path={`${path}/featuring-you`}
component={CollectionsFeaturingYou}
/>
</Switch>
</Scrollable>
<Helmet>
@@ -0,0 +1,133 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import { EmptyState } from 'mastodon/components/empty_state';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ItemList } from 'mastodon/components/scrollable_list/components';
import { useAccountId, useCurrentAccountId } from 'mastodon/hooks/useAccountId';
import {
fetchCollectionsCreatedByAccount,
selectAccountCollections,
} from 'mastodon/reducers/slices/collections';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { CollectionListItem } from '../components/collection_list_item';
import {
messages as editorMessages,
MaxCollectionsCallout,
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'>
<Icon id='plus' icon={AddIcon} />
<FormattedMessage {...editorMessages.newCollection} />
</Link>
);
export const CollectionListError: React.FC = () => (
<EmptyState
image={null}
className={classes.error}
message={
<FormattedMessage
id='collections.error_loading_collections'
defaultMessage='There was an error when trying to load these collections.'
/>
}
/>
);
export function useCollectionsCreatedBy(accountId: string | null | undefined) {
const dispatch = useAppDispatch();
useEffect(() => {
if (accountId && areCollectionsEnabled()) {
void dispatch(fetchCollectionsCreatedByAccount({ accountId }));
}
}, [dispatch, accountId]);
return useAppSelector((state) =>
selectAccountCollections(state, accountId, 'createdBy'),
);
}
export const CollectionsCreatedByYou: React.FC = () => {
const me = useCurrentAccountId();
const accountId = useAccountId();
const { collections, status } = useCollectionsCreatedBy(accountId);
const canCreateMoreCollections = collections.length < userCollectionLimit;
const isOwnCollectionPage = accountId === me;
const showCreateButton =
isOwnCollectionPage && status === 'idle' && canCreateMoreCollections;
if (status === 'error' || !accountId) {
return <CollectionListError />;
}
if (status === 'loading') {
return <LoadingIndicator />;
}
if (collections.length === 0) {
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.'
/>
}
>
<CreateButton />
</EmptyState>
);
}
return (
<>
<div className={classes.listHeader}>
<h2 className={classes.subHeading}>
<FormattedMessage
id='collections.list.collections_with_count'
defaultMessage='{count, plural, one {# Collection} other {# Collections}}'
values={{
count: collections.length,
}}
/>
</h2>
{showCreateButton && <CreateButton />}
</div>
<ItemList>
{!canCreateMoreCollections && (
<MaxCollectionsCallout className={classes.maxCollectionsError} />
)}
{collections.map((item, index) => (
<CollectionListItem
withTimestamp
withAuthorHandle={false}
key={item.id}
collection={item}
positionInList={index + 1}
listSize={collections.length}
/>
))}
</ItemList>
</>
);
};
@@ -0,0 +1,87 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { EmptyState } from 'mastodon/components/empty_state';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ItemList } from 'mastodon/components/scrollable_list/components';
import { useAccountId } from 'mastodon/hooks/useAccountId';
import {
fetchCollectionsFeaturingAccount,
selectAccountCollections,
} from 'mastodon/reducers/slices/collections';
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';
function useCollectionsFeaturing(accountId: string | null | undefined) {
const dispatch = useAppDispatch();
useEffect(() => {
if (accountId && areCollectionsEnabled()) {
void dispatch(fetchCollectionsFeaturingAccount({ accountId }));
}
}, [dispatch, accountId]);
return useAppSelector((state) =>
selectAccountCollections(state, accountId, 'featuring'),
);
}
export const CollectionsFeaturingYou: React.FC = () => {
const accountId = useAccountId();
const { collections, status } = useCollectionsFeaturing(accountId);
if (status === 'error' || !accountId) {
return <CollectionListError />;
}
if (status === 'loading') {
return <LoadingIndicator />;
}
if (collections.length === 0) {
return (
<EmptyState
message={
<FormattedMessage
id='empty_column.collections.featured_in'
defaultMessage='You have not been added to any collections yet.'
/>
}
/>
);
}
return (
<>
<div className={classes.listHeader}>
<h2 className={classes.subHeading}>
<FormattedMessage
id='collections.list.collections_with_count'
defaultMessage='{count, plural, one {# Collection} other {# Collections}}'
values={{
count: collections.length,
}}
/>
</h2>
</div>
<ItemList>
{collections.map((item, index) => (
<CollectionListItem
withAuthorHandle
key={item.id}
collection={item}
positionInList={index + 1}
listSize={collections.length}
/>
))}
</ItemList>
</>
);
};
@@ -0,0 +1,39 @@
.header {
display: flex;
flex-direction: column;
gap: 8px;
padding-top: 24px;
padding-inline: 16px;
border-bottom: 1px solid var(--color-border-primary);
}
.heading {
font-size: 22px;
font-weight: 500;
line-height: 1.2;
overflow-wrap: break-word;
}
.listHeader {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 28px;
padding-top: 16px;
padding-inline: 16px;
}
.subHeading {
font-size: 15px;
font-weight: 500;
line-height: 1.2;
overflow-wrap: break-word;
}
.maxCollectionsError {
margin: 8px 16px;
}
.error {
flex-grow: 1;
}
@@ -5,8 +5,6 @@ import { defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';
@@ -14,7 +12,6 @@ import MoodIcon from '@/material-icons/400-20px/mood.svg?react';
import { IconButton } from 'mastodon/components/icon_button';
import { injectIntl } from '@/mastodon/components/intl';
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
const messages = defineMessages({
@@ -156,7 +153,6 @@ class ModifierPicker extends PureComponent {
class EmojiPickerMenuImpl extends PureComponent {
static propTypes = {
custom_emojis: ImmutablePropTypes.list,
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
loading: PropTypes.bool,
onClose: PropTypes.func.isRequired,
@@ -254,7 +250,7 @@ class EmojiPickerMenuImpl extends PureComponent {
};
render() {
const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
const { loading, style, intl, skinTone, frequentlyUsedEmojis } = this.props;
if (loading) {
return <div style={{ width: 299 }} />;
@@ -264,32 +260,16 @@ class EmojiPickerMenuImpl extends PureComponent {
const { modifierOpen } = this.state;
const categoriesSort = [
'recent',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
];
categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort());
return (
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
<EmojiPicker
perLine={8}
emojiSize={22}
custom={buildCustomEmojis(custom_emojis)}
color=''
emoji=''
title={title}
i18n={this.getI18n()}
onClick={this.handleClick}
include={categoriesSort}
recent={frequentlyUsedEmojis}
skin={skinTone}
showPreview={false}
@@ -315,9 +295,7 @@ class EmojiPickerMenuImpl extends PureComponent {
const EmojiPickerMenu = injectIntl(EmojiPickerMenuImpl);
class EmojiPickerDropdown extends PureComponent {
static propTypes = {
custom_emojis: ImmutablePropTypes.list,
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
intl: PropTypes.object.isRequired,
onPickEmoji: PropTypes.func.isRequired,
@@ -346,7 +324,9 @@ class EmojiPickerDropdown extends PureComponent {
EmojiPicker = EmojiMart.Picker;
Emoji = EmojiMart.Emoji;
this.setState({ loading: false });
void EmojiMart.loadCustomEmojiData().then(() => {
this.setState({ loading: false });
});
}).catch(() => {
this.setState({ loading: false, active: false });
});
@@ -408,7 +388,6 @@ class EmojiPickerDropdown extends PureComponent {
<div {...props} style={{ ...props.style }}>
<div className={`dropdown-animation ${placement}`}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
@@ -47,23 +47,7 @@ const getFrequentlyUsedEmojis = createSelector([
return emojis;
});
const getCustomEmojis = createSelector([
state => state.get('custom_emojis'),
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
const aShort = a.get('shortcode').toLowerCase();
const bShort = b.get('shortcode').toLowerCase();
if (aShort < bShort) {
return -1;
} else if (aShort > bShort ) {
return 1;
} else {
return 0;
}
}));
const mapStateToProps = state => ({
custom_emojis: getCustomEmojis(state),
skinTone: state.getIn(['settings', 'skinTone']),
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
});
@@ -168,29 +168,3 @@ const emojify = (str, customEmojis = {}) => {
};
export default emojify;
export const buildCustomEmojis = (customEmojis) => {
const emojis = [];
customEmojis.forEach(emoji => {
const shortcode = emoji.get('shortcode');
const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
const name = shortcode.replace(':', '');
emojis.push({
id: name,
name,
short_names: [name],
text: '',
emoticons: [],
keywords: [name],
imageUrl: url,
custom: true,
customCategory: emoji.get('category'),
});
});
return emojis;
};
export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));
@@ -1,49 +1,62 @@
import type { FC } from 'react';
import type { EmojiProps, PickerProps } from 'emoji-mart';
import EmojiRaw from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
import { assetHost } from 'mastodon/utils/config';
import { assetHost } from '@/mastodon/utils/config';
import { EMOJI_MODE_NATIVE } from './constants';
import EmojiData from './emoji_data.json';
import { useEmojiAppState } from './mode';
import { usePickerEmojis } from './picker';
const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_16_0.png`;
const Emoji = ({
export { fetchCustomEmojiData as loadCustomEmojiData } from './picker';
export const Picker: FC<PickerProps> = ({
set = 'twitter',
sheetSize = 32,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}: EmojiProps) => {
}) => {
const { mode } = useEmojiAppState();
const { customCategories, customEmojis } = usePickerEmojis();
if (!customEmojis) {
return null;
}
return (
<PickerRaw
data={EmojiData}
custom={customEmojis}
include={customCategories}
set={set}
sheetSize={sheetSize}
sheetColumns={sheetColumns}
sheetRows={sheetRows}
native={mode === EMOJI_MODE_NATIVE}
backgroundImageFn={backgroundImageFn}
{...props}
/>
);
};
export const Emoji: FC<EmojiProps> = ({
set = 'twitter',
sheetSize = 32,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}) => {
const { mode } = useEmojiAppState();
return (
<EmojiRaw
data={EmojiData}
set={set}
sheetSize={sheetSize}
sheetColumns={sheetColumns}
sheetRows={sheetRows}
native={mode === EMOJI_MODE_NATIVE}
backgroundImageFn={backgroundImageFn}
{...props}
/>
);
};
const Picker = ({
set = 'twitter',
sheetSize = 32,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}: PickerProps) => {
const { mode } = useEmojiAppState();
return (
<PickerRaw
data={EmojiData}
set={set}
sheetSize={sheetSize}
@@ -55,5 +68,3 @@ const Picker = ({
/>
);
};
export { Picker, Emoji };
@@ -0,0 +1,85 @@
import { useEffect, useState } from 'react';
import type { CategoryName, CustomEmoji } from 'emoji-mart';
import { autoPlayGif } from '@/mastodon/initial_state';
import { emojiLogger } from './utils';
const log = emojiLogger('picker');
let customEmojis: CustomEmoji[] | null = null;
let customCategories = [
'recent',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
] as CategoryName[];
export async function fetchCustomEmojiData() {
if (customEmojis !== null) {
return customEmojis;
}
const { loadAllCustomEmoji } = await import('./database');
const emojisRaw = await loadAllCustomEmoji();
if (emojisRaw.length === 0) {
return [];
}
const categories = new Set(['custom']);
const emojis = [];
for (const emoji of emojisRaw) {
const name = emoji.shortcode.replaceAll(':', '');
emojis.push({
name,
id: name,
custom: true,
short_names: [name],
imageUrl: autoPlayGif ? emoji.url : emoji.static_url,
customCategory: emoji.category,
});
if (emoji.category) {
categories.add(`custom-${emoji.category}`);
}
}
customEmojis = emojis.toSorted((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
customCategories = customCategories.toSpliced(
1,
0,
...(Array.from(categories).toSorted() as CategoryName[]),
);
log(
'loaded %d custom emojis in %d categories',
customEmojis.length,
categories.size,
);
return customEmojis;
}
export function usePickerEmojis() {
const [, setLoaded] = useState(customEmojis !== null);
useEffect(() => {
if (customEmojis === null) {
void fetchCustomEmojiData().then(() => {
setLoaded(true);
});
}
}, []);
return {
customEmojis,
customCategories,
};
}
@@ -2,7 +2,7 @@ import type { FC, ReactNode } from 'react';
import { FormattedMessage } from 'react-intl';
import { LimitedAccountHint } from '@/mastodon/features/account_timeline/components/limited_account_hint';
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
import type { Account } from '@/mastodon/models/account';
@@ -3,8 +3,12 @@ import type { FC } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import type { MessageDescriptor } from 'react-intl';
import { Link } from 'react-router-dom';
import { Callout } from '@/mastodon/components/callout';
import { DisplayNameSimple } from '@/mastodon/components/display_name/simple';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import classes from '../styles.module.scss';
@@ -15,6 +19,7 @@ export const AccountListHeader: FC<{
}> = ({ accountId, total, titleText }) => {
const intl = useIntl();
const account = useAccount(accountId);
const currentId = useCurrentAccountId();
return (
<>
<h1 className={classes.title}>
@@ -31,6 +36,35 @@ export const AccountListHeader: FC<{
/>
</h2>
)}
{accountId === currentId && account?.hide_collections && (
<Callout className={classes.callout}>
<FormattedMessage
id='account_list.hidden_notice'
defaultMessage='This is only visible to you. To show this list to others, go to <link>{page} > {modal} > {field}</link>.'
values={{
link: (chunks) => <Link to='/profile/edit'>{chunks}</Link>,
page: (
<FormattedMessage
id='account.edit_profile'
defaultMessage='Edit profile'
/>
),
modal: (
<FormattedMessage
id='account_edit.profile_tab.title'
defaultMessage='Profile display settings'
/>
),
field: (
<FormattedMessage
id='account_edit.profile_tab.show_relations.title'
defaultMessage='Show ‘Followers’ and ‘Following’'
/>
),
}}
/>
</Callout>
)}
</>
);
};
@@ -4,8 +4,12 @@
margin: 20px 16px 10px;
}
.subtitle,
.callout {
margin: 10px 16px;
}
.subtitle {
font-size: 14px;
color: var(--color-text-secondary);
margin: 10px 16px;
}
@@ -24,9 +24,14 @@ export const NotificationCollection: React.FC<{
unread: boolean;
}> = ({ notification, unread }) => {
const { collection, type } = notification;
const collectionCreatorAccount = useAccount(collection.account_id);
const collectionCreatorAccount = useAccount(collection?.account_id);
const confirmRevoke = useConfirmRevoke(collection);
if (!collection) {
return null;
}
return (
<div
className={classNames(
@@ -65,7 +65,7 @@ export const ConfirmationModal: React.FC<
}, [onClose, onSecondary]);
return (
<ModalShell>
<ModalShell onSubmit={handleClick}>
<ModalShellBody className={className}>
<h1 id={titleId}>{title}</h1>
{message && <p>{message}</p>}
@@ -99,6 +99,7 @@ export const ConfirmationModal: React.FC<
{/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
<Button
type='submit'
onClick={handleClick}
loading={updating}
disabled={disabled}
@@ -38,8 +38,8 @@ export const ConfirmDeleteCollectionModal: React.FC<
const history = useHistory();
const { acct: currentUserName } = useAccount(me) ?? {};
const onConfirm = useCallback(() => {
void dispatch(deleteCollection({ collectionId: id }));
const onConfirm = useCallback(async () => {
await dispatch(deleteCollection({ collectionId: id }));
history.push(`/@${currentUserName}/collections`);
}, [dispatch, history, id, currentUserName]);
@@ -77,7 +77,7 @@ export function PinnedStatuses () {
}
export function AccountTimeline () {
return import('../../account_timeline/v2');
return import('../../account_timeline');
}
export function AccountGallery () {
-5
View File
@@ -28,12 +28,10 @@
"account.block_domain": "حظر اسم النِّطاق {domain}",
"account.block_short": "حظر",
"account.blocked": "محظور",
"account.blocking": "محظور",
"account.cancel_follow_request": "إلغاء طلب المتابعة",
"account.copy": "نسخ الرابط إلى الملف الشخصي",
"account.direct": "إشارة خاصة لـ @{name}",
"account.disable_notifications": "توقف عن إشعاري عندما ينشر @{name}",
"account.domain_blocking": "نطاق محظور",
"account.edit_note": "تعديل الملاحظة الشخصية",
"account.edit_profile": "تعديل الملف الشخصي",
"account.edit_profile_short": "تعديل",
@@ -95,7 +93,6 @@
"account.mute_notifications_short": "كتم الإشعارات",
"account.mute_short": "اكتم",
"account.muted": "مَكتوم",
"account.muting": "مكتوم",
"account.mutual": "أنتم تتابعون بعضكم البعض",
"account.name.copy": "نسخ المعرف",
"account.name_info": "ما معنى ذلك؟",
@@ -112,7 +109,6 @@
"account.remove_from_followers": "إزالة {name} من المتابعين",
"account.report": "الإبلاغ عن @{name}",
"account.requested_follow": "لقد طلب {name} متابعتك",
"account.requests_to_follow_you": "طلبات المتابعة",
"account.share": "شارِك الملف التعريفي لـ @{name}",
"account.show_reblogs": "اعرض إعادات نشر @{name}",
"account.statuses_counter": "{count, plural, zero {}one {{counter} مشور} two {{counter} منشور} few {{counter} منشور} many {{counter} منشور} other {{counter} منشور}}",
@@ -269,7 +265,6 @@
"collections.copy_link_confirmation": "نسخ الرابط إلى الحافظة",
"collections.create.basic_details_title": "المعلومات الأساسية",
"collections.create.steps": "الخطوة {step}/{total}",
"collections.detail.accounts_heading": "الحسابات",
"collections.detail.revoke_inclusion": "أزلني",
"collections.detail.sensitive_content": "محتوى حساس",
"collections.edit_details": "تعديل التفاصيل",
-4
View File
@@ -28,12 +28,10 @@
"account.block_domain": "{domain} domenini blokla",
"account.block_short": "Blok",
"account.blocked": "Bloklanıb",
"account.blocking": "Əngəlləmə",
"account.cancel_follow_request": "İzləməni ləğv et",
"account.copy": "Profil linkini kopyala",
"account.direct": "@{name} istifadəçisini fərdi olaraq etiketlə",
"account.disable_notifications": "@{name} paylaşım edəndə mənə bildiriş göndərməyi dayandır",
"account.domain_blocking": "Domenin bloklanması",
"account.edit_note": "Fərdi nota düzəliş",
"account.edit_profile": "Profili redaktə et",
"account.edit_profile_short": "Düzəliş",
@@ -109,7 +107,6 @@
"account.mute_notifications_short": "Bildirişləri səssizləşdir",
"account.mute_short": "Səssizləşdir",
"account.muted": "Səssizləşdirilib",
"account.muting": "Səssizə alınır",
"account.mutual": "Bir-birinizi izləyirsiniz",
"account.no_bio": "Təsvir göstərilməyib.",
"account.open_original_page": "Orijinal səhifəni aç",
@@ -117,7 +114,6 @@
"account.remove_from_followers": "{name} - izləyicilərdən çıxart",
"account.report": "@{name} istifadəçisini şikayət et",
"account.requested_follow": "{name} sizi izləmək sorğusu göndərib",
"account.requests_to_follow_you": "Sizi izləmək istəyir",
"account.share": "@{name} profilini paylaş",
"account.show_reblogs": "@{name} - təkrar paylaşımlarını göstər",
"account.statuses_counter": "{count, plural, one {{counter} paylaşım} other {{counter} paylaşım}}",
+5 -11
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Заблакіраваць дамен {domain}",
"account.block_short": "Заблакіраваць",
"account.blocked": "Заблакіраваны",
"account.blocking": "Блакіраванне",
"account.cancel_follow_request": "Скасаваць запыт на падпіску",
"account.copy": "Скапіраваць спасылку на профіль",
"account.direct": "Згадаць прыватна @{name}",
"account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}",
"account.domain_blocking": "Блакіраванне дамена",
"account.edit_note": "Рэдагаваць асабістую нататку",
"account.edit_profile": "Рэдагаваць профіль",
"account.edit_profile_short": "Рэдагаваць",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Не апавяшчаць",
"account.mute_short": "Ігнараваць",
"account.muted": "Ігнаруецца",
"account.muting": "Ігнараванне",
"account.mutual": "Вы падпісаныя адно на аднаго",
"account.name.copy": "Скапір. ідэнтыфікатар",
"account.name.help.domain": "{domain} — сервер, які ўтрымлівае профіль і допісы гэтага карыстальніка.",
@@ -135,7 +132,6 @@
"account.remove_from_followers": "Выдаліць {name} з падпісчыкаў",
"account.report": "Паскардзіцца на @{name}",
"account.requested_follow": "{name} адправіў(-ла) запыт на падпіску",
"account.requests_to_follow_you": "Хоча падпісацца на вас",
"account.share": "Абагуліць профіль @{name}",
"account.show_reblogs": "Паказаць падштурхоўванні ад @{name}",
"account.statuses_counter": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
@@ -227,8 +223,6 @@
"account_edit.profile_tab.show_media.title": "Паказваць укладку \"Медыя\"",
"account_edit.profile_tab.show_media_replies.description": "Калі ўключыць, укладка \"Медыя\" будзе адлюстроўваць як Вашыя допісы, гэтак і Вашыя адказы на допісы іншых людзей.",
"account_edit.profile_tab.show_media_replies.title": "Змяшчаць адказы ва ўкладцы \"Медыя\"",
"account_edit.profile_tab.subtitle": "Змяняйце на свой густ укладкі свайго профілю і тое, што яны паказваюць.",
"account_edit.profile_tab.title": "Налады ўкладкі профілю",
"account_edit.save": "Захаваць",
"account_edit.upload_modal.back": "Назад",
"account_edit.upload_modal.done": "Гатова",
@@ -387,11 +381,9 @@
"collections.create.accounts_title": "Каго Вы ўключыце ў гэтую калекцыю?",
"collections.create.basic_details_title": "Асноўныя звесткі",
"collections.create.steps": "Крок {step}/{total}",
"collections.create_a_collection_hint": "Стварыце калекцыю, каб параіць або падзяліцца сваімі любімымі ўліковымі запісамі з іншымі.",
"collections.create_collection": "Стварыць калекцыю",
"collections.delete_collection": "Выдаліць калекцыю",
"collections.description_length_hint": "Максімум 100 сімвалаў",
"collections.detail.accounts_heading": "Уліковыя запісы",
"collections.detail.author_added_you_on_date": "{author} дадаў(-ла) Вас {date}",
"collections.detail.loading": "Загружаецца калекцыя…",
"collections.detail.revoke_inclusion": "Прыбраць сябе",
@@ -400,11 +392,14 @@
"collections.detail.share": "Падзяліцца гэтай калекцыяй",
"collections.detail.you_are_in_this_collection": "Вас уключылі ў гэтую калекцыю",
"collections.edit_details": "Рэдагаваць падрабязнасці",
"collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.",
"collections.hidden_accounts_description": "Вы заблакіравалі або ігнаруеце {count, plural, one {гэтага карыстальніка} other {гэтых карыстальнікаў}}",
"collections.hidden_accounts_link": "{count, plural, one {# схаваны ўліковы запіс} few {# схаваныя ўліковыя запісы} other {# схаваных уліковых запісаў}}",
"collections.hints.accounts_counter": "{count}/{max} уліковых запісаў",
"collections.last_updated_at": "Апошняе абнаўленне: {date}",
"collections.list.collections_with_count": "{count, plural, one {калекцыя} few {калекцыі} many {калекцыяў} other {калекцыі}}",
"collections.list.created_by_author": "Створаныя {name}",
"collections.list.created_by_you": "Створаныя Вамі",
"collections.list.featuring_you": "У якіх ёсць Вы",
"collections.manage_accounts": "Кіраванне ўліковымі запісамі",
"collections.mark_as_sensitive": "Пазначыць як адчувальную",
"collections.mark_as_sensitive_hint": "Схаваць апісанне калекцыі і ўліковыя запісы за банерам з папярэджаннем. Назва калекцыі застанецца бачнай.",
@@ -412,7 +407,6 @@
"collections.maximum_collection_count_reached": "Вы стварылі максімальную колькасць калекцый",
"collections.name_length_hint": "Максімум 40 сімвалаў",
"collections.new_collection": "Новая калекцыя",
"collections.no_collections_yet": "Пакуль няма калекцый.",
"collections.remove_account": "Выдаліць",
"collections.report_collection": "Паскардзіцца на гэту калекцыю",
"collections.revoke_collection_inclusion": "Прыбраць сябе з гэтай калекцыі",
@@ -454,11 +448,11 @@
"column.list_members": "Кіраванне ўдзельнікамі спіса",
"column.lists": "Спісы",
"column.mutes": "Ігнараваныя карыстальнікі",
"column.my_collections": "Мае калекцыі",
"column.notifications": "Апавяшчэнні",
"column.other_collections": "Калекцыі {name}",
"column.pins": "Замацаваныя допісы",
"column.public": "Інтэграваная стужка",
"column.your_collections": "Вашы калекцыі",
"column_back_button.label": "Назад",
"column_header.hide_settings": "Схаваць налады",
"column_header.moveLeft_settings": "Пасунуць слупок улева",
-4
View File
@@ -21,12 +21,10 @@
"account.block_domain": "Блокиране на домейн {domain}",
"account.block_short": "Блокиране",
"account.blocked": "Блокирани",
"account.blocking": "Блокиране",
"account.cancel_follow_request": "Оттегляне на заявката за последване",
"account.copy": "Копиране на връзка към профила",
"account.direct": "Частно споменаване на @{name}",
"account.disable_notifications": "Спиране на известяване при публикуване от @{name}",
"account.domain_blocking": "Блокиране на домейн",
"account.edit_profile": "Редактиране на профила",
"account.edit_profile_short": "Редактиране",
"account.enable_notifications": "Известяване при публикуване от @{name}",
@@ -63,7 +61,6 @@
"account.mute_notifications_short": "Заглушаване на известията",
"account.mute_short": "Заглушаване",
"account.muted": "Заглушено",
"account.muting": "Заглушаване",
"account.mutual": "Взаимно се следвате",
"account.no_bio": "Няма представен опис.",
"account.open_original_page": "Отваряне на първообразната страница",
@@ -71,7 +68,6 @@
"account.remove_from_followers": "Премахване на {name} от последователи",
"account.report": "Докладване на @{name}",
"account.requested_follow": "{name} поиска да ви последва",
"account.requests_to_follow_you": "Заявки да ви последват",
"account.share": "Споделяне на профила на @{name}",
"account.show_reblogs": "Показване на подсилвания от @{name}",
"account.statuses_counter": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
-4
View File
@@ -21,12 +21,10 @@
"account.block_domain": "Stankañ an domani {domain}",
"account.block_short": "Stankañ",
"account.blocked": "Stanket",
"account.blocking": "Stanket",
"account.cancel_follow_request": "Nullañ ar reked heuliañ",
"account.copy": "Eilañ al liamm war-zu ho profil",
"account.direct": "Menegiñ @{name} ent-prevez",
"account.disable_notifications": "Paouez d'am c'hemenn pa vez embannet traoù gant @{name}",
"account.domain_blocking": "Domani stanket",
"account.edit_profile": "Kemmañ ar profil",
"account.edit_profile_short": "Kemmañ",
"account.enable_notifications": "Ma c'hemenn pa vez embannet traoù gant @{name}",
@@ -62,7 +60,6 @@
"account.mute_notifications_short": "Kuzhat ar c'hemennoù",
"account.mute_short": "Kuzhat",
"account.muted": "Kuzhet",
"account.muting": "O kuzhat",
"account.mutual": "Heuliañ a rit an eil egile",
"account.no_bio": "Deskrivadur ebet da gaout.",
"account.open_original_page": "Digeriñ ar bajenn orin",
@@ -70,7 +67,6 @@
"account.remove_from_followers": "Dilemel {name} eus an heulierien·ezed",
"account.report": "Disklêriañ @{name}",
"account.requested_follow": "Gant {name} eo bet goulennet ho heuliañ",
"account.requests_to_follow_you": "Rekedoù d'ho heuliañ",
"account.share": "Skignañ profil @{name}",
"account.show_reblogs": "Diskouez skignadennoù @{name}",
"account.statuses_counter": "{count, plural, one {{counter} embannadur} two {{counter} embannadur} few {{counter} embannadur} many {{counter} embannadur} other {{counter} embannadur}}",
-7
View File
@@ -27,12 +27,10 @@
"account.block_domain": "Bloca el domini {domain}",
"account.block_short": "Bloca",
"account.blocked": "Blocat",
"account.blocking": "Blocat",
"account.cancel_follow_request": "Cancel·la el seguiment",
"account.copy": "Copia l'enllaç al perfil",
"account.direct": "Menciona privadament @{name}",
"account.disable_notifications": "Deixa de notificar-me els tuts de @{name}",
"account.domain_blocking": "Bloquem el domini",
"account.edit_note": "Edita una nota personal",
"account.edit_profile": "Edita el perfil",
"account.edit_profile_short": "Edita",
@@ -95,7 +93,6 @@
"account.mute_notifications_short": "Silencia les notificacions",
"account.mute_short": "Silencia",
"account.muted": "Silenciat",
"account.muting": "Silenciem",
"account.mutual": "Us seguiu l'un a l'altre",
"account.no_bio": "No s'ha proporcionat cap descripció.",
"account.node_modal.callout": "Ningú més pot veure les vostres notes personals.",
@@ -111,7 +108,6 @@
"account.remove_from_followers": "Elimina {name} dels seguidors",
"account.report": "Informa sobre @{name}",
"account.requested_follow": "{name} ha demanat de seguir-te",
"account.requests_to_follow_you": "Peticions de seguir-vos",
"account.share": "Comparteix el perfil de @{name}",
"account.show_reblogs": "Mostra els impulsos de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}",
@@ -205,15 +201,12 @@
"collections.collection_description": "Descripció",
"collections.collection_name": "Nom",
"collections.collection_topic": "Tema",
"collections.create_a_collection_hint": "Creeu una coŀlecció per a recomanar o compartir amb d'altres els vostres comptes preferits.",
"collections.create_collection": "Crea una coŀlecció",
"collections.delete_collection": "Elimina la coŀlecció",
"collections.description_length_hint": "Límit de 100 caràcters",
"collections.error_loading_collections": "Hi ha hagut un error en carregar les vostres coŀleccions.",
"collections.last_updated_at": "Última actualització: {date}",
"collections.mark_as_sensitive": "Marcar com a sensible",
"collections.mark_as_sensitive_hint": "Amaga la descripció i els comptes de la coŀlecció rere un avís de contingut. El nom de la coŀlecció serà encara visible.",
"collections.no_collections_yet": "Encara no hi ha coŀleccions.",
"collections.topic_hint": "Afegir una etiqueta que ajudi altres a entendre el tema principal de la coŀlecció.",
"collections.view_collection": "Mostra la coŀlecció",
"column.about": "Quant a",
-5
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Blokovat doménu {domain}",
"account.block_short": "Zablokovat",
"account.blocked": "Blokovaný",
"account.blocking": "Blokovaní",
"account.cancel_follow_request": "Zrušit sledování",
"account.copy": "Kopírovat odkaz na profil",
"account.direct": "Soukromě zmínit @{name}",
"account.disable_notifications": "Přestat mě upozorňovat, když @{name} zveřejní příspěvek",
"account.domain_blocking": "Blokované domény",
"account.edit_note": "Upravit vlastní poznámku",
"account.edit_profile": "Upravit profil",
"account.edit_profile_short": "Upravit",
@@ -97,7 +95,6 @@
"account.mute_notifications_short": "Ztlumit upozornění",
"account.mute_short": "Ztlumit",
"account.muted": "Skrytý",
"account.muting": "Ztlumení",
"account.mutual": "Sledujete se navzájem",
"account.name.help.domain": "{domain} je server, který hostuje profily a příspěvky uživatelů.",
"account.name.help.domain_self": "{domain} je váš server, který hostuje váš profil a příspěvky.",
@@ -119,7 +116,6 @@
"account.remove_from_followers": "Odebrat {name} ze sledujících",
"account.report": "Nahlásit @{name}",
"account.requested_follow": "{name} tě požádal o sledování",
"account.requests_to_follow_you": "Žádosti o sledování",
"account.share": "Sdílet profil @{name}",
"account.show_reblogs": "Zobrazit boosty od @{name}",
"account.statuses_counter": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}",
@@ -251,7 +247,6 @@
"collections.mark_as_sensitive_hint": "Skryje popis kolekce a účty za varováním obsahu. Název kolekce bude stále viditelný.",
"collections.name_length_hint": "Limit 40 znaků",
"collections.new_collection": "Nová sbírka",
"collections.no_collections_yet": "Ještě nemáte žádné sbírky.",
"collections.search_accounts_max_reached": "Přidali jste maximální počet účtů",
"collections.sensitive": "Citlivé",
"collections.topic_hint": "Přidat štítek, který pomůže ostatním pochopit hlavní téma této kolekce.",
-10
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Rhwystro'r parth {domain}",
"account.block_short": "Rhwystro",
"account.blocked": "Wedi'i rwystro",
"account.blocking": "Yn Rhwystro",
"account.cancel_follow_request": "Tynnu cais i ddilyn",
"account.copy": "Copïo dolen i'r proffil",
"account.direct": "Crybwyll yn breifat @{name}",
"account.disable_notifications": "Stopiwch fy hysbysu pan fydd @{name} yn postio",
"account.domain_blocking": "Parthau'n cael eu rhwystro",
"account.edit_note": "Golygu nodyn personol",
"account.edit_profile": "Golygu'r proffil",
"account.edit_profile_short": "Golygu",
@@ -99,7 +97,6 @@
"account.mute_notifications_short": "Diffodd hysbysiadau",
"account.mute_short": "Anwybyddu",
"account.muted": "Wedi anwybyddu",
"account.muting": "Tewi",
"account.mutual": "Rydych chi'n dilyn eich gilydd",
"account.name.help.domain": "{domain} yw'r gweinydd sy'n cynnal proffil a phostiadau'r defnyddiwr.",
"account.name.help.domain_self": "{domain} yw eich gweinydd sy'n cynnal eich proffil a'ch postiadau.",
@@ -121,7 +118,6 @@
"account.remove_from_followers": "Tynnu {name} o'ch dilynwyr",
"account.report": "Adrodd @{name}",
"account.requested_follow": "Mae {name} wedi gwneud cais i'ch dilyn",
"account.requests_to_follow_you": "Ceisiadau i'ch dilyn",
"account.share": "Rhannu proffil @{name}",
"account.show_reblogs": "Dangos hybiau gan @{name}",
"account.statuses_counter": "{count, plural, one {{counter} postiad} two {{counter} bostiad} few {{counter} phostiad} many {{counter} postiad} other {{counter} postiad}}",
@@ -195,8 +191,6 @@
"account_edit.profile_tab.show_media.title": "Dangos y tab ‘Cyfryngau’",
"account_edit.profile_tab.show_media_replies.description": "Pan fydd wedi'i alluogi, mae'r tab Cyfryngau yn dangos eich postiadau ac atebion i bostiadau pobl eraill.",
"account_edit.profile_tab.show_media_replies.title": "Cynhwyswch atebion ar y tab ‘Cyfryngau’",
"account_edit.profile_tab.subtitle": "Cyfaddaswch y tabiau ar eich proffil a'r hyn maen nhw'n ei ddangos.",
"account_edit.profile_tab.title": "Gosodiadau tab proffil",
"account_edit.save": "Cadw",
"account_edit.upload_modal.back": "Nôl",
"account_edit.upload_modal.done": "Gorffen",
@@ -339,24 +333,20 @@
"collections.create.accounts_title": "Pwy fyddwch chi'n ei gynnwys yn y casgliad hwn?",
"collections.create.basic_details_title": "Manylion sylfaenol",
"collections.create.steps": "Cam {step}/{total}",
"collections.create_a_collection_hint": "Creu casgliad i argymell neu rannu eich hoff gyfrifon gydag eraill.",
"collections.create_collection": "Creu casgliad",
"collections.delete_collection": "Dileu casgliad",
"collections.description_length_hint": "Terfyn o 100 nod",
"collections.detail.accounts_heading": "Cyfrifon",
"collections.detail.loading": "Yn llwytho casgliad…",
"collections.detail.revoke_inclusion": "Tynnu fi",
"collections.detail.sensitive_note": "Mae'r casgliad hwn yn cynnwys cyfrifon a chynnwys a allai fod yn sensitif i rai defnyddwyr.",
"collections.detail.share": "Rhannu'r casgliad hwn",
"collections.edit_details": "Golygu manylion",
"collections.error_loading_collections": "Bu gwall wrth geisio llwytho eich casgliadau.",
"collections.last_updated_at": "Diweddarwyd ddiwethaf: {date}",
"collections.manage_accounts": "Rheoli cyfrifon",
"collections.mark_as_sensitive": "Marcio fel sensitif",
"collections.mark_as_sensitive_hint": "Yn cuddio disgrifiad a chyfrifon y casgliad y tu ôl i rybudd cynnwys. Bydd enw'r casgliad yn dal i fod yn weladwy.",
"collections.name_length_hint": "Terfyn o 40 nod",
"collections.new_collection": "Casgliad newydd",
"collections.no_collections_yet": "Dim casgliadau eto.",
"collections.report_collection": "Adroddwch am y casgliad hwn",
"collections.revoke_collection_inclusion": "Tynnu fy hun o'r casgliad hwn",
"collections.revoke_inclusion.confirmation": "Rydych chi wedi cael eich tynnu o \"{collection}\"",
+15 -11
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Blokér domænet {domain}",
"account.block_short": "Bloker",
"account.blocked": "Blokeret",
"account.blocking": "Blokering",
"account.cancel_follow_request": "Annullér anmodning om at følge",
"account.copy": "Kopiér link til profil",
"account.direct": "Nævn @{name} privat",
"account.disable_notifications": "Giv mig ikke længere en notifikation, når @{name} laver indlæg",
"account.domain_blocking": "Blokerer domæne",
"account.edit_note": "Rediger personlig note",
"account.edit_profile": "Redigér profil",
"account.edit_profile_short": "Rediger",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Sluk for notifikationer",
"account.mute_short": "Skjul",
"account.muted": "Skjult",
"account.muting": "Skjuler",
"account.mutual": "I følger hinanden",
"account.name.copy": "Kopiér handle",
"account.name.help.domain": "{domain} er den server, der er vært for brugerens profil og indlæg.",
@@ -131,11 +128,11 @@
"account.note.edit_button": "Rediger",
"account.note.title": "Personlig note (kun synlige for dig)",
"account.open_original_page": "Åbn oprindelig side",
"account.pending": "Afventer",
"account.posts": "Indlæg",
"account.remove_from_followers": "Fjern {name} fra følgere",
"account.report": "Anmeld @{name}",
"account.requested_follow": "{name} har anmodet om at følge dig",
"account.requests_to_follow_you": "Anmoder om at følge dig",
"account.share": "Del @{name}s profil",
"account.show_reblogs": "Vis fremhævelser fra @{name}",
"account.statuses_counter": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
@@ -227,8 +224,10 @@
"account_edit.profile_tab.show_media.title": "Vis fanebladet ‘Medier‘",
"account_edit.profile_tab.show_media_replies.description": "Når aktiveret, viser fanen Medier både dine indlæg og svar på andres indlæg.",
"account_edit.profile_tab.show_media_replies.title": "Inkludér svar på fanen 'Medier'",
"account_edit.profile_tab.subtitle": "Tilpas fanerne på din profil og det, de viser.",
"account_edit.profile_tab.title": "Indstillinger for profil-fane",
"account_edit.profile_tab.show_relations.description": "Viser de konti, du følger, samt dine følgere for andre brugere på din profil. Andre kan stadig se, om du følger dem.",
"account_edit.profile_tab.show_relations.title": "Vis ‘Følgere‘ og ‘Fulgte’",
"account_edit.profile_tab.subtitle": "Tilpas, hvordan din profil vises.",
"account_edit.profile_tab.title": "Indstillinger for profilvisning",
"account_edit.save": "Gem",
"account_edit.upload_modal.back": "Tilbage",
"account_edit.upload_modal.done": "Færdig",
@@ -256,6 +255,7 @@
"account_edit_tags.search_placeholder": "Angiv et hashtag…",
"account_edit_tags.suggestions": "Forslag:",
"account_edit_tags.tag_status_count": "{count, plural, one {# indlæg} other {# indlæg}}",
"account_list.hidden_notice": "Dette kan kun ses af dig. Hvis du vil vise denne liste for andre, skal du gå til <link>{page} > {modal} > {field}</link>.",
"account_list.total": "{total, plural, one {# konto} other {# konti}}",
"account_note.placeholder": "Klik for at tilføje notat",
"admin.dashboard.daily_retention": "Brugerfastholdelsesrate pr. dag efter tilmelding",
@@ -387,11 +387,9 @@
"collections.create.accounts_title": "Hvem vil du fremhæve i denne samling?",
"collections.create.basic_details_title": "Grundlæggende oplysninger",
"collections.create.steps": "Trin {step}/{total}",
"collections.create_a_collection_hint": "Opret en samling for at anbefale eller dele dine yndlingskonti med andre.",
"collections.create_collection": "Opret samling",
"collections.delete_collection": "Slet samling",
"collections.description_length_hint": "Begrænset til 100 tegn",
"collections.detail.accounts_heading": "Konti",
"collections.detail.author_added_you_on_date": "{author} tilføjede dig {date}",
"collections.detail.loading": "Indlæser samling…",
"collections.detail.revoke_inclusion": "Fjern mig",
@@ -400,11 +398,15 @@
"collections.detail.share": "Del denne samling",
"collections.detail.you_are_in_this_collection": "Du er med i denne samling",
"collections.edit_details": "Rediger detaljer",
"collections.error_loading_collections": "Der opstod en fejl under indlæsning af dine samlinger.",
"collections.error_loading_collections": "Der opstod en fejl under indlæsning af disse samlinger.",
"collections.hidden_accounts_description": "Du har blokeret eller skjult {count, plural, one {denne bruger} other {disse brugere}}",
"collections.hidden_accounts_link": "{count, plural, one {# skjult konto} other {# skjulte konti}}",
"collections.hints.accounts_counter": "{count}/{max} konti",
"collections.last_updated_at": "Senest opdateret: {date}",
"collections.list.collections_with_count": "{count, plural, one {# samling} other {# samlinger}}",
"collections.list.created_by_author": "Oprettet af {name}",
"collections.list.created_by_you": "Oprettet af dig",
"collections.list.featuring_you": "Med dig",
"collections.manage_accounts": "Administrer konti",
"collections.mark_as_sensitive": "Markér som sensitiv",
"collections.mark_as_sensitive_hint": "Skjuler samlingens beskrivelse og konti bag en indholdsadvarsel. Samlingens navn vil stadig være synligt.",
@@ -412,7 +414,8 @@
"collections.maximum_collection_count_reached": "Du har oprettet det maksimale antal samlinger",
"collections.name_length_hint": "Begrænset til 40 tegn",
"collections.new_collection": "Ny samling",
"collections.no_collections_yet": "Ingen samlinger endnu.",
"collections.pending_accounts.message": "Konti kan vises som afventende, når vi venter på et svar fra brugeren eller dennes server. Det er kun dig, der kan se de afventende konti.",
"collections.pending_accounts.title": "Hvorfor ser jeg afventende konti?",
"collections.remove_account": "Fjern",
"collections.report_collection": "Anmeld denne samling",
"collections.revoke_collection_inclusion": "Fjern mig selv fra denne samling",
@@ -454,11 +457,11 @@
"column.list_members": "Håndtér listemedlemmer",
"column.lists": "Lister",
"column.mutes": "Skjulte brugere",
"column.my_collections": "Mine samlinger",
"column.notifications": "Notifikationer",
"column.other_collections": "Samlinger af {name}",
"column.pins": "Fastgjorte indlæg",
"column.public": "Fælles tidslinje",
"column.your_collections": "Dine samlinger",
"column_back_button.label": "Tilbage",
"column_header.hide_settings": "Skjul indstillinger",
"column_header.moveLeft_settings": "Flyt kolonne til venstre",
@@ -645,6 +648,7 @@
"empty_column.account_unavailable": "Profil utilgængelig",
"empty_column.blocks": "Du har ikke blokeret nogle brugere endnu.",
"empty_column.bookmarked_statuses": "Du har ingen bogmærkede indlæg endnu. Når du bogmærker ét, vil det dukke op hér.",
"empty_column.collections.featured_in": "Du er ikke blevet tilføjet til nogen samlinger endnu.",
"empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at sætte tingene i gang!",
"empty_column.direct": "Du har ikke nogen private omtaler endnu. Når du sender eller modtager en, vil den blive vist her.",
"empty_column.disabled_feed": "Dette feed er blevet deaktiveret af dine serveradministratorer.",
+19 -15
View File
@@ -28,12 +28,10 @@
"account.block_domain": "{domain} blockieren",
"account.block_short": "Blockieren",
"account.blocked": "Blockiert",
"account.blocking": "Blockiert",
"account.cancel_follow_request": "Anfrage zurückziehen",
"account.copy": "Link zum Profil kopieren",
"account.direct": "@{name} privat erwähnen",
"account.disable_notifications": "Benachrichtige mich nicht mehr, wenn @{name} etwas veröffentlicht",
"account.domain_blocking": "Domain blockiert",
"account.edit_note": "Persönliche Notiz bearbeiten",
"account.edit_profile": "Profil bearbeiten",
"account.edit_profile_short": "Bearbeiten",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Benachrichtigungen stummschalten",
"account.mute_short": "Stummschalten",
"account.muted": "Stummgeschaltet",
"account.muting": "Stummgeschaltet",
"account.mutual": "Ihr folgt einander",
"account.name.copy": "Adresse kopieren",
"account.name.help.domain": "{domain} ist der Server, auf dem das Profil registriert ist und die Beiträge verwaltet werden.",
@@ -131,11 +128,11 @@
"account.note.edit_button": "Bearbeiten",
"account.note.title": "Persönliche Notiz (nur für dich sichtbar)",
"account.open_original_page": "Originalseite öffnen",
"account.pending": "Ausstehend",
"account.posts": "Beiträge",
"account.remove_from_followers": "@{name} als Follower entfernen",
"account.report": "@{name} melden",
"account.requested_follow": "{name} möchte dir folgen",
"account.requests_to_follow_you": "Möchte dir folgen",
"account.share": "Profil von @{name} teilen",
"account.show_reblogs": "Geteilte Beiträge von @{name} anzeigen",
"account.statuses_counter": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
@@ -227,8 +224,10 @@
"account_edit.profile_tab.show_media.title": "„Medien“-Tab anzeigen",
"account_edit.profile_tab.show_media_replies.description": "Durch das Aktivieren werden sowohl deine Beiträge als auch deine Antworten auf Beiträge anderer im „Medien“-Tab angezeigt.",
"account_edit.profile_tab.show_media_replies.title": "Antworten im „Medien“-Tab anzeigen",
"account_edit.profile_tab.subtitle": "Passe die Tabs deines Profils und deren Inhalte an.",
"account_edit.profile_tab.title": "Profil-Tab-Einstellungen",
"account_edit.profile_tab.show_relations.description": "Dadurch werden die Konten, die dir folgen, und die Profile, denen du folgst, für andere sichtbar. Sie werden immer sehen können, ob du ihnen folgst.",
"account_edit.profile_tab.show_relations.title": "Follower und „Folge ich“ anzeigen",
"account_edit.profile_tab.subtitle": "Passe die Darstellung deines Profils an.",
"account_edit.profile_tab.title": "Einstellungen für die Profilübersicht",
"account_edit.save": "Speichern",
"account_edit.upload_modal.back": "Zurück",
"account_edit.upload_modal.done": "Fertig",
@@ -256,6 +255,7 @@
"account_edit_tags.search_placeholder": "Gib einen Hashtag ein …",
"account_edit_tags.suggestions": "Vorschläge:",
"account_edit_tags.tag_status_count": "{count, plural, one {# Beitrag} other {# Beiträge}}",
"account_list.hidden_notice": "Die Liste ist nur für dich sichtbar. Damit sie für andere zugänglich wird, rufe <link>{page} > {modal}> {field}</link> auf.",
"account_list.total": "{total, plural, one {# Konto} other {# Konten}}",
"account_note.placeholder": "Klicken, um private Anmerkung hinzuzufügen",
"admin.dashboard.daily_retention": "Verweildauer der Nutzer*innen pro Tag seit der Registrierung",
@@ -387,11 +387,9 @@
"collections.create.accounts_title": "Wen möchtest du in dieser Sammlung präsentieren?",
"collections.create.basic_details_title": "Allgemeine Informationen",
"collections.create.steps": "Schritt {step}/{total}",
"collections.create_a_collection_hint": "Erstelle eine Sammlung, um deine Lieblingsprofile anderen zu empfehlen oder sie zu teilen.",
"collections.create_collection": "Sammlung erstellen",
"collections.delete_collection": "Sammlung löschen",
"collections.description_length_hint": "Maximal 100 Zeichen",
"collections.detail.accounts_heading": "Konten",
"collections.detail.author_added_you_on_date": "{author} fügte dich am {date} hinzu",
"collections.detail.loading": "Sammlung wird geladen …",
"collections.detail.revoke_inclusion": "Mich entfernen",
@@ -400,11 +398,15 @@
"collections.detail.share": "Sammlung teilen",
"collections.detail.you_are_in_this_collection": "Du bist ein Teil dieser Sammlung",
"collections.edit_details": "Details bearbeiten",
"collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.",
"collections.error_loading_collections": "Beim Laden dieser Sammlungen ist ein Fehler aufgetreten.",
"collections.hidden_accounts_description": "Du hast {count, plural, one {dieses Profil} other {diese Profile}} blockiert oder stummgeschaltet",
"collections.hidden_accounts_link": "{count, plural, one {# Konto ausgeblendet} other {# Konten ausgeblendet}}",
"collections.hints.accounts_counter": "{count}/{max} Konten",
"collections.last_updated_at": "Aktualisiert: {date}",
"collections.list.collections_with_count": "{count, plural, one {# Sammlung} other {# Sammlungen}}",
"collections.list.created_by_author": "Von {name} erstellt",
"collections.list.created_by_you": "Von mir erstellt",
"collections.list.featuring_you": "Stellen mich vor",
"collections.manage_accounts": "Profile verwalten",
"collections.mark_as_sensitive": "Mit Inhaltswarnung versehen",
"collections.mark_as_sensitive_hint": "Die Beschreibung sowie enthaltenen Profile werden durch eine Inhaltswarnung ausgeblendet. Der Titel bleibt weiterhin sichtbar.",
@@ -412,7 +414,8 @@
"collections.maximum_collection_count_reached": "Du hast die Höchstzahl an Sammlungen erreicht",
"collections.name_length_hint": "Maximal 40 Zeichen",
"collections.new_collection": "Neue Sammlung",
"collections.no_collections_yet": "Bisher keine Sammlungen vorhanden.",
"collections.pending_accounts.message": "Ein Konto erscheint als ausstehend, wenn eine Antwort vom Konto oder deren Server erwartet wird. Ausstehende Konten sind nur für dich sichtbar.",
"collections.pending_accounts.title": "Warum sehe ich ausstehende Konten?",
"collections.remove_account": "Entfernen",
"collections.report_collection": "Sammlung melden",
"collections.revoke_collection_inclusion": "Mich aus dieser Sammlung entfernen",
@@ -424,7 +427,7 @@
"collections.share_short": "Teilen",
"collections.suggestions.can_not_add": "Kann nicht hinzugefügt werden",
"collections.suggestions.can_not_add_desc": "Diese Konten möchten möglicherweise nicht entdeckt werden oder deren Server unterstützt noch keine Sammlungen.",
"collections.suggestions.must_follow": "Zuerst Profil folgen",
"collections.suggestions.must_follow": "Profil muss gefolgt werden",
"collections.suggestions.must_follow_desc": "Diese Konten überprüfen ihre Follower-Anfragen manuell. Follower können sie zu Sammlungen hinzufügen.",
"collections.topic_hint": "Ein Hashtag für diese Sammlung kann anderen dabei helfen, dein Anliegen besser einordnen zu können.",
"collections.topic_special_chars_hint": "Sonderzeichen werden beim Speichern entfernt",
@@ -454,11 +457,11 @@
"column.list_members": "Listenmitglieder verwalten",
"column.lists": "Listen",
"column.mutes": "Stummgeschaltete Profile",
"column.my_collections": "Meine Sammlungen",
"column.notifications": "Benachrichtigungen",
"column.other_collections": "Sammlungen von {name}",
"column.pins": "Angeheftete Beiträge",
"column.public": "Föderierte Timeline",
"column.your_collections": "Deine Sammlungen",
"column_back_button.label": "Zurück",
"column_header.hide_settings": "Einstellungen ausblenden",
"column_header.moveLeft_settings": "Diese Spalte nach links verschieben",
@@ -635,7 +638,7 @@
"empty_column.account_featured_self.no_collections_button": "Sammlung erstellen",
"empty_column.account_featured_self.no_collections_hide_tab": "Diesen Tab ausblenden",
"empty_column.account_featured_self.pre_collections": "Sammlungen sind bald verfügbar",
"empty_column.account_featured_self.pre_collections_desc": "Mit Sammlungen (ab Mastodon v4.6) kannst du eigene Liste kuratieren, um auf empfehlenswerte Profile hinzuweisen.",
"empty_column.account_featured_self.pre_collections_desc": "Mit Sammlungen (ab Mastodon v4.6) kannst du eigene Listen kuratieren, um auf empfehlenswerte Profile hinzuweisen.",
"empty_column.account_featured_self.showcase_accounts": "Präsentiere deine liebsten Konten",
"empty_column.account_featured_self.showcase_accounts_desc": "Sammlungen sind kuratierte Listen, die anderen helfen, mehr Konten im Fediverse zu entdecken.",
"empty_column.account_featured_unknown.other": "Dieser Account hat noch nichts vorgestellt.",
@@ -645,6 +648,7 @@
"empty_column.account_unavailable": "Profil nicht verfügbar",
"empty_column.blocks": "Du hast bisher keine Profile blockiert.",
"empty_column.bookmarked_statuses": "Du hast bisher keine Beiträge als Lesezeichen abgelegt. Sobald du einen Beitrag als Lesezeichen speicherst, wird er hier erscheinen.",
"empty_column.collections.featured_in": "Du wurdest noch keiner Sammlung hinzugefügt.",
"empty_column.community": "Die lokale Timeline ist leer. Schreibe einen öffentlichen Beitrag, um den Stein ins Rollen zu bringen!",
"empty_column.direct": "Du hast noch keine privaten Erwähnungen. Sobald du eine sendest oder erhältst, wird sie hier erscheinen.",
"empty_column.disabled_feed": "Diesen Feed haben deine Server-Administrator*innen deaktiviert.",
@@ -962,8 +966,8 @@
"notification.quoted_update": "{name} bearbeitete einen von dir zitierten Beitrag",
"notification.reblog": "{name} teilte deinen Beitrag",
"notification.reblog.name_and_others_with_link": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> teilten deinen Beitrag",
"notification.relationships_severance_event": "Verbindungen mit {name} verloren",
"notification.relationships_severance_event.account_suspension": "Ein Admin von {from} hat {target} gesperrt. Du wirst von diesem Profil keine Updates mehr erhalten und auch nicht mit ihm interagieren können.",
"notification.relationships_severance_event": "Verbindung zu {name} verloren",
"notification.relationships_severance_event.account_suspension": "Administrator*innen von {from} haben den Zugriff auf den externen Server {target} gesperrt. Das bedeutet, dass du keine Inhalte mehr von dort erhältst. Du kannst auch nicht mehr mit den Profilen dieses Servers interagieren.",
"notification.relationships_severance_event.domain_block": "Ein Admin von {from} hat {target} gesperrt – darunter {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst.",
"notification.relationships_severance_event.learn_more": "Mehr erfahren",
"notification.relationships_severance_event.user_domain_block": "Du hast {target} blockiert – {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst, wurden entfernt.",
+14 -17
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Αποκλεισμός τομέα {domain}",
"account.block_short": "Αποκλεισμός",
"account.blocked": "Αποκλεισμένος/η",
"account.blocking": "Αποκλείεται",
"account.cancel_follow_request": "Απόσυρση αιτήματος παρακολούθησης",
"account.copy": "Αντιγραφή συνδέσμου προφίλ",
"account.direct": "Ιδιωτική επισήμανση @{name}",
"account.disable_notifications": "Σταμάτα να με ειδοποιείς όταν δημοσιεύει ο @{name}",
"account.domain_blocking": "Αποκλείεται ο τομέας",
"account.edit_note": "Επεξεργασία προσωπικής σημείωσης",
"account.edit_profile": "Επεξεργασία προφίλ",
"account.edit_profile_short": "Επεξεργασία",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Σίγαση ειδοποιήσεων",
"account.mute_short": "Σίγαση",
"account.muted": "Σε σίγαση",
"account.muting": "Σίγαση",
"account.mutual": "Ακολουθείτε ο ένας τον άλλο",
"account.name.copy": "Αντιγραφή πλήρους ονόματος χρήστη",
"account.name.help.domain": "Το {domain} είναι ο διακομιστής που φιλοξενεί το προφίλ και τις αναρτήσεις του χρήστη.",
@@ -131,11 +128,11 @@
"account.note.edit_button": "Επεξεργασία",
"account.note.title": "Προσωπική σημείωση (ορατή μόνο σε εσάς)",
"account.open_original_page": "Άνοιγμα πρωτότυπης σελίδας",
"account.pending": "Εκκρεμεί",
"account.posts": "Αναρτήσεις",
"account.remove_from_followers": "Κατάργηση {name} από τους ακόλουθους",
"account.report": "Αναφορά @{name}",
"account.requested_follow": "Ο/Η {name} αιτήθηκε να σε ακολουθήσει",
"account.requests_to_follow_you": "Αιτήματα για να σε ακολουθήσουν",
"account.share": "Κοινοποίηση του προφίλ @{name}",
"account.show_reblogs": "Εμφάνιση ενισχύσεων από @{name}",
"account.statuses_counter": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
@@ -227,8 +224,6 @@
"account_edit.profile_tab.show_media.title": "Εμφάνιση καρτέλας «Πολυμέσα»",
"account_edit.profile_tab.show_media_replies.description": "Όταν ενεργοποιηθεί, η καρτέλα Πολυμέσα εμφανίζει τόσο τις αναρτήσεις σας όσο και τις απαντήσεις σας σε αναρτήσεις άλλων ατόμων.",
"account_edit.profile_tab.show_media_replies.title": "Συμπερίληψη απαντήσεων στην καρτέλα «Πολυμέσα»",
"account_edit.profile_tab.subtitle": "Προσαρμόστε τις καρτέλες στο προφίλ σας και τι εμφανίζουν.",
"account_edit.profile_tab.title": "Ρυθμίσεις καρτελών προφίλ",
"account_edit.save": "Αποθήκευση",
"account_edit.upload_modal.back": "Πίσω",
"account_edit.upload_modal.done": "Έγινε",
@@ -387,11 +382,9 @@
"collections.create.accounts_title": "Ποιον θα αναδείξετε σε αυτήν τη συλλογή;",
"collections.create.basic_details_title": "Βασικά στοιχεία",
"collections.create.steps": "Βήμα {step}/{total}",
"collections.create_a_collection_hint": "Δημιουργήστε μια συλλογή για να προτείνετε ή να μοιραστείτε τους αγαπημένους σας λογαριασμούς με άλλους.",
"collections.create_collection": "Δημιουργία συλλογής",
"collections.delete_collection": "Διαγραφή συλλογής",
"collections.description_length_hint": "Όριο 100 χαρακτήρων",
"collections.detail.accounts_heading": "Λογαριασμοί",
"collections.detail.author_added_you_on_date": "Ο χρήστης {author} σας πρόσθεσε στις {date}",
"collections.detail.loading": "Γίνεται φόρτωση της συλλογής…",
"collections.detail.revoke_inclusion": "Αφαίρεσε με",
@@ -400,11 +393,14 @@
"collections.detail.share": "Κοινοποιήστε αυτήν τη συλλογή",
"collections.detail.you_are_in_this_collection": "Είστε αναδεδειγμένοι σε αυτήν τη συλλογή",
"collections.edit_details": "Επεξεργασία λεπτομερειών",
"collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.",
"collections.hidden_accounts_description": "Έχετε αποκλείσει ή κάνει σίγαση {count, plural, one {αυτόν τον χρήστη} other {αυτούς τους χρήστες}}",
"collections.hidden_accounts_link": "{count, plural, one {# κρυμμένος λογαριασμός} other {# κρυμμένοι λογαριασμοί}}",
"collections.hints.accounts_counter": "{count}/{max} λογαριασμοί",
"collections.last_updated_at": "Τελευταία ενημέρωση: {date}",
"collections.list.collections_with_count": "{count, plural, one {# Συλλογή} other {# Συλλογές}}",
"collections.list.created_by_author": "Δημιουργήθηκε από {name}",
"collections.list.created_by_you": "Δημιουργήθηκε από εσάς",
"collections.list.featuring_you": "Σας αναδεικνύει",
"collections.manage_accounts": "Διαχείριση λογαριασμών",
"collections.mark_as_sensitive": "Σήμανση ως ευαίσθητο",
"collections.mark_as_sensitive_hint": "Κρύβει την περιγραφή και τους λογαριασμούς της συλλογής πίσω από μια προειδοποίηση περιεχομένου. Το όνομα της συλλογής θα είναι ακόμη ορατό.",
@@ -412,7 +408,8 @@
"collections.maximum_collection_count_reached": "Έχεις δημιουργήσει τον μέγιστο αριθμό συλλογών",
"collections.name_length_hint": "Όριο 40 χαρακτήρων",
"collections.new_collection": "Νέα συλλογή",
"collections.no_collections_yet": "Καμία συλλογή ακόμη.",
"collections.pending_accounts.message": "Οι λογαριασμοί μπορεί να εμφανίζονται ως εκκρεμείς όταν περιμένουμε απάντηση από τον χρήστη ή τον διακομιστή τους. Μόνο εσείς μπορείτε να δείτε εκκρεμείς λογαριασμούς.",
"collections.pending_accounts.title": "Γιατί βλέπω εκκρεμείς λογαριασμούς;",
"collections.remove_account": "Αφαίρεση",
"collections.report_collection": "Αναφορά αυτής της συλλογής",
"collections.revoke_collection_inclusion": "Αφαίρεσε τον εαυτό μου από αυτήν τη συλλογή",
@@ -454,11 +451,11 @@
"column.list_members": "Διαχείριση μελών λίστας",
"column.lists": "Λίστες",
"column.mutes": "Αποσιωπημένοι χρήστες",
"column.my_collections": "Οι συλλογές μου",
"column.notifications": "Ειδοποιήσεις",
"column.other_collections": "Συλλογές από {name}",
"column.pins": "Καρφιτσωμένες αναρτήσεις",
"column.public": "Ομοσπονδιακή ροή",
"column.your_collections": "Οι Συλλογές σας",
"column_back_button.label": "Πίσω",
"column_header.hide_settings": "Απόκρυψη ρυθμίσεων",
"column_header.moveLeft_settings": "Μεταφορά στήλης στα αριστερά",
@@ -959,16 +956,16 @@
"notification.moderation_warning.action_suspend": "Ο λογαριασμός σου έχει ανασταλεί.",
"notification.own_poll": "Η δημοσκόπησή σου έληξε",
"notification.poll": "Μία ψηφοφορία στην οποία συμμετείχες έχει τελειώσει",
"notification.quoted_update": "Ο χρήστης {name} επεξεργάστηκε μία ανάρτηση που παρέθεσες",
"notification.quoted_update": "{name} επεξεργάστηκε μία ανάρτηση που παρέθεσες",
"notification.reblog": "Ο/Η {name} ενίσχυσε την ανάρτηση σου",
"notification.reblog.name_and_others_with_link": "{name} και <a>{count, plural, one {# ακόμα} other {# ακόμα}}</a> ενίσχυσαν την ανάρτησή σου",
"notification.relationships_severance_event": "Χάθηκε η σύνδεση με το {name}",
"notification.relationships_severance_event.account_suspension": "Ένας διαχειριστής από το {from} ανέστειλε το {target}, πράγμα που σημαίνει ότι δεν μπορείς πλέον να λαμβάνεις ενημερώσεις από αυτούς ή να αλληλεπιδράς μαζί τους.",
"notification.relationships_severance_event.domain_block": "Ένας διαχειριστής από {from} έχει μπλοκάρει το {target}, συμπεριλαμβανομένων {followersCount} από τους ακόλουθούς σου και {followingCount, plural, one {# λογαριασμό} other {# λογαριασμοί}} που ακολουθείς.",
"notification.relationships_severance_event": "Χάθηκαν συνδέσεις με το {name}",
"notification.relationships_severance_event.account_suspension": "Ένας διαχειριστής από το {from} ανέστειλε το {target}, το οποίο σημαίνει ότι δεν μπορείς πλέον να λαμβάνεις ενημερώσεις από αυτούς ή να αλληλεπιδράς μαζί τους.",
"notification.relationships_severance_event.domain_block": "Ένας διαχειριστής από {from} έχει μπλοκάρει το {target}, συμπεριλαμβανομένων {followersCount} από τους ακόλουθούς σου και {followingCount, plural, one {# λογαριασμό} other {# λογαριασμούς}} που ακολουθείς.",
"notification.relationships_severance_event.learn_more": "Μάθε περισσότερα",
"notification.relationships_severance_event.user_domain_block": "Έχεις αποκλείσει τον λογαριασμό {target}, αφαιρώντας {followersCount} από τους ακόλουθούς σου και {followingCount, plural, one {# λογαριασμό} other {# λογαριασμοί}} που ακολουθείς.",
"notification.relationships_severance_event.user_domain_block": "Έχεις αποκλείσει τον λογαριασμό {target}, αφαιρώντας {followersCount} από τους ακόλουθούς σου και {followingCount, plural, one {# λογαριασμό} other {# λογαριασμούς}} που ακολουθείς.",
"notification.status": "Ο/Η {name} μόλις ανέρτησε κάτι",
"notification.update": "Ο/Η {name} επεξεργάστηκε μια ανάρτηση",
"notification.update": "{name} επεξεργάστηκε μια ανάρτηση",
"notification_requests.accept": "Αποδοχή",
"notification_requests.accept_multiple": "{count, plural, one {Αποδοχή # αιτήματος…} other {Αποδοχή # αιτημάτων…}}",
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Αποδοχή αιτήματος} other {Αποδοχή αιτημάτων}}",
@@ -28,12 +28,10 @@
"account.block_domain": "Block domain {domain}",
"account.block_short": "Block",
"account.blocked": "Blocked",
"account.blocking": "Blocking",
"account.cancel_follow_request": "Cancel follow",
"account.copy": "Copy link to profile",
"account.direct": "Privately mention @{name}",
"account.disable_notifications": "Stop notifying me when @{name} posts",
"account.domain_blocking": "Blocking domain",
"account.edit_note": "Edit personal note",
"account.edit_profile": "Edit profile",
"account.edit_profile_short": "Edit",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Mute notifications",
"account.mute_short": "Mute",
"account.muted": "Muted",
"account.muting": "Muting",
"account.mutual": "You follow each other",
"account.name.copy": "Copy handle",
"account.name.help.domain": "{domain} is the server that hosts the user’s profile and posts.",
@@ -135,7 +132,6 @@
"account.remove_from_followers": "Remove {name} from followers",
"account.report": "Report @{name}",
"account.requested_follow": "{name} has requested to follow you",
"account.requests_to_follow_you": "Requests to follow you",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
@@ -153,7 +149,6 @@
"account_edit.advanced_settings.bot_hint": "Signal to others that the account mainly performs automated actions and might not be monitored",
"account_edit.advanced_settings.bot_label": "Automated account",
"account_edit.advanced_settings.title": "Advanced settings",
"account_edit.bio.add_label": "",
"account_edit.bio.edit_label": "Edit bio",
"account_edit.bio.placeholder": "Add a short introduction to help others identify you.",
"account_edit.bio.title": "Bio",
@@ -169,23 +164,16 @@
"account_edit.custom_fields.tip_title": "Tip: adding verified links",
"account_edit.custom_fields.title": "Custom fields",
"account_edit.custom_fields.verified_hint": "How do I add a verified link?",
"account_edit.display_name.add_label": "He",
"account_edit.display_name.edit_label": "Xs",
"account_edit.display_name.placeholder": "Your display name is how your name appears on your profile and in timelines.",
"account_edit.display_name.title": "Display name",
"account_edit.featured_hashtags.edit_label": "Add hashtags",
"account_edit.featured_hashtags.placeholder": "Help others identify, and have quick access to, your favourite topics.",
"account_edit.featured_hashtags.title": "Featured hashtags",
"account_edit.field_actions.delete": "Delete",
"account_edit.field_actions.edit": "Edit",
"account_edit.field_delete_modal.confirm": "Are you sure you want to delete this custom field? This action can’t be undone.",
"account_edit.field_delete_modal.delete_button": "Delete",
"account_edit.field_delete_modal.title": "Delete custom field?",
"account_edit.field_edit_modal.add_title": "Add custom field",
"account_edit.field_edit_modal.discard_confirm": "Confirm",
"account_edit.field_edit_modal.discard_message": "Discord them",
"account_edit.field_edit_modal.edit_title": "Edit custom field",
"account_edit.field_edit_modal.length_warning": "Lenght_warning",
"account_edit.field_edit_modal.link_emoji_warning": "We recommend against the use of custom emoji in combination with URLs. Custom fields containing both will display as text only instead of as a link, in order to prevent user confusion.",
"account_edit.field_edit_modal.name_hint": "Eg “Personal website”",
"account_edit.field_edit_modal.name_label": "Label",
@@ -200,7 +188,6 @@
"account_edit.field_reorder_modal.drag_start": "Picked up field \"{item}\".",
"account_edit.field_reorder_modal.handle_label": "Drag field \"{item}\"",
"account_edit.field_reorder_modal.title": "Rearrange fields",
"account_edit.image_alt_modal.add_title": "Iphone xs",
"account_edit.image_alt_modal.details_content": "DO: <ul> <li>Describe yourself as pictured</li> <li>Use third person language (e.g. “Alex” instead of “me”)</li> <li>Be succinct – a few words is often enough</li> </ul> DON’T: <ul> <li>Start with “Photo of” – it’s redundant for screen readers</li> </ul> EXAMPLE: <ul> <li>“Alex wearing a green shirt and glasses”</li> </ul>",
"account_edit.image_alt_modal.details_title": "Tips: Alt text for profile photos",
"account_edit.image_alt_modal.edit_title": "Edit alt text",
@@ -214,8 +201,6 @@
"account_edit.image_edit.alt_edit_button": "Edit alt text",
"account_edit.image_edit.remove_button": "Remove image",
"account_edit.image_edit.replace_button": "Replace image",
"account_edit.item_list.delete": "Rifat Chowdhury",
"account_edit.item_list.edit": "Anas Chowdhury",
"account_edit.name_modal.add_title": "Add display name",
"account_edit.name_modal.edit_title": "Edit display name",
"account_edit.profile_tab.button_label": "Customise",
@@ -227,17 +212,11 @@
"account_edit.profile_tab.show_media.title": "Show ‘Media’ tab",
"account_edit.profile_tab.show_media_replies.description": "When enabled, Media tab shows both your posts and replies to other people’s posts.",
"account_edit.profile_tab.show_media_replies.title": "Include replies on ‘Media’ tab",
"account_edit.profile_tab.subtitle": "Customise the tabs on your profile and what they display.",
"account_edit.profile_tab.title": "Profile tab settings",
"account_edit.save": "Save",
"account_edit.upload_modal.back": "Iphone xs",
"account_edit.upload_modal.done": "Done",
"account_edit.upload_modal.next": "Next",
"account_edit.upload_modal.step_crop.zoom": "I'm ",
"account_edit.upload_modal.step_upload.button": "The only way",
"account_edit.upload_modal.step_upload.dragging": "Drop to upload",
"account_edit.upload_modal.step_upload.header": "Choose an image",
"account_edit.upload_modal.step_upload.hint": "The only way I could do that was if you had to do a lot more work and then you would be done ",
"account_edit.upload_modal.title_add.avatar": "Add profile photo",
"account_edit.upload_modal.title_add.header": "Add cover photo",
"account_edit.upload_modal.title_replace.avatar": "Replace profile photo",
@@ -373,7 +352,6 @@
"collections.accounts.empty_editor_title": "No one is in this collection yet",
"collections.accounts.empty_title": "This collection is empty",
"collections.block_collection_owner": "Block account",
"collections.by_account": "Delete",
"collections.collection_description": "Description",
"collections.collection_language": "Language",
"collections.collection_language_none": "None",
@@ -387,11 +365,9 @@
"collections.create.accounts_title": "Who will you feature in this collection?",
"collections.create.basic_details_title": "Basic details",
"collections.create.steps": "Step {step}/{total}",
"collections.create_a_collection_hint": "Create a collection to recommend or share your favourite accounts with others.",
"collections.create_collection": "Create collection",
"collections.delete_collection": "Delete collection",
"collections.description_length_hint": "100 characters limit",
"collections.detail.accounts_heading": "Accounts",
"collections.detail.author_added_you_on_date": "{author} added you on {date}",
"collections.detail.loading": "Loading collection…",
"collections.detail.revoke_inclusion": "Remove me",
@@ -400,7 +376,6 @@
"collections.detail.share": "Share this collection",
"collections.detail.you_are_in_this_collection": "You're featured in this collection",
"collections.edit_details": "Edit details",
"collections.error_loading_collections": "There was an error when trying to load your collections.",
"collections.hidden_accounts_description": "You’ve blocked or muted {count, plural, one {this user} other {these users}}",
"collections.hidden_accounts_link": "{count, plural, one {# hidden account} other {# hidden accounts}}",
"collections.hints.accounts_counter": "{count}/{max} accounts",
@@ -412,7 +387,6 @@
"collections.maximum_collection_count_reached": "You have created the maximum number of collections",
"collections.name_length_hint": "40 characters limit",
"collections.new_collection": "New collection",
"collections.no_collections_yet": "No collections yet.",
"collections.remove_account": "Remove",
"collections.report_collection": "Report this collection",
"collections.revoke_collection_inclusion": "Remove myself from this collection",
@@ -454,7 +428,6 @@
"column.list_members": "Manage list members",
"column.lists": "Lists",
"column.mutes": "Muted users",
"column.my_collections": "My collections",
"column.notifications": "Notifications",
"column.other_collections": "Collections by {name}",
"column.pins": "Pinned posts",
+16 -12
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Block domain {domain}",
"account.block_short": "Block",
"account.blocked": "Blocked",
"account.blocking": "Blocking",
"account.cancel_follow_request": "Cancel follow",
"account.copy": "Copy link to profile",
"account.direct": "Privately mention @{name}",
"account.disable_notifications": "Stop notifying me when @{name} posts",
"account.domain_blocking": "Blocking domain",
"account.edit_note": "Edit personal note",
"account.edit_profile": "Edit profile",
"account.edit_profile_short": "Edit",
@@ -111,12 +109,11 @@
"account.mute_notifications_short": "Mute notifications",
"account.mute_short": "Mute",
"account.muted": "Muted",
"account.muting": "Muting",
"account.mutual": "You follow each other",
"account.name.copy": "Copy handle",
"account.name.help.domain": "{domain} is the server that hosts the user’s profile and posts.",
"account.name.help.domain_self": "{domain} is your server that hosts your profile and posts.",
"account.name.help.footer": "Just like you can send emails to people using different email providers, you can interact with people on other Mastodon servers, and with anyone on other Mastodon-compatible social apps.",
"account.name.help.footer": "Just like you can send emails to people using different email providers, you can interact with people on other Mastodon servers, and with anyone on other Fediverse apps.",
"account.name.help.header": "A handle is like an email address",
"account.name.help.username": "{username} is this account’s username on their server. Someone on another server might have the same username.",
"account.name.help.username_self": "{username} is your username on this server. Someone on another server might have the same username.",
@@ -131,11 +128,11 @@
"account.note.edit_button": "Edit",
"account.note.title": "Personal note (visible only to you)",
"account.open_original_page": "Open original page",
"account.pending": "Pending",
"account.posts": "Posts",
"account.remove_from_followers": "Remove {name} from followers",
"account.report": "Report @{name}",
"account.requested_follow": "{name} has requested to follow you",
"account.requests_to_follow_you": "Requests to follow you",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
@@ -227,8 +224,10 @@
"account_edit.profile_tab.show_media.title": "Show ‘Media’ tab",
"account_edit.profile_tab.show_media_replies.description": "When enabled, Media tab shows both your posts and replies to other people’s posts.",
"account_edit.profile_tab.show_media_replies.title": "Include replies on ‘Media’ tab",
"account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.",
"account_edit.profile_tab.title": "Profile tab settings",
"account_edit.profile_tab.show_relations.description": "Shows accounts you follow and followers to other users in your profile. People will still be able to see if you are following them.",
"account_edit.profile_tab.show_relations.title": "Show ‘Followers’ and ‘Following’",
"account_edit.profile_tab.subtitle": "Customize how your profile is displayed.",
"account_edit.profile_tab.title": "Profile display settings",
"account_edit.save": "Save",
"account_edit.upload_modal.back": "Back",
"account_edit.upload_modal.done": "Done",
@@ -256,6 +255,7 @@
"account_edit_tags.search_placeholder": "Enter a hashtag…",
"account_edit_tags.suggestions": "Suggestions:",
"account_edit_tags.tag_status_count": "{count, plural, one {# post} other {# posts}}",
"account_list.hidden_notice": "This is only visible to you. To show this list to others, go to <link>{page} > {modal} > {field}</link>.",
"account_list.total": "{total, plural, one {# account} other {# accounts}}",
"account_note.placeholder": "Click to add note",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
@@ -387,11 +387,9 @@
"collections.create.accounts_title": "Who will you feature in this collection?",
"collections.create.basic_details_title": "Basic details",
"collections.create.steps": "Step {step}/{total}",
"collections.create_a_collection_hint": "Create a collection to recommend or share your favourite accounts with others.",
"collections.create_collection": "Create collection",
"collections.delete_collection": "Delete collection",
"collections.description_length_hint": "100 characters limit",
"collections.detail.accounts_heading": "Accounts",
"collections.detail.author_added_you_on_date": "{author} added you on {date}",
"collections.detail.loading": "Loading collection…",
"collections.detail.revoke_inclusion": "Remove me",
@@ -400,11 +398,15 @@
"collections.detail.share": "Share this collection",
"collections.detail.you_are_in_this_collection": "You're featured in this collection",
"collections.edit_details": "Edit details",
"collections.error_loading_collections": "There was an error when trying to load your collections.",
"collections.error_loading_collections": "There was an error when trying to load these collections.",
"collections.hidden_accounts_description": "You’ve blocked or muted {count, plural, one {this user} other {these users}}",
"collections.hidden_accounts_link": "{count, plural, one {# hidden account} other {# hidden accounts}}",
"collections.hints.accounts_counter": "{count}/{max} accounts",
"collections.last_updated_at": "Last updated: {date}",
"collections.list.collections_with_count": "{count, plural, one {# Collection} other {# Collections}}",
"collections.list.created_by_author": "Created by {name}",
"collections.list.created_by_you": "Created by you",
"collections.list.featuring_you": "Featuring you",
"collections.manage_accounts": "Manage accounts",
"collections.mark_as_sensitive": "Mark as sensitive",
"collections.mark_as_sensitive_hint": "Hides the collection's description and accounts behind a content warning. The collection name will still be visible.",
@@ -412,7 +414,8 @@
"collections.maximum_collection_count_reached": "You have created the maximum number of collections",
"collections.name_length_hint": "40 characters limit",
"collections.new_collection": "New collection",
"collections.no_collections_yet": "No collections yet.",
"collections.pending_accounts.message": "Accounts may appear as pending when we’re awaiting a response from the user or their server. Only you can see pending accounts.",
"collections.pending_accounts.title": "Why am I seeing pending accounts?",
"collections.remove_account": "Remove",
"collections.report_collection": "Report this collection",
"collections.revoke_collection_inclusion": "Remove myself from this collection",
@@ -454,11 +457,11 @@
"column.list_members": "Manage list members",
"column.lists": "Lists",
"column.mutes": "Muted users",
"column.my_collections": "My collections",
"column.notifications": "Notifications",
"column.other_collections": "Collections by {name}",
"column.pins": "Pinned posts",
"column.public": "Federated timeline",
"column.your_collections": "Your Collections",
"column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
@@ -645,6 +648,7 @@
"empty_column.account_unavailable": "Profile unavailable",
"empty_column.blocks": "You haven't blocked any users yet.",
"empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
"empty_column.collections.featured_in": "You have not been added to any collections yet.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.",
"empty_column.disabled_feed": "This feed has been disabled by your server administrators.",
-5
View File
@@ -22,12 +22,10 @@
"account.block_domain": "Bloki la domajnon {domain}",
"account.block_short": "Bloko",
"account.blocked": "Blokita",
"account.blocking": "Bloko",
"account.cancel_follow_request": "Nuligi peton por sekvado",
"account.copy": "Kopii ligilon al profilo",
"account.direct": "Private mencii @{name}",
"account.disable_notifications": "Ĉesu sciigi min kiam @{name} afiŝas",
"account.domain_blocking": "Blokas domajnon",
"account.edit_profile": "Redakti la profilon",
"account.edit_profile_short": "Redakti",
"account.enable_notifications": "Sciigu min kiam @{name} afiŝos",
@@ -65,7 +63,6 @@
"account.mute_notifications_short": "Silentigu sciigojn",
"account.mute_short": "Silentigu",
"account.muted": "Silentigita",
"account.muting": "Silentas",
"account.mutual": "Vi sekvas unu la alian",
"account.no_bio": "Neniu priskribo estas provizita.",
"account.node_modal.field_label": "Persona noto",
@@ -74,7 +71,6 @@
"account.remove_from_followers": "Forigi {name}-n de sekvantoj",
"account.report": "Raporti @{name}",
"account.requested_follow": "{name} petis sekvi vin",
"account.requests_to_follow_you": "Petoj sekvi vin",
"account.share": "Diskonigi la profilon de @{name}",
"account.show_reblogs": "Montri diskonigojn de @{name}",
"account.statuses_counter": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}}",
@@ -156,7 +152,6 @@
"closed_registrations_modal.title": "Registriĝi en Mastodon",
"collections.create_collection": "Krei kolekton",
"collections.delete_collection": "Forigi kolekton",
"collections.error_loading_collections": "Okazis eraro provante ŝargi viajn kolektojn.",
"column.about": "Pri",
"column.blocks": "Blokitaj uzantoj",
"column.bookmarks": "Legosignoj",
+8 -11
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Bloquear dominio {domain}",
"account.block_short": "Bloquear",
"account.blocked": "Bloqueado",
"account.blocking": "Bloqueo",
"account.cancel_follow_request": "Dejar de seguir",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada a @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} envíe mensajes",
"account.domain_blocking": "Dominio bloqueado",
"account.edit_note": "Editar nota personal",
"account.edit_profile": "Editar perfil",
"account.edit_profile_short": "Editar",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Silenciar notificaciones",
"account.mute_short": "Silenciar",
"account.muted": "Silenciado",
"account.muting": "Silenciada",
"account.mutual": "Se siguen mutuamente",
"account.name.copy": "Copiar alias",
"account.name.help.domain": "«{domain}» es el servidor que hospeda el perfil y los mensajes del usuario.",
@@ -131,11 +128,11 @@
"account.note.edit_button": "Editar",
"account.note.title": "Nota personal (visible solo para vos)",
"account.open_original_page": "Abrir página original",
"account.pending": "Pendiente",
"account.posts": "Mensajes",
"account.remove_from_followers": "Quitar a {name} de tus seguidores",
"account.report": "Denunciar a @{name}",
"account.requested_follow": "{name} solicitó seguirte",
"account.requests_to_follow_you": "Solicita seguirte",
"account.share": "Compartir el perfil de @{name}",
"account.show_reblogs": "Mostrar adhesiones de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
@@ -227,8 +224,6 @@
"account_edit.profile_tab.show_media.title": "Mostrar pestaña «Medios»",
"account_edit.profile_tab.show_media_replies.description": "Cuando está habilitada, la pestaña «Medios» muestra tanto tus mensajes como tus respuestas a otras cuentas.",
"account_edit.profile_tab.show_media_replies.title": "Incluir respuestas en la pestaña «Medios»",
"account_edit.profile_tab.subtitle": "Personalizá las pestañas en tu perfil y qué van a mostrar.",
"account_edit.profile_tab.title": "Configuración de pestaña de perfil",
"account_edit.save": "Guardar",
"account_edit.upload_modal.back": "Volver",
"account_edit.upload_modal.done": "Listo",
@@ -387,11 +382,9 @@
"collections.create.accounts_title": "¿A quién vas a destacar en esta colección?",
"collections.create.basic_details_title": "Detalles básicos",
"collections.create.steps": "Paso {step}/{total}",
"collections.create_a_collection_hint": "Creá una colección para recomendar o compartir tus cuentas favoritas con otras personas.",
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Límite de 100 caracteres",
"collections.detail.accounts_heading": "Cuentas",
"collections.detail.author_added_you_on_date": "{author} te agregó el {date}",
"collections.detail.loading": "Cargando colección…",
"collections.detail.revoke_inclusion": "Quitarme",
@@ -400,11 +393,14 @@
"collections.detail.share": "Compartir esta colección",
"collections.detail.you_are_in_this_collection": "Te destacaron en esta colección",
"collections.edit_details": "Editar detalles",
"collections.error_loading_collections": "Hubo un error al intentar cargar tus colecciones.",
"collections.hidden_accounts_description": "Bloqueaste o silenciaste a {count, plural, one {este usuario} other {estos usuarios}}",
"collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}",
"collections.hints.accounts_counter": "{count}/{max} cuentas",
"collections.last_updated_at": "Última actualización: {date}",
"collections.list.collections_with_count": "{count, plural, one {# Colección} other {# Colecciones}}",
"collections.list.created_by_author": "Creadas por {name}",
"collections.list.created_by_you": "Creadas por vos",
"collections.list.featuring_you": "En las que te destacan",
"collections.manage_accounts": "Administrar cuentas",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta la descripción de la colección y las cuentas detrás de una advertencia de contenido. El nombre de la colección seguirá siendo visible.",
@@ -412,7 +408,8 @@
"collections.maximum_collection_count_reached": "Creaste el número máximo de colecciones",
"collections.name_length_hint": "Límite de 40 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "No hay colecciones aún.",
"collections.pending_accounts.message": "Las cuentas pueden aparecer como pendientes cuando estamos esperando una respuesta del usuario o su servidor. Solo vos podés ver cuentas pendientes.",
"collections.pending_accounts.title": "¿Por qué estoy viendo cuentas pendientes?",
"collections.remove_account": "Quitar",
"collections.report_collection": "Denunciar esta colección",
"collections.revoke_collection_inclusion": "Quitarme de esta colección",
@@ -454,11 +451,11 @@
"column.list_members": "Administrar miembros de la lista",
"column.lists": "Listas",
"column.mutes": "Usuarios silenciados",
"column.my_collections": "Mis colecciones",
"column.notifications": "Notificaciones",
"column.other_collections": "Colecciones de {name}",
"column.pins": "Mensajes fijados",
"column.public": "Línea temporal federada",
"column.your_collections": "Tus colecciones",
"column_back_button.label": "Volver",
"column_header.hide_settings": "Ocultar configuración",
"column_header.moveLeft_settings": "Mover columna a la izquierda",
+8 -11
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Bloquear dominio {domain}",
"account.block_short": "Bloquear",
"account.blocked": "Bloqueado",
"account.blocking": "Bloqueando",
"account.cancel_follow_request": "Cancelar seguimiento",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo",
"account.domain_blocking": "Bloqueando dominio",
"account.edit_note": "Editar nota personal",
"account.edit_profile": "Editar perfil",
"account.edit_profile_short": "Editar",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Silenciar notificaciones",
"account.mute_short": "Silenciar",
"account.muted": "Silenciado",
"account.muting": "Silenciando",
"account.mutual": "Se siguen el uno al otro",
"account.name.copy": "Copiar identificador",
"account.name.help.domain": "{domain} es el servidor que aloja el perfil y las publicaciones del usuario.",
@@ -131,11 +128,11 @@
"account.note.edit_button": "Editar",
"account.note.title": "Nota personal (visible solo para ti)",
"account.open_original_page": "Abrir página original",
"account.pending": "Pendiente",
"account.posts": "Publicaciones",
"account.remove_from_followers": "Eliminar {name} de tus seguidores",
"account.report": "Denunciar a @{name}",
"account.requested_follow": "{name} ha solicitado seguirte",
"account.requests_to_follow_you": "Solicita seguirte",
"account.share": "Compartir el perfil de @{name}",
"account.show_reblogs": "Mostrar impulsos de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
@@ -227,8 +224,6 @@
"account_edit.profile_tab.show_media.title": "Mostrar pestaña «Multimedia»",
"account_edit.profile_tab.show_media_replies.description": "Cuando está habilitada, la pestaña Multimedia muestra tanto tus publicaciones como tus respuestas a las publicaciones de otras personas.",
"account_edit.profile_tab.show_media_replies.title": "Incluir respuestas en la pestaña «Multimedia»",
"account_edit.profile_tab.subtitle": "Personaliza las pestañas de tu perfil y lo que muestran.",
"account_edit.profile_tab.title": "Configuración de la pestaña de perfil",
"account_edit.save": "Guardar",
"account_edit.upload_modal.back": "Volver",
"account_edit.upload_modal.done": "Hecho",
@@ -387,11 +382,9 @@
"collections.create.accounts_title": "¿A quién incluirás en esta colección?",
"collections.create.basic_details_title": "Detalles básicos",
"collections.create.steps": "Paso {step}/{total}",
"collections.create_a_collection_hint": "Crea una colección para recomendar o compartir tus cuentas favoritas con otras personas.",
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Limitado a 100 caracteres",
"collections.detail.accounts_heading": "Cuentas",
"collections.detail.author_added_you_on_date": "{author} te agregó el {date}",
"collections.detail.loading": "Cargando colección…",
"collections.detail.revoke_inclusion": "Excluirme",
@@ -400,11 +393,14 @@
"collections.detail.share": "Compartir esta colección",
"collections.detail.you_are_in_this_collection": "Apareces en esta colección",
"collections.edit_details": "Editar detalles",
"collections.error_loading_collections": "Se produjo un error al intentar cargar tus colecciones.",
"collections.hidden_accounts_description": "Has bloqueado o silenciado {count, plural, one {a este usuario} other {a estos usuarios}}",
"collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}",
"collections.hints.accounts_counter": "{count}/{max} cuentas",
"collections.last_updated_at": "Última actualización: {date}",
"collections.list.collections_with_count": "{count, plural,one {# Colección} other {# Colecciones}}",
"collections.list.created_by_author": "Creado por {name}",
"collections.list.created_by_you": "Creado por ti",
"collections.list.featuring_you": "Con tu partipación",
"collections.manage_accounts": "Administrar cuentas",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta la descripción y las cuentas de la colección detrás de una advertencia de contenido. El nombre de la colección seguirá siendo visible.",
@@ -412,7 +408,8 @@
"collections.maximum_collection_count_reached": "Has alcanzado el número máximo de colecciones",
"collections.name_length_hint": "Limitado a 40 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "No hay colecciones todavía.",
"collections.pending_accounts.message": "Las cuentas pueden aparecer como pendientes cuando estamos esperando una respuesta del usuario o de su servidor. Solo tú puedes ver las cuentas pendientes.",
"collections.pending_accounts.title": "¿Por qué estoy viendo cuentas pendientes?",
"collections.remove_account": "Eliminar",
"collections.report_collection": "Reportar esta colección",
"collections.revoke_collection_inclusion": "Excluirme de esta colección",
@@ -454,11 +451,11 @@
"column.list_members": "Administrar miembros de la lista",
"column.lists": "Listas",
"column.mutes": "Usuarios silenciados",
"column.my_collections": "Mis colecciones",
"column.notifications": "Notificaciones",
"column.other_collections": "Colecciones de {name}",
"column.pins": "Publicaciones fijadas",
"column.public": "Cronología federada",
"column.your_collections": "Tus colecciones",
"column_back_button.label": "Atrás",
"column_header.hide_settings": "Ocultar configuración",
"column_header.moveLeft_settings": "Mover columna a la izquierda",
-11
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Bloquear dominio {domain}",
"account.block_short": "Bloquear",
"account.blocked": "Bloqueado",
"account.blocking": "Bloqueando",
"account.cancel_follow_request": "Retirar solicitud de seguimiento",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada a @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo",
"account.domain_blocking": "Bloqueando dominio",
"account.edit_note": "Eidtar nota personal",
"account.edit_profile": "Editar perfil",
"account.edit_profile_short": "Editar",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Silenciar notificaciones",
"account.mute_short": "Silenciar",
"account.muted": "Silenciado",
"account.muting": "Silenciando",
"account.mutual": "Os seguís mutuamente",
"account.name.copy": "Copiar alias",
"account.name.help.domain": "{domain} es el servidor que alberga el perfil y las publicaciones del usuario.",
@@ -135,7 +132,6 @@
"account.remove_from_followers": "Eliminar {name} de tus seguidores",
"account.report": "Reportar a @{name}",
"account.requested_follow": "{name} ha solicitado seguirte",
"account.requests_to_follow_you": "Solicita seguirte",
"account.share": "Compartir el perfil de @{name}",
"account.show_reblogs": "Mostrar impulsos de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
@@ -227,8 +223,6 @@
"account_edit.profile_tab.show_media.title": "Mostrar pestaña «Multimedia»",
"account_edit.profile_tab.show_media_replies.description": "Cuando está activado, la pestaña Multimedia muestra tanto tus publicaciones como tus respuestas a las publicaciones de otras personas.",
"account_edit.profile_tab.show_media_replies.title": "Incluir respuestas en la pestaña «Multimedia»",
"account_edit.profile_tab.subtitle": "Personaliza las pestañas de tu perfil y lo que muestran.",
"account_edit.profile_tab.title": "Configuración de la pestaña de perfil",
"account_edit.save": "Guardar",
"account_edit.upload_modal.back": "Atrás",
"account_edit.upload_modal.done": "Hecho",
@@ -387,11 +381,9 @@
"collections.create.accounts_title": "¿A quién vas a destacar en esta colección?",
"collections.create.basic_details_title": "Datos básicos",
"collections.create.steps": "Paso {step}/{total}",
"collections.create_a_collection_hint": "Crea una colección para recomendar o compartir tus cuentas favoritas con otros.",
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Limitado a 100 caracteres",
"collections.detail.accounts_heading": "Cuentas",
"collections.detail.author_added_you_on_date": "{author} te agregó el {date}",
"collections.detail.loading": "Cargando colección…",
"collections.detail.revoke_inclusion": "Sácame de aquí",
@@ -400,7 +392,6 @@
"collections.detail.share": "Compartir esta colección",
"collections.detail.you_are_in_this_collection": "Apareces en esta colección",
"collections.edit_details": "Editar detalles",
"collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.",
"collections.hidden_accounts_description": "Has bloqueado o silenciado {count, plural,one {a este usuario}other {a estos usuarios}}",
"collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}",
"collections.hints.accounts_counter": "{count}/{max} cuentas",
@@ -412,7 +403,6 @@
"collections.maximum_collection_count_reached": "Has creado el número máximo de colecciones",
"collections.name_length_hint": "Límite de 40 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "Aún no hay colecciones.",
"collections.remove_account": "Eliminar",
"collections.report_collection": "Informar de esta colección",
"collections.revoke_collection_inclusion": "Sácame de esta colección",
@@ -454,7 +444,6 @@
"column.list_members": "Administrar miembros de la lista",
"column.lists": "Listas",
"column.mutes": "Usuarios silenciados",
"column.my_collections": "Mis colecciones",
"column.notifications": "Notificaciones",
"column.other_collections": "Colecciones de {name}",
"column.pins": "Publicaciones fijadas",
+33 -36
View File
@@ -28,12 +28,10 @@
"account.block_domain": "Blokeeri kõik domeenist {domain}",
"account.block_short": "Blokeeri",
"account.blocked": "Blokeeritud",
"account.blocking": "Blokeeritud kasutaja",
"account.cancel_follow_request": "Võta jälgimissoov tagasi",
"account.copy": "Kopeeri profiili link",
"account.direct": "Maini privaatselt kasutajat @{name}",
"account.disable_notifications": "Ära teavita, kui @{name} postitab",
"account.domain_blocking": "Blokeeritud domeen",
"account.edit_note": "Muuda isiklikku märget",
"account.edit_profile": "Muuda profiili",
"account.edit_profile_short": "Muuda",
@@ -85,9 +83,9 @@
"account.languages": "Muuda tellitud keeli",
"account.last_active": "Viimati aktiivne",
"account.link_verified_on": "Selle lingi autorsus on kontrollitud {date}",
"account.locked_info": "Selle konto privaatsussätteks on lukustatud. Omanik vaatab käsitsi üle, kes teda jälgida saab.",
"account.locked_info": "Selle konto privaatsusolek on „lukustatud“. Omanik vaatab üle, kes teda jälgida saab.",
"account.media": "Meedium",
"account.mention": "Maini @{name}",
"account.mention": "Maini kasutajat @{name}",
"account.menu.add_to_list": "Lisa loendisse…",
"account.menu.block": "Blokeeri kasutajakonto",
"account.menu.block_domain": "Blokeeri {domain}",
@@ -111,7 +109,6 @@
"account.mute_notifications_short": "Summuta teavitused",
"account.mute_short": "Summuta",
"account.muted": "Summutatud",
"account.muting": "Summutatud konto",
"account.mutual": "Te jälgite teineteist",
"account.name.copy": "Kopeeri kasutajatunnus",
"account.name.help.domain": "{domain} on server, kus antud konto koos profiiliga asub ning kust postitused saavad alguse.",
@@ -131,11 +128,11 @@
"account.note.edit_button": "Muuda",
"account.note.title": "Isiklikud märked (nähtavad vaid sulle)",
"account.open_original_page": "Ava algne leht",
"account.pending": "Ootel",
"account.posts": "Postitused",
"account.remove_from_followers": "Eemalda {name} jälgijate seast",
"account.report": "Teata kasutajast {name}",
"account.requested_follow": "{name} on soovinud sinu jälgimist",
"account.requests_to_follow_you": "soovib sind jälgida",
"account.share": "Jaga @{name} profiili",
"account.show_reblogs": "Näita @{name} jagamisi",
"account.statuses_counter": "{count, plural, one {{counter} postitus} other {{counter} postitust}}",
@@ -192,26 +189,26 @@
"account_edit.field_edit_modal.url_warning": "Lingi lisamisel lisa palun algusesse {protocol}.",
"account_edit.field_edit_modal.value_hint": "Näit. \"https://naiteks.me\"",
"account_edit.field_edit_modal.value_label": "Väärtus",
"account_edit.field_reorder_modal.drag_cancel": "Lohistamine tühistati. Väli \"{item}\" kukutati.",
"account_edit.field_reorder_modal.drag_end": "Väli \"{item}\" kukutati.",
"account_edit.field_reorder_modal.drag_instructions": "Kohandatud väljade ümberjärjestamiseks vajuta tühikut või Enterit. Lohistamise ajal kasuta nooleklahve, et liigutada välja üles või alla. Vajuta uuesti tühikut või Enterit, et paigutada väli uuele kohale, või vajuta Esc, et tühistada.",
"account_edit.field_reorder_modal.drag_move": "Väli \"{item}\" on teisaldatud.",
"account_edit.field_reorder_modal.drag_over": "Väli \"{item}\" teisaldati \"{over}\" kohale.",
"account_edit.field_reorder_modal.drag_start": "Võeti väli \"{item}\".",
"account_edit.field_reorder_modal.handle_label": "Välja \"{item}\" lohistamine",
"account_edit.field_reorder_modal.drag_cancel": "Lohistamine tühistati. „{item}“ väli on eemaldatud.",
"account_edit.field_reorder_modal.drag_end": "„{item}“ väli eemaldati.",
"account_edit.field_reorder_modal.drag_instructions": "Kohandatud väljade järjestuse muutmiseks vajuta tühikut või sisestusklahvi. Lohistamise ajal kasuta nooleklahve, et liigutada välja üles või alla. Vajuta uuesti tühikut või sisestusklahvi, et paigutada väli uuele kohale, või tühistamiseks vajuta Esc-klahvi.",
"account_edit.field_reorder_modal.drag_move": "„{item}“ väli on teisaldatud.",
"account_edit.field_reorder_modal.drag_over": "„{item}“ väli teisaldati „{over}“ välja kohale.",
"account_edit.field_reorder_modal.drag_start": "Valisid „{item}“ välja.",
"account_edit.field_reorder_modal.handle_label": "Lohistad „{item}“ välja",
"account_edit.field_reorder_modal.title": "Väljade järjestuse muutmine",
"account_edit.image_alt_modal.add_title": "Lisa alt-tekst",
"account_edit.image_alt_modal.details_content": "TEE NII: <ul> <li>Kirjelda end pildil olevana</li> <li>Kasuta kolmandat isikut (nt „Alex“ mitte „mina“)</li> <li>Ole lakooniline – paar sõna on sageli piisav</li> </ul> ÄRA TEE NII: <ul> <li>Ära alusta sõnadega „Foto“ – see on ekraanilugeja kasutajale üleliigne</li> </ul> NÄIDE: <ul> <li>„Alex, kes kannab rohelist särki ja prille”</li> </ul>",
"account_edit.image_alt_modal.details_title": "Soovitus: Alt-tekst profiili fotode jaoks",
"account_edit.image_alt_modal.edit_title": "Muuda alt-teksti",
"account_edit.image_alt_modal.text_hint": "Alt-tekst aitab ekraanilugeja kasutajatel sinu sisu mõista.",
"account_edit.image_alt_modal.text_label": "Alt-tekst",
"account_edit.image_alt_modal.add_title": "Lisa selgitustekst",
"account_edit.image_alt_modal.details_content": "TEE NII: <ul> <li>Kirjelda end pildil olevana</li> <li>Kasuta kolmandat isikut (nt „Alex“ mitte „mina“)</li> <li>Ole lakooniline – paar sõna on sageli piisav</li> </ul> ÄRA TEE NII: <ul> <li>Ära alusta sõnadega „Foto“ – see on ekraanilugeja kasutajale üleliigne</li> </ul> NÄIDE: <ul> <li>„Alex, kes kannab rohelist särki ja prille“</li> </ul>",
"account_edit.image_alt_modal.details_title": "Soovitus: Selgitustekst profiili fotode jaoks",
"account_edit.image_alt_modal.edit_title": "Muuda selgitusteksti",
"account_edit.image_alt_modal.text_hint": "Selgitustekst aitab ekraanilugeja kasutajatel sinu sisu mõista.",
"account_edit.image_alt_modal.text_label": "Selgitustekst",
"account_edit.image_delete_modal.confirm": "Kas oled kindel, et soovid selle pildi kustutada? Seda toimingut ei saa tagasi võtta.",
"account_edit.image_delete_modal.delete_button": "Kustuta",
"account_edit.image_delete_modal.title": "Kustuta pilt?",
"account_edit.image_delete_modal.title": "Kas kustutad pildi?",
"account_edit.image_edit.add_button": "Lisa pilt",
"account_edit.image_edit.alt_add_button": "Lisa alt-tekst",
"account_edit.image_edit.alt_edit_button": "Muuda alt-teksti",
"account_edit.image_edit.alt_add_button": "Lisa selgitustekst",
"account_edit.image_edit.alt_edit_button": "Muuda selgitusteksti",
"account_edit.image_edit.remove_button": "Eemalda pilt",
"account_edit.image_edit.replace_button": "Asenda pilt",
"account_edit.item_list.delete": "Kustuta {name}",
@@ -227,8 +224,6 @@
"account_edit.profile_tab.show_media.title": "Näita multimeedia vahekaarti",
"account_edit.profile_tab.show_media_replies.description": "Sisselülitatuna näitab mediumi vahekaart, nii sinu postitusi kui su vastuseid teiste kasutajate postitustele.",
"account_edit.profile_tab.show_media_replies.title": "Kaasa vahekaardile „Meedium“ ka vastused",
"account_edit.profile_tab.subtitle": "Kohanda oma profiili vahekaarte ja nende sisu.",
"account_edit.profile_tab.title": "Profiili vahekaartide seadistused",
"account_edit.save": "Salvesta",
"account_edit.upload_modal.back": "Tagasi",
"account_edit.upload_modal.done": "Valmis",
@@ -299,14 +294,14 @@
"annual_report.summary.archetype.oracle.desc_public": "{name} lõi rohkem uusi postitusi kui vastuseid, hoides Mastodoni värskena ja tulevikku suunatuna.",
"annual_report.summary.archetype.oracle.desc_self": "Sa lõid rohkem uusi postitusi kui vastuseid, hoides Mastodoni värskena ja tulevikku suunatuna.",
"annual_report.summary.archetype.oracle.name": "Oraakel",
"annual_report.summary.archetype.pollster.desc_public": "{name} lõi rohkem küsitlusi kui teisi postituse tüüpe, äratades Mastodonis uudishimu.",
"annual_report.summary.archetype.pollster.desc_self": "Sa lõid rohkem küsitlusi kui teisi postituse tüüpe, äratades Mastodonis uudishimu.",
"annual_report.summary.archetype.pollster.desc_public": "{name} koostas rohkem küsitlusi kui teisi postituse tüüpe, äratades Mastodonis uudishimu.",
"annual_report.summary.archetype.pollster.desc_self": "Sa koostasid rohkem küsitlusi kui teisi postituse tüüpe, äratades Mastodonis uudishimu.",
"annual_report.summary.archetype.pollster.name": "Uudishimulik",
"annual_report.summary.archetype.replier.desc_public": "{name} vastas sageli teiste inimeste postitustele, pakkudes Mastodonile uusi aruteluteemasid.",
"annual_report.summary.archetype.replier.desc_self": "Sa vastasid sageli teiste inimeste postitustele, pakkudes Mastodonile uusi aruteluteemasid.",
"annual_report.summary.archetype.replier.name": "Liblikas",
"annual_report.summary.archetype.reveal": "Näita mu põhitüüpi",
"annual_report.summary.archetype.reveal_description": "Täname, et oled Mastodoni liige! Aeg on välja selgitada, millist põhitüüpi sa {year}. kehastasid.",
"annual_report.summary.archetype.reveal_description": "Täname, et oled tegutsed Mastodoni võrgus! Aeg on välja selgitada, millist põhitüüpi sa {year}. aastal kehastasid.",
"annual_report.summary.archetype.title_public": "Kasutaja {name} põhitüüp",
"annual_report.summary.archetype.title_self": "Sinu põhitüüp",
"annual_report.summary.close": "Sulge",
@@ -321,7 +316,7 @@
"annual_report.summary.most_used_hashtag.used_count": "Sa lisasid selle teemaviite {count, plural, one {ühele postitusele} other {#-le postitusele}}.",
"annual_report.summary.most_used_hashtag.used_count_public": "{name} kasutas seda teemaviidet {count, plural, one {ühes postituses} other {#-s postituses}}.",
"annual_report.summary.new_posts.new_posts": "uued postitused",
"annual_report.summary.percentile.text": "<topLabel>See paneb su top</topLabel><percentage></percentage><bottomLabel> {domain} kasutajate hulka.</bottomLabel>",
"annual_report.summary.percentile.text": "<topLabel>See paneb su top</topLabel><percentage></percentage><bottomLabel> {domain} serveri kasutajate hulka.</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "Vägev.",
"annual_report.summary.share_elsewhere": "Jaga mujal",
"annual_report.summary.share_message": "Minu arhetüüp on {archetype}!",
@@ -331,12 +326,12 @@
"block_modal.remote_users_caveat": "Serverile {domain} edastatakse palve otsust järgida. Ometi pole see tagatud, kuna mõned serverid võivad blokeeringuid käsitleda omal moel. Avalikud postitused võivad tuvastamata kasutajatele endiselt näha olla.",
"block_modal.show_less": "Kuva vähem",
"block_modal.show_more": "Kuva rohkem",
"block_modal.they_cant_mention": "Ta ei saa mainida sind ega jälgida.",
"block_modal.they_cant_mention": "Ta ei saa sind mainida ega jälgida.",
"block_modal.they_cant_see_posts": "Ta ei näe sinu postitusi ja sa ei näe tema omi.",
"block_modal.they_will_know": "Ta näeb, et ta on blokeeritud.",
"block_modal.title": "Blokeeri kasutaja?",
"block_modal.title": "Kas blokeerid kasutaja?",
"block_modal.you_wont_see_mentions": "Sa ei näe postitusi, mis mainivad teda.",
"boost_modal.combo": "Vajutades {combo}, saab selle edaspidi vahele jätta",
"boost_modal.combo": "Vajutades {combo}, saad selle edaspidi vahele jätta",
"boost_modal.reblog": "Jagada postitust?",
"boost_modal.undo_reblog": "Tühista postituse jagamine?",
"bundle_column_error.copy_stacktrace": "Kopeeri veateade",
@@ -387,11 +382,9 @@
"collections.create.accounts_title": "Kes saavad olema selles kogumikus?",
"collections.create.basic_details_title": "Põhiandmed",
"collections.create.steps": "Samm {step}/{total}",
"collections.create_a_collection_hint": "Soovitamaks oma lemmikuid teistele kasutajatele lisa asjakohane kogumik.",
"collections.create_collection": "Loo kogumik",
"collections.delete_collection": "Kustuta kogumik",
"collections.description_length_hint": "Kuni 100 tähemärki",
"collections.detail.accounts_heading": "Kontod",
"collections.detail.author_added_you_on_date": "{author} lisas sind ajal {date}",
"collections.detail.loading": "Kogumiku laadimine…",
"collections.detail.revoke_inclusion": "Eemalda mind",
@@ -400,11 +393,14 @@
"collections.detail.share": "Jaga seda kogumikku",
"collections.detail.you_are_in_this_collection": "Sa oled selles kogumikus esile tõstetud",
"collections.edit_details": "Muuda üksikasju",
"collections.error_loading_collections": "Sinu kogumike laadimisel tekkis viga.",
"collections.hidden_accounts_description": "Oled blokeerinud või vaigistanud {count, plural, one {selle kasutaja} other {need kasutajad}}",
"collections.hidden_accounts_link": "{count, plural, one {# peidetud konto} other {# peidetud kontot}}",
"collections.hints.accounts_counter": "{count}/{max} kontot",
"collections.last_updated_at": "Viimati uuendatud: {date}",
"collections.list.collections_with_count": "{count, plural, one {# kogumik} other {# kogumikku}}",
"collections.list.created_by_author": "Looja: {name}",
"collections.list.created_by_you": "Sinu poolt loodud",
"collections.list.featuring_you": "Tõstab sind esile",
"collections.manage_accounts": "Halda kasutajakontosid",
"collections.mark_as_sensitive": "Märgi delikaatseks",
"collections.mark_as_sensitive_hint": "Peidab kogumiku kirjelduse ja kontod sisuhoiatuse taha. Kogumiku nimi ise on sellele vaatamata nähtav.",
@@ -412,7 +408,8 @@
"collections.maximum_collection_count_reached": "Sa oled loonud nii palju kogumikke, kui on lubatud",
"collections.name_length_hint": "40 märgi piir",
"collections.new_collection": "Uus kogumik",
"collections.no_collections_yet": "Kogumikke veel pole.",
"collections.pending_accounts.message": "Kui oled ootamst vastut kasutajalt oma serverist, võivad kasutajakontod tunduda olema ootel olekus. Vaid sina saad näha ootel kasutajakontosid.",
"collections.pending_accounts.title": "Miks ma näen ootel kasutajakontosid?",
"collections.remove_account": "Eemalda",
"collections.report_collection": "Raporteeri sellest kogumikust",
"collections.revoke_collection_inclusion": "Eemalda mind sellest kogumikust",
@@ -454,11 +451,11 @@
"column.list_members": "Halda loendi liikmeid",
"column.lists": "Loetelud",
"column.mutes": "Summutatud kasutajad",
"column.my_collections": "Minu kogumikud",
"column.notifications": "Teated",
"column.other_collections": "Kasutaja {name} kogumikud",
"column.pins": "Esiletõstetud postitused",
"column.public": "Föderatiivne ajajoon",
"column.your_collections": "Sinu kogumikud",
"column_back_button.label": "Tagasi",
"column_header.hide_settings": "Peida sätted",
"column_header.moveLeft_settings": "Liiguta tulp vasakule",
-4
View File
@@ -21,12 +21,10 @@
"account.block_domain": "Blokeatu {domain} domeinua",
"account.block_short": "Blokeatu",
"account.blocked": "Blokeatuta",
"account.blocking": "Eragotzitakoak",
"account.cancel_follow_request": "Baztertu jarraitzeko eskaera",
"account.copy": "Kopiatu profilerako esteka",
"account.direct": "Aipatu pribatuki @{name}",
"account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzaileak argitaratzean",
"account.domain_blocking": "Eragotzitako domeinua",
"account.edit_profile": "Editatu profila",
"account.edit_profile_short": "Editatu",
"account.enable_notifications": "Jakinarazi @{name} erabiltzaileak argitaratzean",
@@ -65,7 +63,6 @@
"account.mute_notifications_short": "Mututu jakinarazpenak",
"account.mute_short": "Mututu",
"account.muted": "Mutututa",
"account.muting": "Isilarazitakoak",
"account.mutual": "Elkar jarraitzen duzue",
"account.no_bio": "Ez da deskribapenik eman.",
"account.open_original_page": "Ireki jatorrizko orria",
@@ -73,7 +70,6 @@
"account.remove_from_followers": "Kendu {name} zure jarraitzaileengandik",
"account.report": "Salatu @{name}",
"account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du",
"account.requests_to_follow_you": "Zu jarraitzeko eskaera egin du",
"account.share": "Partekatu @{name} erabiltzailearen profila",
"account.show_reblogs": "Erakutsi @{name} erabiltzailearen bultzadak",
"account.statuses_counter": "{count, plural, one {{counter} bidalketa} other {{counter} bidalketa}}",
-4
View File
@@ -21,12 +21,10 @@
"account.block_domain": "انسداد دامنهٔ {domain}",
"account.block_short": "انسداد",
"account.blocked": "مسدود",
"account.blocking": "مسدود کرده",
"account.cancel_follow_request": "رد کردن درخواست پی‌گیری",
"account.copy": "رونوشت از پیوند به نمایه",
"account.direct": "اشارهٔ خصوصی به ‪@{name}‬",
"account.disable_notifications": "آگاه کردن من هنگام فرسته‌های ‎@{name} را متوقّف کن",
"account.domain_blocking": "دامنهٔ مسدود کرده",
"account.edit_profile": "ویرایش نمایه",
"account.edit_profile_short": "ویرایش",
"account.enable_notifications": "هنگام فرسته‌های ‎@{name} مرا آگاه کن",
@@ -65,7 +63,6 @@
"account.mute_notifications_short": "خموشی آگاهی‌ها",
"account.mute_short": "خموشی",
"account.muted": "خموش",
"account.muting": "خموشش کرده‌اید",
"account.mutual": "یکدیگر را پی می‌گیرید",
"account.no_bio": "شرحی فراهم نشده.",
"account.open_original_page": "گشودن صفحهٔ اصلی",
@@ -73,7 +70,6 @@
"account.remove_from_followers": "برداشتن {name} از پی‌گیران",
"account.report": "گزارش ‎@{name}",
"account.requested_follow": "{name} درخواست پی‌گیریتان را داد",
"account.requests_to_follow_you": "درخواست پی‌گیریتان را دارد",
"account.share": "هم‌رسانی نمایهٔ ‎@{name}",
"account.show_reblogs": "نمایش تقویت‌های ‎@{name}",
"account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",

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