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

Conflicts:
- `app/views/layouts/embedded.html.haml`:
  Upstream made a change to javascript tags next to lines changed in glitch-soc
  because of the theming system.
  Added the javascript entrypoint upstream added.
- `app/views/layouts/error.html.haml`:
  Upstream made a change to javascript tags next to lines changed in glitch-soc
  because of the theming system.
  Added the javascript entrypoint upstream added.
This commit is contained in:
Claire
2025-05-25 15:11:58 +02:00
108 changed files with 1297 additions and 355 deletions

View File

@@ -69,6 +69,10 @@ export function importFetchedStatuses(statuses) {
processStatus(status.reblog);
}
if (status.quote?.quoted_status) {
processStatus(status.quote.quoted_status);
}
if (status.poll?.id) {
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
}

View File

@@ -23,12 +23,20 @@ export function normalizeFilterResult(result) {
export function normalizeStatus(status, normalOldStatus) {
const normalStatus = { ...status };
normalStatus.account = status.account.id;
if (status.reblog && status.reblog.id) {
normalStatus.reblog = status.reblog.id;
}
if (status.quote?.quoted_status ?? status.quote?.quoted_status_id) {
normalStatus.quote = {
...status.quote,
quoted_status: status.quote.quoted_status?.id ?? status.quote?.quoted_status_id,
};
}
if (status.poll && status.poll.id) {
normalStatus.poll = status.poll.id;
}

View File

@@ -27,6 +27,7 @@ import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
import { VerifiedBadge } from 'mastodon/components/verified_badge';
import { useIdentity } from 'mastodon/identity_context';
import { me } from 'mastodon/initial_state';
import type { MenuItem } from 'mastodon/models/dropdown_menu';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
@@ -70,10 +71,12 @@ export const Account: React.FC<{
withBio?: boolean;
}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => {
const intl = useIntl();
const { signedIn } = useIdentity();
const account = useAppSelector((state) => state.accounts.get(id));
const relationship = useAppSelector((state) => state.relationships.get(id));
const dispatch = useAppDispatch();
const accountUrl = account?.url;
const isRemote = account?.acct !== account?.username;
const handleBlock = useCallback(() => {
if (relationship?.blocking) {
@@ -116,66 +119,74 @@ export const Account: React.FC<{
},
];
} else if (defaultAction !== 'block') {
const handleAddToLists = () => {
const openAddToListModal = () => {
dispatch(
openModal({
modalType: 'LIST_ADDER',
modalProps: {
accountId: id,
},
}),
);
};
if (relationship?.following || relationship?.requested || id === me) {
openAddToListModal();
} else {
dispatch(
openModal({
modalType: 'CONFIRM_FOLLOW_TO_LIST',
modalProps: {
accountId: id,
onConfirm: () => {
apiFollowAccount(id)
.then((relationship) => {
dispatch(
followAccountSuccess({
relationship,
alreadyFollowing: false,
}),
);
openAddToListModal();
})
.catch((err: unknown) => {
dispatch(showAlertForError(err));
});
},
},
}),
);
}
};
arr = [];
arr = [
{
if (isRemote && accountUrl) {
arr.push({
text: intl.formatMessage(messages.openOriginalPage),
href: accountUrl,
});
}
if (signedIn) {
const handleAddToLists = () => {
const openAddToListModal = () => {
dispatch(
openModal({
modalType: 'LIST_ADDER',
modalProps: {
accountId: id,
},
}),
);
};
if (relationship?.following || relationship?.requested || id === me) {
openAddToListModal();
} else {
dispatch(
openModal({
modalType: 'CONFIRM_FOLLOW_TO_LIST',
modalProps: {
accountId: id,
onConfirm: () => {
apiFollowAccount(id)
.then((relationship) => {
dispatch(
followAccountSuccess({
relationship,
alreadyFollowing: false,
}),
);
openAddToListModal();
})
.catch((err: unknown) => {
dispatch(showAlertForError(err));
});
},
},
}),
);
}
};
arr.push({
text: intl.formatMessage(messages.addToLists),
action: handleAddToLists,
},
];
if (accountUrl) {
arr.unshift(
{
text: intl.formatMessage(messages.openOriginalPage),
href: accountUrl,
},
null,
);
});
}
}
return arr;
}, [dispatch, intl, id, accountUrl, relationship, defaultAction]);
}, [
dispatch,
intl,
id,
accountUrl,
relationship,
defaultAction,
isRemote,
signedIn,
]);
if (hidden) {
return (

View File

@@ -7,10 +7,16 @@ import classNames from 'classnames';
export const AvatarGroup: React.FC<{
compact?: boolean;
avatarHeight?: number;
children: React.ReactNode;
}> = ({ children, compact = false }) => (
}> = ({ children, compact = false, avatarHeight }) => (
<div
className={classNames('avatar-group', { 'avatar-group--compact': compact })}
style={
avatarHeight
? ({ '--avatar-height': `${avatarHeight}px` } as React.CSSProperties)
: undefined
}
>
{children}
</div>

View File

@@ -43,3 +43,17 @@ export const FollowersCounter = (
}}
/>
);
export const FollowersYouKnowCounter = (
displayNumber: React.ReactNode,
pluralReady: number,
) => (
<FormattedMessage
id='account.followers_you_know_counter'
defaultMessage='{counter} you know'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
}}
/>
);

View File

@@ -9,11 +9,16 @@ import { fetchAccount } from 'mastodon/actions/accounts';
import { AccountBio } from 'mastodon/components/account_bio';
import { AccountFields } from 'mastodon/components/account_fields';
import { Avatar } from 'mastodon/components/avatar';
import { FollowersCounter } from 'mastodon/components/counters';
import { AvatarGroup } from 'mastodon/components/avatar_group';
import {
FollowersCounter,
FollowersYouKnowCounter,
} from 'mastodon/components/counters';
import { DisplayName } from 'mastodon/components/display_name';
import { FollowButton } from 'mastodon/components/follow_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number';
import { useFetchFamiliarFollowers } from 'mastodon/features/account_timeline/hooks/familiar_followers';
import { domain } from 'mastodon/initial_state';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
@@ -38,6 +43,8 @@ export const HoverCardAccount = forwardRef<
}
}, [dispatch, accountId, account]);
const { familiarFollowers } = useFetchFamiliarFollowers({ accountId });
return (
<div
ref={ref}
@@ -73,11 +80,27 @@ export const HoverCardAccount = forwardRef<
)}
</div>
<div className='hover-card__number'>
<div className='hover-card__numbers'>
<ShortNumber
value={account.followers_count}
renderer={FollowersCounter}
/>
{familiarFollowers.length > 0 && (
<>
&middot;
<div className='hover-card__familiar-followers'>
<ShortNumber
value={familiarFollowers.length}
renderer={FollowersYouKnowCounter}
/>
<AvatarGroup compact>
{familiarFollowers.slice(0, 3).map((account) => (
<Avatar key={account.id} account={account} size={22} />
))}
</AvatarGroup>
</div>
</>
)}
</div>
<FollowButton accountId={accountId} />

View File

@@ -1,17 +0,0 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { IconLogo } from 'mastodon/components/logo';
import { AuthorLink } from 'mastodon/features/explore/components/author_link';
export const MoreFromAuthor = ({ accountId }) => (
<div className='more-from-author'>
<IconLogo />
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
</div>
);
MoreFromAuthor.propTypes = {
accountId: PropTypes.string.isRequired,
};

View File

@@ -0,0 +1,21 @@
import { FormattedMessage } from 'react-intl';
import { IconLogo } from 'mastodon/components/logo';
import { AuthorLink } from 'mastodon/features/explore/components/author_link';
export const MoreFromAuthor: React.FC<{ accountId: string }> = ({
accountId,
}) => (
<FormattedMessage
id='link_preview.more_from_author'
defaultMessage='More from {name}'
values={{ name: <AuthorLink accountId={accountId} /> }}
>
{(chunks) => (
<div className='more-from-author'>
<IconLogo />
{chunks}
</div>
)}
</FormattedMessage>
);

View File

@@ -5,14 +5,12 @@ import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import { ContentWarning } from 'mastodon/components/content_warning';
import { FilterWarning } from 'mastodon/components/filter_warning';
@@ -88,6 +86,7 @@ class Status extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.record,
children: PropTypes.node,
previousId: PropTypes.string,
nextInReplyToId: PropTypes.string,
rootId: PropTypes.string,
@@ -115,6 +114,7 @@ class Status extends ImmutablePureComponent {
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
showThread: PropTypes.bool,
isQuotedPost: PropTypes.bool,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
@@ -372,7 +372,7 @@ class Status extends ImmutablePureComponent {
};
render () {
const { intl, hidden, featured, unfocusable, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46 } = this.props;
const { intl, hidden, featured, unfocusable, unread, showThread, isQuotedPost = false, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46, children } = this.props;
let { status, account, ...other } = this.props;
@@ -519,7 +519,7 @@ class Status extends ImmutablePureComponent {
</Bundle>
);
}
} else if (status.get('card')) {
} else if (status.get('card') && !status.get('quote')) {
media = (
<Card
onOpenMedia={this.handleOpenMedia}
@@ -543,7 +543,19 @@ class Status extends ImmutablePureComponent {
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
{!skipPrepend && prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
<div
className={
classNames('status', `status-${status.get('visibility')}`,
{
'status-reply': !!status.get('in_reply_to_id'),
'status--in-thread': !!rootId,
'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted,
'status--is-quote': isQuotedPost,
'status--has-quote': !!status.get('quote'),
})
}
data-id={status.get('id')}
>
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
<div onClick={this.handleHeaderClick} onAuxClick={this.handleHeaderClick} className='status__info'>
@@ -576,12 +588,16 @@ class Status extends ImmutablePureComponent {
{...statusContentProps}
/>
{children}
{media}
{hashtagBar}
</>
)}
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
{!isQuotedPost &&
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
}
</div>
</div>
</HotKeys>

View File

@@ -9,7 +9,7 @@ import { TIMELINE_GAP, 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 StatusContainer from '../containers/status_container';
import { StatusQuoteManager } from '../components/status_quoted';
import { LoadGap } from './load_gap';
import ScrollableList from './scrollable_list';
@@ -113,7 +113,7 @@ export default class StatusList extends ImmutablePureComponent {
);
default:
return (
<StatusContainer
<StatusQuoteManager
key={statusId}
id={statusId}
onMoveUp={this.handleMoveUp}
@@ -130,7 +130,7 @@ export default class StatusList extends ImmutablePureComponent {
if (scrollableContent && featuredStatusIds) {
scrollableContent = featuredStatusIds.map(statusId => (
<StatusContainer
<StatusQuoteManager
key={`f-${statusId}`}
id={statusId}
featured

View File

@@ -0,0 +1,208 @@
import { useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type { Map as ImmutableMap } from 'immutable';
import ArticleIcon from '@/material-icons/400-24px/article.svg?react';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon';
import StatusContainer from 'mastodon/containers/status_container';
import type { Status } from 'mastodon/models/status';
import type { RootState } from 'mastodon/store';
import { useAppSelector } from 'mastodon/store';
import QuoteIcon from '../../images/quote.svg?react';
import { makeGetStatus } from '../selectors';
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
const QuoteWrapper: React.FC<{
isError?: boolean;
children: React.ReactElement;
}> = ({ isError, children }) => {
return (
<div
className={classNames('status__quote', {
'status__quote--error': isError,
})}
>
<Icon id='quote' icon={QuoteIcon} className='status__quote-icon' />
{children}
</div>
);
};
const QuoteLink: React.FC<{
status: Status;
}> = ({ status }) => {
const accountId = status.get('account') as string;
const account = useAppSelector((state) =>
accountId ? state.accounts.get(accountId) : undefined,
);
const quoteAuthorName = account?.display_name_html;
if (!quoteAuthorName) {
return null;
}
const quoteAuthorElement = (
<span dangerouslySetInnerHTML={{ __html: quoteAuthorName }} />
);
const quoteUrl = `/@${account.get('acct')}/${status.get('id') as string}`;
return (
<Link to={quoteUrl} className='status__quote-author-button'>
<FormattedMessage
id='status.quote_post_author'
defaultMessage='Post by {name}'
values={{ name: quoteAuthorElement }}
/>
<Icon id='chevron_right' icon={ChevronRightIcon} />
<Icon id='article' icon={ArticleIcon} />
</Link>
);
};
type QuoteMap = ImmutableMap<'state' | 'quoted_status', string | null>;
type GetStatusSelector = (
state: RootState,
props: { id?: string | null; contextType?: string },
) => Status | null;
export const QuotedStatus: React.FC<{
quote: QuoteMap;
contextType?: string;
variant?: 'full' | 'link';
nestingLevel?: number;
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
const quotedStatusId = quote.get('quoted_status');
const quoteState = quote.get('state');
const status = useAppSelector((state) =>
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
);
let quoteError: React.ReactNode = null;
// In order to find out whether the quoted post should be completely hidden
// due to a matching filter, we run it through the selector used by `status_container`.
// If this returns null even though `status` exists, it's because it's filtered.
const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector;
const statusWithExtraData = useAppSelector((state) =>
getStatus(state, { id: quotedStatusId, contextType }),
);
const isFilteredAndHidden = status && statusWithExtraData === null;
if (isFilteredAndHidden) {
quoteError = (
<FormattedMessage
id='status.quote_error.filtered'
defaultMessage='Hidden due to one of your filters'
/>
);
} else if (quoteState === 'deleted') {
quoteError = (
<FormattedMessage
id='status.quote_error.removed'
defaultMessage='This post was removed by its author.'
/>
);
} else if (quoteState === 'unauthorized') {
quoteError = (
<FormattedMessage
id='status.quote_error.unauthorized'
defaultMessage='This post cannot be displayed as you are not authorized to view it.'
/>
);
} else if (quoteState === 'pending') {
quoteError = (
<FormattedMessage
id='status.quote_error.pending_approval'
defaultMessage='This post is pending approval from the original author.'
/>
);
} else if (quoteState === 'rejected' || quoteState === 'revoked') {
quoteError = (
<FormattedMessage
id='status.quote_error.rejected'
defaultMessage='This post cannot be displayed as the original author does not allow it to be quoted.'
/>
);
} else if (!status || !quotedStatusId) {
quoteError = (
<FormattedMessage
id='status.quote_error.not_found'
defaultMessage='This post cannot be displayed.'
/>
);
}
if (quoteError) {
return <QuoteWrapper isError>{quoteError}</QuoteWrapper>;
}
if (variant === 'link' && status) {
return <QuoteLink status={status} />;
}
const childQuote = status?.get('quote') as QuoteMap | undefined;
const canRenderChildQuote =
childQuote && nestingLevel <= MAX_QUOTE_POSTS_NESTING_LEVEL;
return (
<QuoteWrapper>
{/* @ts-expect-error Status is not yet typed */}
<StatusContainer
isQuotedPost
id={quotedStatusId}
contextType={contextType}
avatarSize={40}
>
{canRenderChildQuote && (
<QuotedStatus
quote={childQuote}
contextType={contextType}
variant={
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
}
nestingLevel={nestingLevel + 1}
/>
)}
</StatusContainer>
</QuoteWrapper>
);
};
interface StatusQuoteManagerProps {
id: string;
contextType?: string;
[key: string]: unknown;
}
/**
* This wrapper component takes a status ID and, if the associated status
* is a quote post, it renders the quote into `StatusContainer` as a child.
* It passes all other props through to `StatusContainer`.
*/
export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
const status = useAppSelector((state) => {
const status = state.statuses.get(props.id);
const reblogId = status?.get('reblog') as string | undefined;
return reblogId ? state.statuses.get(reblogId) : status;
});
const quote = status?.get('quote') as QuoteMap | undefined;
if (quote) {
return (
<StatusContainer {...props}>
<QuotedStatus quote={quote} contextType={props.contextType} />
</StatusContainer>
);
}
return <StatusContainer {...props} />;
};

View File

@@ -171,8 +171,8 @@ class About extends PureComponent {
) : (
<ol className='rules-list'>
{server.get('rules').map(rule => {
const text = rule.getIn(['translations', locale, 'text']) || rule.get('text');
const hint = rule.getIn(['translations', locale, 'hint']) || rule.get('hint');
const text = rule.getIn(['translations', locale, 'text']) || rule.getIn(['translations', locale.split('-')[0], 'text']) || rule.get('text');
const hint = rule.getIn(['translations', locale, 'hint']) || rule.getIn(['translations', locale.split('-')[0], 'hint']) || rule.get('hint');
return (
<li key={rule.get('id')}>
<div className='rules-list__text'>{text}</div>

View File

@@ -14,7 +14,7 @@ import { Account } from 'mastodon/components/account';
import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RemoteHint } from 'mastodon/components/remote_hint';
import StatusContainer from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
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';
@@ -142,9 +142,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
/>
</h4>
{featuredStatusIds.map((statusId) => (
<StatusContainer
<StatusQuoteManager
key={`f-${statusId}`}
// @ts-expect-error inferred props are wrong
id={statusId}
contextType='account'
/>

View File

@@ -1,15 +1,12 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { fetchAccountsFamiliarFollowers } from '@/mastodon/actions/accounts_familiar_followers';
import { Avatar } from '@/mastodon/components/avatar';
import { AvatarGroup } from '@/mastodon/components/avatar_group';
import type { Account } from '@/mastodon/models/account';
import { getAccountFamiliarFollowers } from '@/mastodon/selectors/accounts';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { useFetchFamiliarFollowers } from '../hooks/familiar_followers';
const AccountLink: React.FC<{ account?: Account }> = ({ account }) => {
if (!account) {
@@ -64,20 +61,11 @@ const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
accountId,
}) => {
const dispatch = useAppDispatch();
const familiarFollowers = useAppSelector((state) =>
getAccountFamiliarFollowers(state, accountId),
);
const { familiarFollowers, isLoading } = useFetchFamiliarFollowers({
accountId,
});
const hasNoData = familiarFollowers === null;
useEffect(() => {
if (hasNoData) {
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
}
}, [dispatch, accountId, hasNoData]);
if (hasNoData || familiarFollowers.length === 0) {
if (isLoading || familiarFollowers.length === 0) {
return null;
}

View File

@@ -0,0 +1,30 @@
import { useEffect } from 'react';
import { fetchAccountsFamiliarFollowers } from '@/mastodon/actions/accounts_familiar_followers';
import { getAccountFamiliarFollowers } from '@/mastodon/selectors/accounts';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { me } from 'mastodon/initial_state';
export const useFetchFamiliarFollowers = ({
accountId,
}: {
accountId?: string;
}) => {
const dispatch = useAppDispatch();
const familiarFollowers = useAppSelector((state) =>
accountId ? getAccountFamiliarFollowers(state, accountId) : null,
);
const hasNoData = familiarFollowers === null;
useEffect(() => {
if (hasNoData && accountId && accountId !== me) {
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
}
}, [dispatch, accountId, hasNoData]);
return {
familiarFollowers: hasNoData ? [] : familiarFollowers,
isLoading: hasNoData,
};
};

View File

@@ -20,7 +20,7 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
import { Account } from 'mastodon/components/account';
import { Icon } from 'mastodon/components/icon';
import StatusContainer from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { me } from 'mastodon/initial_state';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@@ -175,7 +175,7 @@ class Notification extends ImmutablePureComponent {
renderMention (notification) {
return (
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
withDismiss
hidden={this.props.hidden}
@@ -205,7 +205,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
muted
@@ -235,7 +235,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
muted
@@ -269,7 +269,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
contextType='notifications'
@@ -304,7 +304,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
contextType='notifications'
@@ -345,7 +345,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={account}
contextType='notifications'

View File

@@ -18,12 +18,14 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { DisplayedName } from './displayed_name';
import { EmbeddedStatus } from './embedded_status';
const AVATAR_SIZE = 28;
export const AvatarById: React.FC<{ accountId: string }> = ({ accountId }) => {
const account = useAppSelector((state) => state.accounts.get(accountId));
if (!account) return null;
return <Avatar withLink account={account} size={28} />;
return <Avatar withLink account={account} size={AVATAR_SIZE} />;
};
export type LabelRenderer = (
@@ -108,7 +110,7 @@ export const NotificationGroupWithStatus: React.FC<{
<div className='notification-group__main'>
<div className='notification-group__main__header'>
<div className='notification-group__main__header__wrapper'>
<AvatarGroup>
<AvatarGroup avatarHeight={AVATAR_SIZE}>
{accountIds
.slice(0, NOTIFICATIONS_GROUP_MAX_AVATARS)
.map((id) => (
@@ -123,7 +125,14 @@ export const NotificationGroupWithStatus: React.FC<{
<div className='notification-group__main__header__label'>
{label}
{timestamp && <RelativeTimestamp timestamp={timestamp} />}
{timestamp && (
<>
<span className='notification-group__main__header__label-separator'>
&middot;
</span>
<RelativeTimestamp timestamp={timestamp} />
</>
)}
</div>
</div>

View File

@@ -12,7 +12,7 @@ import {
} from 'mastodon/actions/statuses';
import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import Status from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { getStatusHidden } from 'mastodon/selectors/filters';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
@@ -102,8 +102,7 @@ export const NotificationWithStatus: React.FC<{
{label}
</div>
<Status
// @ts-expect-error -- <Status> is not yet typed
<StatusQuoteManager
id={statusId}
contextType='notifications'
withDismiss

View File

@@ -51,7 +51,7 @@ class Rules extends PureComponent {
value={item.get('id')}
checked={selectedRuleIds.includes(item.get('id'))}
onToggle={this.handleRulesToggle}
label={item.getIn(['translations', locale, 'text']) || item.get('text')}
label={item.getIn(['translations', locale, 'text']) || item.getIn(['translations', locale.split('-')[0], 'text']) || item.get('text')}
multiple
/>
))}

View File

@@ -17,7 +17,7 @@ import { ColumnHeader } from 'mastodon/components/column_header';
import { CompatibilityHashtag as Hashtag } from 'mastodon/components/hashtag';
import { Icon } from 'mastodon/components/icon';
import ScrollableList from 'mastodon/components/scrollable_list';
import Status from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { Search } from 'mastodon/features/compose/components/search';
import { useSearchParam } from 'mastodon/hooks/useSearchParam';
import type { Hashtag as HashtagType } from 'mastodon/models/tags';
@@ -53,8 +53,7 @@ const renderHashtags = (hashtags: HashtagType[]) =>
const renderStatuses = (statusIds: string[]) =>
hidePeek<string>(statusIds).map((id) => (
// @ts-expect-error inferred props are wrong
<Status key={id} id={id} />
<StatusQuoteManager key={id} id={id} />
));
type SearchType = 'all' | ApiSearchType;
@@ -190,8 +189,7 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
onClickMore={handleSelectStatuses}
>
{results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
// @ts-expect-error inferred props are wrong
<Status key={id} id={id} />
<StatusQuoteManager key={id} id={id} />
))}
</SearchSection>
)}

View File

@@ -26,6 +26,7 @@ import { IconLogo } from 'mastodon/components/logo';
import MediaGallery from 'mastodon/components/media_gallery';
import { PictureInPicturePlaceholder } from 'mastodon/components/picture_in_picture_placeholder';
import StatusContent from 'mastodon/components/status_content';
import { QuotedStatus } from 'mastodon/components/status_quoted';
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
import { Audio } from 'mastodon/features/audio';
import scheduleIdleTask from 'mastodon/features/ui/util/schedule_idle_task';
@@ -226,7 +227,7 @@ export const DetailedStatus: React.FC<{
/>
);
}
} else if (status.get('card')) {
} else if (status.get('card') && !status.get('quote')) {
media = (
<Card
sensitive={status.get('sensitive')}
@@ -306,7 +307,12 @@ export const DetailedStatus: React.FC<{
return (
<div style={outerStyle}>
<div ref={handleRef} className={classNames('detailed-status')}>
<div
ref={handleRef}
className={classNames('detailed-status', {
'status--has-quote': !!status.get('quote'),
})}
>
{status.get('visibility') === 'direct' && (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'>
@@ -371,6 +377,10 @@ export const DetailedStatus: React.FC<{
{...(statusContentProps as any)}
/>
{status.get('quote') && (
<QuotedStatus quote={status.get('quote')} />
)}
{media}
{hashtagBar}
</>

View File

@@ -6,8 +6,6 @@ import classNames from 'classnames';
import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
import { createSelector } from '@reduxjs/toolkit';
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
@@ -62,7 +60,7 @@ import {
} from '../../actions/statuses';
import ColumnHeader from '../../components/column_header';
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
import StatusContainer from '../../containers/status_container';
import { StatusQuoteManager } from '../../components/status_quoted';
import { deleteModal } from '../../initial_state';
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
import { getAncestorsIds, getDescendantsIds } from 'mastodon/selectors/contexts';
@@ -477,7 +475,7 @@ class Status extends ImmutablePureComponent {
const { params: { statusId } } = this.props;
return list.map((id, i) => (
<StatusContainer
<StatusQuoteManager
key={id}
id={id}
onMoveUp={this.handleMoveUp}

View File

@@ -24,9 +24,7 @@ export const HotkeyIndicator: React.FC<{
enter: [{ opacity: 1 }],
leave: [{ opacity: 0 }],
onRest: (_result, _ctrl, item) => {
if (item) {
onDismiss(item);
}
onDismiss(item);
},
});

View File

@@ -23,9 +23,11 @@
"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_profile": "Profili redaktə et",
"account.enable_notifications": "@{name} paylaşım edəndə mənə bildiriş göndər",
"account.endorse": "Profildə seçilmişlərə əlavə et",
"account.featured.hashtags": "Etiketler",
"account.featured_tags.last_status_at": "Son paylaşım {date} tarixində olub",
"account.featured_tags.last_status_never": "Paylaşım yoxdur",
"account.follow": "İzlə",

View File

@@ -34,6 +34,7 @@
"account.followers": "Tud koumanantet",
"account.followers.empty": "Den na heul an implijer·ez-mañ c'hoazh.",
"account.followers_counter": "{count, plural, one {{counter} heulier} two {{counter} heulier} few {{counter} heulier} many {{counter} heulier} other {{counter} heulier}}",
"account.followers_you_know_counter": "{counter} a anavezit",
"account.following": "Koumanantoù",
"account.follows.empty": "An implijer·ez-mañ na heul den ebet.",
"account.go_to_profile": "Gwelet ar profil",
@@ -577,6 +578,7 @@
"status.mute": "Kuzhat @{name}",
"status.mute_conversation": "Kuzhat ar gaozeadenn",
"status.open": "Digeriñ ar c'hannad-mañ",
"status.quote_post_author": "Embannadenn gant {name}",
"status.read_more": "Lenn muioc'h",
"status.reblog": "Skignañ",
"status.reblog_private": "Skignañ gant ar weledenn gentañ",

View File

@@ -42,6 +42,7 @@
"account.followers": "Sledující",
"account.followers.empty": "Tohoto uživatele zatím nikdo nesleduje.",
"account.followers_counter": "{count, plural, one {{counter} sledující} few {{counter} sledující} many {{counter} sledujících} other {{counter} sledujících}}",
"account.followers_you_know_counter": "{count, one {{counter}, kterého znáte}, few {{counter}, které znáte}, many {{counter}, kterých znáte} other {{counter}, kterých znáte}}",
"account.following": "Sledujete",
"account.following_counter": "{count, plural, one {{counter} sledovaný} few {{counter} sledovaní} many {{counter} sledovaných} other {{counter} sledovaných}}",
"account.follows.empty": "Tento uživatel zatím nikoho nesleduje.",
@@ -683,7 +684,7 @@
"notifications.policy.filter_not_followers_title": "Lidé, kteří vás nesledují",
"notifications.policy.filter_not_following_hint": "Dokud je ručně neschválíte",
"notifications.policy.filter_not_following_title": "Lidé, které nesledujete",
"notifications.policy.filter_private_mentions_hint": "Vyfiltrováno, pokud to není odpověď na vaši zmínku nebo pokud sledujete odesílatele",
"notifications.policy.filter_private_mentions_hint": "Filtrováno, pokud to není v odpovědi na vaši vlastní zmínku nebo pokud nesledujete odesílatele",
"notifications.policy.filter_private_mentions_title": "Nevyžádané soukromé zmínky",
"notifications.policy.title": "Spravovat oznámení od…",
"notifications_permission_banner.enable": "Povolit oznámení na ploše",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Skrýt konverzaci",
"status.open": "Rozbalit tento příspěvek",
"status.pin": "Zvýraznit na profilu",
"status.quote_error.not_found": "Tento příspěvek nelze zobrazit.",
"status.quote_error.pending_approval": "Tento příspěvek čeká na schválení od původního autora.",
"status.quote_error.rejected": "Tento příspěvek nemůže být zobrazen, protože původní autor neumožňuje, aby byl citován.",
"status.quote_error.removed": "Tento příspěvek byl odstraněn jeho autorem.",
"status.quote_error.unauthorized": "Tento příspěvek nelze zobrazit, protože nemáte oprávnění k jeho zobrazení.",
"status.quote_post_author": "Příspěvek od {name}",
"status.read_more": "Číst více",
"status.reblog": "Boostnout",
"status.reblog_private": "Boostnout s původní viditelností",

View File

@@ -42,6 +42,7 @@
"account.followers": "Dilynwyr",
"account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.",
"account.followers_counter": "{count, plural, one {{counter} dilynwr} two {{counter} ddilynwr} other {{counter} dilynwyr}}",
"account.followers_you_know_counter": "{counter} rydych chi'n adnabod",
"account.following": "Yn dilyn",
"account.following_counter": "{count, plural, one {Yn dilyn {counter}} other {Yn dilyn {counter} arall}}",
"account.follows.empty": "Dyw'r defnyddiwr hwn ddim yn dilyn unrhyw un eto.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Anwybyddu sgwrs",
"status.open": "Ehangu'r post hwn",
"status.pin": "Dangos ar y proffil",
"status.quote_error.not_found": "Does dim modd dangos y postiad hwn.",
"status.quote_error.pending_approval": "Mae'r postiad hwn yn aros am gymeradwyaeth yr awdur gwreiddiol.",
"status.quote_error.rejected": "Does dim modd dangos y postiad hwn gan nad yw'r awdur gwreiddiol yn caniatáu iddo gael ei ddyfynnu.",
"status.quote_error.removed": "Cafodd y postiad hwn ei ddileu gan ei awdur.",
"status.quote_error.unauthorized": "Does dim modd dangos y postiad hwn gan nad oes gennych awdurdod i'w weld.",
"status.quote_post_author": "Postiad gan {name}",
"status.read_more": "Darllen rhagor",
"status.reblog": "Hybu",
"status.reblog_private": "Hybu i'r gynulleidfa wreiddiol",

View File

@@ -863,6 +863,12 @@
"status.mute_conversation": "Skjul samtale",
"status.open": "Udvid dette indlæg",
"status.pin": "Fremhæv på profil",
"status.quote_error.not_found": "Dette indlæg kan ikke vises.",
"status.quote_error.pending_approval": "Dette indlæg afventer godkendelse fra den oprindelige forfatter.",
"status.quote_error.rejected": "Dette indlæg kan ikke vises, da den oprindelige forfatter ikke tillader citering heraf.",
"status.quote_error.removed": "Dette indlæg er fjernet af forfatteren.",
"status.quote_error.unauthorized": "Dette indlæg kan ikke vises, da man ikke har tilladelse til at se det.",
"status.quote_post_author": "Indlæg fra {name}",
"status.read_more": "Læs mere",
"status.reblog": "Fremhæv",
"status.reblog_private": "Fremhæv med oprindelig synlighed",

View File

@@ -42,6 +42,7 @@
"account.followers": "Follower",
"account.followers.empty": "Diesem Profil folgt noch niemand.",
"account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}",
"account.followers_you_know_counter": "{counter} bekannt",
"account.following": "Folge ich",
"account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}",
"account.follows.empty": "Dieses Profil folgt noch niemandem.",
@@ -360,7 +361,7 @@
"filter_modal.select_filter.title": "Diesen Beitrag filtern",
"filter_modal.title.status": "Beitrag per Filter ausblenden",
"filter_warning.matches_filter": "Übereinstimmend mit dem Filter „<span>{title}</span>“",
"filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem, den} one {einer Person, die} other {# Personen, die}} du möglicherweise kennst",
"filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem Profil, das dir möglicherweise bekannt ist} one {einem Profil, das dir möglicherweise bekannt ist} other {# Profilen, die dir möglicherweise bekannt sind}}",
"filtered_notifications_banner.title": "Gefilterte Benachrichtigungen",
"firehose.all": "Alle Server",
"firehose.local": "Dieser Server",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Unterhaltung stummschalten",
"status.open": "Beitrag öffnen",
"status.pin": "Im Profil vorstellen",
"status.quote_error.not_found": "Dieser Beitrag kann nicht angezeigt werden.",
"status.quote_error.pending_approval": "Dieser Beitrag muss noch durch das ursprüngliche Profil genehmigt werden.",
"status.quote_error.rejected": "Dieser Beitrag kann nicht angezeigt werden, weil das ursprüngliche Profil das Zitieren nicht erlaubt.",
"status.quote_error.removed": "Dieser Beitrag wurde durch das Profil entfernt.",
"status.quote_error.unauthorized": "Dieser Beitrag kann nicht angezeigt werden, weil du zum Ansehen nicht berechtigt bist.",
"status.quote_post_author": "Beitrag von {name}",
"status.read_more": "Gesamten Beitrag anschauen",
"status.reblog": "Teilen",
"status.reblog_private": "Mit der ursprünglichen Zielgruppe teilen",

View File

@@ -42,6 +42,7 @@
"account.followers": "Followers",
"account.followers.empty": "No one follows this user yet.",
"account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}",
"account.followers_you_know_counter": "{counter} you know",
"account.following": "Following",
"account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}",
"account.follows.empty": "This user doesn't follow anyone yet.",
@@ -863,6 +864,13 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this post",
"status.pin": "Feature on profile",
"status.quote_error.filtered": "Hidden due to one of your filters",
"status.quote_error.not_found": "This post cannot be displayed.",
"status.quote_error.pending_approval": "This post is pending approval from the original author.",
"status.quote_error.rejected": "This post cannot be displayed as the original author does not allow it to be quoted.",
"status.quote_error.removed": "This post was removed by its author.",
"status.quote_error.unauthorized": "This post cannot be displayed as you are not authorized to view it.",
"status.quote_post_author": "Post by {name}",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} seguidores que conocés",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, one {siguiendo a {counter}} other {siguiendo a {counter}}}",
"account.follows.empty": "Todavía este usuario no sigue a nadie.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir este mensaje",
"status.pin": "Destacar en el perfil",
"status.quote_error.not_found": "No se puede mostrar este mensaje.",
"status.quote_error.pending_approval": "Este mensaje está pendiente de aprobación del autor original.",
"status.quote_error.rejected": "No se puede mostrar este mensaje, ya que el autor original no permite que se cite.",
"status.quote_error.removed": "Este mensaje fue eliminado por su autor.",
"status.quote_error.unauthorized": "No se puede mostrar este mensaje, ya que no tenés autorización para verlo.",
"status.quote_post_author": "Mensaje de @{name}",
"status.read_more": "Leé más",
"status.reblog": "Adherir",
"status.reblog_private": "Adherir a la audiencia original",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Nadie sigue a este usuario todavía.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} que conoces",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}",
"account.follows.empty": "Este usuario no sigue a nadie todavía.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir estado",
"status.pin": "Destacar en el perfil",
"status.quote_error.not_found": "No se puede mostrar esta publicación.",
"status.quote_error.pending_approval": "Esta publicación está pendiente de aprobación del autor original.",
"status.quote_error.rejected": "No se puede mostrar esta publicación, puesto que el autor original no permite que sea citado.",
"status.quote_error.removed": "Esta publicación fue eliminada por su autor.",
"status.quote_error.unauthorized": "No se puede mostrar esta publicación, puesto que no estás autorizado a verla.",
"status.quote_post_author": "Publicado por {name}",
"status.read_more": "Leer más",
"status.reblog": "Impulsar",
"status.reblog_private": "Implusar a la audiencia original",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} seguidores que conoces",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}",
"account.follows.empty": "Este usuario todavía no sigue a nadie.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir publicación",
"status.pin": "Destacar en el perfil",
"status.quote_error.not_found": "No se puede mostrar esta publicación.",
"status.quote_error.pending_approval": "Esta publicación está pendiente de aprobación del autor original.",
"status.quote_error.rejected": "Esta publicación no puede mostrarse porque el autor original no permite que se cite.",
"status.quote_error.removed": "Esta publicación fue eliminada por su autor.",
"status.quote_error.unauthorized": "Esta publicación no puede mostrarse, ya que no estás autorizado a verla.",
"status.quote_post_author": "Publicación de {name}",
"status.read_more": "Leer más",
"status.reblog": "Impulsar",
"status.reblog_private": "Impulsar a la audiencia original",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seuraajat",
"account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.",
"account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}",
"account.followers_you_know_counter": "{count, plural, one {{counter} tuntemasi} other {{counter} tuntemaasi}}",
"account.following": "Seurattavat",
"account.following_counter": "{count, plural, one {{counter} seurattava} other {{counter} seurattavaa}}",
"account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Mykistä keskustelu",
"status.open": "Laajenna julkaisu",
"status.pin": "Suosittele profiilissa",
"status.quote_error.not_found": "Tätä julkaisua ei voi näyttää.",
"status.quote_error.pending_approval": "Tämä julkaisu odottaa alkuperäisen tekijänsä hyväksyntää.",
"status.quote_error.rejected": "Tätä julkaisua ei voi näyttää, sillä sen alkuperäinen tekijä ei salli lainattavan julkaisua.",
"status.quote_error.removed": "Tekijä on poistanut julkaisun.",
"status.quote_error.unauthorized": "Tätä julkaisua ei voi näyttää, koska sinulla ei ole oikeutta tarkastella sitä.",
"status.quote_post_author": "Julkaisu käyttäjältä {name}",
"status.read_more": "Näytä enemmän",
"status.reblog": "Tehosta",
"status.reblog_private": "Tehosta alkuperäiselle yleisölle",

View File

@@ -42,6 +42,7 @@
"account.followers": "Fylgjarar",
"account.followers.empty": "Ongar fylgjarar enn.",
"account.followers_counter": "{count, plural, one {{counter} fylgjari} other {{counter} fylgjarar}}",
"account.followers_you_know_counter": "{counter} tú kennir",
"account.following": "Fylgir",
"account.following_counter": "{count, plural, one {{counter} fylgir} other {{counter} fylgja}}",
"account.follows.empty": "Hesin brúkari fylgir ongum enn.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Doyv samrøðu",
"status.open": "Víðka henda postin",
"status.pin": "Vís á vanga",
"status.quote_error.not_found": "Tað ber ikki til at vísa hendan postin.",
"status.quote_error.pending_approval": "Hesin posturin bíðar eftir góðkenning frá upprunahøvundinum.",
"status.quote_error.rejected": "Hesin posturin kann ikki vísast, tí upprunahøvundurin loyvir ikki at posturin verður siteraður.",
"status.quote_error.removed": "Hesin posturin var strikaður av høvundinum.",
"status.quote_error.unauthorized": "Hesin posturin kann ikki vísast, tí tú hevur ikki rættindi at síggja hann.",
"status.quote_post_author": "Postur hjá @{name}",
"status.read_more": "Les meira",
"status.reblog": "Stimbra",
"status.reblog_private": "Stimbra við upprunasýni",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidoras",
"account.followers.empty": "Aínda ninguén segue esta usuaria.",
"account.followers_counter": "{count, plural, one {{counter} seguidora} other {{counter} seguidoras}}",
"account.followers_you_know_counter": "{counter} que coñeces",
"account.following": "Seguindo",
"account.following_counter": "{count, plural, one {{counter} seguimento} other {{counter} seguimentos}}",
"account.follows.empty": "Esta usuaria aínda non segue a ninguén.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversa",
"status.open": "Estender esta publicación",
"status.pin": "Destacar no perfil",
"status.quote_error.not_found": "Non se pode mostrar a publicación.",
"status.quote_error.pending_approval": "A publicación está pendente da aprobación pola autora orixinal.",
"status.quote_error.rejected": "Non se pode mostrar esta publicación xa que a autora orixinal non permite que se cite.",
"status.quote_error.removed": "Publicación eliminada pola autora.",
"status.quote_error.unauthorized": "Non se pode mostrar esta publicación porque non tes permiso para vela.",
"status.quote_post_author": "Publicación de {name}",
"status.read_more": "Ler máis",
"status.reblog": "Promover",
"status.reblog_private": "Compartir coa audiencia orixinal",

View File

@@ -863,6 +863,12 @@
"status.mute_conversation": "השתקת שיחה",
"status.open": "הרחבת הודעה זו",
"status.pin": "מובלט בפרופיל",
"status.quote_error.not_found": "לא ניתן להציג הודעה זו.",
"status.quote_error.pending_approval": "הודעה זו מחכה לאישור מידי היוצר המקורי.",
"status.quote_error.rejected": "לא ניתן להציג הודעה זו שכן המחבר.ת המקוריים לא הרשו לצטט אותה.",
"status.quote_error.removed": "הודעה זו הוסרה על ידי השולחים המקוריים.",
"status.quote_error.unauthorized": "הודעה זו לא מוצגת כיוון שאין לך רשות לראותה.",
"status.quote_post_author": "פרסום מאת {name}",
"status.read_more": "לקרוא עוד",
"status.reblog": "הדהוד",
"status.reblog_private": "להדהד ברמת הנראות המקורית",

View File

@@ -42,6 +42,7 @@
"account.followers": "Követő",
"account.followers.empty": "Ezt a felhasználót még senki sem követi.",
"account.followers_counter": "{count, plural, one {{counter} követő} other {{counter} követő}}",
"account.followers_you_know_counter": "{counter} ismerős",
"account.following": "Követve",
"account.following_counter": "{count, plural, one {{counter} követett} other {{counter} követett}}",
"account.follows.empty": "Ez a felhasználó még senkit sem követ.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Beszélgetés némítása",
"status.open": "Bejegyzés kibontása",
"status.pin": "Kiemelés a profilodon",
"status.quote_error.not_found": "Ez a bejegyzés nem jeleníthető meg.",
"status.quote_error.pending_approval": "Ez a bejegyzés az eredeti szerző jóváhagyására vár.",
"status.quote_error.rejected": "Ez a bejegyzés nem jeleníthető meg, mert az eredeti szerzője nem engedélyezi az idézését.",
"status.quote_error.removed": "Ezt a bejegyzés eltávolította a szerzője.",
"status.quote_error.unauthorized": "Ez a bejegyzés nem jeleníthető meg, mert nem jogosult a megtekintésére.",
"status.quote_post_author": "Szerző: {name}",
"status.read_more": "Bővebben",
"status.reblog": "Megtolás",
"status.reblog_private": "Megtolás az eredeti közönségnek",

View File

@@ -42,6 +42,7 @@
"account.followers": "Fylgjendur",
"account.followers.empty": "Ennþá fylgist enginn með þessum notanda.",
"account.followers_counter": "{count, plural, one {Fylgjandi: {counter}} other {Fylgjendur: {counter}}}",
"account.followers_you_know_counter": "{counter} sem þú þekkir",
"account.following": "Fylgist með",
"account.following_counter": "{count, plural, one {Fylgist með: {counter}} other {Fylgist með: {counter}}}",
"account.follows.empty": "Þessi notandi fylgist ennþá ekki með neinum.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Þagga niður í samtali",
"status.open": "Opna þessa færslu",
"status.pin": "Birta á notandasniði",
"status.quote_error.not_found": "Þessa færslu er ekki hægt að birta.",
"status.quote_error.pending_approval": "Þessi færsla bíður eftir samþykki frá upprunalegum höfundi hennar.",
"status.quote_error.rejected": "Þessa færslu er ekki hægt að birta þar sem upphaflegur höfundur hennar leyfir ekki að vitnað sé til hennar.",
"status.quote_error.removed": "Þessi færsla var fjarlægð af höfundi hennar.",
"status.quote_error.unauthorized": "Þessa færslu er ekki hægt að birta þar sem þú hefur ekki heimild til að skoða hana.",
"status.quote_post_author": "Færsla frá {name}",
"status.read_more": "Lesa meira",
"status.reblog": "Endurbirting",
"status.reblog_private": "Endurbirta til upphaflegra lesenda",

View File

@@ -28,6 +28,9 @@
"account.edit_profile": "프로필 편집",
"account.enable_notifications": "@{name} 의 게시물 알림 켜기",
"account.endorse": "프로필에 추천하기",
"account.familiar_followers_many": "{name1}, {name2} 님 외 내가 아는 {othersCount, plural, other {#}} 명이 팔로우함",
"account.familiar_followers_one": "{name1} 님이 팔로우함",
"account.familiar_followers_two": "{name1}, {name2} 님이 팔로우함",
"account.featured": "추천",
"account.featured.accounts": "프로필",
"account.featured.hashtags": "해시태그",
@@ -406,8 +409,10 @@
"hashtag.counter_by_accounts": "{count, plural, other {참여자 {counter}명}}",
"hashtag.counter_by_uses": "{count, plural, other {게시물 {counter}개}}",
"hashtag.counter_by_uses_today": "오늘 {count, plural, other {{counter} 개의 게시물}}",
"hashtag.feature": "프로필에 추천하기",
"hashtag.follow": "해시태그 팔로우",
"hashtag.mute": "#{hashtag} 뮤트",
"hashtag.unfeature": "프로필에 추천하지 않기",
"hashtag.unfollow": "해시태그 팔로우 해제",
"hashtags.and_other": "…및 {count, plural,other {#개}}",
"hints.profiles.followers_may_be_missing": "이 프로필의 팔로워 목록은 일부 누락되었을 수 있습니다.",
@@ -858,6 +863,11 @@
"status.mute_conversation": "대화 뮤트",
"status.open": "상세 정보 표시",
"status.pin": "고정",
"status.quote_error.not_found": "이 게시물은 표시할 수 없습니다.",
"status.quote_error.pending_approval": "이 게시물은 원작자의 승인을 기다리고 있습니다.",
"status.quote_error.rejected": "이 게시물은 원작자가 인용을 허용하지 않았기 때문에 표시할 수 없습니다.",
"status.quote_error.removed": "이 게시물은 작성자에 의해 삭제되었습니다.",
"status.quote_error.unauthorized": "이 게시물은 권한이 없기 때문에 볼 수 없습니다.",
"status.read_more": "더 보기",
"status.reblog": "부스트",
"status.reblog_private": "원래의 수신자들에게 부스트",

View File

@@ -41,6 +41,7 @@
"account.followers": "Sekotāji",
"account.followers.empty": "Šim lietotājam vēl nav sekotāju.",
"account.followers_counter": "{count, plural, zero {{count} sekotāju} one {{count} sekotājs} other {{count} sekotāji}}",
"account.followers_you_know_counter": "{counter} jūs pazīstiet",
"account.following": "Seko",
"account.following_counter": "{count, plural, one {seko {counter}} other {seko {counter}}}",
"account.follows.empty": "Šis lietotājs pagaidām nevienam neseko.",
@@ -75,6 +76,7 @@
"account.statuses_counter": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}}",
"account.unblock": "Atbloķēt @{name}",
"account.unblock_domain": "Atbloķēt domēnu {domain}",
"account.unblock_domain_short": "Atbloķēt",
"account.unblock_short": "Atbloķēt",
"account.unendorse": "Neizcelt profilā",
"account.unfollow": "Pārstāt sekot",
@@ -367,6 +369,8 @@
"generic.saved": "Saglabāts",
"getting_started.heading": "Darba sākšana",
"hashtag.admin_moderation": "Atvērt #{name} satura pārraudzības saskarni",
"hashtag.browse": "Pārlūkot #{hashtag} ierakstus",
"hashtag.browse_from_account": "Pārlūkot @{name} #{hashtag} ierakstus",
"hashtag.column_header.tag_mode.all": "un {additional}",
"hashtag.column_header.tag_mode.any": "vai {additional}",
"hashtag.column_header.tag_mode.none": "bez {additional}",
@@ -381,6 +385,7 @@
"hashtag.counter_by_uses_today": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}} šodien",
"hashtag.feature": "Attēlot profilā",
"hashtag.follow": "Sekot tēmturim",
"hashtag.mute": "Apklusināt #{hashtag}",
"hashtag.unfeature": "Neattēlot profilā",
"hashtag.unfollow": "Pārstāt sekot tēmturim",
"hashtags.and_other": "… un {count, plural, other {vēl #}}",
@@ -448,6 +453,7 @@
"keyboard_shortcuts.toggle_hidden": "Rādīt/slēpt tekstu aiz satura brīdinājuma",
"keyboard_shortcuts.toggle_sensitivity": "Rādīt/slēpt multividi",
"keyboard_shortcuts.toot": "Uzsākt jaunu ierakstu",
"keyboard_shortcuts.translate": "tulkot ierakstu",
"keyboard_shortcuts.unfocus": "Atfokusēt veidojamā teksta/meklēšanas lauku",
"keyboard_shortcuts.up": "Pārvietoties augšup sarakstā",
"lightbox.close": "Aizvērt",
@@ -515,6 +521,7 @@
"notification.favourite": "{name} pievienoja izlasei Tavu ierakstu",
"notification.follow": "{name} uzsāka Tev sekot",
"notification.follow_request": "{name} nosūtīja Tev sekošanas pieprasījumu",
"notification.mentioned_you": "{name} pieminēja jūs",
"notification.moderation-warning.learn_more": "Uzzināt vairāk",
"notification.moderation_warning": "Ir saņemts satura pārraudzības brīdinājums",
"notification.moderation_warning.action_delete_statuses": "Daži no Taviem ierakstiem tika noņemti.",
@@ -741,6 +748,12 @@
"status.mute_conversation": "Apklusināt sarunu",
"status.open": "Izvērst šo ierakstu",
"status.pin": "Attēlot profilā",
"status.quote_error.not_found": "Šo ierakstu nevar parādīt.",
"status.quote_error.pending_approval": "Šis ieraksts gaida apstiprinājumu no tā autora.",
"status.quote_error.rejected": "Šo ierakstu nevar parādīt, jo tā autors neļauj to citēt.",
"status.quote_error.removed": "Šo ierakstu noņēma tā autors.",
"status.quote_error.unauthorized": "Šo ierakstu nevar parādīt, jo jums nav atļaujas to skatīt.",
"status.quote_post_author": "Publicēja {name}",
"status.read_more": "Lasīt vairāk",
"status.reblog": "Pastiprināt",
"status.reblog_private": "Pastiprināt ar sākotnējo redzamību",

View File

@@ -42,6 +42,7 @@
"account.followers": "Volgers",
"account.followers.empty": "Deze gebruiker heeft nog geen volgers of heeft deze verborgen.",
"account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}",
"account.followers_you_know_counter": "{counter} die je kent",
"account.following": "Volgend",
"account.following_counter": "{count, plural, one {{counter} volgend} other {{counter} volgend}}",
"account.follows.empty": "Deze gebruiker volgt nog niemand of heeft deze verborgen.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Gesprek negeren",
"status.open": "Volledig bericht tonen",
"status.pin": "Op profiel uitlichten",
"status.quote_error.not_found": "Dit bericht kan niet worden weergegeven.",
"status.quote_error.pending_approval": "Dit bericht is in afwachting van goedkeuring door de oorspronkelijke auteur.",
"status.quote_error.rejected": "Dit bericht kan niet worden weergegeven omdat de oorspronkelijke auteur niet toestaat dat het wordt geciteerd.",
"status.quote_error.removed": "Dit bericht is verwijderd door de auteur.",
"status.quote_error.unauthorized": "Dit bericht kan niet worden weergegeven omdat je niet bevoegd bent om het te bekijken.",
"status.quote_post_author": "Bericht van {name}",
"status.read_more": "Meer lezen",
"status.reblog": "Boosten",
"status.reblog_private": "Boost naar oorspronkelijke ontvangers",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Ainda ninguém segue este utilizador.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} que conhece",
"account.following": "A seguir",
"account.following_counter": "{count, plural, one {A seguir {counter}} other {A seguir {counter}}}",
"account.follows.empty": "Este utilizador ainda não segue ninguém.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Ocultar conversa",
"status.open": "Expandir esta publicação",
"status.pin": "Destacar no perfil",
"status.quote_error.not_found": "Esta publicação não pode ser exibida.",
"status.quote_error.pending_approval": "Esta publicação está a aguardar a aprovação do autor original.",
"status.quote_error.rejected": "Esta publicação não pode ser exibida porque o autor original não permite que seja citada.",
"status.quote_error.removed": "Esta publicação foi removida pelo seu autor.",
"status.quote_error.unauthorized": "Esta publicação não pode ser exibida porque o utilizador não está autorizado a visualizá-la.",
"status.quote_post_author": "Publicação de {name}",
"status.read_more": "Ler mais",
"status.reblog": "Impulsionar",
"status.reblog_private": "Impulsionar com a visibilidade original",

View File

@@ -86,10 +86,10 @@
"admin.dashboard.retention.average": "В среднем",
"admin.dashboard.retention.cohort": "Месяц регистрации",
"admin.dashboard.retention.cohort_size": "Новые пользователи",
"admin.impact_report.instance_accounts": "Профили учетных записей, которые будут удалены",
"admin.impact_report.instance_followers": "Подписчики, которых потеряют наши пользователи",
"admin.impact_report.instance_follows": "Подписчики, которых потеряют их пользователи",
"admin.impact_report.title": "Резюме воздействия",
"admin.impact_report.instance_accounts": "Число профилей, которые будут удалены",
"admin.impact_report.instance_followers": "Число подписчиков, которых лишатся наши пользователи",
"admin.impact_report.instance_follows": "Число подписчиков, которых лишатся их пользователи",
"admin.impact_report.title": "Сводка последствий",
"alert.rate_limited.message": "Подождите до {retry_time, time, medium}, прежде чем делать что-либо ещё.",
"alert.rate_limited.title": "Слишком много запросов",
"alert.unexpected.message": "Произошла непредвиденная ошибка.",
@@ -305,8 +305,8 @@
"emoji_button.search_results": "Результаты поиска",
"emoji_button.symbols": "Символы",
"emoji_button.travel": "Путешествия и места",
"empty_column.account_featured.me": "Вы ещё ничего не закрепили в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хэштеги и даже профили друзей?",
"empty_column.account_featured.other": "{acct} ещё ничего не закрепил(а) в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хэштеги и даже профили друзей?",
"empty_column.account_featured.me": "Вы ещё ничего не закрепили в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хештеги и даже профили друзей?",
"empty_column.account_featured.other": "{acct} ещё ничего не закрепил(а) в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хештеги и даже профили друзей?",
"empty_column.account_featured_other.unknown": "Этот пользователь ещё ничего не закрепил в своём профиле.",
"empty_column.account_hides_collections": "Пользователь предпочёл не раскрывать эту информацию",
"empty_column.account_suspended": "Учётная запись заблокирована",
@@ -357,7 +357,7 @@
"filter_modal.select_filter.title": "Фильтровать этот пост",
"filter_modal.title.status": "Фильтровать пост",
"filter_warning.matches_filter": "Соответствует фильтру «<span>{title}</span>»",
"filtered_notifications_banner.pending_requests": "От {count, plural, =0 {не известных вам людей} one {# возможно вам известного человека} other {# возможно вам известных человек}}",
"filtered_notifications_banner.pending_requests": "От {count, plural, =0 {не знакомых вам людей} one {# человека, которого вы можете знать} other {# человек, которых вы можете знать}}",
"filtered_notifications_banner.title": "Отфильтрованные уведомления",
"firehose.all": "Всё вместе",
"firehose.local": "Этот сервер",
@@ -392,8 +392,8 @@
"generic.saved": "Сохранено",
"getting_started.heading": "Добро пожаловать",
"hashtag.admin_moderation": "Открыть интерфейс модератора для #{name}",
"hashtag.browse": "Обзор постов с хэштегом #{hashtag}",
"hashtag.browse_from_account": "Обзор постов от @{name} с хэштегом #{hashtag}",
"hashtag.browse": "Обзор постов с хештегом #{hashtag}",
"hashtag.browse_from_account": "Обзор постов от @{name} с хештегом #{hashtag}",
"hashtag.column_header.tag_mode.all": "и {additional}",
"hashtag.column_header.tag_mode.any": "или {additional}",
"hashtag.column_header.tag_mode.none": "без {additional}",
@@ -454,7 +454,7 @@
"interaction_modal.title.reblog": "Продвинуть пост {name}",
"interaction_modal.title.reply": "Ответить на пост {name}",
"interaction_modal.title.vote": "Голосовать в опросе {name}",
"interaction_modal.username_prompt": "Например {example}",
"interaction_modal.username_prompt": "Например, {example}",
"intervals.full.days": "{number, plural, one {# день} few {# дня} other {# дней}}",
"intervals.full.hours": "{number, plural, one {# час} few {# часа} other {# часов}}",
"intervals.full.minutes": "{number, plural, one {# минута} few {# минуты} other {# минут}}",

View File

@@ -42,6 +42,7 @@
"account.followers": "Ndjekës",
"account.followers.empty": "Këtë përdorues ende se ndjek kush.",
"account.followers_counter": "{count, plural, one {{counter} ndjekës} other {{counter} ndjekës}}",
"account.followers_you_know_counter": "{counter} që njihni",
"account.following": "Ndjekje",
"account.following_counter": "{count, plural, one {{counter} i ndjekur} other {{counter} të ndjekur}}",
"account.follows.empty": "Ky përdorues ende sndjek kënd.",
@@ -858,6 +859,12 @@
"status.mute_conversation": "Heshtoje bisedën",
"status.open": "Zgjeroje këtë mesazh",
"status.pin": "Pasqyrojeni në profil",
"status.quote_error.not_found": "Ky postim smund të shfaqet.",
"status.quote_error.pending_approval": "Ky postim është në pritje të miratimit nga autori origjinal.",
"status.quote_error.rejected": "Ky postim smund të shfaqet, ngaqë autori origjinal nuk lejon citim të tij.",
"status.quote_error.removed": "Ky postim u hoq nga autori i tij.",
"status.quote_error.unauthorized": "Ky postim smund të shfaqet, ngaqë sjeni i autorizuar ta shihni.",
"status.quote_post_author": "Postim nga {name}",
"status.read_more": "Lexoni më tepër",
"status.reblog": "Përforcojeni",
"status.reblog_private": "Përforcim për publikun origjinal",

View File

@@ -42,6 +42,7 @@
"account.followers": "Takipçi",
"account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.",
"account.followers_counter": "{count, plural, one {{counter} takipçi} other {{counter} takipçi}}",
"account.followers_you_know_counter": "bildiğiniz {counter}",
"account.following": "Takip Ediliyor",
"account.following_counter": "{count, plural, one {{counter} takip edilen} other {{counter} takip edilen}}",
"account.follows.empty": "Bu kullanıcı henüz kimseyi takip etmiyor.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Sohbeti sessize al",
"status.open": "Bu gönderiyi genişlet",
"status.pin": "Profilimde öne çıkar",
"status.quote_error.not_found": "Bu gönderi görüntülenemez.",
"status.quote_error.pending_approval": "Bu gönderi özgün yazarın onayını bekliyor.",
"status.quote_error.rejected": "Bu gönderi, özgün yazar alıntılanmasına izin vermediği için görüntülenemez.",
"status.quote_error.removed": "Bu gönderi yazarı tarafından kaldırıldı.",
"status.quote_error.unauthorized": "Bu gönderiyi, yetkiniz olmadığı için görüntüleyemiyorsunuz.",
"status.quote_post_author": "{name} gönderisi",
"status.read_more": "Devamını okuyun",
"status.reblog": "Yeniden paylaş",
"status.reblog_private": "Özgün görünürlük ile yeniden paylaş",

View File

@@ -843,6 +843,7 @@
"status.mute": "Приховати @{name}",
"status.mute_conversation": "Ігнорувати розмову",
"status.open": "Розгорнути допис",
"status.quote_post_author": "@{name} опублікував допис",
"status.read_more": "Дізнатися більше",
"status.reblog": "Поширити",
"status.reblog_private": "Поширити для початкової аудиторії",

View File

@@ -42,6 +42,7 @@
"account.followers": "跟隨者",
"account.followers.empty": "尚未有人跟隨這位使用者。",
"account.followers_counter": "被 {count, plural, other {{count} 人}}跟隨",
"account.followers_you_know_counter": "{counter} 位您知道的跟隨者",
"account.following": "跟隨中",
"account.following_counter": "正在跟隨 {count,plural,other {{count} 人}}",
"account.follows.empty": "這位使用者尚未跟隨任何人。",
@@ -863,6 +864,12 @@
"status.mute_conversation": "靜音對話",
"status.open": "展開此嘟文",
"status.pin": "於個人檔案推薦",
"status.quote_error.not_found": "這則嘟文無法被顯示。",
"status.quote_error.pending_approval": "此嘟文正在等待原作者審核。",
"status.quote_error.rejected": "由於原作者不允許引用,此嘟文無法被顯示。",
"status.quote_error.removed": "此嘟文已被其作者移除。",
"status.quote_error.unauthorized": "由於您未被授權檢視,此嘟文無法被顯示。",
"status.quote_post_author": "由 {name} 發嘟",
"status.read_more": "閱讀更多",
"status.reblog": "轉嘟",
"status.reblog_private": "依照原嘟可見性轉嘟",

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm80-160h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Z"/></svg>

After

Width:  |  Height:  |  Size: 292 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-280h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Zm-80 480q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -1 +1,12 @@
Files in this directory are Material Symbols icons fetched using the `icons:download` task.
Files in this directory are Material Symbols icons fetched using the `icons:download` rake task (see `/lib/tasks/icons.rake`).
To add another icon, follow these steps:
- Determine the name of the Material Symbols icon you want to download.
You can find a searchable overview of all icons on [https://fonts.google.com/icons].
Click on the icon you want to use and find the icon name towards the bottom of the slide-out panel (it'll be something like `icon_name`)
- Import the icon in your React component using the following format:
`import IconName from '@/material-icons/400-24px/icon_name.svg?react';`
- Run `RAILS_ENV=development rails icons:download` to download any newly imported icons.
The import should now work and the icon should appear when passed to the `<Icon icon={IconName} /> component

View File

@@ -1421,6 +1421,10 @@ body > [data-popper-placement] {
}
}
.status--has-quote .quote-inline {
display: none;
}
.status {
padding: 16px;
min-height: 54px;
@@ -1491,8 +1495,12 @@ body > [data-popper-placement] {
}
}
&--is-quote {
border: none;
}
&--in-thread {
$thread-margin: 46px + 10px;
--thread-margin: calc(46px + 8px);
border-bottom: 0;
@@ -1508,16 +1516,16 @@ body > [data-popper-placement] {
.hashtag-bar,
.content-warning,
.filter-warning {
margin-inline-start: $thread-margin;
width: calc(100% - $thread-margin);
margin-inline-start: var(--thread-margin);
width: calc(100% - var(--thread-margin));
}
.more-from-author {
width: calc(100% - $thread-margin + 2px);
width: calc(100% - var(--thread-margin) + 2px);
}
.status__content__read-more-button {
margin-inline-start: $thread-margin;
margin-inline-start: var(--thread-margin);
}
}
@@ -1873,6 +1881,81 @@ body > [data-popper-placement] {
}
}
.status__quote {
position: relative;
margin-block-start: 16px;
margin-inline-start: 36px;
border-radius: 8px;
color: var(--nested-card-text);
background: var(--nested-card-background);
border: var(--nested-card-border);
@media screen and (min-width: $mobile-breakpoint) {
margin-inline-start: 56px;
}
}
.status__quote--error {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
font-size: 15px;
}
.status__quote-author-button {
position: relative;
overflow: hidden;
display: inline-flex;
width: auto;
margin-block-start: 10px;
padding: 5px 12px;
align-items: center;
gap: 6px;
font-family: inherit;
font-size: 14px;
font-weight: 700;
line-height: normal;
letter-spacing: 0;
text-decoration: none;
color: $highlight-text-color;
background: var(--nested-card-background);
border: var(--nested-card-border);
border-radius: 4px;
&:active,
&:focus,
&:hover {
border-color: lighten($highlight-text-color, 4%);
color: lighten($highlight-text-color, 4%);
}
&:focus-visible {
outline: $ui-button-icon-focus-outline;
}
}
.status__quote-icon {
position: absolute;
inset-block-start: 18px;
inset-inline-start: -40px;
display: block;
width: 26px;
height: 26px;
padding: 5px;
color: #6a49ba;
z-index: 10;
.status__quote--error & {
inset-block-start: 50%;
transform: translateY(-50%);
}
@media screen and (min-width: $mobile-breakpoint) {
inset-inline-start: -50px;
}
}
.detailed-status__link {
display: inline-flex;
align-items: center;
@@ -2170,14 +2253,18 @@ a .account__avatar {
.avatar-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
--avatar-height: 28px;
&:not(.avatar-group--compact) {
gap: 8px;
flex-wrap: wrap;
height: var(--avatar-height);
overflow-y: clip;
}
}
.avatar-group--compact {
gap: 0;
flex-wrap: nowrap;
& > :not(:first-child) {
margin-inline-start: -12px;
}
@@ -2306,11 +2393,6 @@ a.account__display-name {
}
}
.status__avatar {
width: 46px;
height: 46px;
}
.muted {
.status__content,
.status__content p,
@@ -10334,7 +10416,8 @@ noscript {
padding: 15px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
gap: 4px 8px;
.logo {
width: 16px;
@@ -10428,12 +10511,6 @@ noscript {
overflow: hidden;
container-type: inline-size;
@container (width < 350px) {
&__header time {
display: none;
}
}
&__header {
display: flex;
flex-direction: column;
@@ -10446,7 +10523,8 @@ noscript {
&__label {
display: flex;
gap: 8px;
flex-wrap: wrap;
gap: 2px 8px;
font-size: 15px;
line-height: 22px;
color: $darker-text-color;
@@ -10464,6 +10542,13 @@ noscript {
time {
color: $dark-text-color;
}
@container (width < 350px) {
time,
&-separator {
display: none;
}
}
}
}
@@ -10515,6 +10600,7 @@ noscript {
line-height: 22px;
color: $darker-text-color;
-webkit-line-clamp: 4;
line-clamp: 4;
-webkit-box-orient: vertical;
max-height: none;
overflow: hidden;
@@ -10670,7 +10756,15 @@ noscript {
color: inherit;
}
&__number {
&__numbers,
&__familiar-followers {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
&__numbers {
font-size: 15px;
line-height: 22px;
color: $secondary-text-color;
@@ -10818,9 +10912,9 @@ noscript {
.content-warning {
display: block;
box-sizing: border-box;
background: rgba($ui-highlight-color, 0.05);
color: $secondary-text-color;
border: 1px solid rgba($ui-highlight-color, 0.15);
background: var(--nested-card-background);
color: var(--nested-card-text);
border: var(--nested-card-border);
border-radius: 8px;
padding: 8px (5px + 8px);
position: relative;

View File

@@ -27,6 +27,10 @@
--rich-text-container-color: rgba(87, 24, 60, 100%);
--rich-text-text-color: rgba(255, 175, 212, 100%);
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
--nested-card-background: color(from #{$ui-highlight-color} srgb r g b / 5%);
--nested-card-text: #{$secondary-text-color};
--nested-card-border: 1px solid
color(from #{$ui-highlight-color} srgb r g b / 15%);
--input-placeholder-color: #{$dark-text-color};
--input-background-color: var(--surface-variant-background-color);
--on-input-color: #{$secondary-text-color};