mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-05 09:21:11 +00:00
Merge branch 'main' into feature/require-mfa-by-admin
This commit is contained in:
commit
1f6f3bf01a
|
@ -1,6 +1,6 @@
|
|||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
||||
# using RuboCop version 1.79.0.
|
||||
# using RuboCop version 1.79.2.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
|
|
|
@ -90,7 +90,7 @@ GEM
|
|||
public_suffix (>= 2.0.2, < 7.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
android_key_attestation (0.3.0)
|
||||
annotaterb (4.17.0)
|
||||
annotaterb (4.18.0)
|
||||
activerecord (>= 6.0.0)
|
||||
activesupport (>= 6.0.0)
|
||||
ast (2.4.3)
|
||||
|
@ -765,7 +765,7 @@ GEM
|
|||
rspec-mocks (~> 3.0)
|
||||
sidekiq (>= 5, < 9)
|
||||
rspec-support (3.13.4)
|
||||
rubocop (1.79.1)
|
||||
rubocop (1.79.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
|
|
|
@ -82,6 +82,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
text: status_params[:status],
|
||||
thread: @thread,
|
||||
quoted_status: @quoted_status,
|
||||
quote_approval_policy: quote_approval_policy,
|
||||
media_ids: status_params[:media_ids],
|
||||
sensitive: status_params[:sensitive],
|
||||
spoiler_text: status_params[:spoiler_text],
|
||||
|
@ -113,7 +114,8 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
sensitive: status_params[:sensitive],
|
||||
language: status_params[:language],
|
||||
spoiler_text: status_params[:spoiler_text],
|
||||
poll: status_params[:poll]
|
||||
poll: status_params[:poll],
|
||||
quote_approval_policy: quote_approval_policy
|
||||
)
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
|
@ -180,6 +182,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
:status,
|
||||
:in_reply_to_id,
|
||||
:quoted_status_id,
|
||||
:quote_approval_policy,
|
||||
:sensitive,
|
||||
:spoiler_text,
|
||||
:visibility,
|
||||
|
@ -202,6 +205,23 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
)
|
||||
end
|
||||
|
||||
def quote_approval_policy
|
||||
# TODO: handle `nil` separately
|
||||
return nil unless Mastodon::Feature.outgoing_quotes_enabled? && status_params[:quote_approval_policy].present?
|
||||
|
||||
case status_params[:quote_approval_policy]
|
||||
when 'public'
|
||||
Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16
|
||||
when 'followers'
|
||||
Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16
|
||||
when 'nobody'
|
||||
0
|
||||
else
|
||||
# TODO: raise more useful message
|
||||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
end
|
||||
|
||||
def serializer_for_status
|
||||
@status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
|
||||
end
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
Images in this folder are based on [Tabler.io icons](https://tabler.io/icons).
|
||||
|
||||
Seems to be 1.5 width icons scaled to 64×64px and centered above a blue square with round corners (24px).
|
||||
|
|
BIN
app/javascript/images/mailer-new/heading/quote.png
Normal file
BIN
app/javascript/images/mailer-new/heading/quote.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,4 +1,8 @@
|
|||
import { apiReblog, apiUnreblog } from 'mastodon/api/interactions';
|
||||
import {
|
||||
apiReblog,
|
||||
apiUnreblog,
|
||||
apiRevokeQuote,
|
||||
} from 'mastodon/api/interactions';
|
||||
import type { StatusVisibility } from 'mastodon/models/status';
|
||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||
|
||||
|
@ -33,3 +37,19 @@ export const unreblog = createDataLoadingThunk(
|
|||
return discardLoadData;
|
||||
},
|
||||
);
|
||||
|
||||
export const revokeQuote = createDataLoadingThunk(
|
||||
'status/revoke_quote',
|
||||
({
|
||||
statusId,
|
||||
quotedStatusId,
|
||||
}: {
|
||||
statusId: string;
|
||||
quotedStatusId: string;
|
||||
}) => apiRevokeQuote(quotedStatusId, statusId),
|
||||
(data, { dispatch, discardLoadData }) => {
|
||||
dispatch(importFetchedStatus(data));
|
||||
|
||||
return discardLoadData;
|
||||
},
|
||||
);
|
||||
|
|
|
@ -31,7 +31,9 @@ import { NOTIFICATIONS_FILTER_SET } from './notifications';
|
|||
import { saveSettings } from './settings';
|
||||
|
||||
function excludeAllTypesExcept(filter: string) {
|
||||
return allNotificationTypes.filter((item) => item !== filter);
|
||||
return allNotificationTypes.filter(
|
||||
(item) => item !== filter && !(item === 'quote' && filter === 'mention'),
|
||||
);
|
||||
}
|
||||
|
||||
function getExcludedTypes(state: RootState) {
|
||||
|
@ -156,7 +158,8 @@ export const processNewNotificationForGroups = createAppAsyncThunk(
|
|||
const showInColumn =
|
||||
activeFilter === 'all'
|
||||
? notificationShows[notification.type] !== false
|
||||
: activeFilter === notification.type;
|
||||
: activeFilter === notification.type ||
|
||||
(activeFilter === 'mention' && notification.type === 'quote');
|
||||
|
||||
if (!showInColumn) return;
|
||||
|
||||
|
|
|
@ -8,3 +8,8 @@ export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
|
|||
|
||||
export const apiUnreblog = (statusId: string) =>
|
||||
apiRequestPost<Status>(`v1/statuses/${statusId}/unreblog`);
|
||||
|
||||
export const apiRevokeQuote = (quotedStatusId: string, statusId: string) =>
|
||||
apiRequestPost<Status>(
|
||||
`v1/statuses/${quotedStatusId}/quotes/${statusId}/revoke`,
|
||||
);
|
||||
|
|
|
@ -67,21 +67,28 @@ const messages = defineMessages({
|
|||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { status }) => ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
});
|
||||
const mapStateToProps = (state, { status }) => {
|
||||
const quotedStatusId = status.getIn(['quote', 'quoted_status']);
|
||||
return ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null,
|
||||
});
|
||||
};
|
||||
|
||||
class StatusActionBar extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
relationship: ImmutablePropTypes.record,
|
||||
quotedAccountId: ImmutablePropTypes.string,
|
||||
onReply: PropTypes.func,
|
||||
onFavourite: PropTypes.func,
|
||||
onReblog: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onRevokeQuote: PropTypes.func,
|
||||
onDirect: PropTypes.func,
|
||||
onMention: PropTypes.func,
|
||||
onMute: PropTypes.func,
|
||||
|
@ -110,6 +117,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
updateOnProps = [
|
||||
'status',
|
||||
'relationship',
|
||||
'quotedAccountId',
|
||||
'withDismiss',
|
||||
];
|
||||
|
||||
|
@ -190,6 +198,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleRevokeQuoteClick = () => {
|
||||
this.props.onRevokeQuote(this.props.status);
|
||||
}
|
||||
|
||||
handleBlockClick = () => {
|
||||
const { status, relationship, onBlock, onUnblock } = this.props;
|
||||
const account = status.get('account');
|
||||
|
@ -241,7 +253,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||
const { status, relationship, quotedAccountId, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||
const { signedIn, permissions } = this.props.identity;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
|
@ -291,6 +303,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
|
||||
if (quotedAccountId === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
||||
}
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
||||
} else {
|
||||
|
|
|
@ -111,6 +111,10 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onRevokeQuote (status) {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }}));
|
||||
},
|
||||
|
||||
onEdit (status) {
|
||||
dispatch((_, getState) => {
|
||||
let state = getState();
|
||||
|
|
|
@ -61,22 +61,29 @@ const messages = defineMessages({
|
|||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { status }) => ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
});
|
||||
const mapStateToProps = (state, { status }) => {
|
||||
const quotedStatusId = status.getIn(['quote', 'quoted_status']);
|
||||
return ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null,
|
||||
});
|
||||
};
|
||||
|
||||
class ActionBar extends PureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
relationship: ImmutablePropTypes.record,
|
||||
quotedAccountId: ImmutablePropTypes.string,
|
||||
onReply: PropTypes.func.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onFavourite: PropTypes.func.isRequired,
|
||||
onBookmark: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onRevokeQuote: PropTypes.func,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onDirect: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
|
@ -113,6 +120,10 @@ class ActionBar extends PureComponent {
|
|||
this.props.onDelete(this.props.status);
|
||||
};
|
||||
|
||||
handleRevokeQuoteClick = () => {
|
||||
this.props.onRevokeQuote(this.props.status);
|
||||
}
|
||||
|
||||
handleRedraftClick = () => {
|
||||
this.props.onDelete(this.props.status, true);
|
||||
};
|
||||
|
@ -193,7 +204,7 @@ class ActionBar extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { status, relationship, intl } = this.props;
|
||||
const { status, relationship, quotedAccountId, intl } = this.props;
|
||||
const { signedIn, permissions } = this.props.identity;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
|
@ -237,6 +248,10 @@ class ActionBar extends PureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push(null);
|
||||
|
||||
if (quotedAccountId === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
||||
}
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
||||
} else {
|
||||
|
|
|
@ -259,6 +259,12 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleRevokeQuoteClick = (status) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }}));
|
||||
};
|
||||
|
||||
handleEditClick = (status) => {
|
||||
const { dispatch, askReplyConfirmation } = this.props;
|
||||
|
||||
|
@ -635,6 +641,7 @@ class Status extends ImmutablePureComponent {
|
|||
onReblog={this.handleReblogClick}
|
||||
onBookmark={this.handleBookmarkClick}
|
||||
onDelete={this.handleDeleteClick}
|
||||
onRevokeQuote={this.handleRevokeQuoteClick}
|
||||
onEdit={this.handleEditClick}
|
||||
onDirect={this.handleDirectClick}
|
||||
onMention={this.handleMentionClick}
|
||||
|
|
|
@ -10,3 +10,4 @@ export { ConfirmClearNotificationsModal } from './clear_notifications';
|
|||
export { ConfirmLogOutModal } from './log_out';
|
||||
export { ConfirmFollowToListModal } from './follow_to_list';
|
||||
export { ConfirmMissingAltTextModal } from './missing_alt_text';
|
||||
export { ConfirmRevokeQuoteModal } from './revoke_quote';
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { revokeQuote } from 'mastodon/actions/interactions_typed';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
revokeQuoteTitle: {
|
||||
id: 'confirmations.revoke_quote.title',
|
||||
defaultMessage: 'Remove post?',
|
||||
},
|
||||
revokeQuoteMessage: {
|
||||
id: 'confirmations.revoke_quote.message',
|
||||
defaultMessage: 'This action cannot be undone.',
|
||||
},
|
||||
revokeQuoteConfirm: {
|
||||
id: 'confirmations.revoke_quote.confirm',
|
||||
defaultMessage: 'Remove post',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmRevokeQuoteModal: React.FC<
|
||||
{
|
||||
statusId: string;
|
||||
quotedStatusId: string;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({ statusId, quotedStatusId, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
void dispatch(revokeQuote({ quotedStatusId, statusId }));
|
||||
}, [dispatch, statusId, quotedStatusId]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.revokeQuoteTitle)}
|
||||
message={intl.formatMessage(messages.revokeQuoteMessage)}
|
||||
confirm={intl.formatMessage(messages.revokeQuoteConfirm)}
|
||||
onConfirm={onConfirm}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -37,6 +37,7 @@ import {
|
|||
ConfirmLogOutModal,
|
||||
ConfirmFollowToListModal,
|
||||
ConfirmMissingAltTextModal,
|
||||
ConfirmRevokeQuoteModal,
|
||||
} from './confirmation_modals';
|
||||
import { ImageModal } from './image_modal';
|
||||
import MediaModal from './media_modal';
|
||||
|
@ -59,6 +60,7 @@ export const MODAL_COMPONENTS = {
|
|||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||
'MUTE': MuteModal,
|
||||
'BLOCK': BlockModal,
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
|
|
|
@ -600,6 +600,7 @@
|
|||
"notification.label.mention": "Zmínka",
|
||||
"notification.label.private_mention": "Soukromá zmínka",
|
||||
"notification.label.private_reply": "Privátní odpověď",
|
||||
"notification.label.quote": "{name} citovali váš příspěvek",
|
||||
"notification.label.reply": "Odpověď",
|
||||
"notification.mention": "Zmínka",
|
||||
"notification.mentioned_you": "{name} vás zmínil",
|
||||
|
@ -657,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Zmínky:",
|
||||
"notifications.column_settings.poll": "Výsledky anket:",
|
||||
"notifications.column_settings.push": "Push oznámení",
|
||||
"notifications.column_settings.quote": "Citace:",
|
||||
"notifications.column_settings.reblog": "Boosty:",
|
||||
"notifications.column_settings.show": "Zobrazit ve sloupci",
|
||||
"notifications.column_settings.sound": "Přehrát zvuk",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Omtaler:",
|
||||
"notifications.column_settings.poll": "Afstemningsresultater:",
|
||||
"notifications.column_settings.push": "Push-notifikationer",
|
||||
"notifications.column_settings.quote": "Citater:",
|
||||
"notifications.column_settings.reblog": "Fremhævelser:",
|
||||
"notifications.column_settings.show": "Vis i kolonne",
|
||||
"notifications.column_settings.sound": "Afspil lyd",
|
||||
|
|
|
@ -600,6 +600,7 @@
|
|||
"notification.label.mention": "Erwähnung",
|
||||
"notification.label.private_mention": "Private Erwähnung",
|
||||
"notification.label.private_reply": "Private Antwort",
|
||||
"notification.label.quote": "{name} zitierte deinen Beitrag",
|
||||
"notification.label.reply": "Antwort",
|
||||
"notification.mention": "Erwähnung",
|
||||
"notification.mentioned_you": "{name} erwähnte dich",
|
||||
|
@ -657,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Erwähnungen:",
|
||||
"notifications.column_settings.poll": "Umfrageergebnisse:",
|
||||
"notifications.column_settings.push": "Push-Benachrichtigungen",
|
||||
"notifications.column_settings.quote": "Zitate:",
|
||||
"notifications.column_settings.reblog": "Geteilte Beiträge:",
|
||||
"notifications.column_settings.show": "In dieser Spalte anzeigen",
|
||||
"notifications.column_settings.sound": "Ton abspielen",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Επισημάνσεις:",
|
||||
"notifications.column_settings.poll": "Αποτελέσματα δημοσκόπησης:",
|
||||
"notifications.column_settings.push": "Ειδοποιήσεις Push",
|
||||
"notifications.column_settings.quote": "Παραθέσεις:",
|
||||
"notifications.column_settings.reblog": "Ενισχύσεις:",
|
||||
"notifications.column_settings.show": "Εμφάνισε σε στήλη",
|
||||
"notifications.column_settings.sound": "Αναπαραγωγή ήχου",
|
||||
|
|
|
@ -245,6 +245,9 @@
|
|||
"confirmations.remove_from_followers.confirm": "Remove follower",
|
||||
"confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?",
|
||||
"confirmations.remove_from_followers.title": "Remove follower?",
|
||||
"confirmations.revoke_quote.confirm": "Remove post",
|
||||
"confirmations.revoke_quote.message": "This action cannot be undone.",
|
||||
"confirmations.revoke_quote.title": "Remove post?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||
"confirmations.unfollow.title": "Unfollow user?",
|
||||
|
@ -896,6 +899,7 @@
|
|||
"status.reply": "Reply",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.report": "Report @{name}",
|
||||
"status.revoke_quote": "Remove my post from @{name}’s post",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less_all": "Show less for all",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Menciones:",
|
||||
"notifications.column_settings.poll": "Resultados de la encuesta:",
|
||||
"notifications.column_settings.push": "Notificaciones push",
|
||||
"notifications.column_settings.quote": "Citas:",
|
||||
"notifications.column_settings.reblog": "Adhesiones:",
|
||||
"notifications.column_settings.show": "Mostrar en columna",
|
||||
"notifications.column_settings.sound": "Reproducir sonido",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Menciones:",
|
||||
"notifications.column_settings.poll": "Resultados de la votación:",
|
||||
"notifications.column_settings.push": "Notificaciones push",
|
||||
"notifications.column_settings.quote": "Citas:",
|
||||
"notifications.column_settings.reblog": "Impulsos:",
|
||||
"notifications.column_settings.show": "Mostrar en columna",
|
||||
"notifications.column_settings.sound": "Reproducir sonido",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Menciones:",
|
||||
"notifications.column_settings.poll": "Resultados de la votación:",
|
||||
"notifications.column_settings.push": "Notificaciones push",
|
||||
"notifications.column_settings.quote": "Citas:",
|
||||
"notifications.column_settings.reblog": "Impulsos:",
|
||||
"notifications.column_settings.show": "Mostrar en columna",
|
||||
"notifications.column_settings.sound": "Reproducir sonido",
|
||||
|
|
|
@ -600,6 +600,7 @@
|
|||
"notification.label.mention": "Maininta",
|
||||
"notification.label.private_mention": "Yksityismaininta",
|
||||
"notification.label.private_reply": "Yksityinen vastaus",
|
||||
"notification.label.quote": "{name} lainasi julkaisuasi",
|
||||
"notification.label.reply": "Vastaus",
|
||||
"notification.mention": "Maininta",
|
||||
"notification.mentioned_you": "{name} mainitsi sinut",
|
||||
|
@ -657,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Maininnat:",
|
||||
"notifications.column_settings.poll": "Äänestystulokset:",
|
||||
"notifications.column_settings.push": "Puskuilmoitukset",
|
||||
"notifications.column_settings.quote": "Lainaukset:",
|
||||
"notifications.column_settings.reblog": "Tehostukset:",
|
||||
"notifications.column_settings.show": "Näytä sarakkeessa",
|
||||
"notifications.column_settings.sound": "Äänimerkki",
|
||||
|
|
|
@ -600,6 +600,7 @@
|
|||
"notification.label.mention": "Mención",
|
||||
"notification.label.private_mention": "Mención privada",
|
||||
"notification.label.private_reply": "Resposta privada",
|
||||
"notification.label.quote": "{name} citou a túa publicación",
|
||||
"notification.label.reply": "Resposta",
|
||||
"notification.mention": "Mención",
|
||||
"notification.mentioned_you": "{name} mencionoute",
|
||||
|
|
|
@ -600,6 +600,7 @@
|
|||
"notification.label.mention": "אזכור",
|
||||
"notification.label.private_mention": "אזכור פרטי",
|
||||
"notification.label.private_reply": "תשובה בפרטי",
|
||||
"notification.label.quote": "{name} ציטט.ה את הודעתך",
|
||||
"notification.label.reply": "תשובה",
|
||||
"notification.mention": "אזכור",
|
||||
"notification.mentioned_you": "אוזכרת על ידי {name}",
|
||||
|
|
|
@ -600,6 +600,7 @@
|
|||
"notification.label.mention": "Minnst á",
|
||||
"notification.label.private_mention": "Einkaspjall",
|
||||
"notification.label.private_reply": "Einkasvar",
|
||||
"notification.label.quote": "{name} vitnaði í færsluna þína",
|
||||
"notification.label.reply": "Svara",
|
||||
"notification.mention": "Minnst á",
|
||||
"notification.mentioned_you": "{name} minntist á þig",
|
||||
|
@ -657,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Tilvísanir:",
|
||||
"notifications.column_settings.poll": "Niðurstöður könnunar:",
|
||||
"notifications.column_settings.push": "Ýti-tilkynningar",
|
||||
"notifications.column_settings.quote": "Tilvitnanir:",
|
||||
"notifications.column_settings.reblog": "Endurbirtingar:",
|
||||
"notifications.column_settings.show": "Sýna í dálki",
|
||||
"notifications.column_settings.sound": "Spila hljóð",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Menzioni:",
|
||||
"notifications.column_settings.poll": "Risultati del sondaggio:",
|
||||
"notifications.column_settings.push": "Notifiche push",
|
||||
"notifications.column_settings.quote": "Citazioni:",
|
||||
"notifications.column_settings.reblog": "Reblog:",
|
||||
"notifications.column_settings.show": "Mostra nella colonna",
|
||||
"notifications.column_settings.sound": "Riproduci suono",
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"account.followers": "Imeḍfaren",
|
||||
"account.followers.empty": "Ar tura, ulac yiwen i yeṭṭafaṛen amseqdac-agi.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} n umḍfar} other {{counter} n yimeḍfaren}}",
|
||||
"account.followers_you_know_counter": "Tessneḍ {counter}",
|
||||
"account.following": "Yeṭṭafaṛ",
|
||||
"account.following_counter": "{count, plural, one {{counter} yettwaḍfaren} other {{counter} yettwaḍfaren}}",
|
||||
"account.follows.empty": "Ar tura, amseqdac-agi ur yeṭṭafaṛ yiwen.",
|
||||
|
@ -51,10 +52,12 @@
|
|||
"account.mute_notifications_short": "Susem ilɣa",
|
||||
"account.mute_short": "Sgugem",
|
||||
"account.muted": "Yettwasgugem",
|
||||
"account.mutual": "Temmeḍfaṛem",
|
||||
"account.no_bio": "Ulac aglam i d-yettunefken.",
|
||||
"account.open_original_page": "Ldi asebter anasli",
|
||||
"account.posts": "Tisuffaɣ",
|
||||
"account.posts_with_replies": "Tisuffaɣ d tririyin",
|
||||
"account.remove_from_followers": "Kkes {name} seg ineḍfaren",
|
||||
"account.report": "Cetki ɣef @{name}",
|
||||
"account.requested": "Di laɛḍil ad yettwaqbel. Ssit i wakken ad yefsex usuter n uḍfar",
|
||||
"account.requested_follow": "{name} yessuter ad k·m-yeḍfer",
|
||||
|
@ -85,6 +88,7 @@
|
|||
"annual_report.summary.most_used_app.most_used_app": "asnas yettwasqedcen s waṭas",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ula yiwen",
|
||||
"annual_report.summary.new_posts.new_posts": "tisuffaɣ timaynutin",
|
||||
"annual_report.summary.thanks": "Tanemmirt imi i tettekkiḍ deg Mastodon!",
|
||||
"audio.hide": "Ffer amesli",
|
||||
"block_modal.show_less": "Ssken-d drus",
|
||||
"block_modal.show_more": "Ssken-d ugar",
|
||||
|
@ -352,6 +356,8 @@
|
|||
"keyboard_shortcuts.toot": "i wakken attebdud tajewwaqt tamaynut",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "i tulin ɣer d asawen n tebdart",
|
||||
"learn_more_link.got_it": "Gziɣ-t",
|
||||
"learn_more_link.learn_more": "Issin ugar",
|
||||
"lightbox.close": "Mdel",
|
||||
"lightbox.next": "Ɣer zdat",
|
||||
"lightbox.previous": "Ɣer deffir",
|
||||
|
@ -417,6 +423,8 @@
|
|||
"notification.admin.sign_up": "Ijerred {name}",
|
||||
"notification.annual_report.view": "Wali #Wrapstodon",
|
||||
"notification.favourite": "{name} yesmenyaf addad-ik·im",
|
||||
"notification.favourite_pm": "{name} yesmenyef abdar-ik·im uslig",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} akked <a>{count, plural, one {# nnayeḍ} other {# nniḍen}}</a> rnan abdar-ik·im uslig ar ismenyafen-nsen",
|
||||
"notification.follow": "iṭṭafar-ik·em-id {name}",
|
||||
"notification.follow.name_and_others": "{name} akked <a>{count, plural, one {# nniḍen} other {# nniḍen}}</a> iḍfeṛ-k·m-id",
|
||||
"notification.follow_request": "{name} yessuter-d ad k·m-yeḍfeṛ",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Vermeldingen:",
|
||||
"notifications.column_settings.poll": "Peilingresultaten:",
|
||||
"notifications.column_settings.push": "Pushmeldingen",
|
||||
"notifications.column_settings.quote": "Citaten:",
|
||||
"notifications.column_settings.reblog": "Boosts:",
|
||||
"notifications.column_settings.show": "In kolom tonen",
|
||||
"notifications.column_settings.sound": "Geluid afspelen",
|
||||
|
|
|
@ -498,6 +498,8 @@
|
|||
"keyboard_shortcuts.translate": "traduzir uma publicação",
|
||||
"keyboard_shortcuts.unfocus": "remover o foco da área de texto / pesquisa",
|
||||
"keyboard_shortcuts.up": "mover para cima na lista",
|
||||
"learn_more_link.got_it": "Entendido",
|
||||
"learn_more_link.learn_more": "Saber mais",
|
||||
"lightbox.close": "Fechar",
|
||||
"lightbox.next": "Próximo",
|
||||
"lightbox.previous": "Anterior",
|
||||
|
@ -598,6 +600,7 @@
|
|||
"notification.label.mention": "Menção",
|
||||
"notification.label.private_mention": "Menção privada",
|
||||
"notification.label.private_reply": "Resposta privada",
|
||||
"notification.label.quote": "{name} citou a sua publicação",
|
||||
"notification.label.reply": "Resposta",
|
||||
"notification.mention": "Menção",
|
||||
"notification.mentioned_you": "{name} mencionou-te",
|
||||
|
@ -655,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Menções:",
|
||||
"notifications.column_settings.poll": "Resultados da sondagem:",
|
||||
"notifications.column_settings.push": "Notificações \"push\"",
|
||||
"notifications.column_settings.quote": "Citações:",
|
||||
"notifications.column_settings.reblog": "Impulsos:",
|
||||
"notifications.column_settings.show": "Mostrar na coluna",
|
||||
"notifications.column_settings.sound": "Reproduzir som",
|
||||
|
@ -873,6 +877,11 @@
|
|||
"status.open": "Expandir esta publicação",
|
||||
"status.pin": "Afixar no perfil",
|
||||
"status.quote_error.filtered": "Oculto devido a um dos seus filtros",
|
||||
"status.quote_error.not_available": "Publicação indisponível",
|
||||
"status.quote_error.pending_approval": "Publicação pendente",
|
||||
"status.quote_error.pending_approval_popout.body": "As citações partilhadas no Fediverso podem demorar algum tempo a ser exibidas, uma vez que diferentes servidores têm protocolos diferentes.",
|
||||
"status.quote_error.pending_approval_popout.title": "Citação pendente? Mantenha a calma",
|
||||
"status.quote_post_author": "Citou uma publicação de @{name}",
|
||||
"status.read_more": "Ler mais",
|
||||
"status.reblog": "Impulsionar",
|
||||
"status.reblog_private": "Impulsionar com a visibilidade original",
|
||||
|
|
|
@ -321,6 +321,7 @@
|
|||
"explore.trending_links": "Новини",
|
||||
"explore.trending_statuses": "Дописи",
|
||||
"explore.trending_tags": "Хештеґи",
|
||||
"featured_carousel.next": "Далі",
|
||||
"filter_modal.added.context_mismatch_explanation": "Ця категорія фільтра не застосовується до контексту, в якому ви отримали доступ до цього допису. Якщо ви хочете, щоб дописи також фільтрувалися за цим контекстом, вам доведеться редагувати фільтр.",
|
||||
"filter_modal.added.context_mismatch_title": "Невідповідність контексту!",
|
||||
"filter_modal.added.expired_explanation": "Категорія цього фільтра застаріла, Вам потрібно змінити дату закінчення терміну дії, щоб застосувати її.",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "Lượt nhắc đến:",
|
||||
"notifications.column_settings.poll": "Kết quả vốt:",
|
||||
"notifications.column_settings.push": "Thông báo đẩy",
|
||||
"notifications.column_settings.quote": "Trích dẫn:",
|
||||
"notifications.column_settings.reblog": "Lượt đăng lại:",
|
||||
"notifications.column_settings.show": "Báo trên thanh bên",
|
||||
"notifications.column_settings.sound": "Kèm âm báo",
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
"notifications.column_settings.mention": "提及:",
|
||||
"notifications.column_settings.poll": "投票結果:",
|
||||
"notifications.column_settings.push": "推播通知",
|
||||
"notifications.column_settings.quote": "引用嘟文:",
|
||||
"notifications.column_settings.reblog": "轉嘟:",
|
||||
"notifications.column_settings.show": "於欄位中顯示",
|
||||
"notifications.column_settings.sound": "播放音效",
|
||||
|
|
|
@ -26,7 +26,10 @@ const filterNotificationsByAllowedTypes = (
|
|||
);
|
||||
}
|
||||
return notifications.filter(
|
||||
(item) => item.type === 'gap' || allowedType === item.type,
|
||||
(item) =>
|
||||
item.type === 'gap' ||
|
||||
allowedType === item.type ||
|
||||
(allowedType === 'mention' && item.type === 'quote'),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -45,9 +45,7 @@ class EmojiFormatter
|
|||
i += 1
|
||||
|
||||
if inside_shortname && text[i] == ':'
|
||||
# https://github.com/rubocop/rubocop/issues/14383
|
||||
# False positive in line below, remove disable when resolved
|
||||
inside_shortname = false # rubocop:disable Lint/UselessAssignment
|
||||
inside_shortname = false
|
||||
shortcode = text[(shortname_start_index + 1)..(i - 1)]
|
||||
char_after = text[i + 1]
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||
include FormattingHelper
|
||||
|
||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :quotes
|
||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :quotes, :interaction_policies
|
||||
|
||||
attributes :id, :type, :summary,
|
||||
:in_reply_to, :published, :url,
|
||||
|
@ -35,6 +35,8 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||
attribute :quote, key: :quote_uri, if: :quote?
|
||||
attribute :quote_authorization, if: :quote_authorization?
|
||||
|
||||
attribute :interaction_policy, if: -> { Mastodon::Feature.outgoing_quotes_enabled? }
|
||||
|
||||
def id
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
end
|
||||
|
@ -216,6 +218,23 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||
ActivityPub::TagManager.instance.approval_uri_for(object.quote)
|
||||
end
|
||||
|
||||
def interaction_policy
|
||||
approved_uris = []
|
||||
|
||||
# On outgoing posts, only automatic approval is supported
|
||||
policy = object.quote_approval_policy >> 16
|
||||
approved_uris << ActivityPub::TagManager::COLLECTIONS[:public] if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public])
|
||||
approved_uris << ActivityPub::TagManager.instance.followers_uri_for(object.account) if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers])
|
||||
approved_uris << ActivityPub::TagManager.instance.following_uri_for(object.account) if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followed])
|
||||
approved_uris << ActivityPub::TagManager.instance.uri_for(object.account) if approved_uris.empty?
|
||||
|
||||
{
|
||||
canQuote: {
|
||||
automaticApproval: approved_uris,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
class MediaAttachmentSerializer < ActivityPub::Serializer
|
||||
context_extensions :blurhash, :focal_point
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ class PostStatusService < BaseService
|
|||
# @option [String] :text Message
|
||||
# @option [Status] :thread Optional status to reply to
|
||||
# @option [Status] :quoted_status Optional status to quote
|
||||
# @option [String] :quote_approval_policy Approval policy for quotes, one of `public`, `followers` or `nobody`
|
||||
# @option [Boolean] :sensitive
|
||||
# @option [String] :visibility
|
||||
# @option [String] :spoiler_text
|
||||
|
@ -215,6 +216,7 @@ class PostStatusService < BaseService
|
|||
language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
|
||||
application: @options[:application],
|
||||
rate_limit: @options[:with_rate_limit],
|
||||
quote_approval_policy: @options[:quote_approval_policy],
|
||||
}.compact
|
||||
end
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ class UpdateStatusService < BaseService
|
|||
@status.spoiler_text = @options[:spoiler_text] || '' if @options.key?(:spoiler_text)
|
||||
@status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text)
|
||||
@status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale)
|
||||
@status.quote_approval_policy = @options[:quote_approval_policy] if @options[:quote_approval_policy].present?
|
||||
|
||||
# We raise here to rollback the entire transaction
|
||||
raise NoChangesSubmittedError unless significant_changes?
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
= content_for :heading do
|
||||
= render 'application/mailer/heading',
|
||||
image_url: frontend_asset_url('images/mailer-new/heading/boost.png'),
|
||||
image_url: frontend_asset_url('images/mailer-new/heading/quote.png'),
|
||||
subtitle: t('notification_mailer.quote.body', name: @status.account.pretty_acct),
|
||||
title: t('notification_mailer.quote.title')
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
|
|
|
@ -1428,7 +1428,7 @@ da:
|
|||
keywords: Nøgleord
|
||||
statuses: Individuelle indlæg
|
||||
statuses_hint_html: Dette filter gælder for udvalgte, individuelle indlæg, uanset om de matcher nøgleordene nedenfor. <a href="%{path}">Gennemgå eller fjern indlæg fra filteret</a>.
|
||||
title: Redigere filter
|
||||
title: Rediger filter
|
||||
errors:
|
||||
deprecated_api_multiple_keywords: Disse parametre kan ikke ændres fra denne applikation, da de gælder for flere end ét filternøgleord. Brug en nyere applikation eller webgrænsefladen.
|
||||
invalid_context: Ingen eller ugyldig kontekst angivet
|
||||
|
@ -1748,7 +1748,7 @@ da:
|
|||
privacy: Privatliv
|
||||
privacy_hint_html: Styr, hvor meget du vil afsløre til gavn for andre. Folk opdager interessante profiler og apps ved at gennemse andres følgere og se, hvilke apps de sender fra, men du foretrækker måske at holde det skjult.
|
||||
reach: Rækkevidde
|
||||
reach_hint_html: Indstil om du vil blive opdaget og fulgt af nye mennesker. Ønsker du, at dine indlæg skal vises på Udforsk-siden? Ønsker du, at andre skal se dig i deres følg-anbefalinger? Ønsker du at acceptere alle nye følgere automatisk, eller vil du have detaljeret kontrol over hver og en?
|
||||
reach_hint_html: Indstil, om du vil opdages og følges af nye personer. Vil du have, at dine indlæg skal vises på Udforsk-siden? Vil du have, at andre skal kunne se dig i deres følg-anbefalinger? Vil du acceptere alle nye følgere automatisk eller have detaljeret kontrol over hver enkelt?
|
||||
search: Søgning
|
||||
search_hint_html: Indstil hvordan du vil findes. Ønsker du, at folk skal finde dig gennem hvad du har skrevet offentligt? Vil du have folk udenfor Mastodon til at finde din profil, når de søger på nettet? Vær opmærksom på, at det ikke kan garanteres at dine offentlige indlæg er udelukket fra alle søgemaskiner.
|
||||
title: Fortrolighed og rækkevidde
|
||||
|
|
|
@ -485,12 +485,20 @@ kab:
|
|||
title: Ihacṭagen inezzaɣ
|
||||
trending_rank: 'Anezzuɣ #%{rank}'
|
||||
trending: Inezzaɣ
|
||||
username_blocks:
|
||||
add_new: Rnu amaynut
|
||||
comparison:
|
||||
contains: Igber
|
||||
new:
|
||||
create: Rnu alugen
|
||||
title: Rnu alugen n useqdac amaynut
|
||||
warning_presets:
|
||||
add_new: Rnu amaynut
|
||||
delete: Kkes
|
||||
webhooks:
|
||||
delete: Kkes
|
||||
enable: Rmed
|
||||
enabled: D urmid
|
||||
admin_mailer:
|
||||
new_report:
|
||||
body: "%{reporter} yettwazen ɣef %{target}"
|
||||
|
@ -623,6 +631,7 @@ kab:
|
|||
other: Ayen nniḍen
|
||||
emoji_styles:
|
||||
auto: Awurman
|
||||
twemoji: Twemoji
|
||||
errors:
|
||||
'404': Asebter i tettnadiḍ ulac-it da.
|
||||
'500':
|
||||
|
@ -858,6 +867,8 @@ kab:
|
|||
other: "%{count} n tbidyutin"
|
||||
edited_at_html: Tettwaẓreg ass n %{date}
|
||||
quote_policies:
|
||||
followers: Ala wid i k·m-yeṭṭafaṛen
|
||||
nobody: Ula yiwen
|
||||
public: Yal yiwen
|
||||
title: '%{name} : "%{quote}"'
|
||||
visibilities:
|
||||
|
@ -904,6 +915,8 @@ kab:
|
|||
user_mailer:
|
||||
appeal_approved:
|
||||
action: Iɣewwaṛen n umiḍan
|
||||
suspicious_sign_in:
|
||||
change_password: snifel awal-ik·im n uɛeddi
|
||||
terms_of_service_changed:
|
||||
sign_off: Agraw n %{domain}
|
||||
warning:
|
||||
|
|
|
@ -190,6 +190,7 @@ pt-PT:
|
|||
create_relay: Criar retransmissor
|
||||
create_unavailable_domain: Criar domínio indisponível
|
||||
create_user_role: Criar função
|
||||
create_username_block: Criar Regra de Nome de Utilizador
|
||||
demote_user: Despromover utilizador
|
||||
destroy_announcement: Eliminar mensagem de manutenção
|
||||
destroy_canonical_email_block: Eliminar bloqueio de e-mail
|
||||
|
@ -203,6 +204,7 @@ pt-PT:
|
|||
destroy_status: Eliminar publicação
|
||||
destroy_unavailable_domain: Eliminar domínio indisponível
|
||||
destroy_user_role: Eliminar função
|
||||
destroy_username_block: Eliminar Regra de Nome de Utilizador
|
||||
disable_2fa_user: Desativar 2FA
|
||||
disable_custom_emoji: Desativar emoji personalizado
|
||||
disable_relay: Desativar retransmissor
|
||||
|
@ -237,6 +239,7 @@ pt-PT:
|
|||
update_report: Atualizar denúncia
|
||||
update_status: Atualizar publicação
|
||||
update_user_role: Atualizar função
|
||||
update_username_block: Atualizar Regra de Nome de Utilizador
|
||||
actions:
|
||||
approve_appeal_html: "%{name} aprovou a contestação da decisão de moderação de %{target}"
|
||||
approve_user_html: "%{name} aprovou a inscrição de %{target}"
|
||||
|
@ -255,6 +258,7 @@ pt-PT:
|
|||
create_relay_html: "%{name} criou o retransmissor %{target}"
|
||||
create_unavailable_domain_html: "%{name} parou as entregas ao domínio %{target}"
|
||||
create_user_role_html: "%{name} criou a função %{target}"
|
||||
create_username_block_html: "%{name} adicionou regra para nomes de utilizadores que contêm %{target}"
|
||||
demote_user_html: "%{name} despromoveu o utilizador %{target}"
|
||||
destroy_announcement_html: "%{name} eliminou a mensagem de manutenção %{target}"
|
||||
destroy_canonical_email_block_html: "%{name} desbloqueou o e-mail com a hash %{target}"
|
||||
|
@ -268,6 +272,7 @@ pt-PT:
|
|||
destroy_status_html: "%{name} removeu a publicação de %{target}"
|
||||
destroy_unavailable_domain_html: "%{name} retomou as entregas ao domínio %{target}"
|
||||
destroy_user_role_html: "%{name} eliminou a função %{target}"
|
||||
destroy_username_block_html: "%{name} removeu regra para nomes de utilizadores que contêm %{target}"
|
||||
disable_2fa_user_html: "%{name} desativou o requerimento de autenticação em dois passos para o utilizador %{target}"
|
||||
disable_custom_emoji_html: "%{name} desativou o emoji %{target}"
|
||||
disable_relay_html: "%{name} desativou o retransmissor %{target}"
|
||||
|
@ -302,6 +307,7 @@ pt-PT:
|
|||
update_report_html: "%{name} atualizou a denúncia %{target}"
|
||||
update_status_html: "%{name} atualizou a publicação de %{target}"
|
||||
update_user_role_html: "%{name} alterou a função %{target}"
|
||||
update_username_block_html: "%{name} atualizou regra para nomes de utilizadores que contêm %{target}"
|
||||
deleted_account: conta eliminada
|
||||
empty: Não foram encontrados registos.
|
||||
filter_by_action: Filtrar por ação
|
||||
|
@ -1085,6 +1091,25 @@ pt-PT:
|
|||
other: Utilizada por %{count} pessoas na última semana
|
||||
title: Recomendações e destaques
|
||||
trending: Em destaque
|
||||
username_blocks:
|
||||
add_new: Adicionar novo
|
||||
block_registrations: Bloquear inscrições
|
||||
comparison:
|
||||
contains: Contém
|
||||
equals: Igual a
|
||||
contains_html: Contém %{string}
|
||||
created_msg: Regra de nome de utilizador criada com sucesso
|
||||
delete: Eliminar
|
||||
edit:
|
||||
title: Editar regra de nome de utilizador
|
||||
matches_exactly_html: Igual a %{string}
|
||||
new:
|
||||
create: Criar regra
|
||||
title: Criar nova regra de utilizador
|
||||
no_username_block_selected: Não foi alterada nenhuma regra de nome de utilizador, pois nenhuma foi selecionada
|
||||
not_permitted: Não permitido
|
||||
title: Regras de nome de utilizador
|
||||
updated_msg: Regra de nome de utilizador atualizada com sucesso
|
||||
warning_presets:
|
||||
add_new: Adicionar novo
|
||||
delete: Eliminar
|
||||
|
@ -1662,6 +1687,10 @@ pt-PT:
|
|||
title: Nova menção
|
||||
poll:
|
||||
subject: A sondagem de %{name} terminou
|
||||
quote:
|
||||
body: 'A sua publicação foi citada por %{name}:'
|
||||
subject: "%{name} citou a sua publicação"
|
||||
title: Nova citação
|
||||
reblog:
|
||||
body: 'A tua publicação foi impulsionada por %{name}:'
|
||||
subject: "%{name} impulsionou a tua publicação"
|
||||
|
@ -1880,6 +1909,8 @@ pt-PT:
|
|||
ownership: Não podem ser fixadas publicações de outras pessoas
|
||||
reblog: Não é possível fixar um impulso
|
||||
quote_policies:
|
||||
followers: Apenas os seus seguidores
|
||||
nobody: Ninguém
|
||||
public: Todos
|
||||
title: '%{name}: "%{quote}"'
|
||||
visibilities:
|
||||
|
|
|
@ -1922,24 +1922,29 @@ ru:
|
|||
other: 'содержались запрещённые хэштеги: %{tags}'
|
||||
edited_at_html: 'Дата последнего изменения: %{date}'
|
||||
errors:
|
||||
in_reply_not_found: Пост, на который вы пытаетесь ответить, не существует или удалён.
|
||||
over_character_limit: превышен лимит символов (%{max})
|
||||
in_reply_not_found: Пост, на который вы собирались ответить, был удалён или не существует.
|
||||
quoted_status_not_found: Пост, который вы собирались процитировать, был удалён или не существует.
|
||||
over_character_limit: превышает максимально допустимую длину (%{max} символов)
|
||||
pin_errors:
|
||||
direct: Сообщения, видимые только упомянутым пользователям, не могут быть закреплены
|
||||
limit: Вы закрепили максимально возможное число постов
|
||||
direct: Нельзя закрепить пост, который доступен только тем, кто был упомянут в нём
|
||||
limit: Вы достигли максимального количества постов, которые можно закрепить в профиле
|
||||
ownership: Нельзя закрепить чужой пост
|
||||
reblog: Нельзя закрепить продвинутый пост
|
||||
title: '%{name}: "%{quote}"'
|
||||
reblog: Нельзя закрепить продвижение
|
||||
quote_policies:
|
||||
followers: Только ваши подписчики
|
||||
nobody: Никто
|
||||
public: Кто угодно
|
||||
title: "%{name}: «%{quote}»"
|
||||
visibilities:
|
||||
direct: Адресованный
|
||||
private: Для подписчиков
|
||||
private_long: Показывать только подписчикам
|
||||
public: Для всех
|
||||
public_long: Показывать всем
|
||||
unlisted: Скрывать из лент
|
||||
unlisted_long: Показывать всем, но не отображать в публичных лентах
|
||||
direct: Личное упоминание
|
||||
private: Только для подписчиков
|
||||
private_long: Доступен только вашим подписчикам
|
||||
public: Публичный
|
||||
public_long: Доступен кому угодно
|
||||
unlisted: Скрытый
|
||||
unlisted_long: Доступен кому угодно, но не отображается в публичных лентах
|
||||
statuses_cleanup:
|
||||
enabled: Автоматически удалять устаревшие посты
|
||||
enabled: Автоматически удалять старые посты
|
||||
enabled_hint: Автоматически удаляет ваши посты после того, как они достигли определённого возрастного порога, за некоторыми исключениями ниже.
|
||||
exceptions: Исключения
|
||||
explanation: Из-за того, что удаление постов — это ресурсоёмкий процесс, оно производится медленно со временем, когда сервер менее всего загружен. По этой причине, посты могут удаляться не сразу, а спустя определённое время, по достижению возрастного порога.
|
||||
|
|
|
@ -56,6 +56,7 @@ ca:
|
|||
scopes: API permeses per a accedir a l'aplicació. Si selecciones un àmbit de nivell superior, no cal que en seleccionis un d'individual.
|
||||
setting_aggregate_reblogs: No mostra els nous impulsos dels tuts que ja s'han impulsat recentment (només afecta als impulsos nous rebuts)
|
||||
setting_always_send_emails: Normalment, no s'enviarà cap notificació per correu electrònic mentre facis servir Mastodon
|
||||
setting_default_quote_policy: Aquesta configuració només tindrà efecte en les publicacions creades amb la següent versió de Mastodon, però podeu seleccionar-la en preparació.
|
||||
setting_default_sensitive: El contingut sensible està ocult per defecte i es pot mostrar fent-hi clic
|
||||
setting_display_media_default: Amaga el contingut gràfic marcat com a sensible
|
||||
setting_display_media_hide_all: Oculta sempre tot el contingut multimèdia
|
||||
|
|
|
@ -10,7 +10,7 @@ da:
|
|||
indexable: Dine offentlige indlæg vil kunne vises i Mastodon-søgeresultater. Folk, som har interageret med dem, vil kunne finde dem uanset.
|
||||
note: 'Du kan @omtale andre personer eller #hashtags.'
|
||||
show_collections: Folk vil kunne se, hvem du følger, og hvem der følger dig. Personer, som du følger, vil kunne se, at du følger dem.
|
||||
unlocked: Man vil kunne følges af folk uden først at godkende dem. Ønsker man at gennemgå Følg-anmodninger og individuelt acceptere/afvise nye følgere, så fjern markeringen.
|
||||
unlocked: Folk vil kunne følge dig uden at anmode om godkendelse. Fjern markeringen, hvis du vil gennemgå anmodninger om at følge, og vælge, om du vil acceptere eller afvise nye følgere.
|
||||
account_alias:
|
||||
acct: Angiv brugernavn@domain for den konto, hvorfra du vil flytte
|
||||
account_migration:
|
||||
|
@ -329,7 +329,7 @@ da:
|
|||
follow_request: Nogen anmodede om at følge dig
|
||||
mention: Nogen omtalte dig
|
||||
pending_account: Ny konto kræver gennemgang
|
||||
quote: Nogle citerede dig
|
||||
quote: Nogen citerede dig
|
||||
reblog: Nogen fremhævede dit indlæg
|
||||
report: Ny anmeldelse indsendt
|
||||
software_updates:
|
||||
|
|
|
@ -27,6 +27,8 @@ kab:
|
|||
username: Tzemreḍ ad tesqedceḍ isekkilen, uṭṭunen akked yijerriden n wadda
|
||||
featured_tag:
|
||||
name: 'Ha-t-an kra seg ihacṭagen i tesseqdaceḍ ussan-a ineggura maḍi :'
|
||||
form_challenge:
|
||||
current_password: Tkecmeḍ ɣer temnaḍt taɣellsant
|
||||
imports:
|
||||
data: Afaylu CSV id yusan seg uqeddac-nniḍen n Maṣṭudun
|
||||
invite_request:
|
||||
|
@ -143,7 +145,12 @@ kab:
|
|||
name: Ahacṭag
|
||||
terms_of_service:
|
||||
text: Tiwtilin n useqdec
|
||||
terms_of_service_generator:
|
||||
domain: Taɣult
|
||||
user:
|
||||
date_of_birth_1i: Ass
|
||||
date_of_birth_2i: Ayyur
|
||||
date_of_birth_3i: Aseggas
|
||||
role: Tamlilt
|
||||
time_zone: Tamnaḍt tasragant
|
||||
user_role:
|
||||
|
|
|
@ -56,6 +56,7 @@ pt-PT:
|
|||
scopes: Quais as API a que a aplicação terá permissão para aceder. Se selecionar um âmbito de nível superior, não precisa de selecionar âmbitos individuais.
|
||||
setting_aggregate_reblogs: Não mostrar os novos impulsos para publicações que tenham sido recentemente impulsionadas (apenas afeta os impulsos recentemente recebidos)
|
||||
setting_always_send_emails: Normalmente as notificações por e-mail não serão enviadas quando estiver a utilizar ativamente o Mastodon
|
||||
setting_default_quote_policy: Esta configuração só terá efeito nas publicações criadas com a próxima versão do Mastodon, mas pode desde já selecionar a sua preferência.
|
||||
setting_default_sensitive: Os multimédia sensíveis são ocultados por predefinição e podem ser revelados com um clique/toque
|
||||
setting_display_media_default: Esconder multimédia marcada como sensível
|
||||
setting_display_media_hide_all: Esconder sempre toda a multimédia
|
||||
|
@ -159,6 +160,10 @@ pt-PT:
|
|||
name: Nome público da função, se esta estiver definida para ser apresentada com um emblema
|
||||
permissions_as_keys: Utilizadores com esta função terão acesso a...
|
||||
position: Funções mais altas decidem a resolução de conflitos em certas situações. Certas ações só podem ser executadas com certas funções com uma menor prioridade
|
||||
username_block:
|
||||
allow_with_approval: Em vez de impedir totalmente a inscrição, as inscrições correspondentes exigirão a sua aprovação
|
||||
comparison: Tenha em atenção o Problema de Scunthorpe ao bloquear correspondências parciais
|
||||
username: Terá correspondência independentemente das maiúsculas e minúsculas e de homógrafos comuns, como "4" para "a" ou "3" para "e"
|
||||
webhook:
|
||||
events: Selecione os eventos a enviar
|
||||
template: Componha o seu próprio conteúdo JSON utilizando a interpolação de variáveis. Deixar em branco para o JSON predefinido.
|
||||
|
@ -324,6 +329,7 @@ pt-PT:
|
|||
follow_request: Alguém pediu para ser seu seguidor
|
||||
mention: Alguém o mencionou
|
||||
pending_account: Uma nova conta aguarda aprovação
|
||||
quote: Alguém o citou
|
||||
reblog: Alguém impulsionou uma publicação sua
|
||||
report: Uma nova denúncia foi submetida
|
||||
software_updates:
|
||||
|
@ -370,6 +376,10 @@ pt-PT:
|
|||
name: Nome
|
||||
permissions_as_keys: Permissões
|
||||
position: Prioridade
|
||||
username_block:
|
||||
allow_with_approval: Permitir inscrições com aprovação
|
||||
comparison: Método de comparação
|
||||
username: Palavra a corresponder
|
||||
webhook:
|
||||
events: Eventos ativados
|
||||
template: Modelo de conteúdo
|
||||
|
|
|
@ -496,6 +496,15 @@ uk:
|
|||
new:
|
||||
title: Імпорт блокувань домену
|
||||
no_file: Файл не вибрано
|
||||
fasp:
|
||||
debug:
|
||||
callbacks:
|
||||
delete: Видалити
|
||||
providers:
|
||||
delete: Видалити
|
||||
registrations:
|
||||
confirm: Підтвердити
|
||||
save: Зберегти
|
||||
follow_recommendations:
|
||||
description_html: "<strong>Слідувати рекомендаціям та допомогти новим користувачам швидко знайти цікавий вміст</strong>. Коли користувачі не взаємодіяли з іншими людьми достатньо, щоб сформувати персоналізовані рекомендації, радимо замість цього вказувати ці облікові записи. Вони щоденно переобчислюються з масиву облікових записів з найбільшою кількістю недавніх взаємодій і найбільшою кількістю місцевих підписників розраховується для цієї мови."
|
||||
language: Для мови
|
||||
|
|
|
@ -3,6 +3,36 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CanonicalEmailBlock do
|
||||
describe 'Associations' do
|
||||
it { is_expected.to belong_to(:reference_account).class_name('Account').optional }
|
||||
end
|
||||
|
||||
describe 'Scopes' do
|
||||
describe '.matching_email' do
|
||||
subject { described_class.matching_email(email) }
|
||||
|
||||
let!(:block) { Fabricate :canonical_email_block, email: 'test@example.com' }
|
||||
|
||||
context 'when email is exact match' do
|
||||
let(:email) { 'test@example.com' }
|
||||
|
||||
it { is_expected.to contain_exactly(block) }
|
||||
end
|
||||
|
||||
context 'when email does not match' do
|
||||
let(:email) { 'test@example.ORG' }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'when email is different but normalizes to same hash' do
|
||||
let(:email) { 'te.st+more@EXAMPLE.com' }
|
||||
|
||||
it { is_expected.to contain_exactly(block) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#email=' do
|
||||
let(:target_hash) { '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b' }
|
||||
|
||||
|
|
|
@ -258,13 +258,13 @@ RSpec.describe Form::Import do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'on successful import', 'following', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like 'on successful import', 'following', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like 'on successful import', 'blocking', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like 'on successful import', 'blocking', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like 'on successful import', 'muting', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like 'on successful import', 'domain_blocking', 'merge', 'domain_blocks.csv', (%w(bad.domain worse.domain reject.media).map { |domain| { 'domain' => domain } })
|
||||
it_behaves_like 'on successful import', 'bookmarks', 'merge', 'bookmark-imports.txt', (%w(https://example.com/statuses/1312 https://local.com/users/foo/statuses/42 https://unknown-remote.com/users/bar/statuses/1 https://example.com/statuses/direct).map { |uri| { 'uri' => uri } })
|
||||
it_behaves_like('on successful import', 'following', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like('on successful import', 'following', 'overwrite', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like('on successful import', 'blocking', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like('on successful import', 'blocking', 'overwrite', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like('on successful import', 'muting', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
|
||||
it_behaves_like('on successful import', 'domain_blocking', 'merge', 'domain_blocks.csv', %w(bad.domain worse.domain reject.media).map { |domain| { 'domain' => domain } })
|
||||
it_behaves_like('on successful import', 'bookmarks', 'merge', 'bookmark-imports.txt', %w(https://example.com/statuses/1312 https://local.com/users/foo/statuses/42 https://unknown-remote.com/users/bar/statuses/1 https://example.com/statuses/direct).map { |uri| { 'uri' => uri } })
|
||||
|
||||
it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [
|
||||
{ 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil },
|
||||
|
|
|
@ -158,6 +158,31 @@ RSpec.describe '/api/v1/statuses' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a quote policy', feature: :outgoing_quotes do
|
||||
let(:quoted_status) { Fabricate(:status, account: user.account) }
|
||||
let(:params) do
|
||||
{
|
||||
status: 'Hello world, this is a self-quote',
|
||||
quote_approval_policy: 'followers',
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns post with appropriate quote policy, as well as rate limit headers', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.content_type)
|
||||
.to start_with('application/json')
|
||||
expect(response.parsed_body[:quote_approval]).to include({
|
||||
automatic: ['followers'],
|
||||
manual: [],
|
||||
current_user: 'automatic',
|
||||
})
|
||||
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
|
||||
expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a self-quote post', feature: :outgoing_quotes do
|
||||
let(:quoted_status) { Fabricate(:status, account: user.account) }
|
||||
let(:params) do
|
||||
|
@ -307,9 +332,10 @@ RSpec.describe '/api/v1/statuses' do
|
|||
|
||||
describe 'PUT /api/v1/statuses/:id' do
|
||||
subject do
|
||||
put "/api/v1/statuses/#{status.id}", headers: headers, params: { status: 'I am updated' }
|
||||
put "/api/v1/statuses/#{status.id}", headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:params) { { status: 'I am updated' } }
|
||||
let(:scopes) { 'write:statuses' }
|
||||
let(:status) { Fabricate(:status, account: user.account) }
|
||||
|
||||
|
@ -323,6 +349,19 @@ RSpec.describe '/api/v1/statuses' do
|
|||
.to start_with('application/json')
|
||||
expect(status.reload.text).to eq 'I am updated'
|
||||
end
|
||||
|
||||
context 'when updating only the quote policy' do
|
||||
let(:params) { { status: status.text, quote_approval_policy: 'public' } }
|
||||
|
||||
it 'updates the status', :aggregate_failures, feature: :outgoing_quotes do
|
||||
expect { subject }
|
||||
.to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.content_type)
|
||||
.to start_with('application/json')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -56,4 +56,19 @@ RSpec.describe ActivityPub::NoteSerializer do
|
|||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a quote policy', feature: :outgoing_quotes do
|
||||
let(:parent) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) }
|
||||
|
||||
it 'has the expected shape' do
|
||||
expect(subject).to include({
|
||||
'type' => 'Note',
|
||||
'interactionPolicy' => a_hash_including(
|
||||
'canQuote' => a_hash_including(
|
||||
'automaticApproval' => [ActivityPub::TagManager.instance.followers_uri_for(parent.account)]
|
||||
)
|
||||
),
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -160,6 +160,12 @@ RSpec.describe PostStatusService do
|
|||
expect(status.language).to eq 'en'
|
||||
end
|
||||
|
||||
it 'creates a status with the quote approval policy set' do
|
||||
status = create_status_with_options(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16)
|
||||
|
||||
expect(status.quote_approval_policy).to eq(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16)
|
||||
end
|
||||
|
||||
it 'processes mentions' do
|
||||
mention_service = instance_double(ProcessMentionsService)
|
||||
allow(mention_service).to receive(:call)
|
||||
|
|
|
@ -112,7 +112,7 @@ RSpec.describe Scheduler::AccountsStatusesCleanupScheduler do
|
|||
|
||||
expect { subject.perform }
|
||||
.to change(Status, :count).by(-subject.compute_budget) # Cleanable statuses
|
||||
.and (not_change { account_bob.statuses.count }) # No cleanup policy for account
|
||||
.and not_change { account_bob.statuses.count } # No cleanup policy for account
|
||||
.and(not_change { account_dave.statuses.count }) # Disabled cleanup policy
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user