Merge remote-tracking branch 'parent/main' into upstream-20241203
This commit is contained in:
commit
0a0b0d46ea
52 changed files with 599 additions and 451 deletions
|
@ -149,6 +149,7 @@ module ApplicationHelper
|
|||
output << content_for(:body_classes)
|
||||
output << "theme-#{current_theme.parameterize}"
|
||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||
output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui
|
||||
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
|
||||
output << 'rtl' if locale_direction == 'rtl'
|
||||
output << "content-font-size__#{current_account&.user&.setting_content_font_size}"
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
const messages = defineMessages({
|
||||
unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
|
||||
unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
|
||||
rateLimitedTitle: { id: 'alert.rate_limited.title', defaultMessage: 'Rate limited' },
|
||||
rateLimitedMessage: { id: 'alert.rate_limited.message', defaultMessage: 'Please retry after {retry_time, time, medium}.' },
|
||||
});
|
||||
|
||||
export const ALERT_SHOW = 'ALERT_SHOW';
|
||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
|
||||
export const ALERT_CLEAR = 'ALERT_CLEAR';
|
||||
export const ALERT_NOOP = 'ALERT_NOOP';
|
||||
|
||||
export const dismissAlert = alert => ({
|
||||
type: ALERT_DISMISS,
|
||||
alert,
|
||||
});
|
||||
|
||||
export const clearAlert = () => ({
|
||||
type: ALERT_CLEAR,
|
||||
});
|
||||
|
||||
export const showAlert = alert => ({
|
||||
type: ALERT_SHOW,
|
||||
alert,
|
||||
});
|
||||
|
||||
export const showAlertForError = (error, skipNotFound = false) => {
|
||||
if (error.response) {
|
||||
const { data, status, statusText, headers } = error.response;
|
||||
|
||||
// Skip these errors as they are reflected in the UI
|
||||
if (skipNotFound && (status === 404 || status === 410)) {
|
||||
return { type: ALERT_NOOP };
|
||||
}
|
||||
|
||||
// Rate limit errors
|
||||
if (status === 429 && headers['x-ratelimit-reset']) {
|
||||
return showAlert({
|
||||
title: messages.rateLimitedTitle,
|
||||
message: messages.rateLimitedMessage,
|
||||
values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
|
||||
});
|
||||
}
|
||||
|
||||
return showAlert({
|
||||
title: `${status}`,
|
||||
message: data.error || statusText,
|
||||
});
|
||||
}
|
||||
|
||||
// An aborted request, e.g. due to reloading the browser window, it not really error
|
||||
if (error.code === AxiosError.ECONNABORTED) {
|
||||
return { type: ALERT_NOOP };
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
|
||||
return showAlert({
|
||||
title: messages.unexpectedTitle,
|
||||
message: messages.unexpectedMessage,
|
||||
});
|
||||
};
|
90
app/javascript/mastodon/actions/alerts.ts
Normal file
90
app/javascript/mastodon/actions/alerts.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
interface Alert {
|
||||
title: string | MessageDescriptor;
|
||||
message: string | MessageDescriptor;
|
||||
values?: Record<string, string | number | Date>;
|
||||
}
|
||||
|
||||
interface ApiErrorResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
|
||||
unexpectedMessage: {
|
||||
id: 'alert.unexpected.message',
|
||||
defaultMessage: 'An unexpected error occurred.',
|
||||
},
|
||||
rateLimitedTitle: {
|
||||
id: 'alert.rate_limited.title',
|
||||
defaultMessage: 'Rate limited',
|
||||
},
|
||||
rateLimitedMessage: {
|
||||
id: 'alert.rate_limited.message',
|
||||
defaultMessage: 'Please retry after {retry_time, time, medium}.',
|
||||
},
|
||||
});
|
||||
|
||||
export const ALERT_SHOW = 'ALERT_SHOW';
|
||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
|
||||
export const ALERT_CLEAR = 'ALERT_CLEAR';
|
||||
export const ALERT_NOOP = 'ALERT_NOOP';
|
||||
|
||||
export const dismissAlert = (alert: Alert) => ({
|
||||
type: ALERT_DISMISS,
|
||||
alert,
|
||||
});
|
||||
|
||||
export const clearAlert = () => ({
|
||||
type: ALERT_CLEAR,
|
||||
});
|
||||
|
||||
export const showAlert = (alert: Alert) => ({
|
||||
type: ALERT_SHOW,
|
||||
alert,
|
||||
});
|
||||
|
||||
export const showAlertForError = (error: unknown, skipNotFound = false) => {
|
||||
if (error instanceof AxiosError && error.response) {
|
||||
const { status, statusText, headers } = error.response;
|
||||
const { data } = error.response as AxiosResponse<ApiErrorResponse>;
|
||||
|
||||
// Skip these errors as they are reflected in the UI
|
||||
if (skipNotFound && (status === 404 || status === 410)) {
|
||||
return { type: ALERT_NOOP };
|
||||
}
|
||||
|
||||
// Rate limit errors
|
||||
if (status === 429 && headers['x-ratelimit-reset']) {
|
||||
return showAlert({
|
||||
title: messages.rateLimitedTitle,
|
||||
message: messages.rateLimitedMessage,
|
||||
values: {
|
||||
retry_time: new Date(headers['x-ratelimit-reset'] as string),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return showAlert({
|
||||
title: `${status}`,
|
||||
message: data.error ?? statusText,
|
||||
});
|
||||
}
|
||||
|
||||
// An aborted request, e.g. due to reloading the browser window, it not really error
|
||||
if (error instanceof AxiosError && error.code === AxiosError.ECONNABORTED) {
|
||||
return { type: ALERT_NOOP };
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
|
||||
return showAlert({
|
||||
title: messages.unexpectedTitle,
|
||||
message: messages.unexpectedMessage,
|
||||
});
|
||||
};
|
|
@ -5,3 +5,16 @@ export const apiSubmitAccountNote = (id: string, value: string) =>
|
|||
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
|
||||
comment: value,
|
||||
});
|
||||
|
||||
export const apiFollowAccount = (
|
||||
id: string,
|
||||
params?: {
|
||||
reblogs: boolean;
|
||||
},
|
||||
) =>
|
||||
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/follow`, {
|
||||
...params,
|
||||
});
|
||||
|
||||
export const apiUnfollowAccount = (id: string) =>
|
||||
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/unfollow`);
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||
import { FollowButton } from 'mastodon/components/follow_button';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import { me } from '../initial_state';
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import { Button } from './button';
|
||||
import { FollowersCounter } from './counters';
|
||||
import { DisplayName } from './display_name';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
|
||||
const messages = defineMessages({
|
||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
|
||||
unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
|
||||
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
|
||||
block: { id: 'account.block_short', defaultMessage: 'Block' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
});
|
||||
|
||||
const Account = ({ size = 46, account, onBlock, onMute, onMuteNotifications, hidden, hideButtons, minimal, defaultAction, children, withBio }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const handleBlock = useCallback(() => {
|
||||
onBlock(account);
|
||||
}, [onBlock, account]);
|
||||
|
||||
const handleMute = useCallback(() => {
|
||||
onMute(account);
|
||||
}, [onMute, account]);
|
||||
|
||||
const handleMuteNotifications = useCallback(() => {
|
||||
onMuteNotifications(account, true);
|
||||
}, [onMuteNotifications, account]);
|
||||
|
||||
const handleUnmuteNotifications = useCallback(() => {
|
||||
onMuteNotifications(account, false);
|
||||
}, [onMuteNotifications, account]);
|
||||
|
||||
if (!account) {
|
||||
return <EmptyAccount size={size} minimal={minimal} />;
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<>
|
||||
{account.get('display_name')}
|
||||
{account.get('username')}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let buttons;
|
||||
|
||||
if (!hideButtons && account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||
const requested = account.getIn(['relationship', 'requested']);
|
||||
const blocking = account.getIn(['relationship', 'blocking']);
|
||||
const muting = account.getIn(['relationship', 'muting']);
|
||||
|
||||
if (requested) {
|
||||
buttons = <FollowButton accountId={account.get('id')} />;
|
||||
} else if (blocking) {
|
||||
buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={handleBlock} />;
|
||||
} else if (muting) {
|
||||
let menu;
|
||||
|
||||
if (account.getIn(['relationship', 'muting_notifications'])) {
|
||||
menu = [{ text: intl.formatMessage(messages.unmute_notifications), action: handleUnmuteNotifications }];
|
||||
} else {
|
||||
menu = [{ text: intl.formatMessage(messages.mute_notifications), action: handleMuteNotifications }];
|
||||
}
|
||||
|
||||
buttons = (
|
||||
<>
|
||||
<DropdownMenuContainer
|
||||
items={menu}
|
||||
icon='ellipsis-h'
|
||||
iconComponent={MoreHorizIcon}
|
||||
direction='right'
|
||||
title={intl.formatMessage(messages.more)}
|
||||
/>
|
||||
|
||||
<Button text={intl.formatMessage(messages.unmute)} onClick={handleMute} />
|
||||
</>
|
||||
);
|
||||
} else if (defaultAction === 'mute') {
|
||||
buttons = <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />;
|
||||
} else if (defaultAction === 'block') {
|
||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={handleBlock} />;
|
||||
} else {
|
||||
buttons = <FollowButton accountId={account.get('id')} />;
|
||||
}
|
||||
} else if (!hideButtons) {
|
||||
buttons = <FollowButton accountId={account.get('id')} />;
|
||||
}
|
||||
|
||||
let muteTimeRemaining;
|
||||
|
||||
if (account.get('mute_expires_at')) {
|
||||
muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></>;
|
||||
}
|
||||
|
||||
let verification;
|
||||
|
||||
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
|
||||
|
||||
if (firstVerifiedField) {
|
||||
verification = <VerifiedBadge link={firstVerifiedField.get('value')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`} data-hover-card-account={account.get('id')}>
|
||||
<div className='account__avatar-wrapper'>
|
||||
<Avatar account={account} size={size} />
|
||||
</div>
|
||||
|
||||
<div className='account__contents'>
|
||||
<DisplayName account={account} />
|
||||
{!minimal && (
|
||||
<div className='account__details'>
|
||||
<ShortNumber value={account.get('followers_count')} renderer={FollowersCounter}
|
||||
isHide={account.getIn(['other_settings', 'hide_followers_count'])} /> {verification} {muteTimeRemaining}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{!minimal && children && (
|
||||
<div>
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
<div className='account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!minimal && !children && (
|
||||
<div className='account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{withBio && (account.get('note').length > 0 ? (
|
||||
<div
|
||||
className='account__note translate'
|
||||
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
||||
/>
|
||||
) : (
|
||||
<div className='account__note account__note--missing'><FormattedMessage id='account.no_bio' defaultMessage='No description provided.' /></div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Account.propTypes = {
|
||||
size: PropTypes.number,
|
||||
account: ImmutablePropTypes.record,
|
||||
onBlock: PropTypes.func,
|
||||
onMute: PropTypes.func,
|
||||
onMuteNotifications: PropTypes.func,
|
||||
hidden: PropTypes.bool,
|
||||
hideButtons: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
defaultAction: PropTypes.string,
|
||||
withBio: PropTypes.bool,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default Account;
|
260
app/javascript/mastodon/components/account.tsx
Normal file
260
app/javascript/mastodon/components/account.tsx
Normal file
|
@ -0,0 +1,260 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import {
|
||||
blockAccount,
|
||||
unblockAccount,
|
||||
muteAccount,
|
||||
unmuteAccount,
|
||||
} from 'mastodon/actions/accounts';
|
||||
import { initMuteModal } from 'mastodon/actions/mutes';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { FollowersCounter } from 'mastodon/components/counters';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { FollowButton } from 'mastodon/components/follow_button';
|
||||
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 DropdownMenu from 'mastodon/containers/dropdown_menu_container';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
cancel_follow_request: {
|
||||
id: 'account.cancel_follow_request',
|
||||
defaultMessage: 'Withdraw follow request',
|
||||
},
|
||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||
mute_notifications: {
|
||||
id: 'account.mute_notifications_short',
|
||||
defaultMessage: 'Mute notifications',
|
||||
},
|
||||
unmute_notifications: {
|
||||
id: 'account.unmute_notifications_short',
|
||||
defaultMessage: 'Unmute notifications',
|
||||
},
|
||||
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
|
||||
block: { id: 'account.block_short', defaultMessage: 'Block' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
});
|
||||
|
||||
export const Account: React.FC<{
|
||||
size?: number;
|
||||
id: string;
|
||||
hidden?: boolean;
|
||||
minimal?: boolean;
|
||||
defaultAction?: 'block' | 'mute';
|
||||
withBio?: boolean;
|
||||
hideButtons?: boolean;
|
||||
children?: ReactNode;
|
||||
}> = ({
|
||||
id,
|
||||
size = 46,
|
||||
hidden,
|
||||
minimal,
|
||||
defaultAction,
|
||||
withBio,
|
||||
hideButtons,
|
||||
children,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const account = useAppSelector((state) => state.accounts.get(id));
|
||||
const relationship = useAppSelector((state) => state.relationships.get(id));
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleBlock = useCallback(() => {
|
||||
if (relationship?.blocking) {
|
||||
dispatch(unblockAccount(id));
|
||||
} else {
|
||||
dispatch(blockAccount(id));
|
||||
}
|
||||
}, [dispatch, id, relationship]);
|
||||
|
||||
const handleMute = useCallback(() => {
|
||||
if (relationship?.muting) {
|
||||
dispatch(unmuteAccount(id));
|
||||
} else {
|
||||
dispatch(initMuteModal(account));
|
||||
}
|
||||
}, [dispatch, id, account, relationship]);
|
||||
|
||||
const handleMuteNotifications = useCallback(() => {
|
||||
dispatch(muteAccount(id, true));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const handleUnmuteNotifications = useCallback(() => {
|
||||
dispatch(muteAccount(id, false));
|
||||
}, [dispatch, id]);
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<>
|
||||
{account?.display_name}
|
||||
{account?.username}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let buttons;
|
||||
|
||||
if (account && account.id !== me && relationship) {
|
||||
const { requested, blocking, muting } = relationship;
|
||||
|
||||
if (requested) {
|
||||
buttons = <FollowButton accountId={id} />;
|
||||
} else if (blocking) {
|
||||
buttons = (
|
||||
<Button
|
||||
text={intl.formatMessage(messages.unblock)}
|
||||
onClick={handleBlock}
|
||||
/>
|
||||
);
|
||||
} else if (muting) {
|
||||
const menu = [
|
||||
{
|
||||
text: intl.formatMessage(
|
||||
relationship.muting_notifications
|
||||
? messages.unmute_notifications
|
||||
: messages.mute_notifications,
|
||||
),
|
||||
action: relationship.muting_notifications
|
||||
? handleUnmuteNotifications
|
||||
: handleMuteNotifications,
|
||||
},
|
||||
];
|
||||
|
||||
buttons = (
|
||||
<>
|
||||
<DropdownMenu
|
||||
items={menu}
|
||||
icon='ellipsis-h'
|
||||
iconComponent={MoreHorizIcon}
|
||||
direction='right'
|
||||
title={intl.formatMessage(messages.more)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
text={intl.formatMessage(messages.unmute)}
|
||||
onClick={handleMute}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else if (defaultAction === 'mute') {
|
||||
buttons = (
|
||||
<Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />
|
||||
);
|
||||
} else if (defaultAction === 'block') {
|
||||
buttons = (
|
||||
<Button
|
||||
text={intl.formatMessage(messages.block)}
|
||||
onClick={handleBlock}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
buttons = <FollowButton accountId={id} />;
|
||||
}
|
||||
} else {
|
||||
buttons = <FollowButton accountId={id} />;
|
||||
}
|
||||
|
||||
if (hideButtons) {
|
||||
buttons = null;
|
||||
}
|
||||
|
||||
let muteTimeRemaining;
|
||||
|
||||
if (account?.mute_expires_at) {
|
||||
muteTimeRemaining = (
|
||||
<>
|
||||
· <RelativeTimestamp timestamp={account.mute_expires_at} futureDate />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let verification;
|
||||
|
||||
const firstVerifiedField = account?.fields.find((item) => !!item.verified_at);
|
||||
|
||||
if (firstVerifiedField) {
|
||||
verification = <VerifiedBadge link={firstVerifiedField.value} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<Link
|
||||
className='account__display-name'
|
||||
title={account?.acct}
|
||||
to={`/@${account?.acct}`}
|
||||
data-hover-card-account={id}
|
||||
>
|
||||
<div className='account__avatar-wrapper'>
|
||||
{account ? (
|
||||
<Avatar account={account} size={size} />
|
||||
) : (
|
||||
<Skeleton width={size} height={size} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='account__contents'>
|
||||
<DisplayName account={account} />
|
||||
|
||||
{!minimal && (
|
||||
<div className='account__details'>
|
||||
{account ? (
|
||||
<>
|
||||
<ShortNumber
|
||||
value={account.followers_count}
|
||||
renderer={FollowersCounter}
|
||||
isHide={account.other_settings.hide_followers_count}
|
||||
/>{' '}
|
||||
{verification} {muteTimeRemaining}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton width='7ch' />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{!minimal && children && (
|
||||
<div>
|
||||
<div>{children}</div>
|
||||
<div className='account__relationship'>{buttons}</div>
|
||||
</div>
|
||||
)}
|
||||
{!minimal && !children && (
|
||||
<div className='account__relationship'>{buttons}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{account &&
|
||||
withBio &&
|
||||
(account.note.length > 0 ? (
|
||||
<div
|
||||
className='account__note translate'
|
||||
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
|
||||
/>
|
||||
) : (
|
||||
<div className='account__note account__note--missing'>
|
||||
<FormattedMessage
|
||||
id='account.no_bio'
|
||||
defaultMessage='No description provided.'
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,33 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
minimal?: boolean;
|
||||
}
|
||||
|
||||
export const EmptyAccount: React.FC<Props> = ({
|
||||
size = 46,
|
||||
minimal = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<div className='account__display-name'>
|
||||
<div className='account__avatar-wrapper'>
|
||||
<Skeleton width={size} height={size} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DisplayName />
|
||||
<Skeleton width='7ch' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -8,10 +8,10 @@ import { Link } from 'react-router-dom';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { fetchServer } from 'mastodon/actions/server';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import { injectIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
|
||||
import {
|
||||
followAccount,
|
||||
blockAccount,
|
||||
unblockAccount,
|
||||
muteAccount,
|
||||
unmuteAccount,
|
||||
} from '../actions/accounts';
|
||||
import { initMuteModal } from '../actions/mutes';
|
||||
import Account from '../components/account';
|
||||
import { makeGetAccount } from '../selectors';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: getAccount(state, props.id),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
onFollow (account) {
|
||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }));
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
},
|
||||
|
||||
onBlock (account) {
|
||||
if (account.getIn(['relationship', 'blocking'])) {
|
||||
dispatch(unblockAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(blockAccount(account.get('id')));
|
||||
}
|
||||
},
|
||||
|
||||
onMute (account) {
|
||||
if (account.getIn(['relationship', 'muting'])) {
|
||||
dispatch(unmuteAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(initMuteModal(account));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onMuteNotifications (account, notifications) {
|
||||
dispatch(muteAccount(account.get('id'), notifications));
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Account));
|
|
@ -15,11 +15,11 @@ import DisabledIcon from '@/material-icons/400-24px/close-fill.svg?react';
|
|||
import EnabledIcon from '@/material-icons/400-24px/done-fill.svg?react';
|
||||
import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react';
|
||||
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import Column from 'mastodon/components/column';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import LinkFooter from 'mastodon/features/ui/components/link_footer';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
import type { Percentiles } from 'mastodon/models/annual_report';
|
||||
|
||||
export const Percentile: React.FC<{
|
||||
|
@ -12,7 +13,7 @@ export const Percentile: React.FC<{
|
|||
<div className='annual-report__bento__box annual-report__summary__percentile'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.percentile.text'
|
||||
defaultMessage='<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>'
|
||||
defaultMessage='<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of {domain} users.</bottomLabel>'
|
||||
values={{
|
||||
topLabel: (str) => (
|
||||
<div className='annual-report__summary__percentile__label'>
|
||||
|
@ -44,6 +45,8 @@ export const Percentile: React.FC<{
|
|||
)}
|
||||
</div>
|
||||
),
|
||||
|
||||
domain,
|
||||
}}
|
||||
>
|
||||
{(message) => <>{message}</>}
|
||||
|
|
|
@ -9,11 +9,11 @@ import { connect } from 'react-redux';
|
|||
import { debounce } from 'lodash';
|
||||
|
||||
import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
|
||||
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
|
||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -70,7 +70,7 @@ class Blocks extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} defaultAction='block' />,
|
||||
<Account key={id} id={id} defaultAction='block' />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useSelector, useDispatch } from 'react-redux';
|
|||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { cancelReplyCompose } from 'mastodon/actions/compose';
|
||||
import Account from 'mastodon/components/account';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
||||
|
@ -20,7 +20,6 @@ const messages = defineMessages({
|
|||
export const NavigationBar = () => {
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
const account = useSelector(state => state.getIn(['accounts', me]));
|
||||
const isReplying = useSelector(state => !!state.getIn(['compose', 'in_reply_to']));
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
|
@ -29,7 +28,7 @@ export const NavigationBar = () => {
|
|||
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Account account={account} minimal />
|
||||
<Account id={me} minimal />
|
||||
{isReplying ? <IconButton title={intl.formatMessage(messages.cancel)} iconComponent={CloseIcon} onClick={handleCancelClick} /> : <ActionBar />}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
|
|||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
|
||||
import { expandSearch } from 'mastodon/actions/search';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadMore } from 'mastodon/components/load_more';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
|
@ -13,7 +14,6 @@ import { SearchSection } from 'mastodon/features/explore/components/search_secti
|
|||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { ImmutableHashtag as Hashtag } from '../../../components/hashtag';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import StatusContainer from '../../../containers/status_container';
|
||||
|
||||
const INITIAL_PAGE_LIMIT = 10;
|
||||
|
@ -49,7 +49,7 @@ export const SearchResults = () => {
|
|||
if (results.get('accounts') && results.get('accounts').size > 0) {
|
||||
accounts = (
|
||||
<SearchSection title={<><Icon id='users' icon={PeopleIcon} /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}>
|
||||
{withoutLastResult(results.get('accounts')).map(accountId => <AccountContainer key={accountId} id={accountId} />)}
|
||||
{withoutLastResult(results.get('accounts')).map(accountId => <Account key={accountId} id={accountId} />)}
|
||||
{(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={handleLoadMoreAccounts} />}
|
||||
</SearchSection>
|
||||
);
|
||||
|
|
|
@ -10,12 +10,13 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
|
||||
import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
|
||||
import { fetchEmojiReactions, expandEmojiReactions } from 'mastodon/actions/interactions';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
|
||||
|
||||
|
@ -101,11 +102,11 @@ class EmojiReactions extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{Object.keys(groups).map((key) =>(
|
||||
<AccountContainer key={key} id={key} withNote={false} hideButtons>
|
||||
<Account key={key} id={key} hideButtons>
|
||||
<div style={{ 'maxWidth': '100px' }}>
|
||||
{groups[key].map((value, index2) => <EmojiView key={index2} name={value.name} url={value.url} staticUrl={value.static_url} />)}
|
||||
</div>
|
||||
</AccountContainer>
|
||||
</Account>
|
||||
))}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@ import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
|
|||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
|
||||
import { submitSearch, expandSearch } from 'mastodon/actions/search';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import Status from 'mastodon/containers/status_container';
|
||||
|
||||
import { SearchSection } from './components/search_section';
|
||||
|
|
|
@ -14,11 +14,11 @@ import { debounce } from 'lodash';
|
|||
|
||||
import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
|
||||
import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -89,7 +89,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />,
|
||||
<Account key={id} id={id} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import { isHideItem, me } from 'mastodon/initial_state';
|
||||
|
@ -24,7 +25,6 @@ import {
|
|||
import { ColumnBackButton } from '../../components/column_back_button';
|
||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import Column from '../ui/components/column';
|
||||
|
@ -179,7 +179,7 @@ class Followers extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{forceEmptyState ? [] : accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />,
|
||||
<Account key={id} id={id} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import { isHideItem, me } from 'mastodon/initial_state';
|
||||
|
@ -24,7 +25,6 @@ import {
|
|||
import { ColumnBackButton } from '../../components/column_back_button';
|
||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import Column from '../ui/components/column';
|
||||
|
@ -177,7 +177,7 @@ class Following extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{forceEmptyState ? [] : filteredAccountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />,
|
||||
<Account key={id} id={id} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
|
|
@ -9,9 +9,13 @@ import { useDebouncedCallback } from 'use-debounce';
|
|||
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
|
||||
import { fetchRelationships } from 'mastodon/actions/accounts';
|
||||
import { showAlertForError } from 'mastodon/actions/alerts';
|
||||
import { importFetchedAccounts } from 'mastodon/actions/importer';
|
||||
import { fetchList } from 'mastodon/actions/lists';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { apiRequest } from 'mastodon/api';
|
||||
import { apiFollowAccount } from 'mastodon/api/accounts';
|
||||
import {
|
||||
apiGetAccounts,
|
||||
apiAddAccountToList,
|
||||
|
@ -28,13 +32,14 @@ import { DisplayName } from 'mastodon/components/display_name';
|
|||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.list_members', defaultMessage: 'Manage list members' },
|
||||
placeholder: {
|
||||
id: 'lists.search_placeholder',
|
||||
defaultMessage: 'Search people you follow',
|
||||
id: 'lists.search',
|
||||
defaultMessage: 'Search',
|
||||
},
|
||||
enterSearch: { id: 'lists.add_to_list', defaultMessage: 'Add to list' },
|
||||
add: { id: 'lists.add_member', defaultMessage: 'Add' },
|
||||
|
@ -51,17 +56,51 @@ const AccountItem: React.FC<{
|
|||
onToggle: (accountId: string) => void;
|
||||
}> = ({ accountId, listId, partOfList, onToggle }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const relationship = useAppSelector((state) =>
|
||||
accountId ? state.relationships.get(accountId) : undefined,
|
||||
);
|
||||
const following =
|
||||
accountId === me || relationship?.following || relationship?.requested;
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId) {
|
||||
dispatch(fetchRelationships([accountId]));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (partOfList) {
|
||||
void apiRemoveAccountFromList(listId, accountId);
|
||||
onToggle(accountId);
|
||||
} else {
|
||||
void apiAddAccountToList(listId, accountId);
|
||||
if (following) {
|
||||
void apiAddAccountToList(listId, accountId);
|
||||
onToggle(accountId);
|
||||
} else {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'CONFIRM_FOLLOW_TO_LIST',
|
||||
modalProps: {
|
||||
accountId,
|
||||
onConfirm: () => {
|
||||
apiFollowAccount(accountId)
|
||||
.then(() => apiAddAccountToList(listId, accountId))
|
||||
.then(() => {
|
||||
onToggle(accountId);
|
||||
return '';
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
dispatch(showAlertForError(err));
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onToggle(accountId);
|
||||
}, [accountId, listId, partOfList, onToggle]);
|
||||
}, [dispatch, accountId, following, listId, partOfList, onToggle]);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
|
@ -187,8 +226,7 @@ const ListMembers: React.FC<{
|
|||
signal: searchRequestRef.current.signal,
|
||||
params: {
|
||||
q: value,
|
||||
resolve: false,
|
||||
following: true,
|
||||
resolve: true,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
|
|
|
@ -11,10 +11,10 @@ import { connect } from 'react-redux';
|
|||
import { debounce } from 'lodash';
|
||||
|
||||
import { fetchMentionedUsers, expandMentionedUsers } from 'mastodon/actions/interactions';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
|
@ -74,7 +74,7 @@ class MentionedUsers extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />,
|
||||
<Account key={id} id={id} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@ import { connect } from 'react-redux';
|
|||
import { debounce } from 'lodash';
|
||||
|
||||
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
|
||||
import { fetchMutes, expandMutes } from '../../actions/mutes';
|
||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -72,7 +72,7 @@ class Mutes extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} defaultAction='mute' />,
|
||||
<Account key={id} id={id} defaultAction='mute' />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react';
|
|||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||
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 EmojiView from 'mastodon/components/emoji_view';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import StatusContainer from 'mastodon/containers/status_container';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
@ -153,7 +153,7 @@ class Notification extends ImmutablePureComponent {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<AccountContainer id={account.get('id')} hidden={this.props.hidden} />
|
||||
<Account id={account.get('id')} hidden={this.props.hidden} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
|
@ -173,7 +173,7 @@ class Notification extends ImmutablePureComponent {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<FollowRequestContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
|
||||
<FollowRequestContainer id={account.get('id')} hidden={this.props.hidden} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
|
@ -533,7 +533,7 @@ class Notification extends ImmutablePureComponent {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<AccountContainer id={account.get('id')} hidden={this.props.hidden} />
|
||||
<Account id={account.get('id')} hidden={this.props.hidden} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
|
|
|
@ -14,11 +14,11 @@ import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
|||
import { markAsPartial } from 'mastodon/actions/timelines';
|
||||
import { apiRequest } from 'mastodon/api';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { ColumnSearchHeader } from 'mastodon/components/column_search_header';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -170,12 +170,7 @@ export const Follows: React.FC<{
|
|||
}
|
||||
>
|
||||
{displayedAccountIds.map((accountId) => (
|
||||
<Account
|
||||
/* @ts-expect-error inferred props are wrong */
|
||||
id={accountId}
|
||||
key={accountId}
|
||||
withBio
|
||||
/>
|
||||
<Account id={accountId} key={accountId} withBio />
|
||||
))}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@ import { connect } from 'react-redux';
|
|||
import { debounce } from 'lodash';
|
||||
|
||||
import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
import { fetchReblogs, expandReblogs } from '../../actions/interactions';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -88,7 +88,7 @@ class Reblogs extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />,
|
||||
<Account key={id} id={id} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.follow_to_list.title',
|
||||
defaultMessage: 'Follow user?',
|
||||
},
|
||||
confirm: {
|
||||
id: 'confirmations.follow_to_list.confirm',
|
||||
defaultMessage: 'Follow and add to list',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmFollowToListModal: React.FC<
|
||||
{
|
||||
accountId: string;
|
||||
onConfirm: () => void;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({ accountId, onConfirm, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='confirmations.follow_to_list.message'
|
||||
defaultMessage='You need to be following {name} to add them to a list.'
|
||||
values={{ name: <strong>@{account?.acct}</strong> }}
|
||||
/>
|
||||
}
|
||||
confirm={intl.formatMessage(messages.confirm)}
|
||||
onConfirm={onConfirm}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -9,3 +9,4 @@ export { ConfirmEditStatusModal } from './edit_status';
|
|||
export { ConfirmUnfollowModal } from './unfollow';
|
||||
export { ConfirmClearNotificationsModal } from './clear_notifications';
|
||||
export { ConfirmLogOutModal } from './log_out';
|
||||
export { ConfirmFollowToListModal } from './follow_to_list';
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
ConfirmUnfollowModal,
|
||||
ConfirmClearNotificationsModal,
|
||||
ConfirmLogOutModal,
|
||||
ConfirmFollowToListModal,
|
||||
} from './confirmation_modals';
|
||||
import FocalPointModal from './focal_point_modal';
|
||||
import ImageModal from './image_modal';
|
||||
|
@ -65,6 +66,7 @@ export const MODAL_COMPONENTS = {
|
|||
'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }),
|
||||
'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
|
||||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'MUTE': MuteModal,
|
||||
'BLOCK': BlockModal,
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "του χρήστη {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "πιο χρησιμοποιημένη εφαρμογή",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "πιο χρησιμοποιημένη ετικέτα",
|
||||
"annual_report.summary.most_used_hashtag.none": "Κανένα",
|
||||
"annual_report.summary.new_posts.new_posts": "νέες αναρτήσεις",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Αυτό σε βάζει στην κορυφή του </topLabel><percentage></percentage><bottomLabel>των χρηστών του Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Δεν θα το πούμε στον Bernie.",
|
||||
|
@ -128,6 +129,7 @@
|
|||
"bundle_column_error.routing.body": "Η επιθυμητή σελίδα δεν βρέθηκε. Είναι σωστό το URL στο πεδίο διευθύνσεων;",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "Κλείσιμο",
|
||||
"bundle_modal_error.message": "Κάτι πήγε στραβά κατά τη φόρτωση αυτής της οθόνης.",
|
||||
"bundle_modal_error.retry": "Δοκίμασε ξανά",
|
||||
"closed_registrations.other_server_instructions": "Καθώς το Mastodon είναι αποκεντρωμένο, μπορείς να δημιουργήσεις λογαριασμό σε άλλον διακομιστή αλλά να συνεχίσεις να αλληλεπιδράς με αυτόν.",
|
||||
"closed_registrations_modal.description": "Η δημιουργία λογαριασμού στον {domain} προς το παρόν δεν είναι δυνατή, αλλά λάβε υπόψη ότι δεν χρειάζεσαι λογαριασμό ειδικά στον {domain} για να χρησιμοποιήσεις το Mastodon.",
|
||||
|
@ -138,13 +140,16 @@
|
|||
"column.blocks": "Αποκλεισμένοι χρήστες",
|
||||
"column.bookmarks": "Σελιδοδείκτες",
|
||||
"column.community": "Τοπική ροή",
|
||||
"column.create_list": "Δημιουργία λίστας",
|
||||
"column.direct": "Ιδιωτικές αναφορές",
|
||||
"column.directory": "Περιήγηση στα προφίλ",
|
||||
"column.domain_blocks": "Αποκλεισμένοι τομείς",
|
||||
"column.edit_list": "Επεξεργασία λίστας",
|
||||
"column.favourites": "Αγαπημένα",
|
||||
"column.firehose": "Ζωντανές ροές",
|
||||
"column.follow_requests": "Αιτήματα ακολούθησης",
|
||||
"column.home": "Αρχική",
|
||||
"column.list_members": "Διαχείριση μελών λίστας",
|
||||
"column.lists": "Λίστες",
|
||||
"column.mutes": "Αποσιωπημένοι χρήστες",
|
||||
"column.notifications": "Ειδοποιήσεις",
|
||||
|
@ -157,6 +162,7 @@
|
|||
"column_header.pin": "Καρφίτσωμα",
|
||||
"column_header.show_settings": "Εμφάνιση ρυθμίσεων",
|
||||
"column_header.unpin": "Ξεκαρφίτσωμα",
|
||||
"column_search.cancel": "Ακύρωση",
|
||||
"column_subheading.settings": "Ρυθμίσεις",
|
||||
"community.column_settings.local_only": "Τοπικά μόνο",
|
||||
"community.column_settings.media_only": "Μόνο πολυμέσα",
|
||||
|
@ -230,6 +236,8 @@
|
|||
"disabled_account_banner.text": "Ο λογαριασμός σου {disabledAccount} είναι προς το παρόν απενεργοποιημένος.",
|
||||
"dismissable_banner.community_timeline": "Αυτές είναι οι πιο πρόσφατες δημόσιες αναρτήσεις ατόμων των οποίων οι λογαριασμοί φιλοξενούνται στο {domain}.",
|
||||
"dismissable_banner.dismiss": "Παράβλεψη",
|
||||
"dismissable_banner.explore_links": "Αυτές οι ιστορίες ειδήσεων μοιράζονται περισσότερο στο fediverse σήμερα. Νεότερες ιστορίες ειδήσεων που δημοσιεύτηκαν από πιο διαφορετικά άτομα κατατάσσονται υψηλότερα.",
|
||||
"dismissable_banner.explore_statuses": "Αυτές οι αναρτήσεις από όλο το fediverse κερδίζουν την προσοχή σήμερα. Νεότερες αναρτήσεις με περισσότερες ενισχύσεις και αγαπημένα κατατάσσονται υψηλότερα.",
|
||||
"domain_block_modal.block": "Αποκλεισμός διακομιστή",
|
||||
"domain_block_modal.block_account_instead": "Αποκλεισμός @{name} αντ' αυτού",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Άτομα από αυτόν τον διακομιστή μπορούν να αλληλεπιδράσουν με τις παλιές αναρτήσεις σου.",
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "None",
|
||||
"annual_report.summary.new_posts.new_posts": "new posts",
|
||||
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>",
|
||||
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of {domain} users.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
|
||||
"annual_report.summary.thanks": "Thanks for being part of Mastodon!",
|
||||
"antennas.accounts": "{count} accounts",
|
||||
|
@ -342,6 +342,9 @@
|
|||
"confirmations.edit.confirm": "Edit",
|
||||
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||
"confirmations.edit.title": "Overwrite post?",
|
||||
"confirmations.follow_to_list.confirm": "Follow and add to list",
|
||||
"confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.",
|
||||
"confirmations.follow_to_list.title": "Follow user?",
|
||||
"confirmations.logout.confirm": "Log out",
|
||||
"confirmations.logout.message": "Are you sure you want to log out?",
|
||||
"confirmations.logout.title": "Log out?",
|
||||
|
@ -644,7 +647,7 @@
|
|||
"lists.replies_policy.none": "No one",
|
||||
"lists.save": "Save",
|
||||
"lists.save_to_edit_member": "You can edit list members after saving.",
|
||||
"lists.search_placeholder": "Search people you follow",
|
||||
"lists.search": "Search",
|
||||
"lists.show_replies_to": "Include replies from list members to",
|
||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||
"loading_indicator.label": "Loading…",
|
||||
|
|
1
app/javascript/mastodon/locales/nan.json
Normal file
1
app/javascript/mastodon/locales/nan.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -89,7 +89,7 @@
|
|||
"announcement.announcement": "Объявление",
|
||||
"annual_report.summary.archetype.booster": "Репостер",
|
||||
"annual_report.summary.archetype.lurker": "Молчун",
|
||||
"annual_report.summary.archetype.oracle": "Шаман",
|
||||
"annual_report.summary.archetype.oracle": "Гуру",
|
||||
"annual_report.summary.archetype.pollster": "Опросчик",
|
||||
"annual_report.summary.archetype.replier": "Душа компании",
|
||||
"annual_report.summary.followers.followers": "подписчиков",
|
||||
|
@ -284,7 +284,7 @@
|
|||
"empty_column.account_timeline": "Здесь нет постов!",
|
||||
"empty_column.account_unavailable": "Профиль недоступен",
|
||||
"empty_column.blocks": "Вы ещё никого не заблокировали.",
|
||||
"empty_column.bookmarked_statuses": "У вас пока нет постов в закладках. Как добавите один, он отобразится здесь.",
|
||||
"empty_column.bookmarked_statuses": "У вас пока нет закладок. Когда вы добавляете пост в закладки, он появляется здесь.",
|
||||
"empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
|
||||
"empty_column.direct": "У вас пока нет личных сообщений. Как только вы отправите или получите сообщение, оно появится здесь.",
|
||||
"empty_column.domain_blocks": "Скрытых доменов пока нет.",
|
||||
|
@ -293,9 +293,9 @@
|
|||
"empty_column.favourites": "Никто ещё не добавил этот пост в «Избранное». Как только кто-то это сделает, это отобразится здесь.",
|
||||
"empty_column.follow_requests": "Вам ещё не приходили запросы на подписку. Все новые запросы будут показаны здесь.",
|
||||
"empty_column.followed_tags": "Вы еще не подписались ни на один хэштег. Когда вы это сделаете, они появятся здесь.",
|
||||
"empty_column.hashtag": "С этим хэштегом пока ещё ничего не постили.",
|
||||
"empty_column.hashtag": "С этим хэштегом пока ещё ничего не публиковали.",
|
||||
"empty_column.home": "Ваша лента совсем пуста! Подписывайтесь на других, чтобы заполнить её.",
|
||||
"empty_column.list": "В этом списке пока ничего нет.",
|
||||
"empty_column.list": "В этом списке пока ничего нет. Когда пользователи в списке публикуют новые посты, они появляются здесь.",
|
||||
"empty_column.mutes": "Вы ещё никого не добавляли в список игнорируемых.",
|
||||
"empty_column.notification_requests": "Здесь ничего нет! Когда вы получите новые уведомления, они здесь появятся согласно вашим настройкам.",
|
||||
"empty_column.notifications": "У вас пока нет уведомлений. Взаимодействуйте с другими, чтобы завести разговор.",
|
||||
|
@ -566,7 +566,7 @@
|
|||
"notification.moderation_warning.action_sensitive": "С этого момента ваши сообщения будут помечены как деликатные.",
|
||||
"notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.",
|
||||
"notification.moderation_warning.action_suspend": "Действие вашей учётной записи приостановлено.",
|
||||
"notification.own_poll": "Ваш опрос закончился",
|
||||
"notification.own_poll": "Ваш опрос завершился",
|
||||
"notification.poll": "Голосование, в котором вы приняли участие, завершилось",
|
||||
"notification.reblog": "{name} продвинул(а) ваш пост",
|
||||
"notification.reblog.name_and_others_with_link": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> продвинули ваш пост",
|
||||
|
@ -575,7 +575,7 @@
|
|||
"notification.relationships_severance_event.domain_block": "Администратор {from} заблокировал {target} включая {followersCount} ваших подписчиков и {followingCount, plural, one {# аккаунт} few {# аккаунта} other {# аккаунтов}}, на которые вы подписаны.",
|
||||
"notification.relationships_severance_event.learn_more": "Узнать больше",
|
||||
"notification.relationships_severance_event.user_domain_block": "Вы заблокировали {target} включая {followersCount} ваших подписчиков и {followingCount, plural, one {# аккаунт} few {# аккаунта} other {# аккаунтов}}, на которые вы подписаны.",
|
||||
"notification.status": "{name} только что запостил",
|
||||
"notification.status": "{name} опубликовал(а) новый пост",
|
||||
"notification.update": "{name} изменил(а) пост",
|
||||
"notification_requests.accept": "Принять",
|
||||
"notification_requests.accept_multiple": "{count, plural, one {Принять # запрос…} few {Принять # запроса…} other {Принять # запросов…}}",
|
||||
|
@ -722,7 +722,7 @@
|
|||
"report.close": "Готово",
|
||||
"report.comment.title": "Есть ли что-нибудь ещё, что нам стоит знать?",
|
||||
"report.forward": "Переслать в {target}",
|
||||
"report.forward_hint": "Эта учётная запись расположена на другом узле. Отправить туда анонимную копию вашей жалобы?",
|
||||
"report.forward_hint": "Эта учётная запись расположена на другом сервере. Отправить туда анонимную копию вашей жалобы?",
|
||||
"report.mute": "Игнорировать",
|
||||
"report.mute_explanation": "Вы не будете видеть их посты. Они по-прежнему могут подписываться на вас и видеть ваши посты, но не будут знать, что они в списке игнорируемых.",
|
||||
"report.next": "Далее",
|
||||
|
@ -837,7 +837,7 @@
|
|||
"status.replied_to": "Ответил(а) {name}",
|
||||
"status.reply": "Ответить",
|
||||
"status.replyAll": "Ответить всем",
|
||||
"status.report": "Пожаловаться",
|
||||
"status.report": "Пожаловаться на @{name}",
|
||||
"status.sensitive_warning": "Содержимое «деликатного характера»",
|
||||
"status.share": "Поделиться",
|
||||
"status.show_less_all": "Свернуть все спойлеры в ветке",
|
||||
|
@ -875,6 +875,7 @@
|
|||
"upload_form.drag_and_drop.on_drag_cancel": "Перетаскивание было отменено. Вложение медиа {item} было удалено.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "Медиа вложение {item} было удалено.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "Медиа вложение {item} было перемещено.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Загружается медиафайл {item}.",
|
||||
"upload_form.edit": "Изменить",
|
||||
"upload_form.thumbnail": "Изменить обложку",
|
||||
"upload_form.video_description": "Опишите видео для людей с нарушением слуха или зрения",
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
"annual_report.summary.archetype.pollster": "投票狂魔",
|
||||
"annual_report.summary.archetype.replier": "评论区原住民",
|
||||
"annual_report.summary.followers.followers": "关注者",
|
||||
"annual_report.summary.followers.total": "{count} 人",
|
||||
"annual_report.summary.followers.total": "共 {count} 人",
|
||||
"annual_report.summary.here_it_is": "你的 {year} 年度回顾在此:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "最受欢迎嘟嘟",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "传播最广嘟嘟",
|
||||
|
@ -102,10 +102,10 @@
|
|||
"annual_report.summary.most_used_app.most_used_app": "最常用的应用",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "最常用的话题",
|
||||
"annual_report.summary.most_used_hashtag.none": "无",
|
||||
"annual_report.summary.new_posts.new_posts": "发嘟",
|
||||
"annual_report.summary.new_posts.new_posts": "新嘟嘟",
|
||||
"annual_report.summary.percentile.text": "<topLabel>这使你跻身 Mastodon 用户的前</topLabel><percentage></percentage><bottomLabel></bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "我们打死也不会告诉扣税国王的(他知道的话要来收你发嘟税了)。",
|
||||
"annual_report.summary.thanks": "感谢你这一年和 Mastodon 上的大家一起嘟嘟!",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "我们打死也不会告诉扣税国王的。",
|
||||
"annual_report.summary.thanks": "谢谢你这一年和 Mastodon 上的大家一起嘟嘟!",
|
||||
"attachments_list.unprocessed": "(未处理)",
|
||||
"audio.hide": "隐藏音频",
|
||||
"block_modal.remote_users_caveat": "我们将要求服务器 {domain} 尊重你的决定。然而,我们无法保证对方一定遵从,因为某些服务器可能会以不同的方案处理屏蔽操作。公开嘟文仍然可能对未登录的用户可见。",
|
||||
|
@ -392,7 +392,7 @@
|
|||
"home.hide_announcements": "隐藏公告",
|
||||
"home.pending_critical_update.body": "请尽快更新你的 Mastodon 服务器!",
|
||||
"home.pending_critical_update.link": "查看更新",
|
||||
"home.pending_critical_update.title": "紧急安全更新可用!",
|
||||
"home.pending_critical_update.title": "有紧急安全更新!",
|
||||
"home.show_announcements": "显示公告",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon无法通知对方用户你忽略了他们的通知。忽略通知不会阻止消息本身的发送。",
|
||||
"ignore_notifications_modal.filter_instead": "改为过滤",
|
||||
|
|
|
@ -523,6 +523,13 @@ a.sparkline {
|
|||
}
|
||||
}
|
||||
|
||||
.notification-group--annual-report {
|
||||
.notification-group__icon,
|
||||
.notification-group__main .link-button {
|
||||
color: var(--indigo-3);
|
||||
}
|
||||
}
|
||||
|
||||
@supports not selector(::-webkit-scrollbar) {
|
||||
html {
|
||||
scrollbar-color: rgba($action-button-color, 0.25)
|
||||
|
@ -530,13 +537,8 @@ a.sparkline {
|
|||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.notification-group--annual-report {
|
||||
.notification-group__icon,
|
||||
.notification-group__main .link-button {
|
||||
color: var(--indigo-3);
|
||||
.custom-scrollbars {
|
||||
::-webkit-scrollbar-thumb {
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,24 +59,26 @@ table {
|
|||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
.custom-scrollbars {
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: $action-button-color;
|
||||
border: 2px var(--background-border-color);
|
||||
border-radius: 12px;
|
||||
width: 6px;
|
||||
box-shadow: inset 0 0 0 2px var(--background-border-color);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: $action-button-color;
|
||||
border: 2px var(--background-border-color);
|
||||
border-radius: 12px;
|
||||
width: 6px;
|
||||
box-shadow: inset 0 0 0 2px var(--background-border-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: var(--background-border-color);
|
||||
border-radius: 0px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: var(--background-border-color);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,6 +131,10 @@ module User::HasSettings
|
|||
settings['reject_send_limited_to_suspects']
|
||||
end
|
||||
|
||||
def setting_system_scrollbars_ui
|
||||
settings['web.use_system_scrollbars']
|
||||
end
|
||||
|
||||
def setting_noindex
|
||||
settings['noindex']
|
||||
end
|
||||
|
|
|
@ -59,6 +59,7 @@ class UserSettings
|
|||
setting :use_custom_css, default: false
|
||||
setting :content_font_size, default: 'medium', in: %w(medium large x_large xx_large)
|
||||
setting :bookmark_category_needed, default: false
|
||||
setting :use_system_scrollbars, default: false
|
||||
setting :disable_swiping, default: false
|
||||
setting :disable_hover_cards, default: false
|
||||
setting :delete_modal, default: true
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
= ff.input :'web.disable_swiping', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_swiping')
|
||||
= ff.input :'web.disable_hover_cards', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_hover_cards')
|
||||
= ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui')
|
||||
= ff.input :'web.use_system_scrollbars', wrapper: :with_label, hint: I18n.t('simple_form.hints.defaults.setting_system_scrollbars_ui'), label: I18n.t('simple_form.labels.defaults.setting_system_scrollbars_ui')
|
||||
|
||||
.fields-group
|
||||
= ff.input :'web.content_font_size',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue