Merge commit '2e6bf60f1549e5c1f1cfea2d614f978bea17b8a2' into glitch-soc/merge-upstream

Conflicts:
- `README.md`:
  Upstream has updated their README but we have a completely different one.
  Kept our version of `README.md`
pull/2477/head
Claire 2023-12-10 18:05:02 +01:00
commit 9f92b05bd2
101 changed files with 819 additions and 488 deletions

View File

@ -70,7 +70,7 @@ services:
hard: -1 hard: -1
libretranslate: libretranslate:
image: libretranslate/libretranslate:v1.3.12 image: libretranslate/libretranslate:v1.4.0
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- lt-data:/home/libretranslate/.local - lt-data:/home/libretranslate/.local

View File

@ -16,6 +16,5 @@ linters:
exclude: exclude:
- 'app/views/admin/accounts/_buttons.html.haml' - 'app/views/admin/accounts/_buttons.html.haml'
- 'app/views/admin/accounts/_local_account.html.haml' - 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/roles/_form.html.haml' - 'app/views/admin/roles/_form.html.haml'
- 'app/views/layouts/application.html.haml' - 'app/views/layouts/application.html.haml'

View File

@ -533,7 +533,7 @@ GEM
public_suffix (5.0.3) public_suffix (5.0.3)
puma (6.4.0) puma (6.4.0)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.3.0) pundit (2.3.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.7.1) racc (1.7.1)

View File

@ -21,7 +21,7 @@ module Admin
account_action.save! account_action.save!
if account_action.with_report? if account_action.with_report?
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id]) redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
else else
redirect_to admin_account_path(@account.id) redirect_to admin_account_path(@account.id)
end end

View File

@ -20,7 +20,7 @@ class Admin::Disputes::AppealsController < Admin::BaseController
authorize @appeal, :approve? authorize @appeal, :approve?
log_action :reject, @appeal log_action :reject, @appeal
@appeal.reject!(current_account) @appeal.reject!(current_account)
UserMailer.appeal_rejected(@appeal.account.user, @appeal) UserMailer.appeal_rejected(@appeal.account.user, @appeal).deliver_later
redirect_to disputes_strike_path(@appeal.strike) redirect_to disputes_strike_path(@appeal.strike)
end end

View File

@ -40,6 +40,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
show show
end end
def redirect_to_app?
truthy_param?(:redirect_to_app)
end
helper_method :redirect_to_app?
private private
def require_captcha_if_needed! def require_captcha_if_needed!
@ -87,7 +93,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
end end
def after_confirmation_path_for(_resource_name, user) def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && truthy_param?(:redirect_to_app) if user.created_by_application && redirect_to_app?
user.created_by_application.confirmation_redirect_uri user.created_by_application.confirmation_redirect_uri
elsif user_signed_in? elsif user_signed_in?
web_url('start') web_url('start')

View File

@ -1,6 +1,7 @@
import { render, fireEvent, screen } from '@testing-library/react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import { render, fireEvent, screen } from 'mastodon/test_helpers';
import { Button } from '../button'; import { Button } from '../button';
describe('<Button />', () => { describe('<Button />', () => {

View File

@ -1,65 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { createPortal } from 'react-dom';
import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import { Icon } from 'mastodon/components/icon';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
export class ColumnBackButton extends PureComponent {
static propTypes = {
multiColumn: PropTypes.bool,
onClick: PropTypes.func,
...WithRouterPropTypes,
};
handleClick = () => {
const { onClick, history } = this.props;
if (onClick) {
onClick();
} else if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
};
render () {
const { multiColumn } = this.props;
const component = (
<button onClick={this.handleClick} className='column-back-button'>
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
if (multiColumn) {
return component;
} else {
// The portal container and the component may be rendered to the DOM in
// the same React render pass, so the container might not be available at
// the time `render()` is called.
const container = document.getElementById('tabs-bar__portal');
if (container === null) {
// The container wasn't available, force a re-render so that the
// component can eventually be inserted in the container and not scroll
// with the rest of the area.
this.forceUpdate();
return component;
} else {
return createPortal(component, container);
}
}
}
}
export default withRouter(ColumnBackButton);

View File

@ -0,0 +1,45 @@
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { useAppHistory } from './router';
type OnClickCallback = () => void;
function useHandleClick(onClick?: OnClickCallback) {
const history = useAppHistory();
return useCallback(() => {
if (onClick) {
onClick();
} else if (history.location.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
}, [history, onClick]);
}
export const ColumnBackButton: React.FC<{ onClick: OnClickCallback }> = ({
onClick,
}) => {
const handleClick = useHandleClick(onClick);
const component = (
<button onClick={handleClick} className='column-back-button'>
<Icon
id='chevron-left'
icon={ArrowBackIcon}
className='column-back-button__icon'
/>
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
return <ButtonInTabsBar>{component}</ButtonInTabsBar>;
};

View File

@ -1,20 +0,0 @@
import { FormattedMessage } from 'react-intl';
import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import { Icon } from 'mastodon/components/icon';
import { ColumnBackButton } from './column_back_button';
export default class ColumnBackButtonSlim extends ColumnBackButton {
render () {
return (
<div className='column-back-button--slim'>
<div role='button' tabIndex={0} onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</div>
</div>
);
}
}

View File

@ -1,6 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
@ -15,8 +14,11 @@ import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/
import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg'; import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar, useColumnsContext } from 'mastodon/features/ui/util/columns_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { useAppHistory } from './router';
const messages = defineMessages({ const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
@ -24,6 +26,34 @@ const messages = defineMessages({
moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' }, moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
}); });
const BackButton = ({ pinned, show }) => {
const history = useAppHistory();
const { multiColumn } = useColumnsContext();
const handleBackClick = useCallback(() => {
if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
}, [history]);
const showButton = history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || show);
if(!showButton) return null;
return (<button onClick={handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>);
};
BackButton.propTypes = {
pinned: PropTypes.bool,
show: PropTypes.bool,
};
class ColumnHeader extends PureComponent { class ColumnHeader extends PureComponent {
static contextTypes = { static contextTypes = {
@ -72,16 +102,6 @@ class ColumnHeader extends PureComponent {
this.props.onMove(1); this.props.onMove(1);
}; };
handleBackClick = () => {
const { history } = this.props;
if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
};
handleTransitionEnd = () => { handleTransitionEnd = () => {
this.setState({ animating: false }); this.setState({ animating: false });
}; };
@ -95,7 +115,7 @@ class ColumnHeader extends PureComponent {
}; };
render () { render () {
const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
const { collapsed, animating } = this.state; const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', { const wrapperClassName = classNames('column-header__wrapper', {
@ -138,14 +158,7 @@ class ColumnHeader extends PureComponent {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' icon={AddIcon} /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' icon={AddIcon} /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
} }
if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { backButton = <BackButton pinned={pinned} show={showBackButton} />;
backButton = (
<button onClick={this.handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
}
const collapsedContent = [ const collapsedContent = [
extraContent, extraContent,
@ -203,22 +216,12 @@ class ColumnHeader extends PureComponent {
</div> </div>
); );
if (multiColumn || placeholder) { if (placeholder) {
return component; return component;
} else { } else {
// The portal container and the component may be rendered to the DOM in return (<ButtonInTabsBar>
// the same React render pass, so the container might not be available at {component}
// the time `render()` is called. </ButtonInTabsBar>);
const container = document.getElementById('tabs-bar__portal');
if (container === null) {
// The container wasn't available, force a re-render so that the
// component can eventually be inserted in the container and not scroll
// with the rest of the area.
this.forceUpdate();
return component;
} else {
return createPortal(component, container);
}
} }
} }

View File

@ -1,7 +1,7 @@
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import React from 'react'; import React from 'react';
import { Router as OriginalRouter } from 'react-router'; import { Router as OriginalRouter, useHistory } from 'react-router';
import type { import type {
LocationDescriptor, LocationDescriptor,
@ -16,18 +16,23 @@ interface MastodonLocationState {
fromMastodon?: boolean; fromMastodon?: boolean;
mastodonModalKey?: string; mastodonModalKey?: string;
} }
type HistoryPath = Path | LocationDescriptor<MastodonLocationState>;
const browserHistory = createBrowserHistory< type LocationState = MastodonLocationState | null | undefined;
MastodonLocationState | undefined
>(); type HistoryPath = Path | LocationDescriptor<LocationState>;
const browserHistory = createBrowserHistory<LocationState>();
const originalPush = browserHistory.push.bind(browserHistory); const originalPush = browserHistory.push.bind(browserHistory);
const originalReplace = browserHistory.replace.bind(browserHistory); const originalReplace = browserHistory.replace.bind(browserHistory);
export function useAppHistory() {
return useHistory<LocationState>();
}
function normalizePath( function normalizePath(
path: HistoryPath, path: HistoryPath,
state?: MastodonLocationState, state?: LocationState,
): LocationDescriptorObject<MastodonLocationState> { ): LocationDescriptorObject<LocationState> {
const location = typeof path === 'string' ? { pathname: path } : { ...path }; const location = typeof path === 'string' ? { pathname: path } : { ...path };
if (location.state === undefined && state !== undefined) { if (location.state === undefined && state !== undefined) {

View File

@ -12,8 +12,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg';
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg';
import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active.svg'; import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active-fill.svg';
import { Avatar } from 'mastodon/components/avatar'; import { Avatar } from 'mastodon/components/avatar';
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
@ -264,7 +264,7 @@ class Header extends ImmutablePureComponent {
} }
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) { if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} iconComponent={account.getIn(['relationship', 'notifying']) ? NotificationsIcon : NotificationsActiveIcon} size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />; bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} iconComponent={account.getIn(['relationship', 'notifying']) ? NotificationsActiveIcon : NotificationsIcon} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
} }
if (me !== account.get('id')) { if (me !== account.get('id')) {

View File

@ -8,7 +8,7 @@ import { connect } from 'react-redux';
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts'; import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import ColumnBackButton from 'mastodon/components/column_back_button'; import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { LoadMore } from 'mastodon/components/load_more'; import { LoadMore } from 'mastodon/components/load_more';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container'; import ScrollContainer from 'mastodon/containers/scroll_container';
@ -203,7 +203,7 @@ class AccountGallery extends ImmutablePureComponent {
return ( return (
<Column> <Column>
<ColumnBackButton multiColumn={multiColumn} /> <ColumnBackButton />
<ScrollContainer scrollKey='account_gallery'> <ScrollContainer scrollKey='account_gallery'>
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}> <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>

View File

@ -16,7 +16,7 @@ import { getAccountHidden } from 'mastodon/selectors';
import { lookupAccount, fetchAccount } from '../../actions/accounts'; import { lookupAccount, fetchAccount } from '../../actions/accounts';
import { fetchFeaturedTags } from '../../actions/featured_tags'; import { fetchFeaturedTags } from '../../actions/featured_tags';
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines'; import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
import ColumnBackButton from '../../components/column_back_button'; import { ColumnBackButton } from '../../components/column_back_button';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import StatusList from '../../components/status_list'; import StatusList from '../../components/status_list';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
@ -184,7 +184,7 @@ class AccountTimeline extends ImmutablePureComponent {
return ( return (
<Column> <Column>
<ColumnBackButton multiColumn={multiColumn} /> <ColumnBackButton />
<StatusList <StatusList
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />} prepend={<HeaderContainer accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />}

View File

@ -9,10 +9,10 @@ import { is } from 'immutable';
import { ReactComponent as DownloadIcon } from '@material-symbols/svg-600/outlined/download.svg'; import { ReactComponent as DownloadIcon } from '@material-symbols/svg-600/outlined/download.svg';
import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg';
import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg';
import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg';
import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg'; import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg';
import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up.svg'; import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg';
import { throttle, debounce } from 'lodash'; import { throttle, debounce } from 'lodash';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';

View File

@ -10,7 +10,6 @@ import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchBlocks, expandBlocks } from '../../actions/blocks'; import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
@ -60,8 +59,7 @@ class Blocks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />; const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='ban' iconComponent={BlockIcon} heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='ban' iconComponent={BlockIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='blocks' scrollKey='blocks'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View File

@ -12,7 +12,6 @@ import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import DomainContainer from '../../containers/domain_container'; import DomainContainer from '../../containers/domain_container';
@ -61,9 +60,7 @@ class Blocks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />; const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
return ( return (
<Column bindToDocument={!multiColumn} icon='ban' iconComponent={BlockIcon} heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='ban' iconComponent={BlockIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='domain_blocks' scrollKey='domain_blocks'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View File

@ -12,7 +12,6 @@ import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outli
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { me } from '../../initial_state'; import { me } from '../../initial_state';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
@ -68,8 +67,7 @@ class FollowRequests extends ImmutablePureComponent {
); );
return ( return (
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='follow_requests' scrollKey='follow_requests'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View File

@ -19,7 +19,7 @@ import {
fetchFollowers, fetchFollowers,
expandFollowers, expandFollowers,
} from '../../actions/accounts'; } from '../../actions/accounts';
import ColumnBackButton from '../../components/column_back_button'; import { ColumnBackButton } from '../../components/column_back_button';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
@ -147,7 +147,7 @@ class Followers extends ImmutablePureComponent {
return ( return (
<Column> <Column>
<ColumnBackButton multiColumn={multiColumn} /> <ColumnBackButton />
<ScrollableList <ScrollableList
scrollKey='followers' scrollKey='followers'

View File

@ -19,7 +19,7 @@ import {
fetchFollowing, fetchFollowing,
expandFollowing, expandFollowing,
} from '../../actions/accounts'; } from '../../actions/accounts';
import ColumnBackButton from '../../components/column_back_button'; import { ColumnBackButton } from '../../components/column_back_button';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
@ -147,7 +147,7 @@ class Following extends ImmutablePureComponent {
return ( return (
<Column> <Column>
<ColumnBackButton multiColumn={multiColumn} /> <ColumnBackButton />
<ScrollableList <ScrollableList
scrollKey='following' scrollKey='following'

View File

@ -12,7 +12,6 @@ import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outli
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchMutes, expandMutes } from '../../actions/mutes'; import { fetchMutes, expandMutes } from '../../actions/mutes';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
@ -62,8 +61,7 @@ class Mutes extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />; const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='volume-off' iconComponent={VolumeOffIcon} heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='volume-off' iconComponent={VolumeOffIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='mutes' scrollKey='mutes'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View File

@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { fetchSuggestions } from 'mastodon/actions/suggestions'; import { fetchSuggestions } from 'mastodon/actions/suggestions';
import { markAsPartial } from 'mastodon/actions/timelines'; import { markAsPartial } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import ColumnBackButton from 'mastodon/components/column_back_button'; import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { EmptyAccount } from 'mastodon/components/empty_account'; import { EmptyAccount } from 'mastodon/components/empty_account';
import Account from 'mastodon/containers/account_container'; import Account from 'mastodon/containers/account_container';
@ -25,7 +25,6 @@ class Follows extends PureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
suggestions: ImmutablePropTypes.list, suggestions: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
multiColumn: PropTypes.bool,
}; };
componentDidMount () { componentDidMount () {
@ -39,7 +38,7 @@ class Follows extends PureComponent {
} }
render () { render () {
const { onBack, isLoading, suggestions, multiColumn } = this.props; const { onBack, isLoading, suggestions } = this.props;
let loadedContent; let loadedContent;
@ -53,7 +52,7 @@ class Follows extends PureComponent {
return ( return (
<Column> <Column>
<ColumnBackButton multiColumn={multiColumn} onClick={onBack} /> <ColumnBackButton onClick={onBack} />
<div className='scrollable privacy-policy'> <div className='scrollable privacy-policy'>
<div className='column-title'> <div className='column-title'>

View File

@ -47,7 +47,6 @@ class Onboarding extends ImmutablePureComponent {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
multiColumn: PropTypes.bool,
...WithRouterPropTypes, ...WithRouterPropTypes,
}; };
@ -100,14 +99,14 @@ class Onboarding extends ImmutablePureComponent {
} }
render () { render () {
const { account, multiColumn } = this.props; const { account } = this.props;
const { step, shareClicked } = this.state; const { step, shareClicked } = this.state;
switch(step) { switch(step) {
case 'follows': case 'follows':
return <Follows onBack={this.handleBackClick} multiColumn={multiColumn} />; return <Follows onBack={this.handleBackClick} />;
case 'share': case 'share':
return <Share onBack={this.handleBackClick} multiColumn={multiColumn} />; return <Share onBack={this.handleBackClick} />;
} }
return ( return (

View File

@ -14,7 +14,7 @@ import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/out
import SwipeableViews from 'react-swipeable-views'; import SwipeableViews from 'react-swipeable-views';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import ColumnBackButton from 'mastodon/components/column_back_button'; import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { me, domain } from 'mastodon/initial_state'; import { me, domain } from 'mastodon/initial_state';
@ -146,18 +146,17 @@ class Share extends PureComponent {
static propTypes = { static propTypes = {
onBack: PropTypes.func, onBack: PropTypes.func,
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
multiColumn: PropTypes.bool,
intl: PropTypes.object, intl: PropTypes.object,
}; };
render () { render () {
const { onBack, account, multiColumn, intl } = this.props; const { onBack, account, intl } = this.props;
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href; const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
return ( return (
<Column> <Column>
<ColumnBackButton multiColumn={multiColumn} onClick={onBack} /> <ColumnBackButton onClick={onBack} />
<div className='scrollable privacy-policy'> <div className='scrollable privacy-policy'>
<div className='column-title'> <div className='column-title'>

View File

@ -13,7 +13,6 @@ import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outline
import { getStatusList } from 'mastodon/selectors'; import { getStatusList } from 'mastodon/selectors';
import { fetchPinnedStatuses } from '../../actions/pin_statuses'; import { fetchPinnedStatuses } from '../../actions/pin_statuses';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import StatusList from '../../components/status_list'; import StatusList from '../../components/status_list';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
@ -52,8 +51,7 @@ class PinnedStatuses extends ImmutablePureComponent {
const { intl, statusIds, hasMore, multiColumn } = this.props; const { intl, statusIds, hasMore, multiColumn } = this.props;
return ( return (
<Column bindToDocument={!multiColumn} icon='thumb-tack' iconComponent={PushPinIcon} heading={intl.formatMessage(messages.heading)} ref={this.setRef}> <Column bindToDocument={!multiColumn} icon='thumb-tack' iconComponent={PushPinIcon} heading={intl.formatMessage(messages.heading)} ref={this.setRef} alwaysShowBackButton>
<ColumnBackButtonSlim />
<StatusList <StatusList
statusIds={statusIds} statusIds={statusIds}
scrollKey='pinned_statuses' scrollKey='pinned_statuses'

View File

@ -10,9 +10,9 @@ import classNames from 'classnames';
import Immutable from 'immutable'; import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description.svg'; import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description-fill.svg';
import { ReactComponent as OpenInNewIcon } from '@material-symbols/svg-600/outlined/open_in_new.svg'; import { ReactComponent as OpenInNewIcon } from '@material-symbols/svg-600/outlined/open_in_new.svg';
import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg';
import { Blurhash } from 'mastodon/components/blurhash'; import { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
@ -143,7 +143,7 @@ export default class Card extends PureComponent {
<strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong> <strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong>
{card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description'>{card.get('description')}</span>} {card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description' lang={language}>{card.get('description')}</span>}
</div> </div>
); );

View File

@ -58,7 +58,7 @@ class DetailedStatus extends ImmutablePureComponent {
handleAccountClick = (e) => { handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) { if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) {
e.preventDefault(); e.preventDefault();
this.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
} }
e.stopPropagation(); e.stopPropagation();

View File

@ -1,13 +1,15 @@
import { render, fireEvent, screen } from '@testing-library/react'; import { render, fireEvent, screen } from 'mastodon/test_helpers';
import Column from '../column'; import Column from '../column';
const fakeIcon = () => <span />;
describe('<Column />', () => { describe('<Column />', () => {
describe('<ColumnHeader /> click handler', () => { describe('<ColumnHeader /> click handler', () => {
it('runs the scroll animation if the column contains scrollable content', () => { it('runs the scroll animation if the column contains scrollable content', () => {
const scrollToMock = jest.fn(); const scrollToMock = jest.fn();
const { container } = render( const { container } = render(
<Column heading='notifications'> <Column heading='notifications' icon='notifications' iconComponent={fakeIcon}>
<div className='scrollable' /> <div className='scrollable' />
</Column>, </Column>,
); );
@ -17,7 +19,7 @@ describe('<Column />', () => {
}); });
it('does not try to scroll if there is no scrollable content', () => { it('does not try to scroll if there is no scrollable content', () => {
render(<Column heading='notifications' />); render(<Column heading='notifications' icon='notifications' iconComponent={fakeIcon} />);
fireEvent.click(screen.getByText('notifications')); fireEvent.click(screen.getByText('notifications'));
}); });
}); });

View File

@ -3,15 +3,15 @@ import { PureComponent } from 'react';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import ColumnHeader from '../../../components/column_header';
import { isMobile } from '../../../is_mobile'; import { isMobile } from '../../../is_mobile';
import { scrollTop } from '../../../scroll'; import { scrollTop } from '../../../scroll';
import ColumnHeader from './column_header';
export default class Column extends PureComponent { export default class Column extends PureComponent {
static propTypes = { static propTypes = {
heading: PropTypes.string, heading: PropTypes.string,
alwaysShowBackButton: PropTypes.bool,
icon: PropTypes.string, icon: PropTypes.string,
iconComponent: PropTypes.func, iconComponent: PropTypes.func,
children: PropTypes.node, children: PropTypes.node,
@ -51,13 +51,14 @@ export default class Column extends PureComponent {
}; };
render () { render () {
const { heading, icon, iconComponent, children, active, hideHeadingOnMobile } = this.props; const { heading, icon, iconComponent, children, active, hideHeadingOnMobile, alwaysShowBackButton } = this.props;
const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth)));
const columnHeaderId = showHeading && heading.replace(/ /g, '-'); const columnHeaderId = showHeading && heading.replace(/ /g, '-');
const header = showHeading && ( const header = showHeading && (
<ColumnHeader icon={icon} iconComponent={iconComponent} active={active} type={heading} onClick={this.handleHeaderClick} columnHeaderId={columnHeaderId} /> <ColumnHeader icon={icon} iconComponent={iconComponent} active={active} title={heading} onClick={this.handleHeaderClick} columnHeaderId={columnHeaderId} showBackButton={alwaysShowBackButton} />
); );
return ( return (
<div <div

View File

@ -1,41 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import classNames from 'classnames';
import { Icon } from 'mastodon/components/icon';
export default class ColumnHeader extends PureComponent {
static propTypes = {
icon: PropTypes.string,
iconComponent: PropTypes.func,
type: PropTypes.string,
active: PropTypes.bool,
onClick: PropTypes.func,
columnHeaderId: PropTypes.string,
};
handleClick = () => {
this.props.onClick();
};
render () {
const { icon, iconComponent, type, active, columnHeaderId } = this.props;
let iconElement = '';
if (icon) {
iconElement = <Icon id={icon} icon={iconComponent} className='column-header__icon' />;
}
return (
<h1 className={classNames('column-header', { active })} id={columnHeaderId || null}>
<button onClick={this.handleClick}>
{iconElement}
{type}
</button>
</h1>
);
}
}

View File

@ -1,5 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Children, cloneElement } from 'react'; import { Children, cloneElement, useCallback } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -21,6 +21,7 @@ import {
ListTimeline, ListTimeline,
Directory, Directory,
} from '../util/async-components'; } from '../util/async-components';
import { useColumnsContext } from '../util/columns_context';
import BundleColumnError from './bundle_column_error'; import BundleColumnError from './bundle_column_error';
import { ColumnLoading } from './column_loading'; import { ColumnLoading } from './column_loading';
@ -43,6 +44,17 @@ const componentMap = {
'DIRECTORY': Directory, 'DIRECTORY': Directory,
}; };
const TabsBarPortal = () => {
const {setTabsBarElement} = useColumnsContext();
const setRef = useCallback((element) => {
if(element)
setTabsBarElement(element);
}, [setTabsBarElement]);
return <div id='tabs-bar__portal' ref={setRef} />;
};
export default class ColumnsArea extends ImmutablePureComponent { export default class ColumnsArea extends ImmutablePureComponent {
static propTypes = { static propTypes = {
columns: ImmutablePropTypes.list.isRequired, columns: ImmutablePropTypes.list.isRequired,
@ -146,7 +158,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
</div> </div>
<div className='columns-area__panels__main'> <div className='columns-area__panels__main'>
<div className='tabs-bar__wrapper'><div id='tabs-bar__portal' /></div> <div className='tabs-bar__wrapper'><TabsBarPortal /></div>
<div className='columns-area columns-area--mobile'>{children}</div> <div className='columns-area columns-area--mobile'>{children}</div>
</div> </div>

View File

@ -64,8 +64,8 @@ import {
About, About,
PrivacyPolicy, PrivacyPolicy,
} from './util/async-components'; } from './util/async-components';
import { ColumnsContextProvider } from './util/columns_context';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
// Dummy import, to make sure that <Status /> ends up in the application bundle. // Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles. // Without this it ends up in ~8 very commonly used bundles.
import '../../components/status'; import '../../components/status';
@ -179,68 +179,70 @@ class SwitchingColumnsArea extends PureComponent {
} }
return ( return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}> <ColumnsContextProvider multiColumn={!singleColumn}>
<WrappedSwitch> <ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}>
{redirect} <WrappedSwitch>
{redirect}
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null} {singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null} {singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}
{/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */} {/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
{!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null} {!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null}
{!singleColumn && pathName === '/home' ? <Redirect from='/home' to='/deck/getting-started' exact /> : null} {!singleColumn && pathName === '/home' ? <Redirect from='/home' to='/deck/getting-started' exact /> : null}
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/about' component={About} content={children} /> <WrappedRoute path='/about' component={About} content={children} />
<WrappedRoute path='/privacy-policy' component={PrivacyPolicy} content={children} /> <WrappedRoute path='/privacy-policy' component={PrivacyPolicy} content={children} />
<WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} /> <WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} />
<Redirect from='/timelines/public' to='/public' exact /> <Redirect from='/timelines/public' to='/public' exact />
<Redirect from='/timelines/public/local' to='/public/local' exact /> <Redirect from='/timelines/public/local' to='/public/local' exact />
<WrappedRoute path='/public' exact component={Firehose} componentParams={{ feedType: 'public' }} content={children} /> <WrappedRoute path='/public' exact component={Firehose} componentParams={{ feedType: 'public' }} content={children} />
<WrappedRoute path='/public/local' exact component={Firehose} componentParams={{ feedType: 'community' }} content={children} /> <WrappedRoute path='/public/local' exact component={Firehose} componentParams={{ feedType: 'community' }} content={children} />
<WrappedRoute path='/public/remote' exact component={Firehose} componentParams={{ feedType: 'public:remote' }} content={children} /> <WrappedRoute path='/public/remote' exact component={Firehose} componentParams={{ feedType: 'public:remote' }} content={children} />
<WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} /> <WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} />
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} /> <WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} /> <WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} /> <WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} /> <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} /> <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/start' exact component={Onboarding} content={children} /> <WrappedRoute path='/start' exact component={Onboarding} content={children} />
<WrappedRoute path='/directory' component={Directory} content={children} /> <WrappedRoute path='/directory' component={Directory} content={children} />
<WrappedRoute path={['/explore', '/search']} component={Explore} content={children} /> <WrappedRoute path={['/explore', '/search']} component={Explore} content={children} />
<WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} /> <WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} />
<WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} /> <WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} />
<WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} /> <WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} />
<WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} /> <WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} /> <WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} />
<WrappedRoute path={['/accounts/:id/following', '/users/:acct/following', '/@:acct/following']} component={Following} content={children} /> <WrappedRoute path={['/accounts/:id/following', '/users/:acct/following', '/@:acct/following']} component={Following} content={children} />
<WrappedRoute path={['/@:acct/media', '/accounts/:id/media']} component={AccountGallery} content={children} /> <WrappedRoute path={['/@:acct/media', '/accounts/:id/media']} component={AccountGallery} content={children} />
<WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} /> <WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} /> <WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} /> <WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} />
{/* Legacy routes, cannot be easily factored with other routes because they share a param name */} {/* Legacy routes, cannot be easily factored with other routes because they share a param name */}
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} /> <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} /> <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} /> <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} /> <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} /> <WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} /> <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
<WrappedRoute path='/followed_tags' component={FollowedTags} content={children} /> <WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} /> <WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} /> <WrappedRoute path='/lists' component={Lists} content={children} />
<Route component={BundleColumnError} /> <Route component={BundleColumnError} />
</WrappedSwitch> </WrappedSwitch>
</ColumnsAreaContainer> </ColumnsAreaContainer>
</ColumnsContextProvider>
); );
} }

View File

@ -0,0 +1,51 @@
import type { ReactElement } from 'react';
import { createContext, useContext, useMemo, useState } from 'react';
import { createPortal } from 'react-dom';
export const ColumnsContext = createContext<{
tabsBarElement: HTMLElement | null;
setTabsBarElement: (element: HTMLElement) => void;
multiColumn: boolean;
}>({
tabsBarElement: null,
multiColumn: false,
setTabsBarElement: () => undefined, // no-op
});
export function useColumnsContext() {
return useContext(ColumnsContext);
}
export const ButtonInTabsBar: React.FC<{
children: ReactElement | string | undefined;
}> = ({ children }) => {
const { multiColumn, tabsBarElement } = useColumnsContext();
if (multiColumn) {
return children;
} else if (!tabsBarElement) {
return children;
} else {
return createPortal(children, tabsBarElement);
}
};
type ContextValue = React.ContextType<typeof ColumnsContext>;
export const ColumnsContextProvider: React.FC<
React.PropsWithChildren<{ multiColumn: boolean }>
> = ({ multiColumn, children }) => {
const [tabsBarElement, setTabsBarElement] =
useState<ContextValue['tabsBarElement']>(null);
const contextValue = useMemo<ContextValue>(
() => ({ multiColumn, tabsBarElement, setTabsBarElement }),
[multiColumn, tabsBarElement],
);
return (
<ColumnsContext.Provider value={contextValue}>
{children}
</ColumnsContext.Provider>
);
};

View File

@ -10,11 +10,11 @@ import { is } from 'immutable';
import { ReactComponent as FullscreenIcon } from '@material-symbols/svg-600/outlined/fullscreen.svg'; import { ReactComponent as FullscreenIcon } from '@material-symbols/svg-600/outlined/fullscreen.svg';
import { ReactComponent as FullscreenExitIcon } from '@material-symbols/svg-600/outlined/fullscreen_exit.svg'; import { ReactComponent as FullscreenExitIcon } from '@material-symbols/svg-600/outlined/fullscreen_exit.svg';
import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg';
import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg';
import { ReactComponent as RectangleIcon } from '@material-symbols/svg-600/outlined/rectangle.svg'; import { ReactComponent as RectangleIcon } from '@material-symbols/svg-600/outlined/rectangle.svg';
import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg';
import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg'; import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg';
import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up.svg'; import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import { Blurhash } from 'mastodon/components/blurhash'; import { Blurhash } from 'mastodon/components/blurhash';

View File

@ -1,5 +1,5 @@
{ {
"about.blocks": "Valvotut palvelimet", "about.blocks": "Moderoidut palvelimet",
"about.contact": "Ota yhteyttä:", "about.contact": "Ota yhteyttä:",
"about.disclaimer": "Mastodon on vapaa avoimen lähdekoodin ohjelmisto ja Mastodon gGmbH:n tavaramerkki.", "about.disclaimer": "Mastodon on vapaa avoimen lähdekoodin ohjelmisto ja Mastodon gGmbH:n tavaramerkki.",
"about.domain_blocks.no_reason_available": "Syytä ei ole ilmoitettu", "about.domain_blocks.no_reason_available": "Syytä ei ole ilmoitettu",

View File

@ -20,7 +20,7 @@
"account.block_short": "Bloquear", "account.block_short": "Bloquear",
"account.blocked": "Bloqueada", "account.blocked": "Bloqueada",
"account.browse_more_on_origin_server": "Busca máis no perfil orixinal", "account.browse_more_on_origin_server": "Busca máis no perfil orixinal",
"account.cancel_follow_request": "Retirar solicitude de seguimento", "account.cancel_follow_request": "Cancelar a solicitude de seguimento",
"account.direct": "Mencionar de xeito privado a @{name}", "account.direct": "Mencionar de xeito privado a @{name}",
"account.disable_notifications": "Deixar de notificarme cando @{name} publica", "account.disable_notifications": "Deixar de notificarme cando @{name} publica",
"account.domain_blocked": "Dominio agochado", "account.domain_blocked": "Dominio agochado",

View File

@ -590,7 +590,7 @@
"search.quick_action.open_url": "마스토돈에서 URL 열기", "search.quick_action.open_url": "마스토돈에서 URL 열기",
"search.quick_action.status_search": "{x}에 맞는 게시물", "search.quick_action.status_search": "{x}에 맞는 게시물",
"search.search_or_paste": "검색하거나 URL 붙여넣기", "search.search_or_paste": "검색하거나 URL 붙여넣기",
"search_popout.full_text_search_disabled_message": "{domain}에서는 용할 수 없습니다.", "search_popout.full_text_search_disabled_message": "{domain}에서는 용할 수 없습니다.",
"search_popout.language_code": "ISO 언어코드", "search_popout.language_code": "ISO 언어코드",
"search_popout.options": "검색 옵션", "search_popout.options": "검색 옵션",
"search_popout.quick_actions": "빠른 작업", "search_popout.quick_actions": "빠른 작업",

View File

@ -81,7 +81,7 @@
"admin.impact_report.instance_follows": "Obserwujący, których straciliby ich użytkownicy", "admin.impact_report.instance_follows": "Obserwujący, których straciliby ich użytkownicy",
"admin.impact_report.title": "Podsumowanie wpływu", "admin.impact_report.title": "Podsumowanie wpływu",
"alert.rate_limited.message": "Spróbuj ponownie po {retry_time, time, medium}.", "alert.rate_limited.message": "Spróbuj ponownie po {retry_time, time, medium}.",
"alert.rate_limited.title": "Ograniczony czasowo", "alert.rate_limited.title": "Ograniczenie liczby zapytań",
"alert.unexpected.message": "Wystąpił nieoczekiwany błąd.", "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
"alert.unexpected.title": "Ups!", "alert.unexpected.title": "Ups!",
"announcement.announcement": "Ogłoszenie", "announcement.announcement": "Ogłoszenie",

View File

@ -0,0 +1,62 @@
import PropTypes from 'prop-types';
import type { PropsWithChildren } from 'react';
import { Component } from 'react';
import { IntlProvider } from 'react-intl';
import { MemoryRouter } from 'react-router';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render as rtlRender } from '@testing-library/react';
class FakeIdentityWrapper extends Component<
PropsWithChildren<{ signedIn: boolean }>
> {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
getChildContext() {
return {
identity: {
signedIn: this.props.signedIn,
accountId: '123',
accessToken: 'test-access-token',
},
};
}
render() {
return this.props.children;
}
}
function render(
ui: React.ReactElement,
{ locale = 'en', signedIn = true, ...renderOptions } = {},
) {
const Wrapper = (props: { children: React.ReactElement }) => {
return (
<MemoryRouter>
<IntlProvider locale={locale}>
<FakeIdentityWrapper signedIn={signedIn}>
{props.children}
</FakeIdentityWrapper>
</IntlProvider>
</MemoryRouter>
);
};
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
// re-export everything
// eslint-disable-next-line import/no-extraneous-dependencies
export * from '@testing-library/react';
// override render method
export { render };

View File

@ -3137,20 +3137,6 @@ $ui-header-height: 55px;
margin-inline-end: 5px; margin-inline-end: 5px;
} }
.column-back-button--slim {
position: relative;
}
.column-back-button--slim-button {
cursor: pointer;
flex: 0 0 auto;
font-size: 16px;
padding: 15px;
position: absolute;
inset-inline-end: 0;
top: -50px;
}
.react-toggle { .react-toggle {
display: inline-block; display: inline-block;
position: relative; position: relative;
@ -7434,7 +7420,12 @@ noscript {
border: 1px solid lighten($ui-base-color, 12%); border: 1px solid lighten($ui-base-color, 12%);
border-radius: 4px; border-radius: 4px;
box-sizing: content-box; box-sizing: content-box;
padding: 2px; padding: 5px;
.icon {
width: 24px;
height: 24px;
}
} }
} }
@ -7517,6 +7508,11 @@ noscript {
color: lighten($ui-highlight-color, 8%); color: lighten($ui-highlight-color, 8%);
} }
.icon {
width: 18px;
height: 18px;
}
.verified { .verified {
border: 1px solid rgba($valid-value-color, 0.5); border: 1px solid rgba($valid-value-color, 0.5);
margin-top: -1px; margin-top: -1px;
@ -7537,6 +7533,16 @@ noscript {
color: $valid-value-color; color: $valid-value-color;
} }
dd {
display: flex;
align-items: center;
gap: 4px;
span {
display: flex;
}
}
a { a {
color: $valid-value-color; color: $valid-value-color;
} }

View File

@ -75,7 +75,12 @@ class AttachmentBatch
end end
when :fog when :fog
logger.debug { "Deleting #{attachment.path(style)}" } logger.debug { "Deleting #{attachment.path(style)}" }
attachment.send(:directory).files.new(key: attachment.path(style)).destroy
begin
attachment.send(:directory).files.new(key: attachment.path(style)).destroy
rescue Fog::Storage::OpenStack::NotFound
# Ignore failure to delete a file that has already been deleted
end
when :azure when :azure
logger.debug { "Deleting #{attachment.path(style)}" } logger.debug { "Deleting #{attachment.path(style)}" }
attachment.destroy attachment.destroy

View File

@ -8,13 +8,15 @@ class UserMailer < Devise::Mailer
helper :instance helper :instance
helper :statuses helper :statuses
helper :formatting helper :formatting
helper :routing
helper RoutingHelper before_action :set_instance
default to: -> { @resource.email }
def confirmation_instructions(user, token, *, **) def confirmation_instructions(user, token, *, **)
@resource = user @resource = user
@token = token @token = token
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
@ -28,185 +30,177 @@ class UserMailer < Devise::Mailer
def reset_password_instructions(user, token, *, **) def reset_password_instructions(user, token, *, **)
@resource = user @resource = user
@token = token @token = token
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject') mail subject: default_devise_subject
end end
end end
def password_change(user, *, **) def password_change(user, *, **)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject') mail subject: default_devise_subject
end end
end end
def email_changed(user, *, **) def email_changed(user, *, **)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject') mail subject: default_devise_subject
end end
end end
def two_factor_enabled(user, *, **) def two_factor_enabled(user, *, **)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_enabled.subject') mail subject: default_devise_subject
end end
end end
def two_factor_disabled(user, *, **) def two_factor_disabled(user, *, **)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_disabled.subject') mail subject: default_devise_subject
end end
end end
def two_factor_recovery_codes_changed(user, *, **) def two_factor_recovery_codes_changed(user, *, **)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject') mail subject: default_devise_subject
end end
end end
def webauthn_enabled(user, *, **) def webauthn_enabled(user, *, **)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_enabled.subject') mail subject: default_devise_subject
end end
end end
def webauthn_disabled(user, *, **) def webauthn_disabled(user, *, **)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_disabled.subject') mail subject: default_devise_subject
end end
end end
def webauthn_credential_added(user, webauthn_credential) def webauthn_credential_added(user, webauthn_credential)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
@webauthn_credential = webauthn_credential @webauthn_credential = webauthn_credential
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.added.subject') mail subject: I18n.t('devise.mailer.webauthn_credential.added.subject')
end end
end end
def webauthn_credential_deleted(user, webauthn_credential) def webauthn_credential_deleted(user, webauthn_credential)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
@webauthn_credential = webauthn_credential @webauthn_credential = webauthn_credential
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject') mail subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject')
end end
end end
def welcome(user) def welcome(user)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject') mail subject: default_i18n_subject
end end
end end
def backup_ready(user, backup) def backup_ready(user, backup)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
@backup = backup @backup = backup
return unless @resource.active_for_authentication? return unless @resource.active_for_authentication?
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject') mail subject: default_i18n_subject
end end
end end
def warning(user, warning) def warning(user, warning)
@resource = user @resource = user
@warning = warning @warning = warning
@instance = Rails.configuration.x.local_domain
@statuses = @warning.statuses.includes(:account, :preloadable_poll, :media_attachments, active_mentions: [:account]) @statuses = @warning.statuses.includes(:account, :preloadable_poll, :media_attachments, active_mentions: [:account])
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t("user_mailer.warning.subject.#{@warning.action}", acct: "@#{user.account.local_username_and_domain}") mail subject: I18n.t("user_mailer.warning.subject.#{@warning.action}", acct: "@#{user.account.local_username_and_domain}")
end end
end end
def appeal_approved(user, appeal) def appeal_approved(user, appeal)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
@appeal = appeal @appeal = appeal
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.appeal_approved.subject', date: l(@appeal.created_at)) mail subject: default_i18n_subject(date: l(@appeal.created_at))
end end
end end
def appeal_rejected(user, appeal) def appeal_rejected(user, appeal)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
@appeal = appeal @appeal = appeal
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.appeal_rejected.subject', date: l(@appeal.created_at)) mail subject: default_i18n_subject(date: l(@appeal.created_at))
end end
end end
def suspicious_sign_in(user, remote_ip, user_agent, timestamp) def suspicious_sign_in(user, remote_ip, user_agent, timestamp)
@resource = user @resource = user
@instance = Rails.configuration.x.local_domain
@remote_ip = remote_ip @remote_ip = remote_ip
@user_agent = user_agent @user_agent = user_agent
@detection = Browser.new(user_agent) @detection = Browser.new(user_agent)
@timestamp = timestamp.to_time.utc @timestamp = timestamp.to_time.utc
I18n.with_locale(locale) do I18n.with_locale(locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.suspicious_sign_in.subject') mail subject: default_i18n_subject
end end
end end
private private
def default_devise_subject
I18n.t(:subject, scope: ['devise.mailer', action_name])
end
def set_instance
@instance = Rails.configuration.x.local_domain
end
def locale def locale
@resource.locale.presence || I18n.default_locale @resource.locale.presence || I18n.default_locale
end end

View File

@ -35,7 +35,7 @@ class Tag < ApplicationRecord
HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}"
HASHTAG_RE = %r{(?<![=/)[:word]])#(#{HASHTAG_NAME_PAT})}i HASHTAG_RE = %r{(?<![=/)\w])#(#{HASHTAG_NAME_PAT})}i
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]#{HASHTAG_SEPARATORS}]/ HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]#{HASHTAG_SEPARATORS}]/

View File

@ -488,7 +488,7 @@ class User < ApplicationRecord
end end
def validate_email_dns? def validate_email_dns?
email_changed? && !external? && !(Rails.env.test? || Rails.env.development?) email_changed? && !external? && !Rails.env.local? # rubocop:disable Rails/UnknownEnv
end end
def validate_role_elevation def validate_role_elevation

View File

@ -10,7 +10,9 @@ class REST::FeaturedTagSerializer < ActiveModel::Serializer
end end
def url def url
short_account_tag_url(object.account, object.tag) # The path is hardcoded because we have to deal with both local and
# remote users, which are different routes
account_with_domain_url(object.account, "tagged/#{object.tag.to_param}")
end end
def name def name

View File

@ -37,6 +37,8 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService
end end
def process_items(items) def process_items(items)
return if items.nil?
process_note_items(items) if @options[:note] process_note_items(items) if @options[:note]
process_hashtag_items(items) if @options[:hashtag] process_hashtag_items(items) if @options[:hashtag]
end end

View File

@ -71,7 +71,7 @@ class FollowService < BaseService
if @target_account.local? if @target_account.local?
LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request') LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request')
elsif @target_account.activitypub? elsif @target_account.activitypub?
ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), @source_account.id, @target_account.inbox_url) ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), @source_account.id, @target_account.inbox_url, { 'bypass_availability' => true })
end end
follow_request follow_request

View File

@ -22,9 +22,10 @@
.fields-group .fields-group
- %i(username by_domain display_name email ip).each do |key| - %i(username by_domain display_name email ip).each do |key|
- unless key == :by_domain && params[:origin] != 'remote' - next if key == :by_domain && params[:origin] != 'remote'
.input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}") .input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}")
.actions .actions
%button.button= t('admin.accounts.search') %button.button= t('admin.accounts.search')

View File

@ -1,13 +1,29 @@
- content_for :page_title do - content_for :page_title do
= t('auth.resend_confirmation') = t('auth.resend_confirmation')
= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| - if resource.errors.of_kind?(:email, :already_confirmed)
= render 'shared/error_messages', object: resource .simple_form
= render 'auth/shared/progress', stage: resource.approved? ? 'completed' : 'confirmed'
.fields-group - if resource.approved?
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, readonly: current_user.present?, hint: current_user.present? && t('auth.confirmations.wrong_email_hint') %h1.title= t('auth.confirmations.welcome_title', name: resource.account.username)
%p.lead= t('auth.confirmations.registration_complete', domain: site_hostname)
- if resource.created_by_application && redirect_to_app?
- app = resource.created_by_application
%p.lead= t('auth.confirmations.redirect_to_app_html', app_name: app.name, clicking_this_link: link_to(t('auth.confirmations.clicking_this_link'), app.confirmation_redirect_uri))
- else
%p.lead= t('auth.confirmations.proceed_to_login_html', login_link: link_to_login(t('auth.confirmations.login_link')))
- else
%h1.title= t('auth.confirmations.awaiting_review_title')
%p.lead= t('auth.confirmations.awaiting_review', domain: site_hostname)
- else
= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f|
= render 'shared/error_messages', object: resource
.actions .fields-group
= f.button :button, t('auth.resend_confirmation'), type: :submit = f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, readonly: current_user.present?, hint: current_user.present? && t('auth.confirmations.wrong_email_hint')
.actions
= f.button :button, t('auth.resend_confirmation'), type: :submit
.form-footer= render 'auth/shared/links' .form-footer= render 'auth/shared/links'

View File

@ -1,4 +1,4 @@
- progress_index = { rules: 0, details: 1, confirm: 2 }[stage.to_sym] - progress_index = { rules: 0, details: 1, confirm: 2, confirmed: 3, completed: 4 }[stage.to_sym]
%ol.progress-tracker %ol.progress-tracker
%li{ class: progress_index.positive? ? 'completed' : 'active' } %li{ class: progress_index.positive? ? 'completed' : 'active' }
@ -20,6 +20,8 @@
.label= t('auth.progress.confirm') .label= t('auth.progress.confirm')
- if approved_registrations? - if approved_registrations?
%li.separator{ class: progress_index > 2 ? 'completed' : nil } %li.separator{ class: progress_index > 2 ? 'completed' : nil }
%li %li{ class: [progress_index > 3 && 'completed', progress_index == 3 && 'active'] }
.circle .circle
- if progress_index > 3
= check_icon
.label= t('auth.progress.review') .label= t('auth.progress.review')

View File

@ -24,14 +24,14 @@
%th= t('admin.accounts.followers') %th= t('admin.accounts.followers')
%td= number_with_delimiter @export.total_followers %td= number_with_delimiter @export.total_followers
%td %td
%tr
%th= t('exports.blocks')
%td= number_with_delimiter @export.total_blocks
%td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
%tr %tr
%th= t('exports.mutes') %th= t('exports.mutes')
%td= number_with_delimiter @export.total_mutes %td= number_with_delimiter @export.total_mutes
%td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv) %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
%tr
%th= t('exports.blocks')
%td= number_with_delimiter @export.total_blocks
%td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
%tr %tr
%th= t('exports.domain_blocks') %th= t('exports.domain_blocks')
%td= number_with_delimiter @export.total_domain_blocks %td= number_with_delimiter @export.total_domain_blocks

View File

@ -23,9 +23,10 @@ class ActivityPub::DeliveryWorker
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
def perform(json, source_account_id, inbox_url, options = {}) def perform(json, source_account_id, inbox_url, options = {})
return unless DeliveryFailureTracker.available?(inbox_url)
@options = options.with_indifferent_access @options = options.with_indifferent_access
return unless @options[:bypass_availability] || DeliveryFailureTracker.available?(inbox_url)
@json = json @json = json
@source_account = Account.find(source_account_id) @source_account = Account.find(source_account_id)
@inbox_url = inbox_url @inbox_url = inbox_url

View File

@ -206,7 +206,7 @@ module Mastodon
# We use our own middleware for this # We use our own middleware for this
config.public_file_server.enabled = false config.public_file_server.enabled = false
config.middleware.use PublicFileServerMiddleware if Rails.env.development? || Rails.env.test? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true' config.middleware.use PublicFileServerMiddleware if Rails.env.local? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true' # rubocop:disable Rails/UnknownEnv
config.middleware.use Rack::Attack config.middleware.use Rack::Attack
config.middleware.use Mastodon::RackMiddleware config.middleware.use Mastodon::RackMiddleware

View File

@ -72,6 +72,7 @@ ignore_unused:
- 'themes.*' - 'themes.*'
- 'move_handler.carry_{mutes,blocks}_over_text' - 'move_handler.carry_{mutes,blocks}_over_text'
- 'admin_mailer.*.subject' - 'admin_mailer.*.subject'
- 'user_mailer.*.subject'
- 'notification_mailer.*' - 'notification_mailer.*'
- 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html' - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html'
- 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html' - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html'

View File

@ -1,4 +1,4 @@
# frozen_string_literal: true # frozen_string_literal: true
StrongMigrations.start_after = 2017_09_24_022025 StrongMigrations.start_after = 2017_09_24_022025
StrongMigrations.target_version = 10 StrongMigrations.target_version = 12

View File

@ -1174,6 +1174,7 @@ cy:
functional: Mae eich cyfrif nawr yn weithredol. functional: Mae eich cyfrif nawr yn weithredol.
pending: Mae'ch cais yn aros i gael ei adolygu gan ein staff. Gall hyn gymryd cryn amser. Byddwch yn derbyn e-bost os caiff eich cais ei gymeradwyo. pending: Mae'ch cais yn aros i gael ei adolygu gan ein staff. Gall hyn gymryd cryn amser. Byddwch yn derbyn e-bost os caiff eich cais ei gymeradwyo.
redirecting_to: Mae eich cyfrif yn anweithredol oherwydd ei fod ar hyn o bryd yn ailgyfeirio i %{acct}. redirecting_to: Mae eich cyfrif yn anweithredol oherwydd ei fod ar hyn o bryd yn ailgyfeirio i %{acct}.
self_destruct: Gab fod parth %{domain} yn cau, dim ond mynediad cyfyngedig fyddwch yn ei gael i'ch cyfrif.
view_strikes: Gweld rybuddion y gorffennol yn erbyn eich cyfrif view_strikes: Gweld rybuddion y gorffennol yn erbyn eich cyfrif
too_fast: Cafodd y ffurflen ei chyflwyno'n rhy gyflym, ceisiwch eto. too_fast: Cafodd y ffurflen ei chyflwyno'n rhy gyflym, ceisiwch eto.
use_security_key: Defnyddiwch allwedd diogelwch use_security_key: Defnyddiwch allwedd diogelwch
@ -1675,6 +1676,9 @@ cy:
over_daily_limit: Rydych wedi mynd dros y terfyn o %{limit} postiad a drefnwyd ar gyfer heddiw over_daily_limit: Rydych wedi mynd dros y terfyn o %{limit} postiad a drefnwyd ar gyfer heddiw
over_total_limit: Rydych wedi mynd dros y terfyn o %{limit} postiad a drefnwyd over_total_limit: Rydych wedi mynd dros y terfyn o %{limit} postiad a drefnwyd
too_soon: Rhaid i'r dyddiad a drefnwyd fod yn y dyfodol too_soon: Rhaid i'r dyddiad a drefnwyd fod yn y dyfodol
self_destruct:
lead_html: Yn anffodus mae <strong>%{domain}</strong> yn cau'n barhaol. Os oedd gennych gyfrif yno, ni fydd modd i chi barhau i'w ddefnyddio, ond mae dal modd gofyn i gael copi wrth gefn o'ch data.
title: Mae'r gweinydd hwn yn cau
sessions: sessions:
activity: Gweithgaredd ddiwethaf activity: Gweithgaredd ddiwethaf
browser: Porwr browser: Porwr

View File

@ -1041,6 +1041,14 @@ da:
hint_html: Bare en ting mere! Vi er nødt til at bekræfte, at du er et menneske (dette er for at vi kan holde spam ude!). Løs CAPTCHA'en nedenfor og klik på "Fortsæt". hint_html: Bare en ting mere! Vi er nødt til at bekræfte, at du er et menneske (dette er for at vi kan holde spam ude!). Løs CAPTCHA'en nedenfor og klik på "Fortsæt".
title: Sikkerhedstjek title: Sikkerhedstjek
confirmations: confirmations:
awaiting_review: E-mailadressen er bekræftet! %{domain}-personalet gennemgår nu registreringen. En e-mail fremsendes, såfremt din konto godkendes!
awaiting_review_title: Registreringen er ved at blive gennemgået
clicking_this_link: klikke på dette link
login_link: log ind
proceed_to_login_html: Der kan nu fortsættes til %{login_link}.
redirect_to_app_html: En omdirigering til <strong>%{app_name}</strong>-appen burde være sket. Er det ikke tilfældet, prøv da %{clicking_this_link} eller returnér manuelt til appen.
registration_complete: Registreringen på %{domain} er nu fuldført!
welcome_title: Velkommen %{name}!
wrong_email_hint: Er denne e-mailadresse ikke korrekt, kan den ændres i kontoindstillinger. wrong_email_hint: Er denne e-mailadresse ikke korrekt, kan den ændres i kontoindstillinger.
delete_account: Slet konto delete_account: Slet konto
delete_account_html: Ønsker du at slette din konto, kan du <a href="%{path}">gøre dette hér</a>. Du vil blive bedt om bekræftelse. delete_account_html: Ønsker du at slette din konto, kan du <a href="%{path}">gøre dette hér</a>. Du vil blive bedt om bekræftelse.

View File

@ -1041,6 +1041,14 @@ de:
hint_html: Fast geschafft! Wir müssen uns vergewissern, dass du ein Mensch bist (damit wir Spam verhindern können!). Bitte löse das CAPTCHA und klicke auf „Weiter“. hint_html: Fast geschafft! Wir müssen uns vergewissern, dass du ein Mensch bist (damit wir Spam verhindern können!). Bitte löse das CAPTCHA und klicke auf „Weiter“.
title: Sicherheitsüberprüfung title: Sicherheitsüberprüfung
confirmations: confirmations:
awaiting_review: Deine E-Mail-Adresse wurde bestätigt und das Team von %{domain} überprüft nun deine Registrierung. Sobald es dein Konto genehmigt, wirst du eine E-Mail erhalten.
awaiting_review_title: Deine Registrierung wird überprüft
clicking_this_link: Klick auf diesen Link
login_link: anmelden
proceed_to_login_html: Du kannst dich nun %{login_link}.
redirect_to_app_html: Du hättest zur <strong>%{app_name}</strong>-App weitergeleitet werden sollen. Wenn das nicht geschehen ist, versuche es mit einem %{clicking_this_link} oder kehre manuell zur App zurück.
registration_complete: Deine Registrierung auf %{domain} ist nun abgeschlossen!
welcome_title: Willkommen, %{name}!
wrong_email_hint: Sollte diese E-Mail-Adresse nicht korrekt sein, kannst du sie in den Kontoeinstellungen ändern. wrong_email_hint: Sollte diese E-Mail-Adresse nicht korrekt sein, kannst du sie in den Kontoeinstellungen ändern.
delete_account: Konto löschen delete_account: Konto löschen
delete_account_html: Falls du dein Konto endgültig löschen möchtest, kannst du das <a href="%{path}">hier vornehmen</a>. Du musst dies zusätzlich bestätigen. delete_account_html: Falls du dein Konto endgültig löschen möchtest, kannst du das <a href="%{path}">hier vornehmen</a>. Du musst dies zusätzlich bestätigen.

View File

@ -88,7 +88,7 @@ sr-Latn:
updated_not_active: Vaša lozinka nije uspešno promenjena. updated_not_active: Vaša lozinka nije uspešno promenjena.
registrations: registrations:
destroyed: Ćao! Vaš nalog je uspešno obrisan. Nadamo se da ćete se uskoro vratiti. destroyed: Ćao! Vaš nalog je uspešno obrisan. Nadamo se da ćete se uskoro vratiti.
signed_up: Dobrodošli! Uspešno ste se registrovali. signed_up: Dobro došli! Uspešno ste se registrovali.
signed_up_but_inactive: Uspešno ste se registrovali. Nažalost ne možete se prijaviti zato što Vaš nalog još nije aktiviran. signed_up_but_inactive: Uspešno ste se registrovali. Nažalost ne možete se prijaviti zato što Vaš nalog još nije aktiviran.
signed_up_but_locked: Uspešno ste se registrovali. Nažalost ne možete se prijaviti zato što je Vaš nalog zaključan. signed_up_but_locked: Uspešno ste se registrovali. Nažalost ne možete se prijaviti zato što je Vaš nalog zaključan.
signed_up_but_pending: Na vaš imejl poslata je poruka sa vezom za potvrdu. Nakon što kliknete na vezu, pregledaćemo vašu prijavu. Bićete obavešteni ako bude odobreno. signed_up_but_pending: Na vaš imejl poslata je poruka sa vezom za potvrdu. Nakon što kliknete na vezu, pregledaćemo vašu prijavu. Bićete obavešteni ako bude odobreno.

View File

@ -88,7 +88,7 @@ sr:
updated_not_active: Ваша лозинка није успешно промењена. updated_not_active: Ваша лозинка није успешно промењена.
registrations: registrations:
destroyed: Ћао! Ваш налог је успешно обрисан. Надамо се да ћете се ускоро вратити. destroyed: Ћао! Ваш налог је успешно обрисан. Надамо се да ћете се ускоро вратити.
signed_up: Добродошли! Успешно сте се регистровали. signed_up: Добро дошли! Успешно сте се регистровали.
signed_up_but_inactive: Успешно сте се регистровали. Нажалост не можете се пријавити зато што Ваш налог још није активиран. signed_up_but_inactive: Успешно сте се регистровали. Нажалост не можете се пријавити зато што Ваш налог још није активиран.
signed_up_but_locked: Успешно сте се регистровали. Нажалост не можете се пријавити зато што је Ваш налог закључан. signed_up_but_locked: Успешно сте се регистровали. Нажалост не можете се пријавити зато што је Ваш налог закључан.
signed_up_but_pending: На ваш имејл послата је порука са везом за потврду. Након што кликнете на везу, прегледаћемо вашу пријаву. Бићете обавештени ако буде одобрено. signed_up_but_pending: На ваш имејл послата је порука са везом за потврду. Након што кликнете на везу, прегледаћемо вашу пријаву. Бићете обавештени ако буде одобрено.

View File

@ -1041,6 +1041,14 @@ en:
hint_html: Just one more thing! We need to confirm you're a human (this is so we can keep the spam out!). Solve the CAPTCHA below and click "Continue". hint_html: Just one more thing! We need to confirm you're a human (this is so we can keep the spam out!). Solve the CAPTCHA below and click "Continue".
title: Security check title: Security check
confirmations: confirmations:
awaiting_review: Your e-mail address is confirmed! The %{domain} staff is now reviewing your registration. You will receive an e-mail if they approve your account!
awaiting_review_title: Your registration is being reviewed
clicking_this_link: clicking this link
login_link: log in
proceed_to_login_html: You can now proceed to %{login_link}.
redirect_to_app_html: You should have been redirected to the <strong>%{app_name}</strong> app. If that did not happen, try %{clicking_this_link} or manually return to the app.
registration_complete: Your registration on %{domain} is now complete!
welcome_title: Welcome, %{name}!
wrong_email_hint: If that e-mail address is not correct, you can change it in account settings. wrong_email_hint: If that e-mail address is not correct, you can change it in account settings.
delete_account: Delete account delete_account: Delete account
delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation. delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.

View File

@ -1041,6 +1041,14 @@ es-AR:
hint_html: ¡Sólo una cosa más! Necesitamos confirmar que sos humano (¡esto es para que podamos mantener el spam fuera!). Resuelvé la CAPTCHA abajo y hacé clic en "Continuar". hint_html: ¡Sólo una cosa más! Necesitamos confirmar que sos humano (¡esto es para que podamos mantener el spam fuera!). Resuelvé la CAPTCHA abajo y hacé clic en "Continuar".
title: Comprobación de seguridad title: Comprobación de seguridad
confirmations: confirmations:
awaiting_review: "¡Tu dirección de correo electrónico fue confirmada! El equipo de %{domain} está revisando tu registro. ¡Recibirás un correo electrónico si aprueban tu cuenta!"
awaiting_review_title: Tu registro está siendo revisado
clicking_this_link: haciendo clic en este enlace
login_link: iniciar sesión
proceed_to_login_html: Ahora podés %{login_link}.
redirect_to_app_html: Deberías haber sido redirigido a la aplicación <strong>%{app_name}</strong>. Si eso no sucedió, probá %{clicking_this_link} o volvé a la aplicación manualmente.
registration_complete: "¡Tu registro en %{domain} fue completado!"
welcome_title: "¡Te damos la bienvenida, %{name}!"
wrong_email_hint: Si esa dirección de correo electrónico no es correcta, podés cambiarla en la configuración de la cuenta. wrong_email_hint: Si esa dirección de correo electrónico no es correcta, podés cambiarla en la configuración de la cuenta.
delete_account: Eliminar cuenta delete_account: Eliminar cuenta
delete_account_html: Si querés eliminar tu cuenta, podés <a href="%{path}">seguir por acá</a>. Se te va a pedir una confirmación. delete_account_html: Si querés eliminar tu cuenta, podés <a href="%{path}">seguir por acá</a>. Se te va a pedir una confirmación.

View File

@ -1041,6 +1041,14 @@ es-MX:
hint_html: ¡Una última cosita! Necesitamos confirmar que eres humano (¡así podemos evitar el spam!). Resuelve este CAPTCHA de debajo y pulsa en "Continuar". hint_html: ¡Una última cosita! Necesitamos confirmar que eres humano (¡así podemos evitar el spam!). Resuelve este CAPTCHA de debajo y pulsa en "Continuar".
title: Comprobación de seguridad title: Comprobación de seguridad
confirmations: confirmations:
awaiting_review: "¡Tu dirección de correo electrónico ha sido confirmada! El personal de %{domain} está revisando tu registro. ¡Recibirás un correo electrónico si aprueban tu cuenta!"
awaiting_review_title: Su registro está siendo revisado
clicking_this_link: presionando este enlace
login_link: iniciar sesión
proceed_to_login_html: Ahora puedes proceder a %{login_link}.
redirect_to_app_html: Deberías haber sido redirigido a la aplicación <strong>%{app_name}</strong>. Si eso no sucedió, prueba %{clicking_this_link} o vuelve a la aplicación manualmente.
registration_complete: "¡Tu registro en %{domain} ha sido completado!"
welcome_title: "¡Bienvenido, %{name}!"
wrong_email_hint: Si esa dirección de correo electrónico no es correcta, puedes cambiarla en la configuración de la cuenta. wrong_email_hint: Si esa dirección de correo electrónico no es correcta, puedes cambiarla en la configuración de la cuenta.
delete_account: Borrar cuenta delete_account: Borrar cuenta
delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación. delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación.

View File

@ -1041,6 +1041,10 @@ es:
hint_html: ¡Una última cosita! Necesitamos confirmar que eres humano (¡así podemos evitar el spam!). Resuelve este CAPTCHA de debajo y pulsa en "Continuar". hint_html: ¡Una última cosita! Necesitamos confirmar que eres humano (¡así podemos evitar el spam!). Resuelve este CAPTCHA de debajo y pulsa en "Continuar".
title: Comprobación de seguridad title: Comprobación de seguridad
confirmations: confirmations:
awaiting_review: "¡Tu dirección de correo electrónico ha sido confirmada! El personal de %{domain} está revisando tu registro. ¡Recibirás un correo electrónico cuando aprueben tu cuenta!"
awaiting_review_title: Estamos revisando tu registro
clicking_this_link: haciendo clic en este enlace
registration_complete: "¡Has completado tu registro en %{domain}!"
wrong_email_hint: Si esa dirección de correo electrónico no es correcta, puedes cambiarla en la configuración de la cuenta. wrong_email_hint: Si esa dirección de correo electrónico no es correcta, puedes cambiarla en la configuración de la cuenta.
delete_account: Borrar cuenta delete_account: Borrar cuenta
delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación. delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación.
@ -1100,7 +1104,7 @@ es:
account_status: Estado de la cuenta account_status: Estado de la cuenta
confirming: Esperando confirmación de correo electrónico. confirming: Esperando confirmación de correo electrónico.
functional: Tu cuenta está completamente operativa. functional: Tu cuenta está completamente operativa.
pending: Su solicitud está pendiente de revisión por nuestros administradores. Eso puede tardar algún tiempo. Usted recibirá un correo electrónico si el solicitud sea aprobada. pending: Tu solicitud está pendiente de revisión por nuestro personal. Eso puede tardar un tiempo. Recibirás un correo electrónico cuando tu solicitud sea aprobada.
redirecting_to: Tu cuenta se encuentra inactiva porque está siendo redirigida a %{acct}. redirecting_to: Tu cuenta se encuentra inactiva porque está siendo redirigida a %{acct}.
self_destruct: Como %{domain} está en proceso de cierre, solo tendrás acceso limitado a tu cuenta. self_destruct: Como %{domain} está en proceso de cierre, solo tendrás acceso limitado a tu cuenta.
view_strikes: Ver amonestaciones pasadas contra tu cuenta view_strikes: Ver amonestaciones pasadas contra tu cuenta
@ -1786,7 +1790,7 @@ es:
title: Un nuevo inicio de sesión title: Un nuevo inicio de sesión
warning: warning:
appeal: Enviar una apelación appeal: Enviar una apelación
appeal_description: Si crees que esto es un error, puedes enviar una apelación al equipo de %{instance}. appeal_description: Si crees que esto es un error, puedes enviar una apelación al personal de %{instance}.
categories: categories:
spam: Spam spam: Spam
violation: El contenido viola las siguientes directrices de la comunidad violation: El contenido viola las siguientes directrices de la comunidad

View File

@ -1041,6 +1041,14 @@ fi:
hint_html: Vielä yksi juttu! Meidän on vahvistettava, että olet ihminen (tämän avulla pidämme roskapostin poissa!). Ratkaise alla oleva CAPTCHA-vahvistus ja paina "Jatka". hint_html: Vielä yksi juttu! Meidän on vahvistettava, että olet ihminen (tämän avulla pidämme roskapostin poissa!). Ratkaise alla oleva CAPTCHA-vahvistus ja paina "Jatka".
title: Turvatarkastus title: Turvatarkastus
confirmations: confirmations:
awaiting_review: Sähköpostiosoitteesi on vahvistettu! Palvelun %{domain} ylläpito tarkistaa nyt rekisteröitymisesi. Saat sähköpostiviestin, jos tilisi hyväksytään!
awaiting_review_title: Rekisteröitymisesi on tarkistettavana
clicking_this_link: napsauttaa tätä linkkiä
login_link: kirjautumalla sisään
proceed_to_login_html: Voit nyt jatkaa %{login_link}.
redirect_to_app_html: Sinun olisi pitänyt ohjautua sovellukseen <strong>%{app_name}</strong>. Jos näin ei tapahtunut, kokeile %{clicking_this_link} tai palaa sovellukseen manuaalisesti.
registration_complete: Rekisteröitymisesi palveluun %{domain} on nyt valmis!
welcome_title: Tervetuloa, %{name}!
wrong_email_hint: Jos sähköpostiosoite ei ole oikein, voit muuttaa sen tilin asetuksista. wrong_email_hint: Jos sähköpostiosoite ei ole oikein, voit muuttaa sen tilin asetuksista.
delete_account: Poista tili delete_account: Poista tili
delete_account_html: Jos haluat poistaa tilisi, voit <a href="%{path}">edetä tästä</a>. Sinua pyydetään vahvistamaan poisto. delete_account_html: Jos haluat poistaa tilisi, voit <a href="%{path}">edetä tästä</a>. Sinua pyydetään vahvistamaan poisto.
@ -1100,7 +1108,7 @@ fi:
account_status: Tilin tila account_status: Tilin tila
confirming: Odotetaan sähköpostivahvistuksen valmistumista. confirming: Odotetaan sähköpostivahvistuksen valmistumista.
functional: Tilisi on täysin toiminnassa. functional: Tilisi on täysin toiminnassa.
pending: Hakemuksesi odottaa henkilökuntamme tarkastusta. Tämä voi kestää jonkin aikaa. Saat sähköpostiviestin, jos hakemuksesi hyväksytään. pending: Hakemuksesi odottaa palvelimen ylläpidon tarkastusta. Tämä voi kestää jonkin aikaa. Saat sähköpostiviestin, jos hakemuksesi hyväksytään.
redirecting_to: Tilisi ei ole aktiivinen, koska se ohjaa tällä hetkellä tilille %{acct}. redirecting_to: Tilisi ei ole aktiivinen, koska se ohjaa tällä hetkellä tilille %{acct}.
self_destruct: Koska %{domain} sulkeutuu, voit käyttää tiliäsi vain rajoitetusti. self_destruct: Koska %{domain} sulkeutuu, voit käyttää tiliäsi vain rajoitetusti.
view_strikes: Näytä tiliäsi koskevia aiempia varoituksia view_strikes: Näytä tiliäsi koskevia aiempia varoituksia
@ -1163,7 +1171,7 @@ fi:
approve_appeal: Hyväksy valitus approve_appeal: Hyväksy valitus
associated_report: Liittyvä raportti associated_report: Liittyvä raportti
created_at: Päivätty created_at: Päivätty
description_html: Nämä ovat tiliäsi koskevia toimia ja varoituksia, jotka instanssin %{instance} henkilökunta on lähettänyt sinulle. description_html: Nämä ovat tiliäsi koskevia toimia ja varoituksia, jotka instanssin %{instance} ylläpito on lähettänyt sinulle.
recipient: Osoitettu recipient: Osoitettu
reject_appeal: Hylkää valitus reject_appeal: Hylkää valitus
status: 'Julkaisu #%{id}' status: 'Julkaisu #%{id}'
@ -1786,7 +1794,7 @@ fi:
title: Uusi kirjautuminen title: Uusi kirjautuminen
warning: warning:
appeal: Lähetä valitus appeal: Lähetä valitus
appeal_description: Jos uskot, että tämä on virhe, voit hakea muutosta instanssin %{instance} henkilökunnalta. appeal_description: Jos uskot, että tämä on virhe, voit hakea muutosta instanssin %{instance} ylläpidolta.
categories: categories:
spam: Roskaposti spam: Roskaposti
violation: Sisältö rikkoo seuraavia yhteisön sääntöjä violation: Sisältö rikkoo seuraavia yhteisön sääntöjä

View File

@ -1041,6 +1041,14 @@ fo:
hint_html: Bara eitt afturat! Tað er neyðugt hjá okkum at vátta, at tú ert eitt menniskja (fyri at sleppa undan ruskposti!). Loys CAPTCHA niðanfyri og trýst á "Halt fram". hint_html: Bara eitt afturat! Tað er neyðugt hjá okkum at vátta, at tú ert eitt menniskja (fyri at sleppa undan ruskposti!). Loys CAPTCHA niðanfyri og trýst á "Halt fram".
title: Trygdarkanning title: Trygdarkanning
confirmations: confirmations:
awaiting_review: Teldupostadressan hjá góðkend! Nú kanna %{domain} ábyrgdarfólk skrásetingina hjá tær. Góðkenna tey kontu tína, so senda tey tær eitt teldubræv!
awaiting_review_title: Skrásetingin hjá tær verður viðgjørd
clicking_this_link: við at klikkja á hetta leinki
login_link: rita inn
proceed_to_login_html: Nú kanst tú fara víðari til %{login_link}.
redirect_to_app_html: Tú skuldi verið send/ur víðari til <strong>%{app_name}</strong> appina. Hendi tað ikki, so kanst tú royna %{clicking_this_link} ella fara manuelt aftur til appina.
registration_complete: Skráseting tín á %{domain} er nú avgreidd!
welcome_title: Vælkomin, %{name}!
wrong_email_hint: Um hesin teldupoststaðurin ikki er rættur, so kanst tú broyta hann í kontustillingunum. wrong_email_hint: Um hesin teldupoststaðurin ikki er rættur, so kanst tú broyta hann í kontustillingunum.
delete_account: Strika kontu delete_account: Strika kontu
delete_account_html: Ynskir tú at strika kontuna, so kanst tú <a href="%{path}">halda fram her</a>. Tú verður spurd/ur um váttan. delete_account_html: Ynskir tú at strika kontuna, so kanst tú <a href="%{path}">halda fram her</a>. Tú verður spurd/ur um váttan.

View File

@ -1041,6 +1041,14 @@ fr-QC:
hint_html: Juste une autre chose! Nous avons besoin de confirmer que vous êtes un humain (pour que nous puissions empêcher les spams!). Résolvez le CAPTCHA ci-dessous et cliquez sur "Continuer". hint_html: Juste une autre chose! Nous avons besoin de confirmer que vous êtes un humain (pour que nous puissions empêcher les spams!). Résolvez le CAPTCHA ci-dessous et cliquez sur "Continuer".
title: Vérification de sécurité title: Vérification de sécurité
confirmations: confirmations:
awaiting_review: Votre adresse e-mail est confirmée ! Léquipe de %{domain} vérifie désormais votre inscription. Vous recevrez un e-mail si votre compte est approuvé !
awaiting_review_title: Votre inscription est en cours de validation
clicking_this_link: cliquer sur ce lien
login_link: vous connecter
proceed_to_login_html: Vous pouvez désormais %{login_link}.
redirect_to_app_html: Vous auriez dû être redirigé vers lapplication <strong>%{app_name}</strong>. Si cela ne sest pas produit, essayez de %{clicking_this_link} ou de revenir manuellement à lapplication.
registration_complete: Votre inscription sur %{domain} est désormais terminée !
welcome_title: Bienvenue, %{name} !
wrong_email_hint: Si cette adresse de courriel est incorrecte, vous pouvez la modifier dans vos paramètres de compte. wrong_email_hint: Si cette adresse de courriel est incorrecte, vous pouvez la modifier dans vos paramètres de compte.
delete_account: Supprimer le compte delete_account: Supprimer le compte
delete_account_html: Si vous désirez supprimer votre compte, vous pouvez <a href="%{path}">cliquer ici</a>. Il vous sera demandé de confirmer cette action. delete_account_html: Si vous désirez supprimer votre compte, vous pouvez <a href="%{path}">cliquer ici</a>. Il vous sera demandé de confirmer cette action.

View File

@ -1041,6 +1041,14 @@ fr:
hint_html: Encore une chose ! Nous avons besoin de confirmer que vous êtes un humain (c'est pour que nous puissions empêcher les spams !). Résolvez le CAPTCHA ci-dessous et cliquez sur "Continuer". hint_html: Encore une chose ! Nous avons besoin de confirmer que vous êtes un humain (c'est pour que nous puissions empêcher les spams !). Résolvez le CAPTCHA ci-dessous et cliquez sur "Continuer".
title: Vérification de sécurité title: Vérification de sécurité
confirmations: confirmations:
awaiting_review: Votre adresse e-mail est confirmée ! Léquipe de %{domain} vérifie désormais votre inscription. Vous recevrez un e-mail si votre compte est approuvé !
awaiting_review_title: Votre inscription est en cours de validation
clicking_this_link: cliquer sur ce lien
login_link: vous connecter
proceed_to_login_html: Vous pouvez désormais %{login_link}.
redirect_to_app_html: Vous auriez dû être redirigé vers lapplication <strong>%{app_name}</strong>. Si cela ne sest pas produit, essayez de %{clicking_this_link} ou de revenir manuellement à lapplication.
registration_complete: Votre inscription sur %{domain} est désormais terminée !
welcome_title: Bienvenue, %{name} !
wrong_email_hint: Si cette adresse de courriel est incorrecte, vous pouvez la modifier dans vos paramètres de compte. wrong_email_hint: Si cette adresse de courriel est incorrecte, vous pouvez la modifier dans vos paramètres de compte.
delete_account: Supprimer le compte delete_account: Supprimer le compte
delete_account_html: Si vous désirez supprimer votre compte, vous pouvez <a href="%{path}">cliquer ici</a>. Il vous sera demandé de confirmer cette action. delete_account_html: Si vous désirez supprimer votre compte, vous pouvez <a href="%{path}">cliquer ici</a>. Il vous sera demandé de confirmer cette action.

View File

@ -1041,6 +1041,14 @@ fy:
hint_html: Noch ien ding! Jo moatte befêstigje dat jo in minske binne (dit is om de spam bûten de doar te hâlden!). Los de ûndersteande CAPTCHA op en klik op Trochgean. hint_html: Noch ien ding! Jo moatte befêstigje dat jo in minske binne (dit is om de spam bûten de doar te hâlden!). Los de ûndersteande CAPTCHA op en klik op Trochgean.
title: Befeiligingskontrôle title: Befeiligingskontrôle
confirmations: confirmations:
awaiting_review: Jo e-mailadres is befêstige! De %{domain}-meiwurkers binne no dwaande mei it besjen fan jo registraasje. Jo ûntfange in e-mailberjocht as de jo account goedkarre!
awaiting_review_title: Jo registraasje wurdt beoardield
clicking_this_link: klik op dizze keppeling
login_link: oanmelde
proceed_to_login_html: Jo kinne no fierder gean nei %{login_link}.
redirect_to_app_html: Jo soene omlaad wêze moatte nei de <strong>%{app_name}</strong> app. As dat net bard is, probearje dan %{clicking_this_link} of kear hânmjittich werom nei de app.
registration_complete: Jo registraasje op %{domain} is no foltôge!
welcome_title: Wolkom, %{name}!
wrong_email_hint: As it e-mailadres net korrekt is, kinne jo dat wizigje yn de accountynstellingen. wrong_email_hint: As it e-mailadres net korrekt is, kinne jo dat wizigje yn de accountynstellingen.
delete_account: Account fuortsmite delete_account: Account fuortsmite
delete_account_html: Wanneart jo jo account graach fuortsmite wolle, kinne jo dat <a href="%{path}">hjir dwaan</a>. Wy freegje jo dêr om in befêstiging. delete_account_html: Wanneart jo jo account graach fuortsmite wolle, kinne jo dat <a href="%{path}">hjir dwaan</a>. Wy freegje jo dêr om in befêstiging.

View File

@ -1041,6 +1041,14 @@ gl:
hint_html: Unha cousa máis! Temos que confirmar que es un ser humano (para poder evitar contas de spam!). Resolve o CAPTCHA de aquí embaixo e preme en "Continuar". hint_html: Unha cousa máis! Temos que confirmar que es un ser humano (para poder evitar contas de spam!). Resolve o CAPTCHA de aquí embaixo e preme en "Continuar".
title: Comprobación de seguridade title: Comprobación de seguridade
confirmations: confirmations:
awaiting_review: O teu correo está verificado! A administración de %{domain} está revisando a túa solicitude. Recibirás outro correo se aproban a túa conta!
awaiting_review_title: Estamos revisando a solicitude de rexistro
clicking_this_link: premendo nesta ligazón
login_link: acceder
proceed_to_login_html: Xa podes %{login_link}.
redirect_to_app_html: Ímoste redirixir á app <strong>%{app_name}</strong>. Se iso non acontece, proba %{clicking_this_link} ou volve ti manualmente á app.
registration_complete: Completouse a creación da conta en %{domain}!
welcome_title: Benvida, %{name}!
wrong_email_hint: Se o enderezo de email non é correcto, podes cambialo nos axustes da conta. wrong_email_hint: Se o enderezo de email non é correcto, podes cambialo nos axustes da conta.
delete_account: Eliminar conta delete_account: Eliminar conta
delete_account_html: Se queres eliminar a túa conta, podes <a href="%{path}">facelo aquí</a>. Deberás confirmar a acción. delete_account_html: Se queres eliminar a túa conta, podes <a href="%{path}">facelo aquí</a>. Deberás confirmar a acción.

View File

@ -1077,6 +1077,14 @@ he:
hint_html: עוד דבר אחד, עלינו לאשרר שאת(ה) אנושיים (לצורך סינון ספאם). נא לפתור את הקאפצ'ה להלן וללחוץ "המשך". hint_html: עוד דבר אחד, עלינו לאשרר שאת(ה) אנושיים (לצורך סינון ספאם). נא לפתור את הקאפצ'ה להלן וללחוץ "המשך".
title: בדיקות אבטחה title: בדיקות אבטחה
confirmations: confirmations:
awaiting_review: כתובת הדואל שלך אושרה! צוות %{domain} עכשיו יבדוק את הרשמתך. תשלח אליך הודעת דואל אם הצוות יאשר את החשבון!
awaiting_review_title: הרשמתך עוברת בדיקה
clicking_this_link: לחיצה על קישור זה
login_link: כניסה
proceed_to_login_html: ניתן להמשיך עכשיו אל %{login_link}.
redirect_to_app_html: כאן אמורה היתה להיות הפניה אוטמטית ליישומון <strong>%{app_name}</strong>. אם זה לא קרה, ניתן לנסות שוב על ידי %{clicking_this_link} או חזרה ידנית אל היישומון.
registration_complete: הרשמתך לשרת %{domain} הושלמה כעת!
welcome_title: ברוך/ה הבא/ה, %{name}!
wrong_email_hint: אם כתובת הדואל הזו איננה נכונה, ניתן לשנות אותה בעמוד ההגדרות. wrong_email_hint: אם כתובת הדואל הזו איננה נכונה, ניתן לשנות אותה בעמוד ההגדרות.
delete_account: מחיקת חשבון delete_account: מחיקת חשבון
delete_account_html: אם ברצונך למחוק את החשבון, ניתן <a href="%{path}">להמשיך כאן</a>. תתבקש/י לספק אישור נוסף. delete_account_html: אם ברצונך למחוק את החשבון, ניתן <a href="%{path}">להמשיך כאן</a>. תתבקש/י לספק אישור נוסף.

View File

@ -1041,6 +1041,14 @@ hu:
hint_html: Még egy dolog! Meg kell győződnünk róla, hogy tényleg valós személy vagy (így távol tarthatjuk a spam-et). Oldd meg az alábbi CAPTCHA-t és kattints a "Folytatás"-ra. hint_html: Még egy dolog! Meg kell győződnünk róla, hogy tényleg valós személy vagy (így távol tarthatjuk a spam-et). Oldd meg az alábbi CAPTCHA-t és kattints a "Folytatás"-ra.
title: Biztonsági ellenőrzés title: Biztonsági ellenőrzés
confirmations: confirmations:
awaiting_review: Az email címed megerősítésre került. %{domain} stábja jelenleg áttekinti a regisztrációdat. Ha jóváhagyásra került a fiókod, egy emailt küldenek majd!
awaiting_review_title: A regisztrációd áttekintés alatt áll
clicking_this_link: kattintás erre a hivatkozásra
login_link: bejelentkezés
proceed_to_login_html: 'Most továbbléphetsz: %{login_link}.'
redirect_to_app_html: Át kellett volna irányítsunk a <strong>%{app_name}</strong> alkalmazáshoz. Ha ez nem történt meg, próbálkozz a %{clicking_this_link} lehetőséggel vagy térj vissza manuálisan az alkalmazáshoz.
registration_complete: A regisztrációd %{domain} domainen befejeződött!
welcome_title: Üdvözlet, %{name}!
wrong_email_hint: Ha az emailcím nem helyes, a fiókbeállításokban megváltoztathatod. wrong_email_hint: Ha az emailcím nem helyes, a fiókbeállításokban megváltoztathatod.
delete_account: Felhasználói fiók törlése delete_account: Felhasználói fiók törlése
delete_account_html: Felhasználói fiókod törléséhez <a href="%{path}">kattints ide</a>. A rendszer újbóli megerősítést fog kérni. delete_account_html: Felhasználói fiókod törléséhez <a href="%{path}">kattints ide</a>. A rendszer újbóli megerősítést fog kérni.

View File

@ -1045,6 +1045,14 @@ is:
Leystu Turing skynprófið og smelltu á "Áfram". Leystu Turing skynprófið og smelltu á "Áfram".
title: Öryggisathugun title: Öryggisathugun
confirmations: confirmations:
awaiting_review: Tölvupóstfangið þitt er staðfest. Umsjónarfólk %{domain} er núna að yfirfara skráninguna þína. Þú munt fá tölvupóst ef þau samþykkja skráninguna þína!
awaiting_review_title: Verið er að yfirfara skráninguna þína
clicking_this_link: smella á þennan tengil
login_link: skrá þig inn
proceed_to_login_html: Þú getur núna farið í að %{login_link}.
redirect_to_app_html: Þér ætti að hafa verið endurbeint í <strong>%{app_name}</strong> forritið. Ef það gerðist ekki, skaltu prófa að %{clicking_this_link} eða fara aftur í forritið.
registration_complete: Skráning þín á %{domain} er núna tilbúin!
welcome_title: Velkomin/n %{name}!
wrong_email_hint: Ef það tölvupóstfang er ekki rétt geturðu breytt því í stillingum notandaaðgangsins. wrong_email_hint: Ef það tölvupóstfang er ekki rétt geturðu breytt því í stillingum notandaaðgangsins.
delete_account: Eyða notandaaðgangi delete_account: Eyða notandaaðgangi
delete_account_html: Ef þú vilt eyða notandaaðgangnum þínum, þá geturðu <a href="%{path}">farið í það hér</a>. Þú verður beðin/n um staðfestingu. delete_account_html: Ef þú vilt eyða notandaaðgangnum þínum, þá geturðu <a href="%{path}">farið í það hér</a>. Þú verður beðin/n um staðfestingu.

View File

@ -1043,6 +1043,14 @@ it:
hint_html: Solamente un'altra cosa! Dobbiamo confermare che tu sia un essere umano (così possiamo tenere fuori lo spam!). Risolvi il CAPTCHA sottostante e fai clic su "Continua". hint_html: Solamente un'altra cosa! Dobbiamo confermare che tu sia un essere umano (così possiamo tenere fuori lo spam!). Risolvi il CAPTCHA sottostante e fai clic su "Continua".
title: Controllo di sicurezza title: Controllo di sicurezza
confirmations: confirmations:
awaiting_review: Il tuo indirizzo e-mail è confermato! Lo staff di %{domain} sta esaminando la tua registrazione. Riceverai una e-mail se il tuo account sarà approvato!
awaiting_review_title: La tua registrazione è in corso di revisione
clicking_this_link: cliccando su questo link
login_link: accedi
proceed_to_login_html: Ora puoi procedere con il %{login_link}.
redirect_to_app_html: Avresti dovuto essere reindirizzato all'app <strong>%{app_name}</strong>. Se ciò non fosse avvenuto, prova %{clicking_this_link} o torna manualmente all'app.
registration_complete: La tua registrazione su %{domain} è ora completata!
welcome_title: Benvenutə, %{name}!
wrong_email_hint: Se l'indirizzo e-mail non è corretto, puoi modificarlo nelle impostazioni dell'account. wrong_email_hint: Se l'indirizzo e-mail non è corretto, puoi modificarlo nelle impostazioni dell'account.
delete_account: Elimina account delete_account: Elimina account
delete_account_html: Se desideri cancellare il tuo account, puoi <a href="%{path}">farlo qui</a>. Ti sarà chiesta conferma. delete_account_html: Se desideri cancellare il tuo account, puoi <a href="%{path}">farlo qui</a>. Ti sarà chiesta conferma.

View File

@ -1023,6 +1023,14 @@ ja:
hint_html: もう一つだけ!あなたが人間であることを確認する必要があります(スパムを防ぐためです!)。 以下のCAPTCHAを解き、「続ける」をクリックします。 hint_html: もう一つだけ!あなたが人間であることを確認する必要があります(スパムを防ぐためです!)。 以下のCAPTCHAを解き、「続ける」をクリックします。
title: セキュリティチェック title: セキュリティチェック
confirmations: confirmations:
awaiting_review: メールアドレスは確認済みです。%{domain} のモデレーターによりアカウント登録の審査が完了すると、メールでお知らせします。
awaiting_review_title: 登録の審査待ちです
clicking_this_link: こちらのリンク
login_link: こちらのリンク
proceed_to_login_html: "%{login_link}から早速ログインしてみましょう。"
redirect_to_app_html: "<strong>%{app_name}</strong>に戻ります。自動で移動しない場合は%{clicking_this_link}を押すか、手動でアプリを切り替えてください。"
registration_complete: "%{domain} へのアカウント登録が完了しました。"
welcome_title: Mastodonへようこそ、%{name}さん
wrong_email_hint: メールアドレスが正しくない場合は、アカウント設定で変更できます。 wrong_email_hint: メールアドレスが正しくない場合は、アカウント設定で変更できます。
delete_account: アカウントの削除 delete_account: アカウントの削除
delete_account_html: アカウントを削除したい場合、<a href="%{path}">こちら</a>から手続きが行えます。削除する前に、確認画面があります。 delete_account_html: アカウントを削除したい場合、<a href="%{path}">こちら</a>から手続きが行えます。削除する前に、確認画面があります。

View File

@ -1025,6 +1025,14 @@ ko:
hint_html: 하나만 더! 당신이 사람인지 확인이 필요합니다 (스팸 계정을 거르기 위해서 필요한 과정입니다). 아래에 있는 CAPTCHA를 풀고 "계속"을 누르세요 hint_html: 하나만 더! 당신이 사람인지 확인이 필요합니다 (스팸 계정을 거르기 위해서 필요한 과정입니다). 아래에 있는 CAPTCHA를 풀고 "계속"을 누르세요
title: 보안 체크 title: 보안 체크
confirmations: confirmations:
awaiting_review: 이메일 주소를 확인했어요! 이제 %{domain} 스태프가 가입을 검토해요. 계정이 승인되면 이메일을 보내드려요.
awaiting_review_title: 가입 검토 중
clicking_this_link: 이 링크를 클릭
login_link: 로그인
proceed_to_login_html: "%{login_link} 할 수 있게 되었어요."
redirect_to_app_html: 곧 <strong>%{app_name}</strong>으로 리디렉션 되어요. 안 된다면, %{clicking_this_link}하거나 직접 앱으로 돌아가세요.
registration_complete: 지금 막 %{domain} 가입을 마쳤어요!
welcome_title: "%{name} 님 반가워요!"
wrong_email_hint: 만약 이메일 주소가 올바르지 않다면, 계정 설정에서 수정할 수 있습니다. wrong_email_hint: 만약 이메일 주소가 올바르지 않다면, 계정 설정에서 수정할 수 있습니다.
delete_account: 계정 삭제 delete_account: 계정 삭제
delete_account_html: 계정을 삭제하고 싶은 경우, <a href="%{path}">여기서</a> 삭제할 수 있습니다. 삭제 전 확인 화면이 표시됩니다. delete_account_html: 계정을 삭제하고 싶은 경우, <a href="%{path}">여기서</a> 삭제할 수 있습니다. 삭제 전 확인 화면이 표시됩니다.

View File

@ -1041,6 +1041,14 @@ nl:
hint_html: Nog maar één ding! Je moet bevestigen dat je een mens bent (dit is om de spam buiten de deur te houden!). Los de onderstaande CAPTCHA op en klik op "Doorgaan". hint_html: Nog maar één ding! Je moet bevestigen dat je een mens bent (dit is om de spam buiten de deur te houden!). Los de onderstaande CAPTCHA op en klik op "Doorgaan".
title: Veiligheidscontrole title: Veiligheidscontrole
confirmations: confirmations:
awaiting_review: Je e-mailadres is bevestigd! De %{domain}-medewerkers zijn nu bezig met het bekijken van jouw registratie. Je ontvangt een e-mailbericht als ze jouw account goedkeuren!
awaiting_review_title: Je registratie wordt beoordeeld
clicking_this_link: klik op deze koppeling
login_link: aanmelden
proceed_to_login_html: Je kunt nu verder gaan naar %{login_link}.
redirect_to_app_html: Je zou omgeleid moeten zijn naar de <strong>%{app_name}</strong> app. Als dat niet is gebeurd, probeer dan %{clicking_this_link} of keer handmatig terug naar de app.
registration_complete: Je registratie op %{domain} is nu voltooid!
welcome_title: Welkom, %{name}!
wrong_email_hint: Als dat e-mailadres niet correct is, kun je het wijzigen in je accountinstellingen. wrong_email_hint: Als dat e-mailadres niet correct is, kun je het wijzigen in je accountinstellingen.
delete_account: Account verwijderen delete_account: Account verwijderen
delete_account_html: Wanneer je jouw account graag wilt verwijderen, kun je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging. delete_account_html: Wanneer je jouw account graag wilt verwijderen, kun je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging.

View File

@ -1040,6 +1040,8 @@ pt-BR:
hint_html: Só mais uma coisa! Precisamos confirmar que você é um humano (isso é para que possamos evitar o spam!). Resolva o CAPTCHA abaixo e clique em "Continuar". hint_html: Só mais uma coisa! Precisamos confirmar que você é um humano (isso é para que possamos evitar o spam!). Resolva o CAPTCHA abaixo e clique em "Continuar".
title: Verificação de segurança title: Verificação de segurança
confirmations: confirmations:
registration_complete: Seu cadastro no %{domain} foi concluído!
welcome_title: Boas vindas, %{name}!
wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta. wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta.
delete_account: Excluir conta delete_account: Excluir conta
delete_account_html: Se você deseja excluir sua conta, você pode <a href="%{path}">fazer isso aqui</a>. Uma confirmação será solicitada. delete_account_html: Se você deseja excluir sua conta, você pode <a href="%{path}">fazer isso aqui</a>. Uma confirmação será solicitada.

View File

@ -1041,6 +1041,14 @@ pt-PT:
hint_html: Só mais uma coisa! Precisamos confirmar que você é um humano (isto para que possamos evitar spam!). Resolva o CAPTCHA abaixo e clique em "Continuar". hint_html: Só mais uma coisa! Precisamos confirmar que você é um humano (isto para que possamos evitar spam!). Resolva o CAPTCHA abaixo e clique em "Continuar".
title: Verificação de segurança title: Verificação de segurança
confirmations: confirmations:
awaiting_review: O seu endereço de correio eletrónico foi confirmado! A equipa %{domain} está agora a rever a sua inscrição. Receberá uma mensagem de correio eletrónico se aprovarem a sua conta!
awaiting_review_title: A sua inscrição está a ser revista
clicking_this_link: clicar nesta hiperligação
login_link: iniciar sessão
proceed_to_login_html: Pode agora prosseguir para %{login_link}.
redirect_to_app_html: Devia ter sido reencaminhado para a aplicação <strong>%{app_name}</strong>. Se isso não aconteceu, tente %{clicking_this_link} ou volte manualmente para a aplicação.
registration_complete: O seu registo sem %{domain} está agora concluído!
welcome_title: Bem-vindo(a), %{name}!
wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta. wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta.
delete_account: Eliminar conta delete_account: Eliminar conta
delete_account_html: Se deseja eliminar a sua conta, pode <a href="%{path}">continuar aqui</a>. Uma confirmação será solicitada. delete_account_html: Se deseja eliminar a sua conta, pode <a href="%{path}">continuar aqui</a>. Uma confirmação será solicitada.

View File

@ -556,6 +556,7 @@ ru:
total_reported: Жалобы на них total_reported: Жалобы на них
total_storage: Медиафайлы total_storage: Медиафайлы
totals_time_period_hint_html: Итоги, показанные ниже, включают данные за всё время. totals_time_period_hint_html: Итоги, показанные ниже, включают данные за всё время.
unknown_instance: На данный момент нет записей об этом домене на этом сервере.
invites: invites:
deactivate_all: Отключить все deactivate_all: Отключить все
filter: filter:
@ -1076,6 +1077,12 @@ ru:
hint_html: Еще одна вещь! Нам нужно подтвердить, что вы человек (так что мы можем держать спам!). Решите капчу ниже и нажмите кнопку «Продолжить». hint_html: Еще одна вещь! Нам нужно подтвердить, что вы человек (так что мы можем держать спам!). Решите капчу ниже и нажмите кнопку «Продолжить».
title: Проверка безопасности title: Проверка безопасности
confirmations: confirmations:
awaiting_review: Ваш адрес электронной почты подтвержден! Сотрудники %{domain} проверяют вашу регистрацию. Вы получите письмо, если они подтвердят вашу учетную запись!
awaiting_review_title: Ваша регистрация проверяется
login_link: войти
proceed_to_login_html: Теперь вы можете перейти к %{login_link}.
registration_complete: Ваша регистрация на %{domain} завершена!
welcome_title: Добро пожаловать, %{name}!
wrong_email_hint: Если этот адрес электронной почты неверен, вы можете изменить его в настройках аккаунта. wrong_email_hint: Если этот адрес электронной почты неверен, вы можете изменить его в настройках аккаунта.
delete_account: Удалить учётную запись delete_account: Удалить учётную запись
delete_account_html: Удалить свою учётную запись <a href="%{path}">можно в два счёта здесь</a>, но прежде у вас будет спрошено подтверждение. delete_account_html: Удалить свою учётную запись <a href="%{path}">можно в два счёта здесь</a>, но прежде у вас будет спрошено подтверждение.
@ -1137,6 +1144,7 @@ ru:
functional: Ваша учётная запись в полном порядке. functional: Ваша учётная запись в полном порядке.
pending: Ваша заявка ожидает одобрения администраторами, это может занять немного времени. Вы получите письмо, как только заявку одобрят. pending: Ваша заявка ожидает одобрения администраторами, это может занять немного времени. Вы получите письмо, как только заявку одобрят.
redirecting_to: Ваша учётная запись деактивированна, потому что вы настроили перенаправление на %{acct}. redirecting_to: Ваша учётная запись деактивированна, потому что вы настроили перенаправление на %{acct}.
self_destruct: Поскольку %{domain} закрывается, вы получите ограниченный доступ к вашей учетной записи.
view_strikes: Просмотр предыдущих замечаний в адрес вашей учетной записи view_strikes: Просмотр предыдущих замечаний в адрес вашей учетной записи
too_fast: Форма отправлена слишком быстро, попробуйте еще раз. too_fast: Форма отправлена слишком быстро, попробуйте еще раз.
use_security_key: Использовать ключ безопасности use_security_key: Использовать ключ безопасности
@ -1622,6 +1630,8 @@ ru:
over_daily_limit: Вы превысили лимит в %{limit} запланированных постов на указанный день over_daily_limit: Вы превысили лимит в %{limit} запланированных постов на указанный день
over_total_limit: Вы превысили лимит на %{limit} запланированных постов over_total_limit: Вы превысили лимит на %{limit} запланированных постов
too_soon: Запланированная дата должна быть в будущем too_soon: Запланированная дата должна быть в будущем
self_destruct:
title: Этот сервер закрывается
sessions: sessions:
activity: Последняя активность activity: Последняя активность
browser: Браузер browser: Браузер

View File

@ -125,6 +125,8 @@ sk:
removed_header_msg: Úspešne odstránený obrázok hlavičky %{username} removed_header_msg: Úspešne odstránený obrázok hlavičky %{username}
resend_confirmation: resend_confirmation:
already_confirmed: Tento užívateľ je už potvrdený already_confirmed: Tento užívateľ je už potvrdený
send: Odošli potvrdzovací odkaz znovu
success: Potvrdzovací email úspešne odoslaný!
reset: Resetuj reset: Resetuj
reset_password: Obnov heslo reset_password: Obnov heslo
resubscribe: Znovu odoberaj resubscribe: Znovu odoberaj
@ -1040,6 +1042,7 @@ sk:
confirm_remove_selected_followers: Si si istý/á, že chceš odstrániť vybraných sledovateľov? confirm_remove_selected_followers: Si si istý/á, že chceš odstrániť vybraných sledovateľov?
confirm_remove_selected_follows: Si si istý/á, že chceš odstrániť vybraných sledovaných? confirm_remove_selected_follows: Si si istý/á, že chceš odstrániť vybraných sledovaných?
dormant: Spiace dormant: Spiace
follow_failure: Nemožno nasledovať niektoré z vybraných účtov.
follow_selected_followers: Následuj označených sledovatelov follow_selected_followers: Následuj označených sledovatelov
followers: Následovatelia followers: Následovatelia
following: Následovaní following: Následovaní

View File

@ -1059,6 +1059,14 @@ sr-Latn:
hint_html: Samo još jedna stvar! Moramo da potvrdimo da ste ljudsko biće (ovo je da bismo sprečili neželjenu poštu!). Rešite CAPTCHA ispod i kliknite na „Nastavi”. hint_html: Samo još jedna stvar! Moramo da potvrdimo da ste ljudsko biće (ovo je da bismo sprečili neželjenu poštu!). Rešite CAPTCHA ispod i kliknite na „Nastavi”.
title: Bezbedonosna provera title: Bezbedonosna provera
confirmations: confirmations:
awaiting_review: Vaša adresa e-pošte je potvrđena! Osoblje %{domain} sada pregleda vašu registraciju. Dobićete e-poštu ako odobre vaš nalog!
awaiting_review_title: Vaša registracija se pregleda
clicking_this_link: klikom na ovu vezu
login_link: prijavi se
proceed_to_login_html: Sada možete da pređete na %{login_link}.
redirect_to_app_html: Trebalo je da budete preusmereni na aplikaciju <strong>%{app_name}</strong>. Ako se to nije desilo, pokušajte sa %{clicking_this_link} ili se ručno vratite u aplikaciju.
registration_complete: Vaša registracija na %{domain} je sada kompletirana!
welcome_title: Dobro došli, %{name}!
wrong_email_hint: Ako ta imejl adresa nije ispravna, možete je promeniti u podešavanjima naloga. wrong_email_hint: Ako ta imejl adresa nije ispravna, možete je promeniti u podešavanjima naloga.
delete_account: Brisanje naloga delete_account: Brisanje naloga
delete_account_html: Ako želite da izbrišete vaš nalog, možete <a href="%{path}">nastaviti ovde</a>. Od vas će se tražiti potvrda. delete_account_html: Ako želite da izbrišete vaš nalog, možete <a href="%{path}">nastaviti ovde</a>. Od vas će se tražiti potvrda.
@ -1855,8 +1863,8 @@ sr-Latn:
final_step: 'Počnite da objavljujete! Čak i bez pratilaca, Vaše javne objave su vidljive drugim ljudima, na primer na lokalnoj vremenskoj liniji ili u heš oznakama. Možda želite da se predstavite sa heš oznakom #introductions ili #predstavljanja.' final_step: 'Počnite da objavljujete! Čak i bez pratilaca, Vaše javne objave su vidljive drugim ljudima, na primer na lokalnoj vremenskoj liniji ili u heš oznakama. Možda želite da se predstavite sa heš oznakom #introductions ili #predstavljanja.'
full_handle: Vaš pun nadimak full_handle: Vaš pun nadimak
full_handle_hint: Ovo biste rekli svojim prijateljima kako bi vam oni poslali poruku, ili zapratili sa druge instance. full_handle_hint: Ovo biste rekli svojim prijateljima kako bi vam oni poslali poruku, ili zapratili sa druge instance.
subject: Dobrodošli na Mastodon subject: Dobro došli na Mastodon
title: Dobrodošli, %{name}! title: Dobro došli, %{name}!
users: users:
follow_limit_reached: Ne možete pratiti više od %{limit} ljudi follow_limit_reached: Ne možete pratiti više od %{limit} ljudi
go_to_sso_account_settings: Idite na podešavanja naloga svog dobavljača identiteta go_to_sso_account_settings: Idite na podešavanja naloga svog dobavljača identiteta

View File

@ -1059,6 +1059,14 @@ sr:
hint_html: Само још једна ствар! Морамо да потврдимо да сте људско биће (ово је да бисмо спречили нежељену пошту!). Решите CAPTCHA испод и кликните на „Настави”. hint_html: Само још једна ствар! Морамо да потврдимо да сте људско биће (ово је да бисмо спречили нежељену пошту!). Решите CAPTCHA испод и кликните на „Настави”.
title: Безбедоносна провера title: Безбедоносна провера
confirmations: confirmations:
awaiting_review: Ваша адреса е-поште је потврђена! Особље %{domain} сада прегледа вашу регистрацију. Добићете е-пошту ако одобре ваш налог!
awaiting_review_title: Ваша регистрација се прегледа
clicking_this_link: кликом на ову везу
login_link: пријави се
proceed_to_login_html: Сада можете да пређете на %{login_link}.
redirect_to_app_html: Требало је да будете преусмерени на апликацију <strong>%{app_name}</strong>. Ако се то није десило, покушајте са %{clicking_this_link} или се ручно вратите у апликацију.
registration_complete: Ваша регистрација на %{domain} је сада комплетирана!
welcome_title: Добро дошли, %{name}!
wrong_email_hint: Ако та имејл адреса није исправна, можете је променити у подешавањима налога. wrong_email_hint: Ако та имејл адреса није исправна, можете је променити у подешавањима налога.
delete_account: Брисање налога delete_account: Брисање налога
delete_account_html: Ако желите да избришете ваш налог, можете <a href="%{path}">наставити овде</a>. Од вас ће се тражити потврда. delete_account_html: Ако желите да избришете ваш налог, можете <a href="%{path}">наставити овде</a>. Од вас ће се тражити потврда.
@ -1855,8 +1863,8 @@ sr:
final_step: 'Почните да објављујете! Чак и без пратилаца, Ваше јавне објаве су видљиве другим људима, на пример на локалној временској линији или у хеш ознакама. Можда желите да се представите са хеш ознаком #introductions или #представљања.' final_step: 'Почните да објављујете! Чак и без пратилаца, Ваше јавне објаве су видљиве другим људима, на пример на локалној временској линији или у хеш ознакама. Можда желите да се представите са хеш ознаком #introductions или #представљања.'
full_handle: Ваш пун надимак full_handle: Ваш пун надимак
full_handle_hint: Ово бисте рекли својим пријатељима како би вам они послали поруку, или запратили са друге инстанце. full_handle_hint: Ово бисте рекли својим пријатељима како би вам они послали поруку, или запратили са друге инстанце.
subject: Добродошли на Mastodon subject: Добро дошли на Mastodon
title: Добродошли, %{name}! title: Добро дошли, %{name}!
users: users:
follow_limit_reached: Не можете пратити више од %{limit} људи follow_limit_reached: Не можете пратити више од %{limit} људи
go_to_sso_account_settings: Идите на подешавања налога свог добављача идентитета go_to_sso_account_settings: Идите на подешавања налога свог добављача идентитета

View File

@ -1041,6 +1041,11 @@ sv:
hint_html: En sista sak till! Vi måste bekräfta att du är en människa (för att hålla borta skräpinlägg!). Lös CAPTCHA nedan och klicka på "Fortsätt". hint_html: En sista sak till! Vi måste bekräfta att du är en människa (för att hålla borta skräpinlägg!). Lös CAPTCHA nedan och klicka på "Fortsätt".
title: Säkerhetskontroll title: Säkerhetskontroll
confirmations: confirmations:
awaiting_review_title: Din registrering är under granskning
clicking_this_link: klicka på denna länk
login_link: logga in
registration_complete: Din registrering på %{domain} är nu slutförd!
welcome_title: Välkommen %{name}!
wrong_email_hint: Om e-postadressen inte är rätt, kan du ändra den i kontoinställningarna. wrong_email_hint: Om e-postadressen inte är rätt, kan du ändra den i kontoinställningarna.
delete_account: Radera konto delete_account: Radera konto
delete_account_html: Om du vill radera ditt konto kan du <a href="%{path}">fortsätta här</a>. Du kommer att bli ombedd att bekräfta. delete_account_html: Om du vill radera ditt konto kan du <a href="%{path}">fortsätta här</a>. Du kommer att bli ombedd att bekräfta.

View File

@ -1023,6 +1023,14 @@ th:
hint_html: อีกเพียงหนึ่งสิ่งเพิ่มเติม! เราจำเป็นต้องยืนยันว่าคุณเป็นมนุษย์ (นี่ก็เพื่อให้เราสามารถกันสแปมออกไป!) แก้ CAPTCHA ด้านล่างและคลิก "ดำเนินการต่อ" hint_html: อีกเพียงหนึ่งสิ่งเพิ่มเติม! เราจำเป็นต้องยืนยันว่าคุณเป็นมนุษย์ (นี่ก็เพื่อให้เราสามารถกันสแปมออกไป!) แก้ CAPTCHA ด้านล่างและคลิก "ดำเนินการต่อ"
title: การตรวจสอบความปลอดภัย title: การตรวจสอบความปลอดภัย
confirmations: confirmations:
awaiting_review: ยืนยันที่อยู่อีเมลของคุณแล้ว! ตอนนี้พนักงานของ %{domain} กำลังตรวจทานการลงทะเบียนของคุณ คุณจะได้รับอีเมลหากพนักงานอนุมัติบัญชีของคุณ!
awaiting_review_title: กำลังตรวจทานการลงทะเบียนของคุณ
clicking_this_link: คลิกลิงก์นี้
login_link: เข้าสู่ระบบ
proceed_to_login_html: ตอนนี้คุณสามารถดำเนินการต่อเพื่อ %{login_link}
redirect_to_app_html: คุณควรได้รับการเปลี่ยนเส้นทางไปยังแอป <strong>%{app_name}</strong> หากนั่นไม่เกิดขึ้น ลอง %{clicking_this_link} หรือกลับไปยังแอปด้วยตนเอง
registration_complete: ตอนนี้การลงทะเบียนของคุณใน %{domain} เสร็จสมบูรณ์แล้ว!
welcome_title: ยินดีต้อนรับ %{name}!
wrong_email_hint: หากที่อยู่อีเมลนั้นไม่ถูกต้อง คุณสามารถเปลี่ยนที่อยู่อีเมลได้ในการตั้งค่าบัญชี wrong_email_hint: หากที่อยู่อีเมลนั้นไม่ถูกต้อง คุณสามารถเปลี่ยนที่อยู่อีเมลได้ในการตั้งค่าบัญชี
delete_account: ลบบัญชี delete_account: ลบบัญชี
delete_account_html: หากคุณต้องการลบบัญชีของคุณ คุณสามารถ <a href="%{path}">ดำเนินการต่อที่นี่</a> คุณจะได้รับการถามเพื่อการยืนยัน delete_account_html: หากคุณต้องการลบบัญชีของคุณ คุณสามารถ <a href="%{path}">ดำเนินการต่อที่นี่</a> คุณจะได้รับการถามเพื่อการยืนยัน

View File

@ -1041,6 +1041,14 @@ tr:
hint_html: Sadece bir şey daha! Sizin bir insan olduğunuzu doğrulamamız gerekiyor (bu, spam'i dışarıda tutabilmemiz içindir!). Aşağıdaki CAPTCHA'yı çözün ve "Devam Et" düğmesini tıklayın. hint_html: Sadece bir şey daha! Sizin bir insan olduğunuzu doğrulamamız gerekiyor (bu, spam'i dışarıda tutabilmemiz içindir!). Aşağıdaki CAPTCHA'yı çözün ve "Devam Et" düğmesini tıklayın.
title: Güvenlik denetimi title: Güvenlik denetimi
confirmations: confirmations:
awaiting_review: E-posta adresiniz doğrulandı! %{domain} çalışanları şimdi kaydınızı inceliyorlar. Hesabınızı onayladıklarında bir e-posta alacaksınız!
awaiting_review_title: Kaydınız inceleniyor
clicking_this_link: bu bağlantıyı tıklamayı
login_link: oturum aç
proceed_to_login_html: Şimdi %{login_link} devam edebilirsiniz.
redirect_to_app_html: "<strong>%{app_name}</strong> uygulamasına yönlendirileceksiniz. Eğer yönlendirme olmazsa, %{clicking_this_link} veya uygulamaya geri dönmeyi deneyin."
registration_complete: "%{domain} sunucusunda kaydınız şimdi tamamlandı!"
welcome_title: Hoşgeldin %{name}!
wrong_email_hint: Eğer bu e-posta adresi doğru değilse, hesap ayarlarında değiştirebilirsiniz. wrong_email_hint: Eğer bu e-posta adresi doğru değilse, hesap ayarlarında değiştirebilirsiniz.
delete_account: Hesabı sil delete_account: Hesabı sil
delete_account_html: Hesabını silmek istersen, <a href="%{path}">buradan devam edebilirsin</a>. Onay istenir. delete_account_html: Hesabını silmek istersen, <a href="%{path}">buradan devam edebilirsin</a>. Onay istenir.

View File

@ -1077,6 +1077,14 @@ uk:
hint_html: Ще одне! Ми повинні пересвідчитись, що ви людина (щоб ми могли уникнути спаму!). Розв'яжіть CAPTCHA внизу і натисніть кнопку "Продовжити". hint_html: Ще одне! Ми повинні пересвідчитись, що ви людина (щоб ми могли уникнути спаму!). Розв'яжіть CAPTCHA внизу і натисніть кнопку "Продовжити".
title: Перевірка безпеки title: Перевірка безпеки
confirmations: confirmations:
awaiting_review: Ваша електронна адреса підтверджена! Співробітники %{domain} тепер переглядають вашу реєстрацію. Ви отримаєте електронного листа, якщо вони затвердять ваш обліковий запис!
awaiting_review_title: Ваша реєстрація розглядається
clicking_this_link: натисніть це посилання
login_link: увійти
proceed_to_login_html: Тепер ви можете перейти до %{login_link}.
redirect_to_app_html: Ви мали бути перенаправлені до програми <strong>%{app_name}</strong>. Якщо цього не сталося, спробуйте %{clicking_this_link} або вручну поверніться до програми.
registration_complete: Ваша реєстрація на %{domain} завершена!
welcome_title: Ласкаво просимо, %{name}!
wrong_email_hint: Якщо ця адреса електронної пошти неправильна, можна змінити її в налаштуваннях облікового запису. wrong_email_hint: Якщо ця адреса електронної пошти неправильна, можна змінити її в налаштуваннях облікового запису.
delete_account: Видалити обліковий запис delete_account: Видалити обліковий запис
delete_account_html: Якщо ви хочете видалити свій обліковий запис, ви можете <a href="%{path}">перейти сюди</a>. Вас попросять підтвердити дію. delete_account_html: Якщо ви хочете видалити свій обліковий запис, ви можете <a href="%{path}">перейти сюди</a>. Вас попросять підтвердити дію.

View File

@ -1023,6 +1023,14 @@ zh-CN:
hint_html: 只剩最后一件事了!我们需要确认您是一个人类(这样我们才能阻止恶意访问!)。请输入下面的验证码,然后点击“继续”。 hint_html: 只剩最后一件事了!我们需要确认您是一个人类(这样我们才能阻止恶意访问!)。请输入下面的验证码,然后点击“继续”。
title: 安全检查 title: 安全检查
confirmations: confirmations:
awaiting_review: 您的电子邮件地址已确认!%{domain} 的工作人员正在审核您的注册信息。如果他们批准了您的账户,您将收到一封邮件通知!
awaiting_review_title: 您的注册申请正在审核中
clicking_this_link: 点击此链接
login_link: 登录
proceed_to_login_html: 现在您可以继续前往 %{login_link} 。
redirect_to_app_html: 您应该已被重定向到 <strong>%{app_name}</strong> 应用程序。如果没有,请尝试 %{clicking_this_link} 或手动返回应用程序。
registration_complete: 您在 %{domain} 上的注册现已完成!
welcome_title: 欢迎您,%{name}
wrong_email_hint: 如果该电子邮件地址不正确,您可以在帐户设置中进行更改。 wrong_email_hint: 如果该电子邮件地址不正确,您可以在帐户设置中进行更改。
delete_account: 删除帐户 delete_account: 删除帐户
delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。 delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。

View File

@ -1025,6 +1025,14 @@ zh-TW:
hint_html: 僅再一步!我們必須確認您是位人類(這是防止垃圾訊息濫用)。請完成以下 CAPTCHA 挑戰並點擊「繼續」。 hint_html: 僅再一步!我們必須確認您是位人類(這是防止垃圾訊息濫用)。請完成以下 CAPTCHA 挑戰並點擊「繼續」。
title: 安全性檢查 title: 安全性檢查
confirmations: confirmations:
awaiting_review: 已驗證您的電子郵件!%{domain} 的管理員正在審核您的註冊申請。若您的帳號通過審核,您將收到電子郵件通知。
awaiting_review_title: 我們正在審核您的註冊申請
clicking_this_link: 點擊這個連結
login_link: 登入
proceed_to_login_html: 您現在可以前往 %{login_link}。
redirect_to_app_html: 您應被重新導向至 <strong>%{app_name}</strong> 應用程式。如尚未重新導向,請嘗試 %{clicking_this_link} 或手動回到應用程式。
registration_complete: 您於 %{domain} 之註冊申請已完成!
welcome_title: 歡迎,%{name}
wrong_email_hint: 若電子郵件地址不正確,您可以於帳號設定中更改。 wrong_email_hint: 若電子郵件地址不正確,您可以於帳號設定中更改。
delete_account: 刪除帳號 delete_account: 刪除帳號
delete_account_html: 如果您欲刪除您的帳號,請<a href="%{path}">點擊這裡繼續</a>。您需要再三確認您的操作。 delete_account_html: 如果您欲刪除您的帳號,請<a href="%{path}">點擊這裡繼續</a>。您需要再三確認您的操作。

View File

@ -135,7 +135,7 @@ Rails.application.routes.draw do
get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
end end
get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: %r{([^/])+?} }, format: false get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: %r{([^/])+?} }, as: :account_with_domain, format: false
get '/settings', to: redirect('/settings/profile') get '/settings', to: redirect('/settings/profile')
draw(:settings) draw(:settings)

View File

@ -158,10 +158,8 @@ module Mastodon
'in the body of your migration class' 'in the body of your migration class'
end end
if supports_drop_index_concurrently? options = options.merge({ algorithm: :concurrently })
options = options.merge({ algorithm: :concurrently }) disable_statement_timeout
disable_statement_timeout
end
remove_index(table_name, **options.merge({ column: column_name })) remove_index(table_name, **options.merge({ column: column_name }))
end end
@ -182,28 +180,12 @@ module Mastodon
'in the body of your migration class' 'in the body of your migration class'
end end
if supports_drop_index_concurrently? options = options.merge({ algorithm: :concurrently })
options = options.merge({ algorithm: :concurrently }) disable_statement_timeout
disable_statement_timeout
end
remove_index(table_name, **options.merge({ name: index_name })) remove_index(table_name, **options.merge({ name: index_name }))
end end
# Only available on Postgresql >= 9.2
def supports_drop_index_concurrently?
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
version >= 90_200
end
# Only available on Postgresql >= 11
def supports_add_column_with_default?
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
version >= 110_000
end
# Adds a foreign key with only minimal locking on the tables involved. # Adds a foreign key with only minimal locking on the tables involved.
# #
# This method only requires minimal locking when using PostgreSQL. When # This method only requires minimal locking when using PostgreSQL. When
@ -420,42 +402,7 @@ module Mastodon
# This method can also take a block which is passed directly to the # This method can also take a block which is passed directly to the
# `update_column_in_batches` method. # `update_column_in_batches` method.
def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block) def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block)
if supports_add_column_with_default? add_column(table, column, type, default: default, limit: limit, null: allow_null)
add_column(table, column, type, default: default, limit: limit, null: allow_null)
return
end
if transaction_open?
raise 'add_column_with_default can not be run inside a transaction, ' \
'you can disable transactions by calling disable_ddl_transaction! ' \
'in the body of your migration class'
end
disable_statement_timeout
transaction do
if limit
add_column(table, column, type, default: nil, limit: limit)
else
add_column(table, column, type, default: nil)
end
# Changing the default before the update ensures any newly inserted
# rows already use the proper default value.
change_column_default(table, column, default)
end
begin
update_column_in_batches(table, column, default, &block)
change_column_null(table, column, false) unless allow_null
# We want to rescue _all_ exceptions here, even those that don't inherit
# from StandardError.
rescue Exception => error # rubocop: disable all
remove_column(table, column)
raise error
end
end end
# Renames a column without requiring downtime. # Renames a column without requiring downtime.

View File

@ -33,8 +33,6 @@ RSpec.describe Admin::Disputes::AppealsController do
let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do before do
allow(UserMailer).to receive(:appeal_approved)
.and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
post :approve, params: { id: appeal.id } post :approve, params: { id: appeal.id }
end end
@ -47,7 +45,9 @@ RSpec.describe Admin::Disputes::AppealsController do
end end
it 'notifies target account about approved appeal' do it 'notifies target account about approved appeal' do
expect(UserMailer).to have_received(:appeal_approved).with(target_account.user, appeal) expect(UserMailer.deliveries.size).to eq(1)
expect(UserMailer.deliveries.first.to.first).to eq(target_account.user.email)
expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at)))
end end
end end
@ -55,8 +55,6 @@ RSpec.describe Admin::Disputes::AppealsController do
let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do before do
allow(UserMailer).to receive(:appeal_rejected)
.and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
post :reject, params: { id: appeal.id } post :reject, params: { id: appeal.id }
end end
@ -65,7 +63,9 @@ RSpec.describe Admin::Disputes::AppealsController do
end end
it 'notifies target account about rejected appeal' do it 'notifies target account about rejected appeal' do
expect(UserMailer).to have_received(:appeal_rejected).with(target_account.user, appeal) expect(UserMailer.deliveries.size).to eq(1)
expect(UserMailer.deliveries.first.to.first).to eq(target_account.user.email)
expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at)))
end end
end end
end end

View File

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::FeaturedTagsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
end
end

View File

@ -127,8 +127,6 @@ RSpec.describe Auth::SessionsController do
before do before do
allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return(current_ip) allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return(current_ip)
allow(UserMailer).to receive(:suspicious_sign_in)
.and_return(instance_double(ActionMailer::MessageDelivery, deliver_later!: nil))
user.update(current_sign_in_at: 1.month.ago) user.update(current_sign_in_at: 1.month.ago)
post :create, params: { user: { email: user.email, password: user.password } } post :create, params: { user: { email: user.email, password: user.password } }
end end
@ -142,7 +140,9 @@ RSpec.describe Auth::SessionsController do
end end
it 'sends a suspicious sign-in mail' do it 'sends a suspicious sign-in mail' do
expect(UserMailer).to have_received(:suspicious_sign_in).with(user, current_ip, anything, anything) expect(UserMailer.deliveries.size).to eq(1)
expect(UserMailer.deliveries.first.to.first).to eq(user.email)
expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.suspicious_sign_in.subject'))
end end
end end

View File

@ -30,6 +30,14 @@ describe 'email confirmation flow when captcha is enabled' do
click_button I18n.t('challenge.confirm') click_button I18n.t('challenge.confirm')
expect(user.reload.confirmed?).to be true expect(user.reload.confirmed?).to be true
expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true) expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true)
# Browsers will generally reload the original page upon redirection
# to external handlers, so test this as well
visit "/auth/confirmation?confirmation_token=#{user.confirmation_token}&redirect_to_app=true"
# It presents a page with a link to the app callback
expect(page).to have_content(I18n.t('auth.confirmations.registration_complete', domain: 'cb6e6126.ngrok.io'))
expect(page).to have_link(I18n.t('auth.confirmations.clicking_this_link'), href: client_app.confirmation_redirect_uri)
end end
end end
end end

View File

@ -5,7 +5,7 @@ require 'rails_helper'
describe UserMailer do describe UserMailer do
let(:receiver) { Fabricate(:user) } let(:receiver) { Fabricate(:user) }
describe 'confirmation_instructions' do describe '#confirmation_instructions' do
let(:mail) { described_class.confirmation_instructions(receiver, 'spec') } let(:mail) { described_class.confirmation_instructions(receiver, 'spec') }
it 'renders confirmation instructions' do it 'renders confirmation instructions' do
@ -20,7 +20,7 @@ describe UserMailer do
instance: Rails.configuration.x.local_domain instance: Rails.configuration.x.local_domain
end end
describe 'reconfirmation_instructions' do describe '#reconfirmation_instructions' do
let(:mail) { described_class.confirmation_instructions(receiver, 'spec') } let(:mail) { described_class.confirmation_instructions(receiver, 'spec') }
it 'renders reconfirmation instructions' do it 'renders reconfirmation instructions' do
@ -34,7 +34,7 @@ describe UserMailer do
end end
end end
describe 'reset_password_instructions' do describe '#reset_password_instructions' do
let(:mail) { described_class.reset_password_instructions(receiver, 'spec') } let(:mail) { described_class.reset_password_instructions(receiver, 'spec') }
it 'renders reset password instructions' do it 'renders reset password instructions' do
@ -47,7 +47,7 @@ describe UserMailer do
'devise.mailer.reset_password_instructions.subject' 'devise.mailer.reset_password_instructions.subject'
end end
describe 'password_change' do describe '#password_change' do
let(:mail) { described_class.password_change(receiver) } let(:mail) { described_class.password_change(receiver) }
it 'renders password change notification' do it 'renders password change notification' do
@ -59,7 +59,7 @@ describe UserMailer do
'devise.mailer.password_change.subject' 'devise.mailer.password_change.subject'
end end
describe 'email_changed' do describe '#email_changed' do
let(:mail) { described_class.email_changed(receiver) } let(:mail) { described_class.email_changed(receiver) }
it 'renders email change notification' do it 'renders email change notification' do
@ -71,7 +71,7 @@ describe UserMailer do
'devise.mailer.email_changed.subject' 'devise.mailer.email_changed.subject'
end end
describe 'warning' do describe '#warning' do
let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend') } let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend') }
let(:mail) { described_class.warning(receiver, strike) } let(:mail) { described_class.warning(receiver, strike) }
@ -82,7 +82,7 @@ describe UserMailer do
end end
end end
describe 'webauthn_credential_deleted' do describe '#webauthn_credential_deleted' do
let(:credential) { Fabricate(:webauthn_credential, user_id: receiver.id) } let(:credential) { Fabricate(:webauthn_credential, user_id: receiver.id) }
let(:mail) { described_class.webauthn_credential_deleted(receiver, credential) } let(:mail) { described_class.webauthn_credential_deleted(receiver, credential) }
@ -95,7 +95,7 @@ describe UserMailer do
'devise.mailer.webauthn_credential.deleted.subject' 'devise.mailer.webauthn_credential.deleted.subject'
end end
describe 'suspicious_sign_in' do describe '#suspicious_sign_in' do
let(:ip) { '192.168.0.1' } let(:ip) { '192.168.0.1' }
let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' } let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' }
let(:timestamp) { Time.now.utc } let(:timestamp) { Time.now.utc }
@ -110,7 +110,7 @@ describe UserMailer do
'user_mailer.suspicious_sign_in.subject' 'user_mailer.suspicious_sign_in.subject'
end end
describe 'appeal_approved' do describe '#appeal_approved' do
let(:appeal) { Fabricate(:appeal, account: receiver.account, approved_at: Time.now.utc) } let(:appeal) { Fabricate(:appeal, account: receiver.account, approved_at: Time.now.utc) }
let(:mail) { described_class.appeal_approved(receiver, appeal) } let(:mail) { described_class.appeal_approved(receiver, appeal) }
@ -120,7 +120,7 @@ describe UserMailer do
end end
end end
describe 'appeal_rejected' do describe '#appeal_rejected' do
let(:appeal) { Fabricate(:appeal, account: receiver.account, rejected_at: Time.now.utc) } let(:appeal) { Fabricate(:appeal, account: receiver.account, rejected_at: Time.now.utc) }
let(:mail) { described_class.appeal_rejected(receiver, appeal) } let(:mail) { described_class.appeal_rejected(receiver, appeal) }
@ -130,7 +130,7 @@ describe UserMailer do
end end
end end
describe 'two_factor_enabled' do describe '#two_factor_enabled' do
let(:mail) { described_class.two_factor_enabled(receiver) } let(:mail) { described_class.two_factor_enabled(receiver) }
it 'renders two_factor_enabled mail' do it 'renders two_factor_enabled mail' do
@ -139,7 +139,7 @@ describe UserMailer do
end end
end end
describe 'two_factor_disabled' do describe '#two_factor_disabled' do
let(:mail) { described_class.two_factor_disabled(receiver) } let(:mail) { described_class.two_factor_disabled(receiver) }
it 'renders two_factor_disabled mail' do it 'renders two_factor_disabled mail' do
@ -148,7 +148,7 @@ describe UserMailer do
end end
end end
describe 'webauthn_enabled' do describe '#webauthn_enabled' do
let(:mail) { described_class.webauthn_enabled(receiver) } let(:mail) { described_class.webauthn_enabled(receiver) }
it 'renders webauthn_enabled mail' do it 'renders webauthn_enabled mail' do
@ -157,7 +157,7 @@ describe UserMailer do
end end
end end
describe 'webauthn_disabled' do describe '#webauthn_disabled' do
let(:mail) { described_class.webauthn_disabled(receiver) } let(:mail) { described_class.webauthn_disabled(receiver) }
it 'renders webauthn_disabled mail' do it 'renders webauthn_disabled mail' do
@ -166,7 +166,7 @@ describe UserMailer do
end end
end end
describe 'two_factor_recovery_codes_changed' do describe '#two_factor_recovery_codes_changed' do
let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) } let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) }
it 'renders two_factor_recovery_codes_changed mail' do it 'renders two_factor_recovery_codes_changed mail' do
@ -175,7 +175,7 @@ describe UserMailer do
end end
end end
describe 'webauthn_credential_added' do describe '#webauthn_credential_added' do
let(:credential) { Fabricate.build(:webauthn_credential) } let(:credential) { Fabricate.build(:webauthn_credential) }
let(:mail) { described_class.webauthn_credential_added(receiver, credential) } let(:mail) { described_class.webauthn_credential_added(receiver, credential) }
@ -184,4 +184,23 @@ describe UserMailer do
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation') expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation')
end end
end end
describe '#welcome' do
let(:mail) { described_class.welcome(receiver) }
it 'renders welcome mail' do
expect(mail.subject).to eq I18n.t('user_mailer.welcome.subject')
expect(mail.body.encoded).to include I18n.t('user_mailer.welcome.explanation')
end
end
describe '#backup_ready' do
let(:backup) { Fabricate(:backup) }
let(:mail) { described_class.backup_ready(receiver, backup) }
it 'renders backup_ready mail' do
expect(mail.subject).to eq I18n.t('user_mailer.backup_ready.subject')
expect(mail.body.encoded).to include I18n.t('user_mailer.backup_ready.explanation')
end
end
end end

View File

@ -32,6 +32,10 @@ RSpec.describe Tag do
expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit')).to be_nil expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit')).to be_nil
end end
it 'does not match URLs with hashtag-like anchors after a numeral' do
expect(subject.match('https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111895#c4')).to be_nil
end
it 'does not match URLs with hashtag-like anchors after an empty query parameter' do it 'does not match URLs with hashtag-like anchors after an empty query parameter' do
expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)?foo=#Lawsuit')).to be_nil expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)?foo=#Lawsuit')).to be_nil
end end

View File

@ -0,0 +1,50 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'account featured tags API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
describe 'GET /api/v1/accounts/:id/featured_tags' do
subject do
get "/api/v1/accounts/#{account.id}/featured_tags", headers: headers
end
before do
account.featured_tags.create!(name: 'foo')
account.featured_tags.create!(name: 'bar')
end
it 'returns the expected tags', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to contain_exactly(a_hash_including({
name: 'bar',
url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/bar",
}), a_hash_including({
name: 'foo',
url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/foo",
}))
end
context 'when the account is remote' do
it 'returns the expected tags', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to contain_exactly(a_hash_including({
name: 'bar',
url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/bar",
}), a_hash_including({
name: 'foo',
url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/foo",
}))
end
end
end
end

View File

@ -42,12 +42,22 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
} }
end end
let(:featured_with_null) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'https://example.com/account/collections/featured',
totalItems: 0,
type: 'OrderedCollection',
}
end
let(:items) do let(:items) do
[ [
'https://example.com/account/pinned/known', # known 'https://example.com/account/pinned/known', # known
status_json_pinned_unknown_inlined, # unknown inlined status_json_pinned_unknown_inlined, # unknown inlined
'https://example.com/account/pinned/unknown-unreachable', # unknown unreachable 'https://example.com/account/pinned/unknown-unreachable', # unknown unreachable
'https://example.com/account/pinned/unknown-reachable', # unknown reachable 'https://example.com/account/pinned/unknown-reachable', # unknown reachable
'https://example.com/account/collections/featured', # featured with null
] ]
end end
@ -66,6 +76,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined)) stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined))
stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404) stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404)
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_unreachable)) stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_unreachable))
stub_request(:get, 'https://example.com/account/collections/featured').to_return(status: 200, body: Oj.dump(featured_with_null))
subject.call(actor, note: true, hashtag: false) subject.call(actor, note: true, hashtag: false)
end end

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