local
Thor 2 years ago
commit 655573f9c5
  1. 2
      .github/workflows/build-image.yml
  2. 23
      app/controllers/api/web/push_subscriptions_controller.rb
  3. 3
      app/javascript/flavours/glitch/actions/notifications.js
  4. 115
      app/javascript/flavours/glitch/actions/reports.js
  5. 27
      app/javascript/flavours/glitch/actions/rules.js
  6. 9
      app/javascript/flavours/glitch/components/check.js
  7. 101
      app/javascript/flavours/glitch/features/notifications/components/admin_signup.js
  8. 16
      app/javascript/flavours/glitch/features/notifications/components/column_settings.js
  9. 14
      app/javascript/flavours/glitch/features/notifications/components/notification.js
  10. 93
      app/javascript/flavours/glitch/features/report/category.js
  11. 83
      app/javascript/flavours/glitch/features/report/comment.js
  12. 60
      app/javascript/flavours/glitch/features/report/components/option.js
  13. 56
      app/javascript/flavours/glitch/features/report/components/status_check_box.js
  14. 22
      app/javascript/flavours/glitch/features/report/containers/status_check_box_container.js
  15. 64
      app/javascript/flavours/glitch/features/report/rules.js
  16. 58
      app/javascript/flavours/glitch/features/report/statuses.js
  17. 84
      app/javascript/flavours/glitch/features/report/thanks.js
  18. 239
      app/javascript/flavours/glitch/features/ui/components/report_modal.js
  19. 4
      app/javascript/flavours/glitch/reducers/index.js
  20. 77
      app/javascript/flavours/glitch/reducers/reports.js
  21. 13
      app/javascript/flavours/glitch/reducers/rules.js
  22. 3
      app/javascript/flavours/glitch/reducers/settings.js
  23. 4
      app/javascript/flavours/glitch/styles/components/composer.scss
  24. 22
      app/javascript/flavours/glitch/styles/components/index.scss
  25. 186
      app/javascript/flavours/glitch/styles/components/modal.scss
  26. 51
      app/javascript/flavours/glitch/styles/components/status.scss
  27. 3
      app/javascript/mastodon/actions/notifications.js
  28. 115
      app/javascript/mastodon/actions/reports.js
  29. 27
      app/javascript/mastodon/actions/rules.js
  30. 9
      app/javascript/mastodon/components/check.js
  31. 2
      app/javascript/mastodon/components/loading_indicator.js
  32. 2
      app/javascript/mastodon/features/emoji/emoji_compressed.js
  33. 16
      app/javascript/mastodon/features/notifications/components/column_settings.js
  34. 25
      app/javascript/mastodon/features/notifications/components/notification.js
  35. 93
      app/javascript/mastodon/features/report/category.js
  36. 83
      app/javascript/mastodon/features/report/comment.js
  37. 60
      app/javascript/mastodon/features/report/components/option.js
  38. 61
      app/javascript/mastodon/features/report/components/status_check_box.js
  39. 22
      app/javascript/mastodon/features/report/containers/status_check_box_container.js
  40. 64
      app/javascript/mastodon/features/report/rules.js
  41. 58
      app/javascript/mastodon/features/report/statuses.js
  42. 84
      app/javascript/mastodon/features/report/thanks.js
  43. 241
      app/javascript/mastodon/features/ui/components/report_modal.js
  44. 4
      app/javascript/mastodon/locales/ca.json
  45. 4
      app/javascript/mastodon/locales/da.json
  46. 4
      app/javascript/mastodon/locales/el.json
  47. 4
      app/javascript/mastodon/locales/es-AR.json
  48. 4
      app/javascript/mastodon/locales/es.json
  49. 34
      app/javascript/mastodon/locales/eu.json
  50. 4
      app/javascript/mastodon/locales/fi.json
  51. 12
      app/javascript/mastodon/locales/fr.json
  52. 4
      app/javascript/mastodon/locales/gl.json
  53. 4
      app/javascript/mastodon/locales/he.json
  54. 4
      app/javascript/mastodon/locales/hu.json
  55. 4
      app/javascript/mastodon/locales/id.json
  56. 4
      app/javascript/mastodon/locales/is.json
  57. 4
      app/javascript/mastodon/locales/it.json
  58. 2
      app/javascript/mastodon/locales/ja.json
  59. 4
      app/javascript/mastodon/locales/kmr.json
  60. 4
      app/javascript/mastodon/locales/ko.json
  61. 4
      app/javascript/mastodon/locales/lv.json
  62. 4
      app/javascript/mastodon/locales/pl.json
  63. 4
      app/javascript/mastodon/locales/pt-PT.json
  64. 4
      app/javascript/mastodon/locales/ru.json
  65. 4
      app/javascript/mastodon/locales/sq.json
  66. 2
      app/javascript/mastodon/locales/sv.json
  67. 4
      app/javascript/mastodon/locales/th.json
  68. 4
      app/javascript/mastodon/locales/tr.json
  69. 4
      app/javascript/mastodon/locales/uk.json
  70. 4
      app/javascript/mastodon/locales/vi.json
  71. 2
      app/javascript/mastodon/locales/zh-CN.json
  72. 14
      app/javascript/mastodon/locales/zh-TW.json
  73. 4
      app/javascript/mastodon/reducers/index.js
  74. 64
      app/javascript/mastodon/reducers/reports.js
  75. 13
      app/javascript/mastodon/reducers/rules.js
  76. 3
      app/javascript/mastodon/reducers/settings.js
  77. 1
      app/javascript/mastodon/service_worker/web_push_locales.js
  78. 258
      app/javascript/styles/mastodon/components.scss
  79. 2
      app/lib/feed_manager.rb
  80. 14
      app/models/notification.rb
  81. 7
      app/services/bootstrap_timeline_service.rb
  82. 2
      app/services/fetch_link_card_service.rb
  83. 44
      app/services/notify_service.rb
  84. 2
      app/views/admin_mailer/new_trending_tags.text.erb
  85. 1
      config/i18n-tasks.yml
  86. 2
      config/initializers/preload_link_headers.rb
  87. 2
      config/locales/activerecord.lv.yml
  88. 2
      config/locales/devise.th.yml
  89. 3
      config/locales/en.yml
  90. 47
      config/locales/eu.yml
  91. 52
      config/locales/fi.yml
  92. 95
      config/locales/fr.yml
  93. 15
      config/locales/he.yml
  94. 21
      config/locales/id.yml
  95. 32
      config/locales/ja.yml
  96. 8
      config/locales/ru.yml
  97. 37
      config/locales/simple_form.fi.yml
  98. 7
      config/locales/simple_form.fr.yml
  99. 2
      config/locales/simple_form.gl.yml
  100. 7
      config/locales/simple_form.id.yml
  101. Some files were not shown because too many files have changed in this diff Show More

@ -22,6 +22,7 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
if: github.event_name != 'pull_request'
- uses: docker/metadata-action@v3
id: meta
with:
@ -31,6 +32,7 @@ jobs:
tags: |
type=edge,branch=main
type=semver,pattern={{ raw }}
type=ref,event=pr
- uses: docker/build-push-action@v2
with:
context: .

@ -17,17 +17,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
data = {
policy: 'all',
alerts: {
follow: alerts_enabled,
follow_request: alerts_enabled,
favourite: alerts_enabled,
reblog: alerts_enabled,
mention: alerts_enabled,
poll: alerts_enabled,
status: alerts_enabled,
update: alerts_enabled,
},
alerts: Notification::TYPES.index_with { alerts_enabled },
}
data.deep_merge!(data_params) if params[:data]
@ -62,15 +52,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def data_params
@data_params ||= params.require(:data).permit(:policy, alerts: [
:follow,
:follow_request,
:favourite,
:reblog,
:mention,
:poll,
:status,
:update,
])
@data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
end
end

@ -57,7 +57,7 @@ defineMessages({
});
const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
if (accountIds > 0) {
dispatch(fetchRelationships(accountIds));
@ -144,6 +144,7 @@ const excludeTypesFromFilter = filter => {
'poll',
'status',
'update',
'admin.sign_up',
]);
return allTypes.filterNot(item => item === filter).toJS();

@ -1,89 +1,38 @@
import api from 'flavours/glitch/util/api';
import { openModal, closeModal } from './modal';
export const REPORT_INIT = 'REPORT_INIT';
export const REPORT_CANCEL = 'REPORT_CANCEL';
import { openModal } from './modal';
export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
export function initReport(account, status) {
return dispatch => {
dispatch({
type: REPORT_INIT,
account,
status,
});
dispatch(openModal('REPORT'));
};
};
export function cancelReport() {
return {
type: REPORT_CANCEL,
};
};
export function toggleStatusReport(statusId, checked) {
return {
type: REPORT_STATUS_TOGGLE,
statusId,
checked,
};
};
export function submitReport() {
return (dispatch, getState) => {
dispatch(submitReportRequest());
api(getState).post('/api/v1/reports', {
account_id: getState().getIn(['reports', 'new', 'account_id']),
status_ids: getState().getIn(['reports', 'new', 'status_ids']),
comment: getState().getIn(['reports', 'new', 'comment']),
forward: getState().getIn(['reports', 'new', 'forward']),
}).then(response => {
dispatch(closeModal());
dispatch(submitReportSuccess(response.data));
}).catch(error => dispatch(submitReportFail(error)));
};
};
export function submitReportRequest() {
return {
type: REPORT_SUBMIT_REQUEST,
};
};
export function submitReportSuccess(report) {
return {
type: REPORT_SUBMIT_SUCCESS,
report,
};
};
export function submitReportFail(error) {
return {
type: REPORT_SUBMIT_FAIL,
error,
};
};
export function changeReportComment(comment) {
return {
type: REPORT_COMMENT_CHANGE,
comment,
};
};
export function changeReportForward(forward) {
return {
type: REPORT_FORWARD_CHANGE,
forward,
};
};
export const initReport = (account, status) => dispatch =>
dispatch(openModal('REPORT', {
accountId: account.get('id'),
statusId: status?.get('id'),
}));
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {
dispatch(submitReportRequest());
api(getState).post('/api/v1/reports', params).then(response => {
dispatch(submitReportSuccess(response.data));
if (onSuccess) onSuccess();
}).catch(error => {
dispatch(submitReportFail(error));
if (onFail) onFail();
});
};
export const submitReportRequest = () => ({
type: REPORT_SUBMIT_REQUEST,
});
export const submitReportSuccess = report => ({
type: REPORT_SUBMIT_SUCCESS,
report,
});
export const submitReportFail = error => ({
type: REPORT_SUBMIT_FAIL,
error,
});

@ -0,0 +1,27 @@
import api from 'flavours/glitch/util/api';
export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST';
export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS';
export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL';
export const fetchRules = () => (dispatch, getState) => {
dispatch(fetchRulesRequest());
api(getState)
.get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules)))
.catch(err => dispatch(fetchRulesFail(err)));
};
const fetchRulesRequest = () => ({
type: RULES_FETCH_REQUEST,
});
const fetchRulesSuccess = rules => ({
type: RULES_FETCH_SUCCESS,
rules,
});
const fetchRulesFail = error => ({
type: RULES_FETCH_FAIL,
error,
});

@ -0,0 +1,9 @@
import React from 'react';
const Check = () => (
<svg width='14' height='11' viewBox='0 0 14 11'>
<path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' />
</svg>
);
export default Check;

@ -0,0 +1,101 @@
// Package imports.
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
// Our imports.
import Permalink from 'flavours/glitch/components/permalink';
import AccountContainer from 'flavours/glitch/containers/account_container';
import NotificationOverlayContainer from '../containers/overlay_container';
import Icon from 'flavours/glitch/components/icon';
export default class NotificationFollow extends ImmutablePureComponent {
static propTypes = {
hidden: PropTypes.bool,
id: PropTypes.string.isRequired,
account: ImmutablePropTypes.map.isRequired,
notification: ImmutablePropTypes.map.isRequired,
unread: PropTypes.bool,
};
handleMoveUp = () => {
const { notification, onMoveUp } = this.props;
onMoveUp(notification.get('id'));
}
handleMoveDown = () => {
const { notification, onMoveDown } = this.props;
onMoveDown(notification.get('id'));
}
handleOpen = () => {
this.handleOpenProfile();
}
handleOpenProfile = () => {
const { notification } = this.props;
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`);
}
handleMention = e => {
e.preventDefault();
const { notification, onMention } = this.props;
onMention(notification.get('account'), this.context.router.history);
}
getHandlers () {
return {
moveUp: this.handleMoveUp,
moveDown: this.handleMoveDown,
open: this.handleOpen,
openProfile: this.handleOpenProfile,
mention: this.handleMention,
reply: this.handleMention,
};
}
render () {
const { account, notification, hidden, unread } = this.props;
// Links to the display name.
const displayName = account.get('display_name_html') || account.get('username');
const link = (
<bdi><Permalink
className='notification__display-name'
href={account.get('url')}
title={account.get('acct')}
to={`/@${account.get('acct')}`}
dangerouslySetInnerHTML={{ __html: displayName }}
/></bdi>
);
// Renders.
return (
<HotKeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0'>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon fixedWidth id='user-plus' />
</div>
<FormattedMessage
id='notification.admin.sign_up'
defaultMessage='{name} signed up'
values={{ name: link }}
/>
</div>
<AccountContainer hidden={hidden} id={account.get('id')} withNote={false} />
<NotificationOverlayContainer notification={notification} />
</div>
</HotKeys>
);
}
}

@ -6,6 +6,7 @@ import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle';
import PillBarButton from './pill_bar_button';
import { isStaff } from 'flavours/glitch/util/initial_state';
export default class ColumnSettings extends React.PureComponent {
@ -156,7 +157,7 @@ export default class ColumnSettings extends React.PureComponent {
</div>
<div role='group' aria-labelledby='notifications-update'>
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
<span id='notifications-update' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
@ -165,6 +166,19 @@ export default class ColumnSettings extends React.PureComponent {
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
</div>
</div>
{isStaff && (
<div role='group' aria-labelledby='notifications-admin-sign-up'>
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span>
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'admin.sign_up']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'admin.sign_up']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'admin.sign_up']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'admin.sign_up']} onChange={onChange} label={soundStr} />
</div>
</div>
)}
</div>
);
}

@ -8,6 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import StatusContainer from 'flavours/glitch/containers/status_container';
import NotificationFollow from './follow';
import NotificationFollowRequestContainer from '../containers/follow_request_container';
import NotificationAdminSignup from './admin_signup';
export default class Notification extends ImmutablePureComponent {
@ -63,6 +64,19 @@ export default class Notification extends ImmutablePureComponent {
unread={this.props.unread}
/>
);
case 'admin.sign_up':
return (
<NotificationAdminSignup
hidden={hidden}
id={notification.get('id')}
account={notification.get('account')}
notification={notification}
onMoveDown={onMoveDown}
onMoveUp={onMoveUp}
onMention={onMention}
unread={this.props.unread}
/>
);
case 'mention':
return (
<StatusContainer

@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Button from 'flavours/glitch/components/button';
import Option from './components/option';
const messages = defineMessages({
dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' },
dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' },
violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
other_description: { id: 'report.reasons.other_description', defaultMessage: 'The issue does not fit into other categories' },
status: { id: 'report.category.title_status', defaultMessage: 'post' },
account: { id: 'report.category.title_account', defaultMessage: 'profile' },
});
export default @injectIntl
class Category extends React.PureComponent {
static propTypes = {
onNextStep: PropTypes.func.isRequired,
category: PropTypes.string,
onChangeCategory: PropTypes.func.isRequired,
startedFrom: PropTypes.oneOf(['status', 'account']),
intl: PropTypes.object.isRequired,
};
handleNextClick = () => {
const { onNextStep, category } = this.props;
switch(category) {
case 'dislike':
onNextStep('thanks');
break;
case 'violation':
onNextStep('rules');
break;
default:
onNextStep('statuses');
break;
}
};
handleCategoryToggle = (value, checked) => {
const { onChangeCategory } = this.props;
if (checked) {
onChangeCategory(value);
}
};
render () {
const { category, startedFrom, intl } = this.props;
const options = [
'dislike',
'spam',
'violation',
'other',
];
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} /></h3>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p>
<div>
{options.map(item => (
<Option
key={item}
name='category'
value={item}
checked={category === item}
onToggle={this.handleCategoryToggle}
label={intl.formatMessage(messages[item])}
description={intl.formatMessage(messages[`${item}_description`])}
/>
))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleNextClick} disabled={category === null}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import Button from 'flavours/glitch/components/button';
import Toggle from 'react-toggle';
const messages = defineMessages({
placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' },
});
export default @injectIntl
class Comment extends React.PureComponent {
static propTypes = {
onSubmit: PropTypes.func.isRequired,
comment: PropTypes.string.isRequired,
onChangeComment: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
isSubmitting: PropTypes.bool,
forward: PropTypes.bool,
isRemote: PropTypes.bool,
domain: PropTypes.string,
onChangeForward: PropTypes.func.isRequired,
};
handleClick = () => {
const { onSubmit } = this.props;
onSubmit();
};
handleChange = e => {
const { onChangeComment } = this.props;
onChangeComment(e.target.value);
};
handleKeyDown = e => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleClick();
}
};
handleForwardChange = e => {
const { onChangeForward } = this.props;
onChangeForward(e.target.checked);
};
render () {
const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>
<textarea
className='report-dialog-modal__textarea'
placeholder={intl.formatMessage(messages.placeholder)}
value={comment}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
disabled={isSubmitting}
/>
{isRemote && (
<React.Fragment>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
<label className='report-dialog-modal__toggle'>
<Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
<FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} />
</label>
</React.Fragment>
)}
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleClick}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Check from 'flavours/glitch/components/check';
export default class Option extends React.PureComponent {
static propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
checked: PropTypes.bool,
label: PropTypes.node,
description: PropTypes.node,
onToggle: PropTypes.func,
multiple: PropTypes.bool,
labelComponent: PropTypes.node,
};
handleKeyPress = e => {
const { value, checked, onToggle } = this.props;
if (e.key === 'Enter' || e.key === ' ') {
e.stopPropagation();
e.preventDefault();
onToggle(value, !checked);
}
}
handleChange = e => {
const { value, onToggle } = this.props;
onToggle(value, e.target.checked);
}
render () {
const { name, value, checked, label, labelComponent, description, multiple } = this.props;
return (
<label className='dialog-option poll__option selectable'>
<input type={multiple ? 'checkbox' : 'radio'} name={name} value={value} checked={checked} onChange={this.handleChange} />
<span
className={classNames('poll__input', { active: checked, checkbox: multiple })}
tabIndex='0'
role='radio'
onKeyPress={this.handleKeyPress}
aria-checked={checked}
aria-label={label}
>{checked && <Check />}</span>
{labelComponent ? labelComponent : (
<span className='poll__option__text'>
<strong>{label}</strong>
{description}
</span>
)}
</label>
);
}
}

@ -1,23 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Toggle from 'react-toggle';
import noop from 'lodash/noop';
import StatusContent from 'flavours/glitch/components/status_content';
import { MediaGallery, Video } from 'flavours/glitch/util/async-components';
import Bundle from 'flavours/glitch/features/ui/components/bundle';
import Avatar from 'flavours/glitch/components/avatar';
import DisplayName from 'flavours/glitch/components/display_name';
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
import Option from './option';
export default class StatusCheckBox extends React.PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
checked: PropTypes.bool,
onToggle: PropTypes.func.isRequired,
disabled: PropTypes.bool,
};
handleStatusesToggle = (value, checked) => {
const { onToggle } = this.props;
onToggle(value, checked);
};
render () {
const { status, checked, onToggle, disabled } = this.props;
const { status, checked } = this.props;
let media = null;
if (status.get('reblog')) {
@ -51,26 +60,45 @@ export default class StatusCheckBox extends React.PureComponent {
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} revealed={false} height={110} onOpenMedia={noop} />}
{Component => (
<Component
media={status.get('media_attachments')}
sensitive={status.get('sensitive')}
revealed={false}
height={110}
onOpenMedia={noop}
/>
)}
</Bundle>
);
}
}
return (
<div className='status-check-box'>
<div className='status-check-box__status'>
<StatusContent
status={status}
media={media}
/>
</div>
const labelComponent = (
<div className='status-check-box__status poll__option__text'>
<div className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'>
<Avatar account={status.get('account')} size={46} />
</div>
<div className='status-check-box-toggle'>
<Toggle checked={checked} onChange={onToggle} disabled={disabled} />
<div><DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} /></div>
</div>
<StatusContent status={status} media={media} />
</div>
);
return (
<Option
name='status_ids'
value={status.get('id')}
checked={checked}
onToggle={this.handleStatusesToggle}
label={status.get('search_index')}
labelComponent={labelComponent}
multiple
/>
);
}
}

@ -1,19 +1,15 @@
import { connect } from 'react-redux';
import StatusCheckBox from '../components/status_check_box';
import { toggleStatusReport } from 'flavours/glitch/actions/reports';
import { Set as ImmutableSet } from 'immutable';
import { makeGetStatus } from 'flavours/glitch/selectors';
const mapStateToProps = (state, { id }) => ({
status: state.getIn(['statuses', id]),
checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id),
});
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapDispatchToProps = (dispatch, { id }) => ({
const mapStateToProps = (state, { id }) => ({
status: getStatus(state, { id }),
});
onToggle (e) {
dispatch(toggleStatusReport(id, e.target.checked));
},
return mapStateToProps;
};
});
export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox);
export default connect(makeMapStateToProps)(StatusCheckBox);

@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import Button from 'flavours/glitch/components/button';
import Option from './components/option';
const mapStateToProps = state => ({
rules: state.get('rules'),
});
export default @connect(mapStateToProps)
class Rules extends React.PureComponent {
static propTypes = {
onNextStep: PropTypes.func.isRequired,
rules: ImmutablePropTypes.list,
selectedRuleIds: ImmutablePropTypes.set.isRequired,
onToggle: PropTypes.func.isRequired,
};
handleNextClick = () => {
const { onNextStep } = this.props;
onNextStep('statuses');
};
handleRulesToggle = (value, checked) => {
const { onToggle } = this.props;
onToggle(value, checked);
};
render () {
const { rules, selectedRuleIds } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' /></h3>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p>
<div>
{rules.map(item => (
<Option
key={item.get('id')}
name='rule_ids'
value={item.get('id')}
checked={selectedRuleIds.includes(item.get('id'))}
onToggle={this.handleRulesToggle}
label={item.get('text')}
multiple
/>
))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleNextClick} disabled={selectedRuleIds.size < 1}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import StatusCheckBox from 'flavours/glitch/features/report/containers/status_check_box_container';
import { OrderedSet } from 'immutable';
import { FormattedMessage } from 'react-intl';
import Button from 'flavours/glitch/components/button';
const mapStateToProps = (state, { accountId }) => ({
availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])),
});
export default @connect(mapStateToProps)
class Statuses extends React.PureComponent {
static propTypes = {
onNextStep: PropTypes.func.isRequired,
accountId: PropTypes.string.isRequired,
availableStatusIds: ImmutablePropTypes.set.isRequired,
selectedStatusIds: ImmutablePropTypes.set.isRequired,
onToggle: PropTypes.func.isRequired,
};
handleNextClick = () => {
const { onNextStep } = this.props;
onNextStep('comment');
};
render () {
const { availableStatusIds, selectedStatusIds, onToggle } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' /></h3>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p>
<div className='report-dialog-modal__statuses'>
{availableStatusIds.union(selectedStatusIds).map(statusId => (
<StatusCheckBox
id={statusId}
key={statusId}
checked={selectedStatusIds.includes(statusId)}
onToggle={onToggle}
/>
))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleNextClick}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,84 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import Button from 'flavours/glitch/components/button';
import { connect } from 'react-redux';
import {
unfollowAccount,
muteAccount,
blockAccount,
} from 'mastodon/actions/accounts';
const mapStateToProps = () => ({});
export default @connect(mapStateToProps)
class Thanks extends React.PureComponent {
static propTypes = {
submitted: PropTypes.bool,
onClose: PropTypes.func.isRequired,
account: ImmutablePropTypes.map.isRequired,
dispatch: PropTypes.func.isRequired,
};
handleCloseClick = () => {
const { onClose } = this.props;
onClose();
};
handleUnfollowClick = () => {
const { dispatch, account, onClose } = this.props;
dispatch(unfollowAccount(account.get('id')));
onClose();
};
handleMuteClick = () => {
const { dispatch, account, onClose } = this.props;
dispatch(muteAccount(account.get('id')));
onClose();
};
handleBlockClick = () => {
const { dispatch, account, onClose } = this.props;
dispatch(blockAccount(account.get('id')));
onClose();
};
render () {
const { account, submitted } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'>{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}</h3>
<p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p>
{account.getIn(['relationship', 'following']) && (
<React.Fragment>
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='report.unfollow' defaultMessage='Unfollow @{name}' values={{ name: account.get('username') }} /></h4>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.unfollow_explanation' defaultMessage='You are following this account. To not see their posts in your home feed anymore, unfollow them.' /></p>
<Button secondary onClick={this.handleUnfollowClick}><FormattedMessage id='account.unfollow' defaultMessage='Unfollow' /></Button>
<hr />
</React.Fragment>
)}
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.mute' defaultMessage='Mute @{name}' values={{ name: account.get('username') }} /></h4>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.mute_explanation' defaultMessage='You will not see their posts. They can still follow you and see your posts and will not know that they are muted.' /></p>
<Button secondary onClick={this.handleMuteClick}>{!account.getIn(['relationship', 'muting']) ? <FormattedMessage id='report.mute' defaultMessage='Mute' /> : <FormattedMessage id='account.muted' defaultMessage='Muted' />}</Button>
<hr />
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.block' defaultMessage='Block @{name}' values={{ name: account.get('username') }} /></h4>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.block_explanation' defaultMessage='You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.' /></p>
<Button secondary onClick={this.handleBlockClick}>{!account.getIn(['relationship', 'blocking']) ? <FormattedMessage id='report.block' defaultMessage='Block' /> : <FormattedMessage id='account.blocked' defaultMessage='Blocked' />}</Button>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleCloseClick}><FormattedMessage id='report.close' defaultMessage='Done' /></Button>
</div>
</React.Fragment>
);
}
}

@ -1,38 +1,32 @@
import React from 'react';
import { connect } from 'react-redux';
import { changeReportComment, changeReportForward, submitReport } from 'flavours/glitch/actions/reports';
import { submitReport } from 'flavours/glitch/actions/reports';
import { expandAccountTimeline } from 'flavours/glitch/actions/timelines';
import { fetchRules } from 'flavours/glitch/actions/rules';
import { fetchRelationships } from 'flavours/glitch/actions/accounts';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { makeGetAccount } from 'flavours/glitch/selectors';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import StatusCheckBox from 'flavours/glitch/features/report/containers/status_check_box_container';
import { OrderedSet } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Button from 'flavours/glitch/components/button';
import Toggle from 'react-toggle';
import IconButton from '../../../components/icon_button';
import IconButton from 'flavours/glitch/components/icon_button';
import Category from 'flavours/glitch/features/report/category';
import Statuses from 'flavours/glitch/features/report/statuses';
import Rules from 'flavours/glitch/features/report/rules';
import Comment from 'flavours/glitch/features/report/comment';
import Thanks from 'flavours/glitch/features/report/thanks';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
submit: { id: 'report.submit', defaultMessage: 'Submit' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = state => {
const accountId = state.getIn(['reports', 'new', 'account_id']);
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: getAccount(state, accountId),
comment: state.getIn(['reports', 'new', 'comment']),
forward: state.getIn(['reports', 'new', 'forward']),
statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
};
};
const mapStateToProps = (state, { accountId }) => ({
account: getAccount(state, accountId),
});
return mapStateToProps;
};
@ -42,92 +36,183 @@ export default @connect(makeMapStateToProps)
class ReportModal extends ImmutablePureComponent {
static propTypes = {
isSubmitting: PropTypes.bool,
account: ImmutablePropTypes.map,
statusIds: ImmutablePropTypes.orderedSet.isRequired,
comment: PropTypes.string.isRequired,
forward: PropTypes.bool,
accountId: PropTypes.string.isRequired,
statusId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
account: ImmutablePropTypes.map.isRequired,
};
handleCommentChange = e => {
this.props.dispatch(changeReportComment(e.target.value));
}
handleForwardChange = e => {
this.props.dispatch(changeReportForward(e.target.checked));
}
state = {
step: 'category',
selectedStatusIds: OrderedSet(this.props.statusId ? [this.props.statusId] : []),
comment: '',
category: null,
selectedRuleIds: OrderedSet(),
forward: true,
isSubmitting: false,
isSubmitted: false,
};
handleSubmit = () => {
this.props.dispatch(submitReport());
}
const { dispatch, accountId } = this.props;
const { selectedStatusIds, comment, category, selectedRuleIds, forward } = this.state;
this.setState({ isSubmitting: true });
dispatch(submitReport({
account_id: accountId,
status_ids: selectedStatusIds.toArray(),
comment,
forward,
category,
rule_ids: selectedRuleIds.toArray(),
}, this.handleSuccess, this.handleFail));
};
handleSuccess = () => {
this.setState({ isSubmitting: false, isSubmitted: true, step: 'thanks' });
};
handleFail = () => {
this.setState({ isSubmitting: false });
};
handleStatusToggle = (statusId, checked) => {
const { selectedStatusIds } = this.state;
if (checked) {
this.setState({ selectedStatusIds: selectedStatusIds.add(statusId) });
} else {
this.setState({ selectedStatusIds: selectedStatusIds.remove(statusId) });
}
};
handleKeyDown = e => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleSubmit();
handleRuleToggle = (ruleId, checked) => {
const { selectedRuleIds } = this.state;
if (checked) {
this.setState({ selectedRuleIds: selectedRuleIds.add(ruleId) });
} else {
this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
}
}
handleChangeCategory = category => {
this.setState({ category });
};
handleChangeComment = comment => {
this.setState({ comment });
};
handleChangeForward = forward => {
this.setState({ forward });
};
handleNextStep = step => {
this.setState({ step });
};
componentDidMount () {
this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { withReplies: true }));
}
const { dispatch, accountId } = this.props;
componentWillReceiveProps (nextProps) {
if (this.props.account !== nextProps.account && nextProps.account) {
this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { withReplies: true }));
}
dispatch(fetchRelationships([accountId]));
dispatch(expandAccountTimeline(accountId, { withReplies: true }));
dispatch(fetchRules());
}
render () {
const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
const {
accountId,
account,
intl,
onClose,
} = this.props;
if (!account) {
return null;
}
const domain = account.get('acct').split('@')[1];
const {
step,
selectedStatusIds,
selectedRuleIds,
comment,
forward,
category,
isSubmitting,
isSubmitted,
} = this.state;
const domain = account.get('acct').split('@')[1];
const isRemote = !!domain;
let stepComponent;
switch(step) {
case 'category':
stepComponent = (
<Category
onNextStep={this.handleNextStep}
startedFrom={this.props.statusId ? 'status' : 'account'}
category={category}
onChangeCategory={this.handleChangeCategory}
/>
);
break;
case 'rules':
stepComponent = (
<Rules
onNextStep={this.handleNextStep}
selectedRuleIds={selectedRuleIds}
onToggle={this.handleRuleToggle}
/>
);
break;
case 'statuses':
stepComponent = (
<Statuses
onNextStep={this.handleNextStep}
accountId={accountId}
selectedStatusIds={selectedStatusIds}
onToggle={this.handleStatusToggle}
/>
);
break;
case 'comment':
stepComponent = (
<Comment
onSubmit={this.handleSubmit}
isSubmitting={isSubmitting}
isRemote={isRemote}
comment={comment}
forward={forward}
domain={domain}
onChangeComment={this.handleChangeComment}
onChangeForward={this.handleChangeForward}
/>
);
break;
case 'thanks':
stepComponent = (
<Thanks
submitted={isSubmitted}
account={account}
onClose={onClose}
/>
);
}
return (
<div className='modal-root__modal report-modal'>
<div className='modal-root__modal report-dialog-modal'>
<div className='report-modal__target'>
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
<FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
</div>
<div className='report-modal__container'>
<div className='report-modal__comment'>
<p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p>
<textarea
className='setting-text light'
placeholder={intl.formatMessage(messages.placeholder)}
value={comment}
onChange={this.handleCommentChange}
onKeyDown={this.handleKeyDown}
disabled={isSubmitting}
autoFocus
/>
{domain && (
<div>
<p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
<div className='setting-toggle'>
<Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
<label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
</div>
</div>
)}
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
</div>
<div className='report-modal__statuses'>
<div>
{statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
</div>
</div>
<div className='report-dialog-modal__container'>
{stepComponent}
</div>
</div>
);

@ -17,7 +17,7 @@ import push_notifications from './push_notifications';
import status_lists from './status_lists';
import mutes from './mutes';
import blocks from './blocks';
import reports from './reports';
import rules from './rules';
import boosts from './boosts';
import contexts from './contexts';
import compose from './compose';
@ -64,7 +64,7 @@ const reducers = {
push_notifications,
mutes,
blocks,
reports,
rules,
boosts,
contexts,
compose,

@ -1,77 +0,0 @@
import {
REPORT_INIT,
REPORT_SUBMIT_REQUEST,
REPORT_SUBMIT_SUCCESS,
REPORT_SUBMIT_FAIL,
REPORT_CANCEL,
REPORT_STATUS_TOGGLE,
REPORT_COMMENT_CHANGE,
REPORT_FORWARD_CHANGE,
} from 'flavours/glitch/actions/reports';
import {
TIMELINE_DELETE,
} from 'flavours/glitch/actions/timelines';
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
const initialState = ImmutableMap({
new: ImmutableMap({
isSubmitting: false,
account_id: null,
status_ids: ImmutableSet(),
comment: '',
forward: false,
}),
});
const deleteStatus = (state, id, references) => {
references.forEach(ref => {
state = deleteStatus(state, ref[0], []);
});
return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.remove(id));
};
export default function reports(state = initialState, action) {
switch(action.type) {
case REPORT_INIT:
return state.withMutations(map => {
map.setIn(['new', 'isSubmitting'], false);
map.setIn(['new', 'account_id'], action.account.get('id'));
if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet());
map.setIn(['new', 'comment'], '');
} else if (action.status) {
map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
}
});
case REPORT_STATUS_TOGGLE:
return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => {
if (action.checked) {
return set.add(action.statusId);
}
return set.remove(action.statusId);
});
case REPORT_COMMENT_CHANGE:
return state.setIn(['new', 'comment'], action.comment);
case REPORT_FORWARD_CHANGE:
return state.setIn(['new', 'forward'], action.forward);
case REPORT_SUBMIT_REQUEST:
return state.setIn(['new', 'isSubmitting'], true);
case REPORT_SUBMIT_FAIL:
return state.setIn(['new', 'isSubmitting'], false);
case REPORT_CANCEL:
case REPORT_SUBMIT_SUCCESS:
return state.withMutations(map => {
map.setIn(['new', 'account_id'], null);
map.setIn(['new', 'status_ids'], ImmutableSet());
map.setIn(['new', 'comment'], '');
map.setIn(['new', 'isSubmitting'], false);
});
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
default:
return state;
}
};

@ -0,0 +1,13 @@
import { RULES_FETCH_SUCCESS } from 'flavours/glitch/actions/rules';
import { List as ImmutableList, fromJS } from 'immutable';
const initialState = ImmutableList();
export default function rules(state = initialState, action) {
switch (action.type) {
case RULES_FETCH_SUCCESS:
return fromJS(action.rules);
default:
return state;
}
}

@ -41,6 +41,7 @@ const initialState = ImmutableMap({
poll: false,
status: false,
update: false,
'admin.sign_up': false,
}),
quickFilter: ImmutableMap({
@ -61,6 +62,7 @@ const initialState = ImmutableMap({
poll: true,
status: true,
update: true,
'admin.sign_up': true,
}),
sounds: ImmutableMap({
@ -72,6 +74,7 @@ const initialState = ImmutableMap({
poll: true,
status: true,
update: true,
'admin.sign_up': true,
}),
}),

@ -652,14 +652,14 @@
& > .primary {
display: inline-block;
margin: 0;
padding: 0 10px;
padding: 7px 10px;
text-align: center;
}
& > .side_arm {
display: inline-block;
margin: 0 2px;
padding: 0;
padding: 7px 0;
width: 36px;
text-align: center;
}

@ -41,16 +41,14 @@
cursor: pointer;
display: inline-block;
font-family: inherit;
font-size: 14px;
font-size: 17px;
font-weight: 500;
height: 36px;
letter-spacing: 0;
line-height: 36px;
line-height: 22px;
overflow: hidden;
padding: 0 16px;
padding: 7px 18px;
position: relative;
text-align: center;
text-transform: uppercase;
text-decoration: none;
text-overflow: ellipsis;
transition: all 100ms ease-in;
@ -82,17 +80,6 @@
cursor: default;
}
&.button-primary,
&.button-alternative,
&.button-secondary,
&.button-alternative-2 {
font-size: 16px;
line-height: 36px;
height: auto;
text-transform: none;
padding: 4px 16px;
}
&.button-alternative {
color: $inverted-text-color;
background: $ui-primary-color;
@ -121,8 +108,7 @@
color: $darker-text-color;
text-transform: none;
background: transparent;
padding: 3px 15px;
border-radius: 4px;
padding: 6px 17px;
border: 1px solid $ui-primary-color;
&:active,

@ -537,6 +537,192 @@
max-width: 700px;
}
.report-dialog-modal {
max-width: 90vw;
width: 480px;
height: 80vh;
background: lighten($ui-secondary-color, 8%);
color: $inverted-text-color;
border-radius: 8px;
overflow: hidden;
position: relative;
flex-direction: column;
display: flex;
&__container {
box-sizing: border-box;
border-top: 1px solid $ui-secondary-color;
padding: 20px;
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: auto;
}
&__title {
font-size: 28px;
line-height: 33px;
font-weight: 700;
margin-bottom: 15px;
@media screen and (max-height: 800px) {
font-size: 22px;
}
}
&__subtitle {
font-size: 17px;
font-weight: 600;
line-height: 22px;
margin-bottom: 4px;
}
&__lead {
font-size: 17px;
line-height: 22px;
color: lighten($inverted-text-color, 16%);
margin-bottom: 30px;
}
&__actions {
margin-top: 30px;
display: flex;
.button {
flex: 1 1 auto;
}
}
&__statuses {
flex-grow: 1;
min-height: 0;
overflow: auto;
}
.status__content a {
color: $highlight-text-color;
}
.status__content,
.status__content p {
color: $inverted-text-color;
}
.dialog-option .poll__input {
border-color: $inverted-text-color;
color: $ui-secondary-color;
display: inline-flex;
align-items: center;
justify-content: center;
svg {
width: 8px;
height: auto;
}
&:active,
&:focus,
&:hover {
border-color: lighten($inverted-text-color, 15%);
border-width: 4px;
}
&.active {
border-color: $inverted-text-color;
background: $inverted-text-color;
}
}
.poll__option.dialog-option {
padding: 15px 0;
flex: 0 0 auto;
border-bottom: 1px solid $ui-secondary-color;
&:last-child {
border-bottom: 0;
}
& > .poll__option__text {
font-size: 13px;
color: lighten($inverted-text-color, 16%);
strong {
font-size: 17px;
font-weight: 500;
line-height: 22px;
color: $inverted-text-color;
display: block;
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
}
}
.flex-spacer {
background: transparent;
}
&__textarea {
display: block;
box-sizing: border-box;
width: 100%;
margin: 0;
color: $inverted-text-color;
background: $simple-background-color;
padding: 10px;
font-family: inherit;
font-size: 17px;
line-height: 22px;
resize: vertical;
border: 0;
outline: 0;
border-radius: 4px;
margin: 20px 0;
&::placeholder {
color: $dark-text-color;
}
&:focus {
outline: 0;
}
}
&__toggle {
display: flex;
align-items: center;
& > span {
font-size: 17px;
font-weight: 500;
margin-left: 10px;
}
}
.button.button-secondary {
border-color: $inverted-text-color;
color: $inverted-text-color;
flex: 0 0 auto;
&:hover,
&:focus,
&:active {
border-color: lighten($inverted-text-color, 15%);
color: lighten($inverted-text-color, 15%);
}
}
hr {
border: 0;
background: transparent;
margin: 15px 0;
}
}
.report-modal__container {
display: flex;
border-top: 1px solid $ui-secondary-color;

@ -517,42 +517,39 @@
justify-content: flex-start;
}
.status-check-box {
border-bottom: 1px solid $ui-secondary-color;
display: flex;
.status-check-box__status {
display: block;
box-sizing: border-box;
width: 100%;
padding: 0 10px;
.status-check-box__status {
margin: 10px 0 10px 10px;
flex: 1;
overflow: hidden;
.detailed-status__display-name {
color: lighten($inverted-text-color, 16%);
.media-gallery {
max-width: 250px;
span {
display: inline;
}
.status__content {
padding: 0;
white-space: normal;
&:hover strong {
text-decoration: none;
}
}
.video-player,
.audio-player {
margin-top: 8px;
max-width: 250px;
}
.media-gallery,
.audio-player,
.video-player {
margin-top: 8px;
max-width: 250px;
}
.media-gallery__item-thumbnail {
cursor: default;
}
.status__content {
padding: 0;
white-space: normal;
}
}
.status-check-box-toggle {
align-items: center;
display: flex;
flex: 0 0 auto;
justify-content: center;
padding: 10px;
.media-gallery__item-thumbnail {
cursor: default;
}
}
.status__prepend {

@ -45,7 +45,7 @@ defineMessages({
});
const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
if (accountIds.length > 0) {
dispatch(fetchRelationships(accountIds));
@ -132,6 +132,7 @@ const excludeTypesFromFilter = filter => {
'poll',
'status',
'update',
'admin.sign_up',
]);
return allTypes.filterNot(item => item === filter).toJS();

@ -1,89 +1,38 @@
import api from '../api';
import { openModal, closeModal } from './modal';
export const REPORT_INIT = 'REPORT_INIT';
export const REPORT_CANCEL = 'REPORT_CANCEL';
import { openModal } from './modal';
export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
export function initReport(account, status) {
return dispatch => {
dispatch({
type: REPORT_INIT,
account,
status,
});
dispatch(openModal('REPORT'));
};
};
export function cancelReport() {
return {
type: REPORT_CANCEL,
};
};
export function toggleStatusReport(statusId, checked) {
return {
type: REPORT_STATUS_TOGGLE,
statusId,
checked,
};
};
export function submitReport() {
return (dispatch, getState) => {
dispatch(submitReportRequest());
api(getState).post('/api/v1/reports', {
account_id: getState().getIn(['reports', 'new', 'account_id']),
status_ids: getState().getIn(['reports', 'new', 'status_ids']),
comment: getState().getIn(['reports', 'new', 'comment']),
forward: getState().getIn(['reports', 'new', 'forward']),
}).then(response => {
dispatch(closeModal());
dispatch(submitReportSuccess(response.data));
}).catch(error => dispatch(submitReportFail(error)));
};
};
export function submitReportRequest() {
return {
type: REPORT_SUBMIT_REQUEST,
};
};
export function submitReportSuccess(report) {
return {
type: REPORT_SUBMIT_SUCCESS,
report,
};
};
export function submitReportFail(error) {
return {
type: REPORT_SUBMIT_FAIL,
error,
};
};
export function changeReportComment(comment) {
return {
type: REPORT_COMMENT_CHANGE,
comment,
};
};
export function changeReportForward(forward) {
return {
type: REPORT_FORWARD_CHANGE,
forward,
};
};
export const initReport = (account, status) => dispatch =>
dispatch(openModal('REPORT', {
accountId: account.get('id'),
statusId: status?.get('id'),
}));
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {
dispatch(submitReportRequest());
api(getState).post('/api/v1/reports', params).then(response => {
dispatch(submitReportSuccess(response.data));
if (onSuccess) onSuccess();
}).catch(error => {
dispatch(submitReportFail(error));
if (onFail) onFail();
});
};
export const submitReportRequest = () => ({
type: REPORT_SUBMIT_REQUEST,
});
export const submitReportSuccess = report => ({
type: REPORT_SUBMIT_SUCCESS,
report,
});
export const submitReportFail = error => ({
type: REPORT_SUBMIT_FAIL,
error,
});

@ -0,0 +1,27 @@
import api from '../api';
export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST';
export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS';
export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL';
export const fetchRules = () => (dispatch, getState) => {
dispatch(fetchRulesRequest());
api(getState)
.get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules)))
.catch(err => dispatch(fetchRulesFail(err)));
};
const fetchRulesRequest = () => ({
type: RULES_FETCH_REQUEST,
});
const fetchRulesSuccess = rules => ({
type: RULES_FETCH_SUCCESS,
rules,
});
const fetchRulesFail = error => ({
type: RULES_FETCH_FAIL,
error,
});

@ -0,0 +1,9 @@
import React from 'react';
const Check = () => (
<svg width='14' height='11' viewBox='0 0 14 11'>
<path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' />
</svg>
);
export default Check;

@ -6,7 +6,7 @@ export const CircularProgress = ({ size, strokeWidth }) => {
const radius = (size - strokeWidth) / 2;
return (
<svg width={size} heigh={size} viewBox={viewBox} className='circular-progress' role='progressbar'>
<svg width={size} height={size} viewBox={viewBox} className='circular-progress' role='progressbar'>
<circle
fill='none'
cx={size / 2}

@ -90,7 +90,7 @@ Object.keys(emojiIndex.emojis).forEach(key => {
let { short_names, search, unified } = emojiMartData.emojis[key];
if (short_names[0] !== key) {
throw new Error('The compresser expects the first short_code to be the ' +
throw new Error('The compressor expects the first short_code to be the ' +
'key. It may need to be rewritten if the emoji change such that this ' +
'is no longer the case.');
}

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle';
import { isStaff } from 'mastodon/initial_state';
export default class ColumnSettings extends React.PureComponent {
@ -155,7 +156,7 @@ export default class ColumnSettings extends React.PureComponent {
</div>
<div role='group' aria-labelledby='notifications-update'>
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
<span id='notifications-update' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
@ -164,6 +165,19 @@ export default class ColumnSettings extends React.PureComponent {
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
</div>
</div>
{isStaff && (
<div role='group' aria-labelledby='notifications-admin-sign-up'>
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'admin.sign_up']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'admin.sign_up']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'admin.sign_up']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'admin.sign_up']} onChange={onChange} label={soundStr} />
</div>
</div>
)}
</div>
);
}

@ -20,6 +20,7 @@ const messages = defineMessages({
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
});
const notificationForScreenReader = (intl, message, timestamp) => {
@ -344,6 +345,28 @@ class Notification extends ImmutablePureComponent {
);
}
renderAdminSignUp (notification, account, link) {
const { intl, unread } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon id='user-plus' fixedWidth />
</div>
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.admin.sign_up' defaultMessage='{name} signed up' values={{ name: link }} />
</span>
</div>
<AccountContainer id={account.get('id')} hidden={this.props.hidden} />
</div>
</HotKeys>
);
}
render () {
const { notification } = this.props;
const account = notification.get('account');
@ -367,6 +390,8 @@ class Notification extends ImmutablePureComponent {
return this.renderUpdate(notification, link);
case 'poll':
return this.renderPoll(notification, account);
case 'admin.sign_up':
return this.renderAdminSignUp(notification, account, link);
}
return null;

@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Button from 'mastodon/components/button';
import Option from './components/option';
const messages = defineMessages({
dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' },
dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' },
violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
other_description: { id: 'report.reasons.other_description', defaultMessage: 'The issue does not fit into other categories' },
status: { id: 'report.category.title_status', defaultMessage: 'post' },
account: { id: 'report.category.title_account', defaultMessage: 'profile' },
});
export default @injectIntl
class Category extends React.PureComponent {
static propTypes = {
onNextStep: PropTypes.func.isRequired,
category: PropTypes.string,
onChangeCategory: PropTypes.func.isRequired,
startedFrom: PropTypes.oneOf(['status', 'account']),
intl: PropTypes.object.isRequired,
};
handleNextClick = () => {
const { onNextStep, category } = this.props;
switch(category) {
case 'dislike':
onNextStep('thanks');
break;
case 'violation':
onNextStep('rules');
break;
default:
onNextStep('statuses');
break;
}
};
handleCategoryToggle = (value, checked) => {
const { onChangeCategory } = this.props;
if (checked) {
onChangeCategory(value);
}
};
render () {
const { category, startedFrom, intl } = this.props;
const options = [
'dislike',
'spam',
'violation',
'other',
];
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} /></h3>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p>
<div>
{options.map(item => (
<Option
key={item}
name='category'
value={item}
checked={category === item}
onToggle={this.handleCategoryToggle}
label={intl.formatMessage(messages[item])}
description={intl.formatMessage(messages[`${item}_description`])}
/>
))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleNextClick} disabled={category === null}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import Button from 'mastodon/components/button';
import Toggle from 'react-toggle';
const messages = defineMessages({
placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' },
});
export default @injectIntl
class Comment extends React.PureComponent {
static propTypes = {
onSubmit: PropTypes.func.isRequired,
comment: PropTypes.string.isRequired,
onChangeComment: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
isSubmitting: PropTypes.bool,
forward: PropTypes.bool,
isRemote: PropTypes.bool,
domain: PropTypes.string,
onChangeForward: PropTypes.func.isRequired,
};
handleClick = () => {
const { onSubmit } = this.props;
onSubmit();
};
handleChange = e => {
const { onChangeComment } = this.props;
onChangeComment(e.target.value);
};
handleKeyDown = e => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleClick();
}
};
handleForwardChange = e => {
const { onChangeForward } = this.props;
onChangeForward(e.target.checked);
};
render () {
const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>
<textarea
className='report-dialog-modal__textarea'
placeholder={intl.formatMessage(messages.placeholder)}
value={comment}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
disabled={isSubmitting}
/>
{isRemote && (
<React.Fragment>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
<label className='report-dialog-modal__toggle'>
<Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
<FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} />
</label>
</React.Fragment>
)}
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleClick}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Check from 'mastodon/components/check';
export default class Option extends React.PureComponent {
static propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
checked: PropTypes.bool,
label: PropTypes.node,
description: PropTypes.node,
onToggle: PropTypes.func,
multiple: PropTypes.bool,
labelComponent: PropTypes.node,
};
handleKeyPress = e => {
const { value, checked, onToggle } = this.props;
if (e.key === 'Enter' || e.key === ' ') {
e.stopPropagation();
e.preventDefault();
onToggle(value, !checked);
}
}
handleChange = e => {
const { value, onToggle } = this.props;
onToggle(value, e.target.checked);
}
render () {
const { name, value, checked, label, labelComponent, description, multiple } = this.props;
return (
<label className='dialog-option poll__option selectable'>
<input type={multiple ? 'checkbox' : 'radio'} name={name} value={value} checked={checked} onChange={this.handleChange} />
<span
className={classNames('poll__input', { active: checked, checkbox: multiple })}
tabIndex='0'
role='radio'
onKeyPress={this.handleKeyPress}
aria-checked={checked}
aria-label={label}
>{checked && <Check />}</span>
{labelComponent ? labelComponent : (
<span className='poll__option__text'>
<strong>{label}</strong>
{description}
</span>
)}
</label>
);
}
}

@ -1,23 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Toggle from 'react-toggle';
import noop from 'lodash/noop';
import StatusContent from '../../../components/status_content';
import { MediaGallery, Video } from '../../ui/util/async-components';
import Bundle from '../../ui/components/bundle';
import StatusContent from 'mastodon/components/status_content';
import { MediaGallery, Video } from 'mastodon/features/ui/util/async-components';
import Bundle from 'mastodon/features/ui/components/bundle';
import Avatar from 'mastodon/components/avatar';
import DisplayName from 'mastodon/components/display_name';
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
import Option from './option';
export default class StatusCheckBox extends React.PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
checked: PropTypes.bool,
onToggle: PropTypes.func.isRequired,
disabled: PropTypes.bool,
};
handleStatusesToggle = (value, checked) => {
const { onToggle } = this.props;
onToggle(value, checked);
};
render () {
const { status, checked, onToggle, disabled } = this.props;
const { status, checked } = this.props;
let media = null;
if (status.get('reblog')) {
@ -50,24 +59,46 @@ export default class StatusCheckBox extends React.PureComponent {
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={noop} />}
{Component => (
<Component
media={status.get('media_attachments')}
sensitive={status.get('sensitive')}
height={110}
onOpenMedia={noop}
/>
)}
</Bundle>
);
}
}
return (
<div className='status-check-box'>
<div className='status-check-box__status'>
<StatusContent status={status} />
{media}
</div>
const labelComponent = (
<div className='status-check-box__status poll__option__text'>
<div className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'>
<Avatar account={status.get('account')} size={46} />
</div>
<div className='status-check-box-toggle'>
<Toggle checked={checked} onChange={onToggle} disabled={disabled} />
<div><DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} /></div>
</div>
<StatusContent status={status} />
{media}
</div>
);
return (
<Option
name='status_ids'
value={status.get('id')}
checked={checked}
onToggle={this.handleStatusesToggle}
label={status.get('search_index')}
labelComponent={labelComponent}
multiple
/>
);
}
}

@ -1,19 +1,15 @@
import { connect } from 'react-redux';
import StatusCheckBox from '../components/status_check_box';
import { toggleStatusReport } from '../../../actions/reports';
import { Set as ImmutableSet } from 'immutable';
import { makeGetStatus } from 'mastodon/selectors';
const mapStateToProps = (state, { id }) => ({
status: state.getIn(['statuses', id]),
checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id),
});
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapDispatchToProps = (dispatch, { id }) => ({
const mapStateToProps = (state, { id }) => ({
status: getStatus(state, { id }),
});
onToggle (e) {
dispatch(toggleStatusReport(id, e.target.checked));
},
return mapStateToProps;
};
});
export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox);
export default connect(makeMapStateToProps)(StatusCheckBox);

@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import Button from 'mastodon/components/button';
import Option from './components/option';
const mapStateToProps = state => ({
rules: state.get('rules'),
});
export default @connect(mapStateToProps)
class Rules extends React.PureComponent {
static propTypes = {
onNextStep: PropTypes.func.isRequired,
rules: ImmutablePropTypes.list,
selectedRuleIds: ImmutablePropTypes.set.isRequired,
onToggle: PropTypes.func.isRequired,
};
handleNextClick = () => {
const { onNextStep } = this.props;
onNextStep('statuses');
};
handleRulesToggle = (value, checked) => {
const { onToggle } = this.props;
onToggle(value, checked);
};
render () {
const { rules, selectedRuleIds } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' /></h3>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p>
<div>
{rules.map(item => (
<Option
key={item.get('id')}
name='rule_ids'
value={item.get('id')}
checked={selectedRuleIds.includes(item.get('id'))}
onToggle={this.handleRulesToggle}
label={item.get('text')}
multiple
/>
))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleNextClick} disabled={selectedRuleIds.size < 1}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container';
import { OrderedSet } from 'immutable';
import { FormattedMessage } from 'react-intl';
import Button from 'mastodon/components/button';
const mapStateToProps = (state, { accountId }) => ({
availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])),
});
export default @connect(mapStateToProps)
class Statuses extends React.PureComponent {
static propTypes = {
onNextStep: PropTypes.func.isRequired,
accountId: PropTypes.string.isRequired,
availableStatusIds: ImmutablePropTypes.set.isRequired,
selectedStatusIds: ImmutablePropTypes.set.isRequired,
onToggle: PropTypes.func.isRequired,
};
handleNextClick = () => {
const { onNextStep } = this.props;
onNextStep('comment');
};
render () {
const { availableStatusIds, selectedStatusIds, onToggle } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' /></h3>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p>
<div className='report-dialog-modal__statuses'>
{availableStatusIds.union(selectedStatusIds).map(statusId => (
<StatusCheckBox
id={statusId}
key={statusId}
checked={selectedStatusIds.includes(statusId)}
onToggle={onToggle}
/>
))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleNextClick}><FormattedMessage id='report.next' defaultMessage='Next' /></Button>
</div>
</React.Fragment>
);
}
}

@ -0,0 +1,84 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import Button from 'mastodon/components/button';
import { connect } from 'react-redux';
import {
unfollowAccount,
muteAccount,
blockAccount,
} from 'mastodon/actions/accounts';
const mapStateToProps = () => ({});
export default @connect(mapStateToProps)
class Thanks extends React.PureComponent {
static propTypes = {
submitted: PropTypes.bool,
onClose: PropTypes.func.isRequired,
account: ImmutablePropTypes.map.isRequired,
dispatch: PropTypes.func.isRequired,
};
handleCloseClick = () => {
const { onClose } = this.props;
onClose();
};
handleUnfollowClick = () => {
const { dispatch, account, onClose } = this.props;
dispatch(unfollowAccount(account.get('id')));
onClose();
};
handleMuteClick = () => {
const { dispatch, account, onClose } = this.props;
dispatch(muteAccount(account.get('id')));
onClose();
};
handleBlockClick = () => {
const { dispatch, account, onClose } = this.props;
dispatch(blockAccount(account.get('id')));
onClose();
};
render () {
const { account, submitted } = this.props;
return (
<React.Fragment>
<h3 className='report-dialog-modal__title'>{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}</h3>
<p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p>
{account.getIn(['relationship', 'following']) && (
<React.Fragment>
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='report.unfollow' defaultMessage='Unfollow @{name}' values={{ name: account.get('username') }} /></h4>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.unfollow_explanation' defaultMessage='You are following this account. To not see their posts in your home feed anymore, unfollow them.' /></p>
<Button secondary onClick={this.handleUnfollowClick}><FormattedMessage id='account.unfollow' defaultMessage='Unfollow' /></Button>
<hr />
</React.Fragment>
)}
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.mute' defaultMessage='Mute @{name}' values={{ name: account.get('username') }} /></h4>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.mute_explanation' defaultMessage='You will not see their posts. They can still follow you and see your posts and will not know that they are muted.' /></p>
<Button secondary onClick={this.handleMuteClick}>{!account.getIn(['relationship', 'muting']) ? <FormattedMessage id='report.mute' defaultMessage='Mute' /> : <FormattedMessage id='account.muted' defaultMessage='Muted' />}</Button>
<hr />
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.block' defaultMessage='Block @{name}' values={{ name: account.get('username') }} /></h4>
<p className='report-dialog-modal__lead'><FormattedMessage id='report.block_explanation' defaultMessage='You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.' /></p>
<Button secondary onClick={this.handleBlockClick}>{!account.getIn(['relationship', 'blocking']) ? <FormattedMessage id='report.block' defaultMessage='Block' /> : <FormattedMessage id='account.blocked' defaultMessage='Blocked' />}</Button>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button onClick={this.handleCloseClick}><FormattedMessage id='report.close' defaultMessage='Done' /></Button>
</div>
</React.Fragment>
);
}
}

@ -1,38 +1,31 @@
import React from 'react';
import { connect } from 'react-redux';
import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports';
import { expandAccountTimeline } from '../../../actions/timelines';
import { submitReport } from 'mastodon/actions/reports';
import { expandAccountTimeline } from 'mastodon/actions/timelines';
import { fetchRules } from 'mastodon/actions/rules';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { makeGetAccount } from '../../../selectors';
import { makeGetAccount } from 'mastodon/selectors';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import StatusCheckBox from '../../report/containers/status_check_box_container';
import { OrderedSet } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Button from '../../../components/button';
import Toggle from 'react-toggle';
import IconButton from '../../../components/icon_button';
import IconButton from 'mastodon/components/icon_button';
import Category from 'mastodon/features/report/category';
import Statuses from 'mastodon/features/report/statuses';
import Rules from 'mastodon/features/report/rules';
import Comment from 'mastodon/features/report/comment';
import Thanks from 'mastodon/features/report/thanks';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
submit: { id: 'report.submit', defaultMessage: 'Submit' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = state => {
const accountId = state.getIn(['reports', 'new', 'account_id']);
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: getAccount(state, accountId),
comment: state.getIn(['reports', 'new', 'comment']),
forward: state.getIn(['reports', 'new', 'forward']),
statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
};
};
const mapStateToProps = (state, { accountId }) => ({
account: getAccount(state, accountId),
});
return mapStateToProps;
};
@ -42,92 +35,182 @@ export default @connect(makeMapStateToProps)
class ReportModal extends ImmutablePureComponent {
static propTypes = {
isSubmitting: PropTypes.bool,
account: ImmutablePropTypes.map,
statusIds: ImmutablePropTypes.orderedSet.isRequired,
comment: PropTypes.string.isRequired,
forward: PropTypes.bool,
accountId: PropTypes.string.isRequired,
statusId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
account: ImmutablePropTypes.map.isRequired,
};
handleCommentChange = e => {
this.props.dispatch(changeReportComment(e.target.value));
}
handleForwardChange = e => {
this.props.dispatch(changeReportForward(e.target.checked));
}
state = {
step: 'category',
selectedStatusIds: OrderedSet(this.props.statusId ? [this.props.statusId] : []),
comment: '',
category: null,
selectedRuleIds: OrderedSet(),
forward: true,
isSubmitting: false,
isSubmitted: false,
};
handleSubmit = () => {
this.props.dispatch(submitReport());
}
const { dispatch, accountId } = this.props;
const { selectedStatusIds, comment, category, selectedRuleIds, forward } = this.state;
this.setState({ isSubmitting: true });
dispatch(submitReport({
account_id: accountId,
status_ids: selectedStatusIds.toArray(),
comment,
forward,
category,
rule_ids: selectedRuleIds.toArray(),
}, this.handleSuccess, this.handleFail));
};
handleSuccess = () => {
this.setState({ isSubmitting: false, isSubmitted: true, step: 'thanks' });
};
handleFail = () => {
this.setState({ isSubmitting: false });
};
handleStatusToggle = (statusId, checked) => {
const { selectedStatusIds } = this.state;
if (checked) {
this.setState({ selectedStatusIds: selectedStatusIds.add(statusId) });
} else {
this.setState({ selectedStatusIds: selectedStatusIds.remove(statusId) });
}
};
handleKeyDown = e => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleSubmit();
handleRuleToggle = (ruleId, checked) => {
const { selectedRuleIds } = this.state;
if (checked) {
this.setState({ selectedRuleIds: selectedRuleIds.add(ruleId) });
} else {
this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
}
}
handleChangeCategory = category => {
this.setState({ category });
};
handleChangeComment = comment => {
this.setState({ comment });
};
handleChangeForward = forward => {
this.setState({ forward });
};
handleNextStep = step => {
this.setState({ step });
};
componentDidMount () {
this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { withReplies: true }));
}
const { dispatch, accountId } = this.props;
componentWillReceiveProps (nextProps) {
if (this.props.account !== nextProps.account && nextProps.account) {
this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { withReplies: true }));
}
dispatch(expandAccountTimeline(accountId, { withReplies: true }));
dispatch(fetchRules());
}
render () {
const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
const {
accountId,
account,
intl,
onClose,
} = this.props;
if (!account) {
return null;
}
const domain = account.get('acct').split('@')[1];
const {
step,
selectedStatusIds,
selectedRuleIds,
comment,
forward,
category,
isSubmitting,
isSubmitted,
} = this.state;
const domain = account.get('acct').split('@')[1];
const isRemote = !!domain;
let stepComponent;
switch(step) {
case 'category':
stepComponent = (
<Category
onNextStep={this.handleNextStep}
startedFrom={this.props.statusId ? 'status' : 'account'}
category={category}
onChangeCategory={this.handleChangeCategory}
/>
);
break;
case 'rules':
stepComponent = (
<Rules
onNextStep={this.handleNextStep}
selectedRuleIds={selectedRuleIds}
onToggle={this.handleRuleToggle}
/>
);
break;
case 'statuses':
stepComponent = (
<Statuses
onNextStep={this.handleNextStep}
accountId={accountId}
selectedStatusIds={selectedStatusIds}
onToggle={this.handleStatusToggle}
/>
);
break;
case 'comment':
stepComponent = (
<Comment
onSubmit={this.handleSubmit}
isSubmitting={isSubmitting}
isRemote={isRemote}
comment={comment}
forward={forward}
domain={domain}
onChangeComment={this.handleChangeComment}
onChangeForward={this.handleChangeForward}
/>
);
break;
case 'thanks':
stepComponent = (
<Thanks
submitted={isSubmitted}
account={account}
onClose={onClose}
/>
);
}
return (
<div className='modal-root__modal report-modal'>
<div className='modal-root__modal report-dialog-modal'>
<div className='report-modal__target'>
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
<FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
</div>
<div className='report-modal__container'>
<div className='report-modal__comment'>
<p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p>
<textarea
className='setting-text light'
placeholder={intl.formatMessage(messages.placeholder)}
value={comment}
onChange={this.handleCommentChange}
onKeyDown={this.handleKeyDown}
disabled={isSubmitting}
autoFocus
/>
{domain && (
<div>
<p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
<div className='setting-toggle'>
<Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
<label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
</div>
</div>
)}
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
</div>
<div className='report-modal__statuses'>
<div>
{statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
</div>
</div>
<div className='report-dialog-modal__container'>
{stepComponent}
</div>
</div>
);

@ -308,7 +308,7 @@
"notification.poll": "Ha finalitzat una enquesta en la que has votat",
"notification.reblog": "{name} ha impulsat el teu estat",
"notification.status": "ha publicat {name}",
"notification.update": "{name} edited a post",
"notification.update": "{name} ha editat una publicació",
"notifications.clear": "Netejar notificacions",
"notifications.clear_confirmation": "Estàs segur que vols esborrar permanentment totes les teves notificacions?",
"notifications.column_settings.alert": "Notificacions d'escriptori",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Nous tuts:",
"notifications.column_settings.unread_notifications.category": "Notificacions no llegides",
"notifications.column_settings.unread_notifications.highlight": "Destaca notificacions no llegides",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Edicions:",
"notifications.filter.all": "Tots",
"notifications.filter.boosts": "Impulsos",
"notifications.filter.favourites": "Favorits",

@ -308,7 +308,7 @@
"notification.poll": "En afstemning, du deltog i, er færdig",
"notification.reblog": "{name} fremhævede dit indlæg",
"notification.status": "{name} har netop udgivet",
"notification.update": "{name} edited a post",
"notification.update": "{name} redigerede et indlæg",
"notifications.clear": "Ryd notifikationer",
"notifications.clear_confirmation": "Er du sikker på, du vil rydde alle dine notifikationer permanent?",
"notifications.column_settings.alert": "Skrivebordsnotifikationer",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Nye indlæg:",
"notifications.column_settings.unread_notifications.category": "Ulæste notifikationer",
"notifications.column_settings.unread_notifications.highlight": "Fremhæv ulæste notifikationer",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Redigeringer:",
"notifications.filter.all": "Alle",
"notifications.filter.boosts": "Fremhævelser",
"notifications.filter.favourites": "Favoritter",

@ -308,7 +308,7 @@
"notification.poll": "Τελείωσε μια από τις ψηφοφορίες που συμμετείχες",
"notification.reblog": "Ο/Η {name} προώθησε την κατάστασή σου",
"notification.status": "Ο/Η {name} μόλις έγραψε κάτι",
"notification.update": "{name} edited a post",
"notification.update": "{name} επεξεργάστηκε μια δημοσίευση",
"notifications.clear": "Καθαρισμός ειδοποιήσεων",
"notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις όλες τις ειδοποιήσεις σου;",
"notifications.column_settings.alert": "Ειδοποιήσεις επιφάνειας εργασίας",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Νέα τουτ:",
"notifications.column_settings.unread_notifications.category": "Μη αναγνωσμένες ειδοποιήσεις",
"notifications.column_settings.unread_notifications.highlight": "Επισήμανση μη αναγνωσμένων ειδοποιήσεων",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Επεξεργασίες:",
"notifications.filter.all": "Όλες",
"notifications.filter.boosts": "Προωθήσεις",
"notifications.filter.favourites": "Αγαπημένα",

@ -308,7 +308,7 @@
"notification.poll": "Finalizó una encuesta en la que votaste",
"notification.reblog": "{name} adhirió a tu mensaje",
"notification.status": "{name} acaba de enviar un mensaje",
"notification.update": "{name} edited a post",
"notification.update": "{name} editó un mensaje",
"notifications.clear": "Limpiar notificaciones",
"notifications.clear_confirmation": "¿Estás seguro que querés limpiar todas tus notificaciones permanentemente?",
"notifications.column_settings.alert": "Notificaciones de escritorio",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Nuevos mensajes:",
"notifications.column_settings.unread_notifications.category": "Notificaciones sin leer",
"notifications.column_settings.unread_notifications.highlight": "Resaltar notificaciones no leídas",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Ediciones:",
"notifications.filter.all": "Todas",
"notifications.filter.boosts": "Adhesiones",
"notifications.filter.favourites": "Favoritos",

@ -308,7 +308,7 @@
"notification.poll": "Una encuesta en la que has votado ha terminado",
"notification.reblog": "{name} ha retooteado tu publicación",
"notification.status": "{name} acaba de publicar",
"notification.update": "{name} edited a post",
"notification.update": "{name} editó una publicación",
"notifications.clear": "Limpiar notificaciones",
"notifications.clear_confirmation": "¿Seguro que quieres limpiar permanentemente todas tus notificaciones?",
"notifications.column_settings.alert": "Notificaciones de escritorio",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Nuevas publicaciones:",
"notifications.column_settings.unread_notifications.category": "Notificaciones sin leer",
"notifications.column_settings.unread_notifications.highlight": "Destacar notificaciones no leídas",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Ediciones:",
"notifications.filter.all": "Todos",
"notifications.filter.boosts": "Retoots",
"notifications.filter.favourites": "Favoritos",

@ -47,8 +47,8 @@
"account.unmute": "Desmututu @{name}",
"account.unmute_notifications": "Desmututu @{name}(r)en jakinarazpenak",
"account_note.placeholder": "Click to add a note",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
"admin.dashboard.daily_retention": "Erabiltzaile atxikitze-tasa izena eman ondorengo eguneko",
"admin.dashboard.monthly_retention": "Erabiltzaile atxikitze-tasa izena eman ondorengo hilabeteko",
"admin.dashboard.retention.average": "Batezbestekoa",
"admin.dashboard.retention.cohort": "Izen emate hilean",
"admin.dashboard.retention.cohort_size": "Erabiltzaile berriak",
@ -105,7 +105,7 @@
"compose_form.poll.switch_to_single": "Aldatu inkesta aukera bakarra onartzeko",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.save_changes": "Save changes",
"compose_form.save_changes": "Gorde aldaketak",
"compose_form.sensitive.hide": "Markatu multimedia hunkigarri gisa",
"compose_form.sensitive.marked": "Multimedia edukia hunkigarri gisa markatu da",
"compose_form.sensitive.unmarked": "Multimedia edukia ez da hunkigarri gisa markatu",
@ -308,7 +308,7 @@
"notification.poll": "Zuk erantzun duzun inkesta bat bukatu da",
"notification.reblog": "{name}(e)k bultzada eman dio zure bidalketari",
"notification.status": "{name} erabiltzaileak bidalketa egin berri du",
"notification.update": "{name} edited a post",
"notification.update": "{name} erabiltzaileak bidalketa bat editatu du",
"notifications.clear": "Garbitu jakinarazpenak",
"notifications.clear_confirmation": "Ziur zure jakinarazpen guztiak behin betirako garbitu nahi dituzula?",
"notifications.column_settings.alert": "Mahaigaineko jakinarazpenak",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Bidalketa berriak:",
"notifications.column_settings.unread_notifications.category": "Irakurri gabeko jakinarazpenak",
"notifications.column_settings.unread_notifications.highlight": "Nabarmendu irakurri gabeko jakinarazpenak",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Edizioak:",
"notifications.filter.all": "Denak",
"notifications.filter.boosts": "Bultzadak",
"notifications.filter.favourites": "Gogokoak",
@ -367,20 +367,20 @@
"regeneration_indicator.label": "Kargatzen…",
"regeneration_indicator.sublabel": "Zure hasiera-jarioa prestatzen ari da!",
"relative_time.days": "{number}e",
"relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
"relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
"relative_time.full.just_now": "just now",
"relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
"relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
"relative_time.full.days": "Duela {number, plural, one {egun #} other {# egun}}",
"relative_time.full.hours": "Duela {number, plural, one {ordu #} other {# ordu}}",
"relative_time.full.just_now": "oraintxe",
"relative_time.full.minutes": "Duela {number, plural, one {minutu #} other {# minutu}}",
"relative_time.full.seconds": "Duela {number, plural, one {segundo #} other {# segundo}}",
"relative_time.hours": "{number}h",
"relative_time.just_now": "orain",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"relative_time.today": "gaur",
"reply_indicator.cancel": "Utzi",
"report.categories.other": "Other",
"report.categories.other": "Bestelakoak",
"report.categories.spam": "Spam",
"report.categories.violation": "Content violates one or more server rules",
"report.categories.violation": "Edukiak zerbitzariko arau bat edo gehiago urratzen ditu",
"report.forward": "Birbidali hona: {target}",
"report.forward_hint": "Kontu hau beste zerbitzari batekoa da. Bidali txostenaren kopia anonimo hara ere?",
"report.hint": "Txostena zure zerbitzariaren moderatzaileei bidaliko zaie. Kontu hau zergatik salatzen duzun behean azaldu dezakezu:",
@ -409,14 +409,14 @@
"status.delete": "Ezabatu",
"status.detailed_status": "Elkarrizketaren ikuspegi xehetsua",
"status.direct": "Mezu zuzena @{name}(r)i",
"status.edit": "Edit",
"status.edited": "Edited {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
"status.edit": "Editatu",
"status.edited": "Editatua {date}",
"status.edited_x_times": "{count, plural, one {behin} other {{count} aldiz}} editatua",
"status.embed": "Txertatu",
"status.favourite": "Gogokoa",
"status.filtered": "Iragazita",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.history.created": "{name} erabiltzaileak sortua {date}",
"status.history.edited": "{name} erabiltzaileak editatua {date}",
"status.load_more": "Kargatu gehiago",
"status.media_hidden": "Multimedia ezkutatua",
"status.mention": "Aipatu @{name}",

@ -308,7 +308,7 @@
"notification.poll": "Kysely, johon osallistuit, on päättynyt",
"notification.reblog": "{name} buustasi julkaisusi",
"notification.status": "{name} julkaisi juuri",
"notification.update": "{name} edited a post",
"notification.update": "{name} muokkasi viestiä",
"notifications.clear": "Tyhjennä ilmoitukset",
"notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?",
"notifications.column_settings.alert": "Työpöytäilmoitukset",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Uudet julkaisut:",
"notifications.column_settings.unread_notifications.category": "Lukemattomat ilmoitukset",
"notifications.column_settings.unread_notifications.highlight": "Korosta lukemattomat ilmoitukset",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Muokkaukset:",
"notifications.filter.all": "Kaikki",
"notifications.filter.boosts": "Buustit",
"notifications.filter.favourites": "Suosikit",

@ -13,7 +13,7 @@
"account.domain_blocked": "Domaine bloqué",
"account.edit_profile": "Modifier le profil",
"account.enable_notifications": "Me notifier quand @{name} publie",
"account.endorse": "Recommander sur le profil",
"account.endorse": "Recommander sur votre profil",
"account.follow": "Suivre",
"account.followers": "Abonnés",
"account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.",
@ -47,7 +47,7 @@
"account.unmute": "Ne plus masquer @{name}",
"account.unmute_notifications": "Ne plus masquer les notifications de @{name}",
"account_note.placeholder": "Cliquez pour ajouter une note",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.daily_retention": "Taux de maintien des utilisateur·rice·s par jour après inscription",
"admin.dashboard.monthly_retention": "Brugerfastholdelsesrate efter måned efter tilmelding",
"admin.dashboard.retention.average": "Moyenne",
"admin.dashboard.retention.cohort": "Mois d'inscription",
@ -308,7 +308,7 @@
"notification.poll": "Un sondage auquel vous avez participé vient de se terminer",
"notification.reblog": "{name} a partagé votre message",
"notification.status": "{name} vient de publier",
"notification.update": "{name} edited a post",
"notification.update": "{name} a modifié un message",
"notifications.clear": "Effacer les notifications",
"notifications.clear_confirmation": "Voulez-vous vraiment effacer toutes vos notifications?",
"notifications.column_settings.alert": "Notifications du navigateur",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Nouveaux messages :",
"notifications.column_settings.unread_notifications.category": "Notifications non lues",
"notifications.column_settings.unread_notifications.highlight": "Surligner les notifications non lues",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Modifications :",
"notifications.filter.all": "Tout",
"notifications.filter.boosts": "Partages",
"notifications.filter.favourites": "Favoris",
@ -415,8 +415,8 @@
"status.embed": "Intégrer",
"status.favourite": "Ajouter aux favoris",
"status.filtered": "Filtré",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.history.created": "créé par {name} {date}",
"status.history.edited": "édité par {name} {date}",
"status.load_more": "Charger plus",
"status.media_hidden": "Média caché",
"status.mention": "Mentionner @{name}",

@ -308,7 +308,7 @@
"notification.poll": "Unha enquisa na que votaches rematou",
"notification.reblog": "{name} compartiu a túa publicación",
"notification.status": "{name} publicou",
"notification.update": "{name} edited a post",
"notification.update": "{name} editou unha publicación",
"notifications.clear": "Limpar notificacións",
"notifications.clear_confirmation": "Tes a certeza de querer limpar de xeito permanente todas as túas notificacións?",
"notifications.column_settings.alert": "Notificacións de escritorio",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Novas publicacións:",
"notifications.column_settings.unread_notifications.category": "Notificacións non lidas",
"notifications.column_settings.unread_notifications.highlight": "Resaltar notificacións non lidas",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Edicións:",
"notifications.filter.all": "Todo",
"notifications.filter.boosts": "Compartidos",
"notifications.filter.favourites": "Favoritos",

@ -308,7 +308,7 @@
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "חצרוצך הודהד על ידי {name}",
"notification.status": "{name} just posted",
"notification.update": "{name} edited a post",
"notification.update": "{name} ערכו פוסט",
"notifications.clear": "הסרת התראות",
"notifications.clear_confirmation": "להסיר את כל ההתראות? בטוח?",
"notifications.column_settings.alert": "התראות לשולחן העבודה",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "New toots:",
"notifications.column_settings.unread_notifications.category": "Unread notifications",
"notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "שינויים:",
"notifications.filter.all": "All",
"notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Favourites",

@ -308,7 +308,7 @@
"notification.poll": "Egy szavazás, melyben részt vettél, véget ért",
"notification.reblog": "{name} megtolta a bejegyzésedet",
"notification.status": "{name} bejegyzést tett közzé",
"notification.update": "{name} edited a post",
"notification.update": "{name} szerkesztett egy bejegyzést",
"notifications.clear": "Értesítések törlése",
"notifications.clear_confirmation": "Biztos, hogy véglegesen törölni akarod az összes értesítésed?",
"notifications.column_settings.alert": "Asztali értesítések",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Új bejegyzések:",
"notifications.column_settings.unread_notifications.category": "Olvasatlan értesítések",
"notifications.column_settings.unread_notifications.highlight": "Olvasatlan értesítések kiemelése",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Szerkesztések:",
"notifications.filter.all": "Mind",
"notifications.filter.boosts": "Megtolások",
"notifications.filter.favourites": "Kedvencnek jelölések",

@ -308,7 +308,7 @@
"notification.poll": "Japat yang Anda ikuti telah berakhir",
"notification.reblog": "{name} mem-boost status anda",
"notification.status": "{name} baru saja memposting",
"notification.update": "{name} edited a post",
"notification.update": "{name} mengedit kiriman",
"notifications.clear": "Hapus notifikasi",
"notifications.clear_confirmation": "Apa anda yakin hendak menghapus semua notifikasi anda?",
"notifications.column_settings.alert": "Notifikasi desktop",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Toot baru:",
"notifications.column_settings.unread_notifications.category": "Notifikasi yang belum dibaca",
"notifications.column_settings.unread_notifications.highlight": "Sorot notifikasi yang belum dibaca",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Edit:",
"notifications.filter.all": "Semua",
"notifications.filter.boosts": "Boost",
"notifications.filter.favourites": "Favorit",

@ -308,7 +308,7 @@
"notification.poll": "Könnun sem þú tókst þátt í er lokið",
"notification.reblog": "{name} endurbirti færsluna þína",
"notification.status": "{name} sendi inn rétt í þessu",
"notification.update": "{name} edited a post",
"notification.update": "{name} breytti færslu",
"notifications.clear": "Hreinsa tilkynningar",
"notifications.clear_confirmation": "Ertu viss um að þú viljir endanlega eyða öllum tilkynningunum þínum?",
"notifications.column_settings.alert": "Tilkynningar á skjáborði",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Nýjar færslur:",
"notifications.column_settings.unread_notifications.category": "Ólesnar tilkynningar",
"notifications.column_settings.unread_notifications.highlight": "Áherslulita ólesnar tilkynningar",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Breytingar:",
"notifications.filter.all": "Allt",
"notifications.filter.boosts": "Endurbirtingar",
"notifications.filter.favourites": "Eftirlæti",

@ -308,7 +308,7 @@
"notification.poll": "Un sondaggio in cui hai votato è terminato",
"notification.reblog": "{name} ha condiviso il tuo post",
"notification.status": "{name} ha appena pubblicato un post",
"notification.update": "{name} edited a post",
"notification.update": "{name} ha modificato un post",
"notifications.clear": "Cancella notifiche",
"notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?",
"notifications.column_settings.alert": "Notifiche desktop",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Nuovi post:",
"notifications.column_settings.unread_notifications.category": "Notifiche non lette",
"notifications.column_settings.unread_notifications.highlight": "Evidenzia notifiche non lette",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Modifiche:",
"notifications.filter.all": "Tutti",
"notifications.filter.boosts": "Condivisioni",
"notifications.filter.favourites": "Apprezzati",

@ -313,7 +313,7 @@
"notification.poll": "アンケートが終了しました",
"notification.reblog": "{name}さんがあなたの投稿をブーストしました",
"notification.status": "{name}さんが投稿しました",
"notification.update": "{name} edited a post",
"notification.update": "{name} が投稿を編集しました",
"notifications.clear": "通知を消去",
"notifications.clear_confirmation": "本当に通知を消去しますか?",
"notifications.column_settings.alert": "デスクトップ通知",

@ -308,7 +308,7 @@
"notification.poll": "Rapirsiyeke ku te deng daye qediya",
"notification.reblog": "{name} şandiya te bilind kir",
"notification.status": "{name} niha şand",
"notification.update": "{name} edited a post",
"notification.update": "{name} şandiyek serrast kir",
"notifications.clear": "Agahdariyan pak bike",
"notifications.clear_confirmation": "Bi rastî tu dixwazî bi awayekî dawî hemû agahdariyên xwe pak bikî?",
"notifications.column_settings.alert": "Agahdariyên sermaseyê",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Şandiyên nû:",
"notifications.column_settings.unread_notifications.category": "Agahdariyên nexwendî",
"notifications.column_settings.unread_notifications.highlight": "Agahiyên nexwendî nîşan bike",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Serrastkirin:",
"notifications.filter.all": "Hemû",
"notifications.filter.boosts": "Bilindkirî",
"notifications.filter.favourites": "Bijarte",

@ -308,7 +308,7 @@
"notification.poll": "당신이 참여 한 투표가 종료되었습니다",
"notification.reblog": "{name} 님이 부스트 했습니다",
"notification.status": "{name} 님이 방금 게시물을 올렸습니다",
"notification.update": "{name} edited a post",
"notification.update": "{name} 님이 게시물을 수정했습니다",
"notifications.clear": "알림 지우기",
"notifications.clear_confirmation": "정말로 알림을 삭제하시겠습니까?",
"notifications.column_settings.alert": "데스크탑 알림",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "새 게시물:",
"notifications.column_settings.unread_notifications.category": "읽지 않은 알림",
"notifications.column_settings.unread_notifications.highlight": "읽지 않은 알림 강조",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "수정내역:",
"notifications.filter.all": "모두",
"notifications.filter.boosts": "부스트",
"notifications.filter.favourites": "즐겨찾기",

@ -308,7 +308,7 @@
"notification.poll": "Aprauja, kurā tu piedalījies, ir pabeigta",
"notification.reblog": "{name} paaugstināja tavu ziņu",
"notification.status": "{name} tikko publicēja",
"notification.update": "{name} edited a post",
"notification.update": "{name} ir rediģējis rakstu",
"notifications.clear": "Notīrīt paziņojumus",
"notifications.clear_confirmation": "Vai tiešām vēlies neatgriezeniski notīrīt visus savus paziņojumus?",
"notifications.column_settings.alert": "Darbvirsmas paziņojumi",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Jaunas ziņas:",
"notifications.column_settings.unread_notifications.category": "Nelasītie paziņojumi",
"notifications.column_settings.unread_notifications.highlight": "Iezīmēt nelasītos paziņojumus",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Labojumi:",
"notifications.filter.all": "Visi",
"notifications.filter.boosts": "Palielinājumi",
"notifications.filter.favourites": "Izlases",

@ -313,7 +313,7 @@
"notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyło się",
"notification.reblog": "{name} podbił(a) Twój wpis",
"notification.status": "{name} właśnie utworzył(a) wpis",
"notification.update": "{name} edited a post",
"notification.update": "{name} edytował post",
"notifications.clear": "Wyczyść powiadomienia",
"notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
"notifications.column_settings.alert": "Powiadomienia na pulpicie",
@ -332,7 +332,7 @@
"notifications.column_settings.status": "Nowe wpisy:",
"notifications.column_settings.unread_notifications.category": "Nieprzeczytane powiadomienia",
"notifications.column_settings.unread_notifications.highlight": "Podświetl nieprzeczytane powiadomienia",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Edycje:",
"notifications.filter.all": "Wszystkie",
"notifications.filter.boosts": "Podbicia",
"notifications.filter.favourites": "Ulubione",

@ -308,7 +308,7 @@
"notification.poll": "Uma votação em que participaste chegou ao fim",
"notification.reblog": "{name} partilhou a tua publicação",
"notification.status": "{name} acabou de publicar",
"notification.update": "{name} edited a post",
"notification.update": "{name} editou uma publicação",
"notifications.clear": "Limpar notificações",
"notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
"notifications.column_settings.alert": "Notificações no ambiente de trabalho",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Novos toots:",
"notifications.column_settings.unread_notifications.category": "Notificações não lidas",
"notifications.column_settings.unread_notifications.highlight": "Destacar notificações não lidas",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Edições:",
"notifications.filter.all": "Todas",
"notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Favoritos",

@ -35,7 +35,7 @@
"account.never_active": "Никогда",
"account.posts": "Посты",
"account.posts_with_replies": "Посты и ответы",
"account.report": "Жалоба №{name}",
"account.report": "Пожаловаться на @{name}",
"account.requested": "Ожидает подтверждения. Нажмите для отмены запроса",
"account.share": "Поделиться профилем @{name}",
"account.show_reblogs": "Показывать продвижения от @{name}",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Новые посты:",
"notifications.column_settings.unread_notifications.category": "Непрочитанные уведомления",
"notifications.column_settings.unread_notifications.highlight": "Выделять непрочитанные уведомления",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Изменения:",
"notifications.filter.all": "Все",
"notifications.filter.boosts": "Продвижения",
"notifications.filter.favourites": "Отметки «избранного»",

@ -308,7 +308,7 @@
"notification.poll": "Ka përfunduar një pyetësor ku keni votuar",
"notification.reblog": "{name} përforcoi mesazhin tuaj",
"notification.status": "{name} sapo postoi",
"notification.update": "{name} edited a post",
"notification.update": "{name} përpunoi një postim",
"notifications.clear": "Spastroji njoftimet",
"notifications.clear_confirmation": "Jeni i sigurt se doni të spastrohen përgjithmonë krejt njoftimet tuaja?",
"notifications.column_settings.alert": "Njoftime desktopi",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Mesazhe të rinj:",
"notifications.column_settings.unread_notifications.category": "Njoftime të palexuara",
"notifications.column_settings.unread_notifications.highlight": "Theksoji njoftimet e palexuara",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Përpunime:",
"notifications.filter.all": "Krejt",
"notifications.filter.boosts": "Përforcime",
"notifications.filter.favourites": "Të parapëlqyer",

@ -308,7 +308,7 @@
"notification.poll": "En omröstning du röstat i har avslutats",
"notification.reblog": "{name} knuffade din status",
"notification.status": "{name} skrev just",
"notification.update": "{name} edited a post",
"notification.update": "{name} redigerade ett inlägg",
"notifications.clear": "Rensa aviseringar",
"notifications.clear_confirmation": "Är du säker på att du vill rensa alla dina aviseringar permanent?",
"notifications.column_settings.alert": "Skrivbordsaviseringar",

@ -308,7 +308,7 @@
"notification.poll": "การสำรวจความคดเหนทณไดลงคะแนนไดนสดแลว",
"notification.reblog": "{name} ไดนโพสตของคณ",
"notification.status": "{name} เพงโพสต",
"notification.update": "{name} edited a post",
"notification.update": "{name} ไดแกไขโพสต",
"notifications.clear": "ลางการแจงเตอน",
"notifications.clear_confirmation": "คณแนใจหรอไมาตองการลางการแจงเตอนทงหมดของคณอยางถาวร?",
"notifications.column_settings.alert": "การแจงเตอนบนเดสกอป",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "โพสตใหม:",
"notifications.column_settings.unread_notifications.category": "การแจงเตอนทงไมไดาน",
"notifications.column_settings.unread_notifications.highlight": "เนนการแจงเตอนทงไมไดาน",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "การแกไข:",
"notifications.filter.all": "ทงหมด",
"notifications.filter.boosts": "การดน",
"notifications.filter.favourites": "รายการโปรด",

@ -308,7 +308,7 @@
"notification.poll": "Oy verdiğiniz bir anket sona erdi",
"notification.reblog": "{name} gönderini teşvik etti",
"notification.status": "{name} az önce gönderdi",
"notification.update": "{name} edited a post",
"notification.update": "{name} bir gönderiyi düzenledi",
"notifications.clear": "Bildirimleri temizle",
"notifications.clear_confirmation": "Tüm bildirimlerinizi kalıcı olarak temizlemek ister misiniz?",
"notifications.column_settings.alert": "Masaüstü bildirimleri",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Yeni gönderiler:",
"notifications.column_settings.unread_notifications.category": "Okunmamış bildirimler",
"notifications.column_settings.unread_notifications.highlight": "Okunmamış bildirimleri öne çıkar",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Düzenlemeler:",
"notifications.filter.all": "Tümü",
"notifications.filter.boosts": "Boostlar",
"notifications.filter.favourites": "Beğeniler",

@ -308,7 +308,7 @@
"notification.poll": "Опитування, у якому ви голосували, закінчилося",
"notification.reblog": "{name} передмухнув(-ла) Ваш допис",
"notification.status": "{name} щойно дописує",
"notification.update": "{name} edited a post",
"notification.update": "{name} змінює допис",
"notifications.clear": "Очистити сповіщення",
"notifications.clear_confirmation": "Ви впевнені, що хочете назавжди видалити всі сповіщеня?",
"notifications.column_settings.alert": "Сповіщення на комп'ютері",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Нові дмухи:",
"notifications.column_settings.unread_notifications.category": "Непрочитані сповіщення",
"notifications.column_settings.unread_notifications.highlight": "Виділити непрочитані сповіщення",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Зміни:",
"notifications.filter.all": "Усі",
"notifications.filter.boosts": "Передмухи",
"notifications.filter.favourites": "Улюблені",

@ -308,7 +308,7 @@
"notification.poll": "Cuộc bình chọn đã kết thúc",
"notification.reblog": "{name} chia sẻ tút của bạn",
"notification.status": "{name} vừa đăng",
"notification.update": "{name} edited a post",
"notification.update": "{name} đã viết lại một tút",
"notifications.clear": "Xóa hết thông báo",
"notifications.clear_confirmation": "Bạn thật sự muốn xóa vĩnh viễn tất cả thông báo của mình?",
"notifications.column_settings.alert": "Thông báo trên máy tính",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "Tút mới:",
"notifications.column_settings.unread_notifications.category": "Thông báo chưa đọc",
"notifications.column_settings.unread_notifications.highlight": "Nổi bật thông báo chưa đọc",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Lượt sửa:",
"notifications.filter.all": "Toàn bộ",
"notifications.filter.boosts": "Chia sẻ",
"notifications.filter.favourites": "Thích",

@ -308,7 +308,7 @@
"notification.poll": "你参与的一个投票已经结束",
"notification.reblog": "{name} 转嘟了你的嘟文",
"notification.status": "{name} 刚刚发嘟",
"notification.update": "{name} edited a post",
"notification.update": "{name} 编辑了嘟文",
"notifications.clear": "清空通知列表",
"notifications.clear_confirmation": "你确定要永久清空通知列表吗?",
"notifications.column_settings.alert": "桌面通知",

@ -308,7 +308,7 @@
"notification.poll": "您曾投過的投票已經結束",
"notification.reblog": "{name} 轉嘟了您的嘟文",
"notification.status": "{name} 剛剛嘟文",
"notification.update": "{name} edited a post",
"notification.update": "{name} 編輯了嘟文",
"notifications.clear": "清除通知",
"notifications.clear_confirmation": "確定要永久清除您的通知嗎?",
"notifications.column_settings.alert": "桌面通知",
@ -327,7 +327,7 @@
"notifications.column_settings.status": "新嘟文:",
"notifications.column_settings.unread_notifications.category": "未讀通知",
"notifications.column_settings.unread_notifications.highlight": "突顯未讀通知",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "編輯:",
"notifications.filter.all": "全部",
"notifications.filter.boosts": "轉嘟",
"notifications.filter.favourites": "最愛",
@ -367,11 +367,11 @@
"regeneration_indicator.label": "載入中…",
"regeneration_indicator.sublabel": "您的主頁時間軸正在準備中!",
"relative_time.days": "{number} 天",
"relative_time.full.days": "{number, plural, one {# 天} other {# 天}} 前",
"relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}} 前",
"relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前",
"relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前",
"relative_time.full.just_now": "剛剛",
"relative_time.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}} 前",
"relative_time.full.seconds": "{number, plural, one {# 秒} other {# 秒}} 前",
"relative_time.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}}前",
"relative_time.full.seconds": "{number, plural, one {# 秒} other {# 秒}}前",
"relative_time.hours": "{number}小時前",
"relative_time.just_now": "剛剛",
"relative_time.minutes": "{number} 分前",
@ -410,7 +410,7 @@
"status.detailed_status": "詳細的對話內容",
"status.direct": "發送私訊給 @{name}",
"status.edit": "編輯",
"status.edited": "已編輯:{date}",
"status.edited": "編輯於 {date}",
"status.edited_x_times": "已編輯 {count, plural, one {{count} 次} other {{count} 次}}",
"status.embed": "內嵌",
"status.favourite": "最愛",

@ -17,7 +17,7 @@ import status_lists from './status_lists';
import mutes from './mutes';
import blocks from './blocks';
import boosts from './boosts';
import reports from './reports';
import rules from './rules';
import contexts from './contexts';
import compose from './compose';
import search from './search';
@ -61,7 +61,7 @@ const reducers = {
mutes,
blocks,
boosts,
reports,
rules,
contexts,
compose,
search,

@ -1,64 +0,0 @@
import {
REPORT_INIT,
REPORT_SUBMIT_REQUEST,
REPORT_SUBMIT_SUCCESS,
REPORT_SUBMIT_FAIL,
REPORT_CANCEL,
REPORT_STATUS_TOGGLE,
REPORT_COMMENT_CHANGE,
REPORT_FORWARD_CHANGE,
} from '../actions/reports';
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
const initialState = ImmutableMap({
new: ImmutableMap({
isSubmitting: false,
account_id: null,
status_ids: ImmutableSet(),
comment: '',
forward: false,
}),
});
export default function reports(state = initialState, action) {
switch(action.type) {
case REPORT_INIT:
return state.withMutations(map => {
map.setIn(['new', 'isSubmitting'], false);
map.setIn(['new', 'account_id'], action.account.get('id'));
if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet());
map.setIn(['new', 'comment'], '');
} else if (action.status) {
map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
}
});
case REPORT_STATUS_TOGGLE:
return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => {
if (action.checked) {
return set.add(action.statusId);
}
return set.remove(action.statusId);
});
case REPORT_COMMENT_CHANGE:
return state.setIn(['new', 'comment'], action.comment);
case REPORT_FORWARD_CHANGE:
return state.setIn(['new', 'forward'], action.forward);
case REPORT_SUBMIT_REQUEST:
return state.setIn(['new', 'isSubmitting'], true);
case REPORT_SUBMIT_FAIL:
return state.setIn(['new', 'isSubmitting'], false);
case REPORT_CANCEL:
case REPORT_SUBMIT_SUCCESS:
return state.withMutations(map => {
map.setIn(['new', 'account_id'], null);
map.setIn(['new', 'status_ids'], ImmutableSet());
map.setIn(['new', 'comment'], '');
map.setIn(['new', 'isSubmitting'], false);
});
default:
return state;
}
};

@ -0,0 +1,13 @@
import { RULES_FETCH_SUCCESS } from 'mastodon/actions/rules';
import { List as ImmutableList, fromJS } from 'immutable';
const initialState = ImmutableList();
export default function rules(state = initialState, action) {
switch (action.type) {
case RULES_FETCH_SUCCESS:
return fromJS(action.rules);
default:
return state;
}
}

@ -37,6 +37,7 @@ const initialState = ImmutableMap({
poll: false,
status: false,
update: false,
'admin.sign_up': false,
}),
quickFilter: ImmutableMap({
@ -57,6 +58,7 @@ const initialState = ImmutableMap({
poll: true,
status: true,
update: true,
'admin.sign_up': true,
}),
sounds: ImmutableMap({
@ -68,6 +70,7 @@ const initialState = ImmutableMap({
poll: true,
status: true,
update: true,
'admin.sign_up': true,
}),
}),

@ -22,6 +22,7 @@ filenames.forEach(filename => {
'notification.poll': full['notification.poll'] || '',
'notification.status': full['notification.status'] || '',
'notification.update': full['notification.update'] || '',
'notification.admin.sign_up': full['notification.admin.sign_up'] || '',
'status.show_more': full['status.show_more'] || '',
'status.reblog': full['status.reblog'] || '',

@ -50,16 +50,14 @@
cursor: pointer;
display: inline-block;
font-family: inherit;
font-size: 14px;
font-size: 17px;
font-weight: 500;
height: 36px;
letter-spacing: 0;
line-height: 36px;
line-height: 22px;
overflow: hidden;
padding: 0 16px;
padding: 7px 18px;
position: relative;
text-align: center;
text-transform: uppercase;
text-decoration: none;
text-overflow: ellipsis;
transition: all 100ms ease-in;
@ -100,17 +98,6 @@
outline: 0 !important;
}
&.button-primary,
&.button-alternative,
&.button-secondary,
&.button-alternative-2 {
font-size: 16px;
line-height: 36px;
height: auto;
text-transform: none;
padding: 4px 16px;
}
&.button-alternative {
color: $inverted-text-color;
background: $ui-primary-color;
@ -135,7 +122,7 @@
&.button-secondary {
color: $darker-text-color;
background: transparent;
padding: 3px 15px;
padding: 6px 17px;
border: 1px solid $ui-primary-color;
&:active,
@ -1114,42 +1101,39 @@
font-size: 15px;
}
.status-check-box {
border-bottom: 1px solid $ui-secondary-color;
display: flex;
.status-check-box__status {
display: block;
box-sizing: border-box;
width: 100%;
padding: 0 10px;
.status-check-box__status {
margin: 10px 0 10px 10px;
flex: 1;
overflow: hidden;
.detailed-status__display-name {
color: lighten($inverted-text-color, 16%);
.media-gallery {
max-width: 250px;
span {
display: inline;
}
.status__content {
padding: 0;
white-space: normal;
&:hover strong {
text-decoration: none;
}
}
.video-player,
.audio-player {
margin-top: 8px;
max-width: 250px;
}
.media-gallery,
.audio-player,
.video-player {
margin-top: 8px;
max-width: 250px;
}
.media-gallery__item-thumbnail {
cursor: default;
}
.status__content {
padding: 0;
white-space: normal;
}
}
.status-check-box-toggle {
align-items: center;
display: flex;
flex: 0 0 auto;
justify-content: center;
padding: 10px;
.media-gallery__item-thumbnail {
cursor: default;
}
}
.status__prepend {
@ -5103,6 +5087,192 @@ a.status-card.compact:hover {
max-width: 700px;
}
.report-dialog-modal {
max-width: 90vw;
width: 480px;
height: 80vh;
background: lighten($ui-secondary-color, 8%);
color: $inverted-text-color;
border-radius: 8px;
overflow: hidden;
position: relative;
flex-direction: column;
display: flex;
&__container {
box-sizing: border-box;
border-top: 1px solid $ui-secondary-color;
padding: 20px;
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: auto;
}
&__title {
font-size: 28px;
line-height: 33px;
font-weight: 700;
margin-bottom: 15px;
@media screen and (max-height: 800px) {
font-size: 22px;
}
}
&__subtitle {
font-size: 17px;
font-weight: 600;
line-height: 22px;
margin-bottom: 4px;
}
&__lead {
font-size: 17px;
line-height: 22px;
color: lighten($inverted-text-color, 16%);
margin-bottom: 30px;
}
&__actions {
margin-top: 30px;
display: flex;
.button {
flex: 1 1 auto;
}
}
&__statuses {
flex-grow: 1;
min-height: 0;
overflow: auto;
}
.status__content a {
color: $highlight-text-color;
}
.status__content,
.status__content p {
color: $inverted-text-color;
}
.dialog-option .poll__input {
border-color: $inverted-text-color;
color: $ui-secondary-color;
display: inline-flex;
align-items: center;
justify-content: center;
svg {
width: 8px;
height: auto;
}
&:active,
&:focus,
&:hover {
border-color: lighten($inverted-text-color, 15%);
border-width: 4px;
}
&.active {
border-color: $inverted-text-color;
background: $inverted-text-color;
}
}
.poll__option.dialog-option {
padding: 15px 0;
flex: 0 0 auto;
border-bottom: 1px solid $ui-secondary-color;
&:last-child {
border-bottom: 0;
}
& > .poll__option__text {
font-size: 13px;
color: lighten($inverted-text-color, 16%);
strong {
font-size: 17px;
font-weight: 500;
line-height: 22px;
color: $inverted-text-color;
display: block;
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
}
}
.flex-spacer {
background: transparent;
}
&__textarea {
display: block;
box-sizing: border-box;
width: 100%;
margin: 0;
color: $inverted-text-color;
background: $simple-background-color;
padding: 10px;
font-family: inherit;
font-size: 17px;
line-height: 22px;
resize: vertical;
border: 0;
outline: 0;
border-radius: 4px;
margin: 20px 0;
&::placeholder {
color: $dark-text-color;
}
&:focus {
outline: 0;
}
}
&__toggle {
display: flex;
align-items: center;
& > span {
font-size: 17px;
font-weight: 500;
margin-left: 10px;
}
}
.button.button-secondary {
border-color: $inverted-text-color;
color: $inverted-text-color;
flex: 0 0 auto;
&:hover,
&:focus,
&:active {
border-color: lighten($inverted-text-color, 15%);
color: lighten($inverted-text-color, 15%);
}
}
hr {
border: 0;
background: transparent;
margin: 15px 0;
}
}
.report-modal__container {
display: flex;
border-top: 1px solid $ui-secondary-color;

@ -549,7 +549,7 @@ class FeedManager
end
else
# A reblog may reach earlier than the original status because of the
# delay of the worker deliverying the original status, the late addition
# delay of the worker delivering the original status, the late addition
# by merging timelines, and other reasons.
# If such a reblog already exists, just do not re-insert it into the feed.
return false unless redis.zscore(reblog_key, status.id).nil?

@ -36,6 +36,7 @@ class Notification < ApplicationRecord
favourite
poll
update
admin.sign_up
).freeze
TARGET_STATUS_INCLUDES_BY_TYPE = {
@ -63,13 +64,10 @@ class Notification < ApplicationRecord
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
scope :browserable, ->(exclude_types = [], account_id = nil) {
types = TYPES - exclude_types.map(&:to_sym)
if account_id.nil?
where(type: types)
else
where(type: types, from_account_id: account_id)
end
scope = all
scope = where(from_account_id: account_id) if account_id.present?
scope = scope.where(type: TYPES - exclude_types.map(&:to_sym)) unless exclude_types.empty?
scope
}
def type
@ -142,6 +140,8 @@ class Notification < ApplicationRecord
self.from_account_id = activity&.account_id
when 'Mention'
self.from_account_id = activity&.status&.account_id
when 'Account'
self.from_account_id = activity&.id
end
end
end

@ -5,6 +5,7 @@ class BootstrapTimelineService < BaseService
@source_account = source_account
autofollow_inviter!
notify_staff!
end
private
@ -14,4 +15,10 @@ class BootstrapTimelineService < BaseService
FollowService.new.call(@source_account, @source_account.user.invite.user.account)
end
def notify_staff!
User.staff.includes(:account).find_each do |user|
NotifyService.new.call(user.account, :'admin.sign_up', @source_account)
end
end
end

@ -2,7 +2,7 @@
class FetchLinkCardService < BaseService
URL_PATTERN = %r{
(#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]}) # $1 preceeding chars
(#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]}) # $1 preceding chars
( # $2 URL
(https?:\/\/) # $3 Protocol (required)
(#{Twitter::TwitterText::Regex[:valid_domain]}) # $4 Domain(s)

@ -22,34 +22,6 @@ class NotifyService < BaseService
FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
end
def blocked_status?
false
end
def blocked_favourite?
false
end
def blocked_follow?
false
end
def blocked_reblog?
false
end
def blocked_follow_request?
false
end
def blocked_poll?
false
end
def blocked_update?
false
end
def following_sender?
return @following_sender if defined?(@following_sender)
@following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
@ -71,7 +43,7 @@ class NotifyService < BaseService
message? && @notification.target_status.direct_visibility?
end
# Returns true if the sender has been mentionned by the recipient up the thread
# Returns true if the sender has been mentioned by the recipient up the thread
def response_to_recipient?
return false if @notification.target_status.in_reply_to_id.nil?
@ -149,15 +121,15 @@ class NotifyService < BaseService
return blocked if message? && from_staff?
blocked ||= domain_blocking? # Skip for domain blocked accounts
blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
blocked ||= domain_blocking?
blocked ||= @recipient.blocking?(@notification.from_account)
blocked ||= @recipient.muting_notifications?(@notification.from_account)
blocked ||= hellbanned? # Hellban
blocked ||= optional_non_follower? # Options
blocked ||= optional_non_following? # Options
blocked ||= optional_non_following_and_direct? # Options
blocked ||= hellbanned?
blocked ||= optional_non_follower?
blocked ||= optional_non_following?
blocked ||= optional_non_following_and_direct?
blocked ||= conversation_muted?
blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters
blocked ||= blocked_mention? if @notification.type == :mention
blocked
end

@ -13,4 +13,4 @@
<%= t('admin_mailer.new_trending_tags.no_approved_tags') %>
<% end %>
<%= raw t('application_mailer.view')%> <%= admin_trends_tags_url(pending_review: '1') %>
<%= raw t('application_mailer.view')%> <%= admin_trends_tags_url(status: 'pending_review') %>

@ -62,6 +62,7 @@ ignore_unused:
- 'themes.*'
- 'statuses.attached.*'
- 'move_handler.carry_{mutes,blocks}_over_text'
- 'notification_mailer.*'
ignore_inconsistent_interpolations:
- '*.one'

@ -2,7 +2,7 @@
# in the Links header per default.
# In our case, that will bloat headers too much and potentially cause
# issues with reverse proxies. Furhermore, we don't need those links,
# issues with reverse proxies. Furthermore, we don't need those links,
# as we already output them as HTML link tags.
Rails.application.config.action_view.preload_links_header = false

@ -3,7 +3,7 @@ lv:
activerecord:
attributes:
poll:
expires_at: Gala termiņš
expires_at: Deadline
options: Izvēles
user:
agreement: Pakalpojuma līgums

@ -55,7 +55,7 @@ th:
subject: 'Mastodon: เปดใชงานการรบรองความถกตองดวยสองปจจยแลว'
title: เปดใชงาน 2FA แล
two_factor_recovery_codes_changed:
explanation: ยกเลกรหสกนกอนหนาและสรางรหสใหมแล
explanation: ยกเลกรหสกนกอนหนและสรางรหสใหมแล
subject: 'Mastodon: สรางรหสกนสองปจจยใหมแลว'
title: เปลยนรหสกน 2FA แล
unlock_instructions:

@ -1176,6 +1176,9 @@ en:
carry_mutes_over_text: This user moved from %{acct}, which you had muted.
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
notification_mailer:
admin:
sign_up:
subject: "%{name} signed up"
digest:
action: View all notifications
body: Here is a brief summary of the messages you missed since your last visit on %{since}

@ -110,6 +110,7 @@ eu:
confirm: Berretsi
confirmed: Berretsita
confirming: Berresten
custom: Pertsonalizatua
delete: Ezabatu datuak
deleted: Ezabatua
demote: Jaitsi mailaz
@ -201,6 +202,7 @@ eu:
silenced: Isilarazita
statuses: Bidalketa
subscribe: Harpidetu
suspend: Kanporatu
suspended: Kanporatuta
suspension_irreversible: Kontu honen datuak behin betiko ezabatu dira. Kontua kanporatzea atzera bota dezakezu, berriz erabilgarri izan dadin, baina datuak ezingo dira berreskuratu.
suspension_reversible_hint_html: Kontu hau kanporatua izan da eta bere datuak %{date}(e)an behin betiko ezabatuko dira. Ordura arte kontua kalterik gabe leheneratu daiteke. Kontuaren datu guztiak oraintxe bertan ezabatu nahi badituzu, jarraian egin dezakezu.
@ -221,6 +223,7 @@ eu:
whitelisted: Zerrenda zurian
action_logs:
action_types:
approve_appeal: Onartu apelazioa
approve_user: Onartu erabiltzailea
assigned_to_self_report: Esleitu salaketa
change_email_user: Aldatu erabiltzailearen e-maila
@ -252,6 +255,7 @@ eu:
enable_user: Gaitu erabiltzailea
memorialize_account: Bihurtu kontua oroigarri
promote_user: Igo erabiltzailea mailaz
reject_appeal: Baztertu apelazioa
reject_user: Baztertu erabiltzailea
remove_avatar_user: Kendu abatarra
reopen_report: Berrireki txostena
@ -270,6 +274,7 @@ eu:
update_domain_block: Eguneratu domeinu-blokeoa
update_status: Eguneratu bidalketa
actions:
approve_appeal_html: "%{name} erabiltzaileak %{target} erabiltzailearen moderazio erabakiaren apelazioa onartu du"
approve_user_html: "%{name} erabiltzaileak %{target} erabiltzailearen izen-ematea onartu du"
assigned_to_self_report_html: "%{name} erabiltzaileak %{target} salaketa bere buruari esleitu dio"
change_email_user_html: "%{name} erabiltzaileak %{target} erabiltzailearen e-posta helbidea aldatu du"
@ -301,6 +306,7 @@ eu:
enable_user_html: "%{name} erabiltzaileak %{target} erabiltzailearen saioa gaitu du"
memorialize_account_html: "%{name} erabiltzaileak %{target} kontua memoriala bihurtu du"
promote_user_html: "%{name} erabiltzaileak %{target} erabiltzailea mailaz igo du"
reject_appeal_html: "%{name} erabiltzaileak %{target} erabiltzailearen moderazio erabakiaren apelazioa baztertu du"
reject_user_html: "%{name} erabiltzaileak %{target} erabiltzailearen izen-ematea baztertu du"
remove_avatar_user_html: "%{name} erabiltzaileak %{target} erabiltzailearen abatarra kendu du"
reopen_report_html: "%{name} erabiltzaileak %{target} txostena berrireki du"
@ -387,6 +393,10 @@ eu:
top_languages: Hizkuntza aktiboenak
top_servers: Zerbitzari aktiboenak
website: Webgunea
disputes:
appeals:
empty: Ez da apelaziorik aurkitu.
title: Apelazioak
domain_allows:
add_new: Sartu domeinua zerrenda zurian
created_msg: Domeinua ongi sartu da zerrenda zurian
@ -424,6 +434,10 @@ eu:
silence: isilarazia
suspend: kanporatua
show:
affected_accounts:
one: Datu-baseko kontu bati eragiten dio
other: Datu-baseko %{count} konturi eragiten die
zero: Ez die eragiten datu-baseko kontuei
retroactive:
silence: Kendu isilarazteko agindua domeinu honetako kontu guztiei
suspend: Kendu kanporatzeko agindua domeinu honetako kontu guztiei
@ -474,6 +488,10 @@ eu:
delivery_error_hint: Banaketa ezin bada %{count} egunean egin, banaezin bezala markatuko da automatikoki.
destroyed_msg: "%{domain} domeinuko datuak berehala ezabatzeko ilaran daude orain."
empty: Ez da domeinurik aurkitu.
known_accounts:
one: Kontu ezagun %{count}
other: "%{count} kontu ezagun"
zero: Kontu ezagunik ez
moderation:
all: Denak
limited: Mugatua
@ -531,41 +549,61 @@ eu:
report_notes:
created_msg: Salaketa oharra ongi sortu da!
destroyed_msg: Salaketa oharra ongi ezabatu da!
today_at: Gaur %{time}(e)tan
reports:
account:
notes:
one: Ohar %{count}
other: "%{count} ohar"
action_log: Auditoria-egunkaria
action_taken_by: Neurrien hartzailea
actions:
other_description_html: Ikusi kontuaren portaera kontrolatzeko eta salatutako kontuarekin komunikazioa pertsonalizatzeko aukera gehiago.
silence_description_html: Profila dagoeneko jarraitzen dutenei edo eskuz bilatzen dutenei bakarrik agertuko zaie, bere irismena asko mugatuz. Beti bota daiteke atzera.
suspend_description_html: Profila eta bere eduki guztiak iritsiezinak bihurtuko dira, ezabatzen den arte. Kontuarekin ezin da interakziorik eduki. Atzera bota daiteke 30 eguneko epean.
actions_description_html: Erabaki txosten hau konpontzeko ze ekintza hartu. Salatutako kontuaren aurka zigor ekintza bat hartzen baduzu, eposta jakinarazpen bat bidaliko zaie, <strong>Spam</strong> kategoria hautatzean ezik.
add_to_report: Gehitu gehiago txostenera
are_you_sure: Ziur zaude?
assign_to_self: Esleitu niri
assigned: Esleitutako moderatzailea
by_target_domain: Jakinarazitako kontuaren domeinua
category: Kategoria
category_description_html: Kontu edo/eta eduki hau salatu izanaren arrazoia salatutako kontuarekiko komunikazioan aipatuko da
comment:
none: Bat ere ez
comment_description_html: 'Informazio gehiago emateko, %{name} idatzi:'
created_at: Salatua
delete_and_resolve: Ezabatu bidalketak
forwarded: Birbidalia
forwarded_to: 'Hona birbidalia: %{domain}'
mark_as_resolved: Markatu konpondutako gisa
mark_as_unresolved: Markatu konpondu gabeko gisa
no_one_assigned: Inor ez
notes:
create: Gehitu oharra
create_and_resolve: Konpondu ohar batekin
create_and_unresolve: Berrireki ohar batekin
delete: Ezabatu
placeholder: Azaldu hartutako neurriak, edo erlazioa duten bestelako berriak...
title: Oharrak
notes_description_html: Ikusi eta idatzi oharrak beste moderatzaileentzat eta zuretzat etorkizunerako
quick_actions_description_html: 'Hartu ekintza azkar bat edo korritu behera salatutako edukia ikusteko:'
reopen: Berrireki salaketa
report: 'Salaketa #%{id}'
reported_account: Salatutako kontua
reported_by: Salatzailea
resolved: Konponduta
resolved_msg: Salaketa ongi konpondu da!
skip_to_actions: Salto ekintzetara
status: Mezua
statuses: Salatutako edukia
statuses_description_html: Salatutako edukia salatutako kontuarekiko komunikazioan aipatuko da
target_origin: Salatutako kontuaren jatorria
title: Salaketak
unassign: Kendu esleipena
unresolved: Konpondu gabea
updated_at: Eguneratua
view_profile: Ikusi profila
rules:
add_new: Gehitu araua
delete: Ezabatu
@ -667,12 +705,21 @@ eu:
destroyed_msg: Guneko igoera ongi ezabatu da!
statuses:
back_to_account: Atzera kontuaren orrira
back_to_report: Atzera txostenaren orrira
batch:
remove_from_report: Kendu txostenetik
report: Salatu
deleted: Ezabatuta
media:
title: Multimedia
no_status_selected: Ez da bidalketarik aldatu ez delako bidalketarik aukeratu
title: Kontuaren bidalketak
with_media: Multimediarekin
strikes:
actions:
delete_statuses: "%{name} erabiltzaileak %{target} erabiltzailearen bidalketak ezabatu ditu"
disable: "%{name} erabiltzailea %{target} erabiltzailearen kontua izoztu du"
none: "%{name} erabiltzaileak abisua bidali dio %{target} erabiltzaileari"
system_checks:
database_schema_check:
message_html: Aplikatu gabeko datu-basearen migrazioak daude. Exekutatu aplikazioak esperotako portaera izan dezan

@ -162,6 +162,11 @@ fi:
not_subscribed: Ei tilaaja
pending: Odottaa tarkistusta
perform_full_suspension: Siirrä kokonaan jäähylle
previous_strikes: Aiemmat varoitukset
previous_strikes_description_html:
one: Tällä tilillä on <strong>yksi</strong> varoitus.
other: Tällä tilillä on <strong>%{count}</strong> varoitusta.
zero: Tämä tili on <strong>hyvässä kunnossa</strong>.
promote: Ylennä
protocol: Protokolla
public: Julkinen
@ -226,6 +231,7 @@ fi:
whitelisted: Sallittu liittämiselle
action_logs:
action_types:
approve_appeal: Hyväksy valitus
approve_user: Hyväksy käyttäjä
assigned_to_self_report: Määritä raportti
change_email_user: Vaihda sähköposti käyttäjälle
@ -257,6 +263,7 @@ fi:
enable_user: Tili otettu käyttöön
memorialize_account: Muuta muistotiliksi
promote_user: Käyttäjä ylennetty
reject_appeal: Hylkää valitus
reject_user: Hylkää käyttäjä
remove_avatar_user: Profiilikuvan poisto
reopen_report: Uudelleenavaa raportti
@ -275,6 +282,7 @@ fi:
update_domain_block: Päivitä verkkotunnuksen esto
update_status: Päivitä viesti
actions:
approve_appeal_html: "%{name} hyväksyi moderointipäätöksen muutoksenhaun lähettäjältä %{target}"
approve_user_html: "%{name} hyväksyi käyttäjän rekisteröitymisen kohteesta %{target}"
assigned_to_self_report_html: "%{name} otti raportin %{target} tehtäväkseen"
change_email_user_html: "%{name} vaihtoi käyttäjän %{target} sähköpostiosoitteen"
@ -306,6 +314,7 @@ fi:
enable_user_html: "%{name} salli kirjautumisen käyttäjälle %{target}"
memorialize_account_html: "%{name} muutti käyttäjän %{target} tilin muistosivuksi"
promote_user_html: "%{name} ylensi käyttäjän %{target}"
reject_appeal_html: "%{name} hylkäsi moderointipäätöksen muutoksenhaun %{target}"
reject_user_html: "%{name} hylkäsi käyttäjän rekisteröitymisen kohteesta %{target}"
remove_avatar_user_html: "%{name} poisti käyttäjän %{target} profiilikuvan"
reopen_report_html: "%{name} avasi uudelleen raportin %{target}"
@ -384,6 +393,18 @@ fi:
media_storage: Median tallennustila
new_users: uudet käyttäjät
opened_reports: raportit avattu
pending_appeals_html:
one: "<strong>%{count}</strong> vireillä oleva valitus"
other: "<strong>%{count}</strong> vireillä olevat valitukset"
pending_reports_html:
one: "<strong>%{count}</strong> odottava raportti"
other: "<strong>%{count}</strong> odottavat raportit"
pending_tags_html:
one: "<strong>%{count}</strong> odottava hashtagi"
other: "<strong>%{count}</strong> odottavat hashtagit"
pending_users_html:
one: "<strong>%{count}</strong> odottava käyttäjä"
other: "<strong>%{count}</strong> odottavat käyttäjät"
resolved_reports: raportit ratkaistu
software: Ohjelmisto
sources: Kirjautumisen lähteet
@ -392,6 +413,10 @@ fi:
top_languages: Aktiiviset kielet
top_servers: Aktiiviset palvelimet
website: Sivusto
disputes:
appeals:
empty: Valituksia ei löytynyt.
title: Valitukset
domain_allows:
add_new: Salli liitto verkkotunnuksella
created_msg: Verkkotunnus on onnistuneesti sallittu federaatiolle
@ -429,6 +454,10 @@ fi:
silence: hiljennetty
suspend: jäähyllä
show:
affected_accounts:
one: Tämä vaikuttaa yhteen tiliin tietokannassa
other: Vaikuttaa %{count} tiliin tietokannassa
zero: Tämä ei vaikuta mihinkään tietokannan tiliin
retroactive:
silence: Peru kaikkien tässä verkkotunnuksessa jo olemassa olevien tilien hiljennys
suspend: Peru kaikkien tässä verkkotunnuksessa jo olemassa olevien tilien jäähy
@ -471,11 +500,18 @@ fi:
unavailable: Ei saatavilla
unavailable_message: Toimitus ei käytettävissä
warning: Varoitus
warning_message:
one: Toimitusvirhe %{count} päivä
other: Toimitushäiriö %{count} päivää
delivery_available: Toimitus on saatavilla
delivery_error_days: Toimitusvirheen päivät
delivery_error_hint: Jos toimitus ei ole mahdollista %{count} päivän aikana, se merkitään automaattisesti toimittamattomaksi.
destroyed_msg: Tiedot %{domain} on nyt jonossa välitöntä poistoa varten.
empty: Verkkotunnuksia ei löytynyt.
known_accounts:
one: "%{count} tunnettu tili"
other: "%{count} tunnettua tiliä"
zero: Ei tunnettua tiliä
moderation:
all: Kaikki
limited: Rajoitettu
@ -535,6 +571,10 @@ fi:
destroyed_msg: Muistiinpano onnistuneesti poistettu raportista!
today_at: Tänään klo %{time}
reports:
account:
notes:
one: "%{count} ilmoitus"
other: "%{count} ilmoitusta"
action_log: Tarkastusloki
action_taken_by: Toimenpiteen tekijä
actions:
@ -901,6 +941,12 @@ fi:
directory: Profiilihakemisto
explanation: Löydä käyttäjiä heidän kiinnostustensa mukaan
explore_mastodon: Tutki %{title}ia
disputes:
strikes:
created_at: Päivätty
recipient: Osoitettu
status: 'Viesti #%{id}'
status_removed: Viesti on jo poistettu järjestelmästä
domain_validator:
invalid_domain: ei ole kelvollinen toimialueen nimi
errors:
@ -1359,6 +1405,7 @@ fi:
formats:
default: "%d.%m.%Y klo %H.%M"
month: "%b %Y"
time: "%H:%M"
two_factor_authentication:
add: Lisää
disable: Poista käytöstä
@ -1385,12 +1432,17 @@ fi:
subject: Ole hyvä ja vahvista sisäänkirjautumisyritys
title: Sisäänkirjautumisyritys
warning:
categories:
spam: Roskaposti
reason: 'Syy:'
subject:
disable: Tilisi %{acct} on jäädytetty
none: Varoitus %{acct}
title:
delete_statuses: Viestit poistettu
disable: Tili jäädytetty
none: Varoitus
sensitive: Media piilotettu
silence: Rajoitettu tili
suspend: Tilin käyttäminen keskeytetty
welcome:

@ -16,11 +16,11 @@ fr:
contact: Contact
contact_missing: Non défini
contact_unavailable: Non disponible
discover_users: Découvrez des utilisateurs
discover_users: Découvrez des utilisateur·rice·s
documentation: Documentation
federation_hint_html: Avec un compte sur %{instance}, vous pourrez suivre les gens sur n’importe quel serveur Mastodon et au-delà.
federation_hint_html: Avec un compte sur %{instance}, vous pourrez suivre des gens sur n’importe quel serveur Mastodon et au-delà.
get_apps: Essayez une application mobile
hosted_on: Serveur Mastodon hébergé par %{domain}
hosted_on: Serveur Mastodon hébergé sur %{domain}
instance_actor_flash: |
Ce compte est un acteur virtuel utilisé pour représenter le serveur lui-même et non un·e utilisateur·rice individuel·le.
Il est utilisé à des fins de fédération et ne doit pas être bloqué à moins que vous ne vouliez bloquer l’instance entière, auquel cas vous devriez utiliser un blocage de domaine.
@ -35,7 +35,7 @@ fr:
one: message
other: messages
status_count_before: Ayant publié
tagline: Suivez vos amis et découvrez-en de nouveaux
tagline: Suivez vos ami·e·s et découvrez-en de nouveaux·elles
terms: Conditions d’utilisation
unavailable_content: Serveurs modérés
unavailable_content_description:
@ -162,6 +162,11 @@ fr:
not_subscribed: Non abonné
pending: En attente d’approbation
perform_full_suspension: Suspendre
previous_strikes: Sanctions précédentes
previous_strikes_description_html:
one: Ce compte a reçu <strong>une</strong> sanction.
other: Ce compte a reçu <strong>%{count}</strong> sanctions.
zero: Ce compte est <strong>en règle</strong>.
promote: Promouvoir
protocol: Protocole
public: Publique
@ -226,6 +231,7 @@ fr:
whitelisted: Sur liste blanche
action_logs:
action_types:
approve_appeal: Approuver l'appel
approve_user: Approuver l’utilisateur
assigned_to_self_report: Affecter le signalement
change_email_user: Modifier le courriel pour
@ -257,6 +263,7 @@ fr:
enable_user: Activer l’utilisateur
memorialize_account: Ériger en mémorial
promote_user: Promouvoir l’utilisateur
reject_appeal: Rejeter l'appel
reject_user: Rejeter l’utilisateur
remove_avatar_user: Supprimer l’avatar
reopen_report: Rouvrir le signalement
@ -275,6 +282,7 @@ fr:
update_domain_block: Mettre à jour le blocage de domaine
update_status: Mettre à jour le message
actions:
approve_appeal_html: "%{name} a approuvé l'appel de la décision de modération émis par %{target}"
approve_user_html: "%{name} a approuvé l’inscription de %{target}"
assigned_to_self_report_html: "%{name} s’est assigné·e le signalement de %{target}"
change_email_user_html: "%{name} a modifié l'adresse de courriel de l'utilisateur·rice %{target}"
@ -306,6 +314,7 @@ fr:
enable_user_html: "%{name} a activé la connexion de l'utilisateur·rice %{target}"
memorialize_account_html: "%{name} a converti le compte de %{target} en un mémorial"
promote_user_html: "%{name} a promu l'utilisateur·rice %{target}"
reject_appeal_html: "%{name} a rejeté l'appel de la décision de modération émis par %{target}"
reject_user_html: "%{name} a rejeté l’inscription de %{target}"
remove_avatar_user_html: "%{name} a supprimé l'avatar de %{target}"
reopen_report_html: "%{name} a rouvert le signalement %{target}"
@ -384,6 +393,9 @@ fr:
media_storage: Stockage des médias
new_users: nouveaux utilisateurs
opened_reports: rapports ouverts
pending_appeals_html:
one: "<strong>%{count}</strong> appel en attente"
other: "<strong>%{count}</strong> appels en attente"
pending_reports_html:
one: "<strong>%{count}</strong> rapport en attente"
other: "<strong>%{count}</strong> rapports en attente"
@ -401,6 +413,10 @@ fr:
top_languages: Langues les plus actives
top_servers: Serveurs les plus actifs
website: Site Web
disputes:
appeals:
empty: Aucun appel trouvé.
title: Appels
domain_allows:
add_new: Mettre le domaine sur liste sur blanche
created_msg: Ce domaine a été ajouté à la liste blanche avec succès
@ -562,9 +578,12 @@ fr:
action_log: Journal d’audit
action_taken_by: Intervention de
actions:
delete_description_html: Les messages signalés seront supprimés et une sanction sera enregistrée pour vous aider à prendre les mesures appropriées en cas d'infractions futures par le même compte.
other_description_html: Voir plus d'options pour contrôler le comportement du compte et personnaliser la communication vers le compte signalé.
resolve_description_html: Aucune mesure ne sera prise contre le compte signalé, aucune sanction ne sera enregistrée et le sigalement sera clôturé.
silence_description_html: Le profil ne sera visible que pour ceux qui le suivent déjà ou le consultent manuellement, ce qui limite considérablement sa portée. Peut toujours être restauré.
suspend_description_html: Le profil et tout son contenu deviendront inaccessibles jusqu'à ce qu'il soit éventuellement supprimé. Interagir avec le compte sera impossible. Réversible dans les 30 jours.
actions_description_html: Décidez des mesures à prendre pour résoudre ce signalement. Si vous prenez des mesures punitives contre le compte signalé, une notification sera envoyée par e-mail, sauf si la catégorie <strong>Spam</strong> est sélectionnée.
add_to_report: Ajouter davantage au rapport
are_you_sure: Voulez-vous vraiment faire ça ?
assign_to_self: Me l’assigner
@ -718,6 +737,16 @@ fr:
no_status_selected: Aucun statut n’a été modifié car aucun n’a été sélectionné
title: Messages du compte
with_media: Avec médias
strikes:
actions:
delete_statuses: "%{name} a supprimé les messages de %{target}"
disable: "%{name} a bloqué le compte de %{target}"
none: "%{name} a envoyé un avertissement à %{target}"
sensitive: "%{name} a marqué le compte de %{target} comme sensible"
silence: "%{name} a limité le compte de %{target}"
suspend: "%{name} a suspendu le compte de %{target}"
appeal_approved: Appel soumis
appeal_pending: Appel en attente
system_checks:
database_schema_check:
message_html: Vous avez des migrations de base de données en attente. Veuillez les exécuter pour vous assurer que l'application se comporte comme prévu
@ -739,6 +768,10 @@ fr:
allow_provider: Autoriser l'éditeur
disallow: Interdire le lien
disallow_provider: Interdire l'éditeur
shared_by_over_week:
one: Partagé par une personne au cours de la semaine dernière
other: Partagé par %{count} personnes au cours de la semaine dernière
zero: Non partagé au cours de la semaine dernière
title: Liens tendances
usage_comparison: Partagé %{today} fois aujourd'hui, comparé à %{yesterday} hier
pending_review: En attente de révision
@ -765,6 +798,10 @@ fr:
trending_rank: 'Tendance #%{rank}'
usable: Peut être utilisé
usage_comparison: Utilisé %{today} fois aujourd'hui, comparé à %{yesterday} hier
used_by_over_week:
one: Utilisé par une personne au cours de la semaine dernière
other: Utilisé par %{count} personnes au cours de la semaine dernière
zero: Non utilisé au cours de la semaine dernière
title: Tendances
warning_presets:
add_new: Ajouter un nouveau
@ -773,6 +810,17 @@ fr:
empty: Vous n'avez pas encore créé de paramètres prédéfinis pour les avertissements.
title: Gérer les avertissements prédéfinis
admin_mailer:
new_appeal:
actions:
delete_statuses: effacer les messages
disable: bloquer le compte
none: un avertissement
sensitive: marquer le compte comme sensible
silence: limiter le compte
suspend: suspendre le compte
body: "%{target} fait appel de la décision de modération émise par %{action_taken_by} le %{date} et qui était : %{type}. Cette personne a écrit :"
next_steps: Vous pouvez approuver l'appel pour annuler la décision de modération, ou l'ignorer.
subject: "%{username} fait appel d'une décision de modération sur %{instance}"
new_pending_account:
body: Les détails du nouveau compte se trouvent ci-dessous. Vous pouvez approuver ou rejeter cette demande.
subject: Nouveau compte à examiner sur %{instance} (%{username})
@ -928,6 +976,31 @@ fr:
directory: Annuaire des profils
explanation: Découvrir des utilisateur·rice·s en fonction de leurs centres d’intérêt
explore_mastodon: Explorer %{title}
disputes:
strikes:
action_taken: Mesure prise
appeal_approved: Cette sanction a été annulée en appel et n'est plus valide
appeal_rejected: L'appel a été rejeté
appeal_submitted_at: Appel soumis le
appealed_msg: Votre demande d'appel a été soumise. Si elle est approuvée, vous en serez informé·e.
appeals:
submit: Faire appel
associated_report: Rapport associé
created_at: En date du
recipient: Adressé à
status: 'Message #%{id}'
status_removed: Message déjà supprimé du système
title: "%{action} du %{date}"
title_actions:
delete_statuses: Suppression de message
disable: Suspension de compte
none: Avertissement
sensitive: Marquage d'un compte comme sensible
silence: Limitation du compte
suspend: Suspension de compte
your_appeal_approved: Votre appel a été approuvé
your_appeal_pending: Vous avez soumis un appel
your_appeal_rejected: Votre appel a été rejeté
domain_validator:
invalid_domain: n’est pas un nom de domaine valide
errors:
@ -1222,6 +1295,9 @@ fr:
reply:
proceed: Confirmer la réponse
prompt: 'Vous souhaitez répondre à ce message :'
reports:
errors:
invalid_rules: ne fait pas référence à des règles valides
scheduled_statuses:
over_daily_limit: Vous avez dépassé la limite de %{limit} messages planifiés par jour
over_total_limit: Vous avez dépassé la limite de %{limit} messages planifiés
@ -1488,6 +1564,15 @@ fr:
recovery_instructions_html: Si vous perdez l’accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour retrouver l’accès à votre compte. <strong>Conservez les codes de récupération en sécurité</strong>. Par exemple, en les imprimant et en les stockant avec vos autres documents importants.
webauthn: Clés de sécurité
user_mailer:
appeal_approved:
action: Aller à votre compte
explanation: L'appel de la sanction contre votre compte mise en place le %{strike_date} que vous avez soumis le %{appeal_date} a été approuvé. Votre compte est de nouveau en règle.
subject: Votre appel du %{date} a été approuvé
title: Appel approuvé
appeal_rejected:
explanation: L'appel de la sanction contre votre compte mise en place le %{strike_date} que vous avez soumis le %{appeal_date} a été rejeté.
subject: Votre appel du %{date} a été rejeté
title: Appel rejeté
backup_ready:
explanation: Vous avez demandé une sauvegarde complète de votre compte Mastodon. Elle est maintenant prête à être téléchargée!
subject: Votre archive est prête à être téléchargée
@ -1499,6 +1584,8 @@ fr:
subject: Veuillez confirmer la tentative de connexion
title: Tentative de connexion
warning:
appeal: Faire appel
appeal_description: Si vous pensez qu'il s'agit d'une erreur, vous pouvez faire appel auprès de l'équipe de %{instance}.
categories:
spam: Indésirable
violation: Le contenu viole les directives de la communauté suivantes

@ -120,6 +120,10 @@ he:
reject_media: חסימת קבצי מדיה
reject_media_hint: מסירה קבצי מדיה השמורים מקומית ומונעת מהורדת קבצים נוספים בעתיד. לא רלוונטי להשעיות
show:
affected_accounts:
one: חשבון אחד במסד נתונים מושפע
other: "%{count} חשבונות במסד נתונים מושפעים"
zero: אף חשבון במסד נתונים מושפע
retroactive:
silence: הסרת השתקה מכל החשבונות על שרת זה
suspend: הסרת השעייה מכל החשבונות על שרת זה
@ -175,6 +179,17 @@ he:
remove_from_report: הסרה מהדיווח
report: דווח
title: ניהול
trends:
links:
shared_by_over_week:
one: שותף ע"י משתמש\ת אחד\ת בשבוע האחרון
other: שותף ע"י %{count} משתמשים בשבוע האחרון
zero: לא שותף בכלל בשבוע האחרון
tags:
used_by_over_week:
one: היה בשימוש משתמש\ת אחד\ת בשבוע האחרון
other: היה בשימוש ע"י %{count} משתמשים בשבוע האחרון
zero: לא היה בשימוש בכלל בשבוע האחרון
application_mailer:
settings: 'שינוי הגדרות דוא"ל: %{link}'
view: 'תצוגה:'

@ -786,6 +786,10 @@ id:
trending_rank: 'Sedang tren #%{rank}'
usable: Dapat digunakan
usage_comparison: Digunakan %{today} kali hari ini, dibandingkan %{yesterday} kemarin
used_by_over_week:
one: Dipakai oleh satu orang lebih dari seminggu lalu
other: Dipakai oleh %{count} orang selama seminggu terakhir
zero: Tidak dipakai siapapun lebih dari seminggu lalu
title: Tren
warning_presets:
add_new: Tambah baru
@ -980,6 +984,12 @@ id:
delete_statuses: Penghapusan kiriman
disable: Pembekuan akun
none: Peringatan
sensitive: Tandai akun sebagai sensitif
silence: Pembatasan akun
suspend: Penangguhan akun
your_appeal_approved: Banding Anda disetujui
your_appeal_pending: Anda telah mengirim banding
your_appeal_rejected: Banding Anda ditolak
domain_validator:
invalid_domain: bukan nama domain yang valid
errors:
@ -1533,6 +1543,15 @@ id:
recovery_instructions_html: Jika anda kehilangan akses pada handphone anda, anda bisa menggunakan kode pemulihan dibawah ini untuk mendapatkan kembali akses pada akun anda. Simpan kode pemulihan anda baik-baik, misalnya dengan mencetaknya atau menyimpannya bersama dokumen penting lainnya.
webauthn: Kunci keamanan
user_mailer:
appeal_approved:
action: Ke akun Anda
explanation: Banding peringatan terhadap akun Anda pada %{strike_date} yang Anda kirim pada %{appeal_date} telah disetujui. Akun Anda akan kembali ditandai sebagai akun bagus.
subject: Banding Anda dari %{date} telah disetujui
title: Banding disetujui
appeal_rejected:
explanation: Banding terhadap akun Anda pada %{strike_date} yang Anda ajukan %{appeal_date} telah ditolak.
subject: Banding Anda dari %{date} telah ditolak
title: Banding ditolak
backup_ready:
explanation: Cadangan penuh akun Mastodon Anda sudah dapat diunduh!
subject: Arsip Anda sudah siap diunduh
@ -1544,6 +1563,8 @@ id:
subject: Harap konfirmasi usaha masuk akun
title: Usaha masuk akun
warning:
appeal: Ajukan banding
appeal_description: Jika Anda yakin ini galat, Anda dapat mengajukan banding ke staf %{instance}.
categories:
spam: Spam
violation: Konten melanggar panduan komunitas berikut

@ -198,6 +198,7 @@ ja:
silenced: サイレンス済み
statuses: 投稿数
subscribe: 購読する
suspend: サスペンド
suspended: 停止済み
suspension_irreversible: このアカウントのデータは削除され元に戻せなくなります。後日アカウントの凍結を解除することはできますがデータは元に戻せません。
suspension_reversible_hint_html: アカウントは停止されており、データは %{date} に完全に削除されます。それまではアカウントを元に戻すことができます。今すぐ完全に削除したい場合は以下から行うことができます。
@ -218,6 +219,7 @@ ja:
whitelisted: 連合許可済み
action_logs:
action_types:
approve_appeal: 抗議を承認
approve_user: ユーザーの承認
assigned_to_self_report: 通報の担当者に設定
change_email_user: ユーザーのメールアドレスを変更
@ -249,6 +251,7 @@ ja:
enable_user: ユーザーを有効化
memorialize_account: 追悼アカウント化
promote_user: ユーザーを昇格
reject_appeal: 抗議を却下
reject_user: ユーザーを拒否
remove_avatar_user: アイコンを削除
reopen_report: 未解決に戻す
@ -267,6 +270,7 @@ ja:
update_domain_block: ドメインブロックを更新
update_status: 投稿を更新
actions:
approve_appeal_html: "%{name} さんが %{target} からの抗議を承認しました"
approve_user_html: "%{target} から登録された %{name} さんを承認しました"
assigned_to_self_report_html: "%{name} さんが通報 %{target} を自身の担当に割り当てました"
change_email_user_html: "%{name} さんが %{target} さんのメールアドレスを変更しました"
@ -298,6 +302,7 @@ ja:
enable_user_html: "%{name} さんが %{target} さんのログインを有効化しました"
memorialize_account_html: "%{name} さんが %{target} さんを追悼アカウントページに登録しました"
promote_user_html: "%{name} さんが %{target} さんを昇格しました"
reject_appeal_html: "%{name} さんが %{target} からの抗議を却下しました"
reject_user_html: "%{target} から登録された %{name} さんを拒否しました"
remove_avatar_user_html: "%{name} さんが %{target} さんのアイコンを削除しました"
reopen_report_html: "%{name} さんが通報 %{target} を未解決に戻しました"
@ -376,6 +381,14 @@ ja:
media_storage: メディアストレージ
new_users: 新規ユーザー
opened_reports: 新規通報
pending_appeals_html:
other: 保留中の抗議 <strong>%{count}</strong> 件
pending_reports_html:
other: 保留中の通報 <strong>%{count}</strong> 件
pending_tags_html:
other: 保留中のハッシュタグ <strong>%{count}</strong> 件
pending_users_html:
other: 保留中のユーザー <strong>%{count}</strong> 件
resolved_reports: 解決済みの通報
software: ソフトウェア
sources: サインアップソース
@ -384,6 +397,10 @@ ja:
top_languages: トップのアクティブ言語
top_servers: トップアクティブサーバー
website: ウェブサイト
disputes:
appeals:
empty: 抗議はありません。
title: 抗議
domain_allows:
add_new: 連合を許可
created_msg: 連合を許可しました
@ -893,6 +910,9 @@ ja:
directory: ディレクトリ
explanation: 関心を軸にユーザーを発見しよう
explore_mastodon: "%{title}を探索"
disputes:
strikes:
appeal: 抗議
domain_validator:
invalid_domain: は無効なドメイン名です
errors:
@ -1300,8 +1320,8 @@ ja:
enabled_hint: 設定した期間を過ぎた投稿は、以下の例外に該当しない限り、自動的に削除されます
exceptions: 例外
explanation: 投稿の削除はサーバーに負荷がかかるため、サーバーが混み合っていないときに時間をかけて行われます。
ignore_favs: お気に入りされた数を無視
ignore_reblogs: ブーストされた数を無視
ignore_favs: 設定しない
ignore_reblogs: 設定しない
interaction_exceptions: インタラクションに基づく例外
interaction_exceptions_explanation: お気に入りやブーストの基準値を一度超えてしまった投稿は、基準値を下回った後であっても、削除される保証はありません。
keep_direct: ダイレクトメッセージを保持
@ -1325,10 +1345,10 @@ ja:
'63113904': 2年
'7889238': 3ヶ月
min_age_label: 投稿を保持する期間
min_favs: これ以上お気に入りに登録された投稿を無視する
min_favs_hint: この数以上、お気に入りに登録された投稿を削除しません。空白にしておくと、お気に入りに登録された数に関わらず投稿を削除します。
min_reblogs: これ以上ブーストされた投稿を無視する
min_reblogs_hint: この数以上、ブーストされた投稿を削除しません。空白にしておくと、ブーストされた数に関わらず投稿を削除します。
min_favs: お気に入りの基準値
min_favs_hint: この数以上、お気に入りされた投稿を削除せずに残します。空白にしておくと、お気に入りに登録された数に関わらず投稿を削除します。
min_reblogs: ブーストの基準値
min_reblogs_hint: この数以上、ブーストされた投稿を削除せずに残します。空白にしておくと、ブーストされた数に関わらず投稿を削除します。
stream_entries:
pinned: 固定された投稿
reblogged: さんがブースト

@ -929,7 +929,12 @@ ru:
explore_mastodon: Изучайте %{title}
disputes:
strikes:
action_taken: Предпринятые меры
appeal_approved: Это замечание было успешно обжаловано и более не действительно
appeals:
submit: Подать обжалование
created_at: Дата
recipient: Адресовано
status: 'Пост #%{id}'
title: "%{action} от %{date}"
your_appeal_approved: Ваша апелляция одобрена
@ -1293,7 +1298,7 @@ ru:
authorized_apps: Приложения
back: Назад в Mastodon
delete: Удаление учётной записи
development: Разработка
development: Разработчикам
edit_profile: Изменить профиль
export: Экспорт данных
featured_tags: Особенные хэштеги
@ -1514,6 +1519,7 @@ ru:
webauthn: Ключи безопасности
user_mailer:
appeal_approved:
action: Перейти к своему профилю
subject: Ваша апелляция от %{date} была одобрена
title: Апелляция одобрена
appeal_rejected:

@ -77,16 +77,35 @@ fi:
ip_block:
comment: Valinnainen. Muista miksi lisäsit tämän säännön.
expires_in: IP-osoitteet ovat rajallinen resurssi, joskus niitä jaetaan ja vaihtavat usein omistajaa. Tästä syystä epämääräisiä IP-lohkoja ei suositella.
ip: Kirjoita IPv4- tai IPv6-osoite. Voit estää kokonaisia alueita käyttämällä CIDR-syntaksia. Varo, että et lukitse itseäsi!
severities:
no_access: Estä pääsy kaikkiin resursseihin
sign_up_requires_approval: Uudet rekisteröitymiset edellyttävät hyväksyntääsi
severity: Valitse, mitä tapahtuu tämän IP-osoitteen pyynnöille
rule:
text: Kuvaile sääntöä tai vaatimusta tämän palvelimen käyttäjille. Yritä pitää se lyhyenä ja yksinkertaisena
sessions:
otp: Syötä puhelimeen saamasi kaksivaiheisen tunnistautumisen koodi tai käytä palautuskoodia.
webauthn: Jos kyseessä on USB-avain, muista laittaa se paikalleen ja tarvittaessa napauttaa sitä.
tag:
name: Voit muuttaa esimerkiksi kirjaimia paremmin luettavaksi
user:
chosen_languages: Kun valittu, vain valituilla kielillä julkaistut viestit näkyvät julkisilla aikajanoilla
labels:
account:
fields:
name: Nimike
value: Sisältö
account_alias:
acct: Vanhan tilin käsittely
account_migration:
acct: Uuden tilin käsittely
account_warning_preset:
text: Esiasetettu teksti
title: Otsikko
admin_account_action:
include_statuses: Sisällytä raportoidut viestit sähköpostiin
send_email_notification: Ilmoita käyttäjälle sähköpostitse
text: Mukautettu varoitus
type: Toimenpide
types:
@ -99,8 +118,11 @@ fi:
announcement:
all_day: Koko päivän kestävä tapahtuma
ends_at: Tapahtuman loppu
scheduled_at: Ajasta julkaisu
starts_at: Tapahtuman alku
text: Ilmoitus
appeal:
text: Perustele, miksi tämä päätös olisi kumottava
defaults:
autofollow: Kutsu seuraamaan tiliäsi
avatar: Profiilikuva
@ -117,6 +139,9 @@ fi:
expires_in: Vanhenee
fields: Profiilin metadata
header: Otsakekuva
honeypot: "%{label} (älä täytä)"
inbox_url: Välityspostilaatikon URL-osoite
irreversible: Pudota piilottamisen sijaan
locale: Kieli
locked: Lukitse tili
max_uses: Käyttökertoja enintään
@ -134,10 +159,12 @@ fi:
setting_default_privacy: Julkaisun näkyvyys
setting_default_sensitive: Merkitse media aina arkaluontoiseksi
setting_delete_modal: Kysy vahvistusta ennen viestin poistamista
setting_disable_swiping: Poista pyyhkäisyt käytöstä
setting_display_media: Median näyttäminen
setting_display_media_default: Oletus
setting_display_media_hide_all: Piilota kaikki
setting_display_media_show_all: Näytä kaikki
setting_expand_spoilers: Laajenna aina sisältövaroituksilla merkityt viestit
setting_hide_network: Piilota verkkosi
setting_noindex: Jättäydy pois hakukoneindeksoinnista
setting_reduce_motion: Vähennä animaatioiden liikettä
@ -146,6 +173,7 @@ fi:
setting_theme: Sivuston teema
setting_trends: Näytä päivän trendit
setting_unfollow_modal: Kysy vahvistusta, ennen kuin lopetat seuraamisen
setting_use_blurhash: Näytä värikkäät liukuvärit piilotetulle medialle
setting_use_pending_items: Hidastila
severity: Vakavuus
sign_in_token_attempt: Turvakoodi
@ -166,12 +194,14 @@ fi:
invite_request:
text: Miksi haluat liittyä?
ip_block:
comment: Kommentti
ip: IP
severities:
no_access: Estä pääsy
sign_up_requires_approval: Rajoita rekisteröitymisiä
severity: Sääntö
notification_emails:
appeal: Joku valittaa valvojan päätöksestä
digest: Lähetä koosteviestejä sähköpostitse
favourite: Lähetä sähköposti, kun joku tykkää tilastasi
follow: Lähetä sähköposti, kun joku seuraa sinua
@ -179,14 +209,21 @@ fi:
mention: Lähetä sähköposti, kun sinut mainitaan
pending_account: Uusi tili tarvitsee tarkastusta
reblog: Lähetä sähköposti, kun joku buustaa julkaisusi
report: Uusi raportti on lähetetty
trending_tag: Uusi trendi vaatii tarkastelua
rule:
text: Sääntö
tag:
listable: Salli tämän hashtagin näkyä hauissa ja ehdotuksissa
name: Aihetunniste
trendable: Salli tämän aihetunnisteen näkyä trendeissä
usable: Salli postauksien käyttää tätä aihetunnistetta
'no': Ei
recommended: Suositeltu
required:
mark: "*"
text: pakollinen tieto
title:
sessions:
webauthn: Käytä jotakin suojausavainta kirjautuaksesi sisään
'yes': Kyllä

@ -27,6 +27,8 @@ fr:
scheduled_at: Laisser vide pour publier l’annonce immédiatement
starts_at: Optionnel. Si votre annonce est liée à une période spécifique
text: Vous pouvez utiliser la syntaxe des messages. Veuillez prendre en compte l’espace que l'annonce prendra sur l’écran de l'utilisateur·rice
appeal:
text: Vous ne pouvez faire appel d'une sanction qu'une seule fois
defaults:
autofollow: Les personnes qui s’inscrivent grâce à l’invitation vous suivront automatiquement
avatar: Au format PNG, GIF ou JPG. %{size} maximum. Sera réduit à %{dimensions}px
@ -119,6 +121,8 @@ fr:
scheduled_at: Planifier la publication
starts_at: Début de l’événement
text: Annonce
appeal:
text: Expliquez pourquoi cette décision devrait être annulée
defaults:
autofollow: Invitation à suivre votre compte
avatar: Image de profil
@ -197,6 +201,7 @@ fr:
sign_up_requires_approval: Limite des inscriptions
severity: Règle
notification_emails:
appeal: Une personne fait appel d'une décision des modérateur·rice·s
digest: Envoyer des courriels récapitulatifs
favourite: Quelqu’un a ajouté mon message à ses favoris
follow: Quelqu’un vient de me suivre
@ -204,6 +209,8 @@ fr:
mention: Quelqu’un me mentionne
pending_account: Nouveau compte en attente d’approbation
reblog: Quelqu’un a partagé mon message
report: Nouveau signalement soumis
trending_tag: Nouvelle tendance nécessitant supervision
rule:
text: Règle
tag:

@ -141,7 +141,7 @@ gl:
header: Cabeceira
honeypot: "%{label} (non completar)"
inbox_url: URL da caixa de entrada do repetidor
irreversible: Soltar en lugar de agochar
irreversible: Desbotar en lugar de agochar
locale: Idioma da interface
locked: Requerir aprobar seguimento
max_uses: Número máximo de usos

@ -27,6 +27,8 @@ id:
scheduled_at: Biarkan kosong jika ingin menerbitkan pengumuman secara langsung
starts_at: Opsional. Persiapan jika pengumuman Anda terikat pada rentang waktu tertentu
text: Anda dapat menggunakan sintaks toot. Mohon perhatikan ruang pengumuman yang mungkin akan memenuhi layar pengguna
appeal:
text: Anda hanya dapat mengajukan banding peringatan sekali
defaults:
autofollow: Orang yang ingin mendaftar lewat undangan, otomatis mengikuti Anda
avatar: PNG, GIF atau JPG. Maksimal %{size}. Ukuran dikecilkan menjadi %{dimensions}px
@ -119,6 +121,8 @@ id:
scheduled_at: Jadwal publikasi
starts_at: Awal mulai event
text: Pengumuman
appeal:
text: Jelaskan mengapa keputusan ini perlu dibatalkan
defaults:
autofollow: Undang untuk mengikuti Anda
avatar: Avatar
@ -197,6 +201,7 @@ id:
sign_up_requires_approval: Batasi pendaftaran
severity: Aturan
notification_emails:
appeal: Seseorang mengajukan banding tehadap keputusan moderator
digest: Kirim email berisi rangkuman
favourite: Kirim email saat seseorang menyukai status anda
follow: Kirim email saat seseorang mengikuti anda
@ -204,6 +209,8 @@ id:
mention: Kirim email saat seseorang menyebut anda
pending_account: Kirim email ketika akun baru perlu ditinjau
reblog: Kirim email saat seseorang mem-boost status anda
report: Laporan baru dikirim
trending_tag: Tren baru harus ditinjau
rule:
text: Aturan
tag:

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

Loading…
Cancel
Save