Merge branch 'main' into feature/require-mfa-by-admin

This commit is contained in:
FredysFonseca 2025-08-04 18:51:13 -04:00 committed by GitHub
commit 7e1eeff268
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 1173 additions and 530 deletions

View File

@ -5,6 +5,7 @@
.gitattributes
.gitignore
.github
.vscode
public/system
public/assets
public/packs
@ -20,6 +21,7 @@ postgres14
redis
elasticsearch
chart
storybook-static
.yarn/
!.yarn/patches
!.yarn/plugins

View File

@ -50,7 +50,7 @@ jobs:
# Create or update the pull request
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.6
uses: peter-evans/create-pull-request@v7.0.8
with:
commit-message: 'New Crowdin translations'
title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)'

2
.nvmrc
View File

@ -1 +1 @@
22.17
22.18

View File

@ -287,7 +287,7 @@ GEM
activesupport (>= 5.1)
haml (>= 4.0.6)
railties (>= 5.1)
haml_lint (0.65.1)
haml_lint (0.66.0)
haml (>= 5.0)
parallel (~> 1.10)
rainbow
@ -607,7 +607,7 @@ GEM
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.6.0)
pg (1.6.1)
pghero (3.7.0)
activerecord (>= 7.1)
playwright-ruby-client (1.54.0)
@ -721,7 +721,7 @@ GEM
connection_pool
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.10.0)
regexp_parser (2.11.0)
reline (0.6.2)
io-console (~> 0.5)
request_store (1.7.0)
@ -805,7 +805,7 @@ GEM
ruby-prof (1.7.2)
base64
ruby-progressbar (1.13.0)
ruby-saml (1.18.0)
ruby-saml (1.18.1)
nokogiri (>= 1.13.10)
rexml
ruby-vips (2.2.4)

View File

@ -37,7 +37,7 @@ export interface BaseApiAccountJSON {
roles?: ApiAccountJSON[];
statuses_count: number;
uri: string;
url: string;
url?: string;
username: string;
moved?: ApiAccountJSON;
suspended?: boolean;

View File

@ -13,6 +13,7 @@ export const allNotificationTypes = [
'favourite',
'reblog',
'mention',
'quote',
'poll',
'status',
'update',
@ -28,6 +29,7 @@ export type NotificationWithStatusType =
| 'reblog'
| 'status'
| 'mention'
| 'quote'
| 'poll'
| 'update';

View File

@ -15,17 +15,6 @@ export const SKIN_TONE_CODES = [
0x1f3ff, // Dark skin tone
] as const;
// TODO: Test and create fallback for browsers that do not handle the /v flag.
export const UNICODE_EMOJI_REGEX = /\p{RGI_Emoji}/v;
// See: https://www.unicode.org/reports/tr51/#valid-emoji-tag-sequences
export const UNICODE_FLAG_EMOJI_REGEX =
/\p{RGI_Emoji_Flag_Sequence}|\p{RGI_Emoji_Tag_Sequence}/v;
export const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i;
export const ANY_EMOJI_REGEX = new RegExp(
`(${UNICODE_EMOJI_REGEX.source}|${CUSTOM_EMOJI_REGEX.source})`,
'gv',
);
// Emoji rendering modes. A mode is what we are using to render emojis, a style is what the user has selected.
export const EMOJI_MODE_NATIVE = 'native';
export const EMOJI_MODE_NATIVE_WITH_FLAGS = 'native-flags';

View File

@ -1,5 +1,7 @@
import type { ComponentPropsWithoutRef, ElementType } from 'react';
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
import { useEmojify } from './hooks';
import type { CustomEmojiMapArg } from './types';
@ -12,7 +14,7 @@ type EmojiHTMLProps<Element extends ElementType = 'div'> = Omit<
as?: Element;
};
export const EmojiHTML = <Element extends ElementType>({
export const ModernEmojiHTML = <Element extends ElementType>({
extraEmojis,
htmlString,
as: asElement, // Rename for syntax highlighting
@ -29,3 +31,18 @@ export const EmojiHTML = <Element extends ElementType>({
<Wrapper {...props} dangerouslySetInnerHTML={{ __html: emojifiedHtml }} />
);
};
export const EmojiHTML = <Element extends ElementType>(
props: EmojiHTMLProps<Element>,
) => {
if (isModernEmojiEnabled()) {
return <ModernEmojiHTML {...props} />;
}
const Wrapper = props.as ?? 'div';
return (
<Wrapper
{...props}
dangerouslySetInnerHTML={{ __html: props.htmlString }}
/>
);
};

View File

@ -8,7 +8,6 @@ import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
import { toSupportedLocale } from './locale';
import { determineEmojiMode } from './mode';
import { emojifyElement } from './render';
import type {
CustomEmojiMapArg,
EmojiAppState,
@ -39,6 +38,7 @@ export function useEmojify(text: string, extraEmojis?: CustomEmojiMapArg) {
async (input: string) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = input;
const { emojifyElement } = await import('./render');
const result = await emojifyElement(wrapper, appState, extra);
if (result) {
setEmojifiedText(result.innerHTML);

View File

@ -9,7 +9,6 @@ import {
EMOJI_TYPE_UNICODE,
EMOJI_TYPE_CUSTOM,
EMOJI_STATE_MISSING,
ANY_EMOJI_REGEX,
} from './constants';
import {
searchCustomEmojisByShortcodes,
@ -32,7 +31,12 @@ import type {
LocaleOrCustom,
UnicodeEmojiToken,
} from './types';
import { emojiLogger, stringHasAnyEmoji, stringHasUnicodeFlags } from './utils';
import {
anyEmojiRegex,
emojiLogger,
stringHasAnyEmoji,
stringHasUnicodeFlags,
} from './utils';
const log = emojiLogger('render');
@ -207,7 +211,7 @@ export function tokenizeText(text: string): TokenizedText {
const tokens = [];
let lastIndex = 0;
for (const match of text.matchAll(ANY_EMOJI_REGEX)) {
for (const match of text.matchAll(anyEmojiRegex())) {
if (match.index > lastIndex) {
tokens.push(text.slice(lastIndex, match.index));
}

View File

@ -1,23 +1,32 @@
import debug from 'debug';
import {
CUSTOM_EMOJI_REGEX,
UNICODE_EMOJI_REGEX,
UNICODE_FLAG_EMOJI_REGEX,
} from './constants';
import { emojiRegexPolyfill } from '@/mastodon/polyfills';
export function emojiLogger(segment: string) {
return debug(`emojis:${segment}`);
}
export function stringHasUnicodeEmoji(input: string): boolean {
return UNICODE_EMOJI_REGEX.test(input);
return new RegExp(EMOJI_REGEX, supportedFlags()).test(input);
}
export function stringHasUnicodeFlags(input: string): boolean {
return UNICODE_FLAG_EMOJI_REGEX.test(input);
if (supportsRegExpSets()) {
return new RegExp(
'\\p{RGI_Emoji_Flag_Sequence}|\\p{RGI_Emoji_Tag_Sequence}',
'v',
).test(input);
}
return new RegExp(
// First range is regional indicator symbols,
// Second is a black flag + 0-9|a-z tag chars + cancel tag.
// See: https://en.wikipedia.org/wiki/Regional_indicator_symbol
'(?:\uD83C[\uDDE6-\uDDFF]){2}|\uD83C\uDFF4(?:\uDB40[\uDC30-\uDC7A])+\uDB40\uDC7F',
).test(input);
}
// Constant as this is supported by all browsers.
const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i;
export function stringHasCustomEmoji(input: string) {
return CUSTOM_EMOJI_REGEX.test(input);
}
@ -25,3 +34,23 @@ export function stringHasCustomEmoji(input: string) {
export function stringHasAnyEmoji(input: string) {
return stringHasUnicodeEmoji(input) || stringHasCustomEmoji(input);
}
export function anyEmojiRegex() {
return new RegExp(
`${EMOJI_REGEX}|${CUSTOM_EMOJI_REGEX.source}`,
supportedFlags('gi'),
);
}
function supportsRegExpSets() {
return 'unicodeSets' in RegExp.prototype;
}
function supportedFlags(flags = '') {
if (supportsRegExpSets()) {
return `${flags}v`;
}
return flags;
}
const EMOJI_REGEX = emojiRegexPolyfill?.source ?? '\\p{RGI_Emoji}';

View File

@ -8,9 +8,9 @@ import { Link, withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react';
import FormatQuoteIcon from '@/material-icons/400-24px/format_quote.svg?react';
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react';
@ -42,6 +42,7 @@ const messages = defineMessages({
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' },
moderationWarning: { id: 'notification.moderation_warning', defaultMessage: 'You have received a moderation warning' },
quote: { id: 'notification.label.quote', defaultMessage: '{name} quoted your post'}
});
const notificationForScreenReader = (intl, message, timestamp) => {
@ -251,6 +252,36 @@ class Notification extends ImmutablePureComponent {
);
}
renderQuote (notification, link) {
const { intl, unread } = this.props;
return (
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-quote focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.quote, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='quote' icon={FormatQuoteIcon} />
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.label.quote' defaultMessage='{name} quoted your post' values={{ name: link }} />
</span>
</div>
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</Hotkeys>
);
}
renderStatus (notification, link) {
const { intl, unread, status } = this.props;
@ -467,6 +498,8 @@ class Notification extends ImmutablePureComponent {
return this.renderFollowRequest(notification, account, link);
case 'mention':
return this.renderMention(notification);
case 'quote':
return this.renderQuote(notification);
case 'favourite':
return this.renderFavourite(notification, link);
case 'reblog':

View File

@ -15,6 +15,7 @@ import { NotificationFollowRequest } from './notification_follow_request';
import { NotificationMention } from './notification_mention';
import { NotificationModerationWarning } from './notification_moderation_warning';
import { NotificationPoll } from './notification_poll';
import { NotificationQuote } from './notification_quote';
import { NotificationReblog } from './notification_reblog';
import { NotificationSeveredRelationships } from './notification_severed_relationships';
import { NotificationStatus } from './notification_status';
@ -91,6 +92,11 @@ export const NotificationGroup: React.FC<{
<NotificationMention unread={unread} notification={notificationGroup} />
);
break;
case 'quote':
content = (
<NotificationQuote unread={unread} notification={notificationGroup} />
);
break;
case 'follow':
content = (
<NotificationFollow unread={unread} notification={notificationGroup} />

View File

@ -0,0 +1,33 @@
import { FormattedMessage } from 'react-intl';
import FormatQuoteIcon from '@/material-icons/400-24px/format_quote.svg?react';
import type { NotificationGroupQuote } from 'mastodon/models/notification_group';
import type { LabelRenderer } from './notification_group_with_status';
import { NotificationWithStatus } from './notification_with_status';
const quoteLabelRenderer: LabelRenderer = (displayName) => (
<FormattedMessage
id='notification.label.quote'
defaultMessage='{name} quoted your post'
values={{ name: displayName }}
/>
);
export const NotificationQuote: React.FC<{
notification: NotificationGroupQuote;
unread: boolean;
}> = ({ notification, unread }) => {
return (
<NotificationWithStatus
type='quote'
icon={FormatQuoteIcon}
iconId='quote'
accountIds={notification.sampleAccountIds}
count={notification.notifications_count}
statusId={notification.statusId}
labelRenderer={quoteLabelRenderer}
unread={unread}
/>
);
};

View File

@ -600,6 +600,7 @@
"notification.label.mention": "Mention",
"notification.label.private_mention": "Private mention",
"notification.label.private_reply": "Private reply",
"notification.label.quote": "{name} quoted your post",
"notification.label.reply": "Reply",
"notification.mention": "Mention",
"notification.mentioned_you": "{name} mentioned you",

View File

@ -756,7 +756,7 @@
"reply_indicator.cancel": "Peruuta",
"reply_indicator.poll": "Äänestys",
"report.block": "Estä",
"report.block_explanation": "Et näe hänen julkaisujaan. Hän voi nähdä julkaisujasi eikä seurata sinua. Hän näkee, että olet estänyt hänet.",
"report.block_explanation": "Et näe hänen julkaisujaan. Hän ei voi nähdä julkaisujasi eikä seurata sinua. Hän näkee, että olet estänyt hänet.",
"report.categories.legal": "Lakiseikat",
"report.categories.other": "Muu",
"report.categories.spam": "Roskaposti",

View File

@ -1,6 +1,7 @@
{
"about.blocks": "Servī moderātī",
"about.contact": "Ratio:",
"about.default_locale": "Default",
"about.disclaimer": "Mastodon est software līberum, apertum fontem, et nōtam commercium Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Ratio abdere est",
"about.domain_blocks.preamble": "Mastodon genērāliter sinit tē contentum ex aliīs servientibus in fedīversō vidēre et cum usoribus ab iīs interāgere. Haē sunt exceptionēs quae in hōc particulārī servientē factae sunt.",
@ -8,6 +9,7 @@
"about.domain_blocks.silenced.title": "Limitātus",
"about.domain_blocks.suspended.explanation": "Nulla data ab hōc servientē processābuntur, servābuntur aut commūtābuntur, faciendumque omnem interactionem aut communicātiōnem cum usoribus ab hōc servientē impossibilem.",
"about.domain_blocks.suspended.title": "suspensus",
"about.language_label": "Linguae",
"about.not_available": "Haec informātiō in hōc servientē nōn praebita est.",
"about.powered_by": "Nuntii socīālēs decentralizātī ā {mastodon} sustentātī.",
"about.rules": "Servo praecepta",
@ -41,7 +43,12 @@
"account.followers": "Sectatores",
"account.followers.empty": "Nemo hunc usorem adhuc sequitur.",
"account.followers_counter": "{count, plural, one {{counter} sectator} other {{counter} sectatores}}",
"account.followers_you_know_counter": "{counter} scis",
"account.following": "Sequentia",
"account.following_counter": "{count, plural, one {{counter} sectans} other {{counter} sectans}}",
"account.follows.empty": "Hic usor adhuc neminem sequitur.",
"account.follows_you": "Sequitur te",
"account.go_to_profile": "Vade ad profile",
"account.moved_to": "{name} significavit eum suam rationem novam nunc esse:",
"account.muted": "Confutatus",
"account.requested_follow": "{name} postulavit ut te sequeretur",

View File

@ -876,6 +876,9 @@
"status.quote_error.filtered": "Lí所設定ê過濾器kā tse khàm起來",
"status.quote_error.not_available": "鋪文bē當看",
"status.quote_error.pending_approval": "鋪文當咧送",
"status.quote_error.pending_approval_popout.body": "因為無kâng ê服侍器有無kâng ê協定,佇聯邦宇宙分享ê引文可能愛開時間來顯示。",
"status.quote_error.pending_approval_popout.title": "Leh送引文請sió等leh",
"status.quote_post_author": "引用 @{name} ê PO文ah",
"status.read_more": "讀詳細",
"status.reblog": "轉送",
"status.reblog_private": "照原PO ê通看見ê範圍轉送",

View File

@ -1,6 +1,7 @@
{
"about.blocks": "Serwery moderowane",
"about.contact": "Kontakt:",
"about.default_locale": "Domyślny",
"about.disclaimer": "Mastodon jest darmowym, otwartym oprogramowaniem i znakiem towarowym Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Powód niedostępny",
"about.domain_blocks.preamble": "Domyślnie Mastodon pozwala ci przeglądać i reagować na treści od innych użytkowników z jakiegokolwiek serwera w fediwersum. Poniżej znajduje się lista wyjątków, które zostały stworzone na tym konkretnym serwerze.",
@ -8,6 +9,7 @@
"about.domain_blocks.silenced.title": "Ograniczone",
"about.domain_blocks.suspended.explanation": "Żadne dane z tego serwera nie będą przetwarzane, przechowywane lub wymieniane, co uniemożliwia jakąkolwiek interakcję lub komunikację z użytkownikami z tego serwera.",
"about.domain_blocks.suspended.title": "Zawieszono",
"about.language_label": "Język",
"about.not_available": "Ta informacja nie została udostępniona na tym serwerze.",
"about.powered_by": "Zdecentralizowane media społecznościowe napędzane przez {mastodon}",
"about.rules": "Regulamin serwera",
@ -19,13 +21,21 @@
"account.block_domain": "Blokuj wszystko z {domain}",
"account.block_short": "Zablokuj",
"account.blocked": "Zablokowany(-a)",
"account.blocking": "Blokowanie",
"account.cancel_follow_request": "Nie obserwuj",
"account.copy": "Skopiuj link do profilu",
"account.direct": "Napisz bezpośrednio do @{name}",
"account.disable_notifications": "Przestań powiadamiać mnie o wpisach @{name}",
"account.domain_blocking": "Blokowanie domeny",
"account.edit_profile": "Edytuj profil",
"account.enable_notifications": "Powiadamiaj mnie o wpisach @{name}",
"account.endorse": "Wyróżnij na profilu",
"account.familiar_followers_many": "Obserwowane przez: {name1}, {name2} i {othersCount, plural, one {jeszcze jedną osobę, którą znasz} few {# inne osoby, które znasz} many {# innych osób, które znasz} other {# innych osób, które znasz}}",
"account.familiar_followers_one": "Obserwowane przez {name1}",
"account.familiar_followers_two": "Obserwowane przez {name1} i {name2}",
"account.featured": "Wyróżnione",
"account.featured.accounts": "Profile",
"account.featured.hashtags": "Tagi",
"account.featured_tags.last_status_at": "Ostatni post {date}",
"account.featured_tags.last_status_never": "Brak postów",
"account.follow": "Obserwuj",
@ -33,9 +43,11 @@
"account.followers": "Obserwujący",
"account.followers.empty": "Nikt jeszcze nie obserwuje tego użytkownika.",
"account.followers_counter": "{count, plural, one {{counter} obserwujący} few {{counter} obserwujących} many {{counter} obserwujących} other {{counter} obserwujących}}",
"account.followers_you_know_counter": "{counter} które znasz",
"account.following": "Obserwowani",
"account.following_counter": "{count, plural, one {{counter} obserwowany} few {{counter} obserwowanych} many {{counter} obserwowanych} other {{counter} obserwowanych}}",
"account.follows.empty": "Ten użytkownik nie obserwuje jeszcze nikogo.",
"account.follows_you": "Obserwuje cię",
"account.go_to_profile": "Przejdź do profilu",
"account.hide_reblogs": "Ukryj podbicia od @{name}",
"account.in_memoriam": "Ku pamięci.",
@ -50,18 +62,23 @@
"account.mute_notifications_short": "Wycisz powiadomienia",
"account.mute_short": "Wycisz",
"account.muted": "Wyciszony",
"account.muting": "Wyciszenie",
"account.mutual": "Obserwujecie siebie nazwajem",
"account.no_bio": "Brak opisu.",
"account.open_original_page": "Otwórz stronę oryginalną",
"account.posts": "Wpisy",
"account.posts_with_replies": "Wpisy i odpowiedzi",
"account.remove_from_followers": "Usuń {name} z obserwujących",
"account.report": "Zgłoś @{name}",
"account.requested": "Oczekująca prośba, kliknij aby anulować",
"account.requested_follow": "{name} chce cię zaobserwować",
"account.requests_to_follow_you": "Prośby o obserwowanie",
"account.share": "Udostępnij profil @{name}",
"account.show_reblogs": "Pokazuj podbicia od @{name}",
"account.statuses_counter": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}",
"account.unblock": "Odblokuj @{name}",
"account.unblock_domain": "Odblokuj domenę {domain}",
"account.unblock_domain_short": "Odblokuj",
"account.unblock_short": "Odblokuj",
"account.unendorse": "Nie wyświetlaj w profilu",
"account.unfollow": "Nie obserwuj",
@ -202,6 +219,12 @@
"confirmations.delete_list.confirm": "Usuń",
"confirmations.delete_list.message": "Czy na pewno chcesz trwale usunąć tę listę?",
"confirmations.delete_list.title": "Usunąć listę?",
"confirmations.discard_draft.confirm": "Odrzuć i kontynuuj",
"confirmations.discard_draft.edit.cancel": "Wznów edytowanie",
"confirmations.discard_draft.edit.message": "Kontynuowanie spowoduje utratę wszystkich zmian wprowadzonych przez Ciebie w aktualnie edytowanym poście.",
"confirmations.discard_draft.edit.title": "Odrzucić zmiany w poście?",
"confirmations.discard_draft.post.cancel": "Wznów wersję roboczą",
"confirmations.discard_draft.post.title": "Anulować wersję roboczą?",
"confirmations.discard_edit_media.confirm": "Odrzuć",
"confirmations.discard_edit_media.message": "Masz niezapisane zmiany w opisie lub podglądzie, odrzucić je mimo to?",
"confirmations.follow_to_list.confirm": "Zaobserwuj i dodaj do listy",
@ -218,6 +241,9 @@
"confirmations.redraft.confirm": "Usuń i popraw",
"confirmations.redraft.message": "Czy na pewno chcesz usunąć i poprawić ten wpis? Polubienia, podbicia i komentarze pierwotnego wpisu zostaną utracone.",
"confirmations.redraft.title": "Usunąć i poprawić wpis?",
"confirmations.remove_from_followers.confirm": "Usuń obserwującego",
"confirmations.remove_from_followers.message": "{name} przestanie Cię obserwować. Czy na pewno chcesz kontynuować?",
"confirmations.remove_from_followers.title": "Usunąć obserwującego?",
"confirmations.unfollow.confirm": "Nie obserwuj",
"confirmations.unfollow.message": "Czy na pewno nie chcesz obserwować {name}?",
"confirmations.unfollow.title": "Cofnąć obserwację?",
@ -307,9 +333,15 @@
"errors.unexpected_crash.copy_stacktrace": "Skopiuj stacktrace do schowka",
"errors.unexpected_crash.report_issue": "Zgłoś problem",
"explore.suggested_follows": "Ludzie",
"explore.title": "Na czasie",
"explore.trending_links": "Aktualności",
"explore.trending_statuses": "Wpisy",
"explore.trending_tags": "Hasztagi",
"featured_carousel.header": "{count, plural, one {Przypięty post} other {Przypięte posty}}",
"featured_carousel.next": "Następny",
"featured_carousel.post": "Opublikuj",
"featured_carousel.previous": "Poprzedni",
"featured_carousel.slide": "{index} z {total}",
"filter_modal.added.context_mismatch_explanation": "To filtrowanie nie dotyczy kategorii, w której pojawił się ten wpis. Jeśli chcesz, aby wpis był filtrowany również w tym kontekście, musisz edytować ustawienia filtrowania.",
"filter_modal.added.context_mismatch_title": "Niewłaściwy kontekst!",
"filter_modal.added.expired_explanation": "Ta kategoria filtrowania wygasła, aby ją zastosować, należy zmienić datę wygaśnięcia.",
@ -362,6 +394,8 @@
"generic.saved": "Zapisano",
"getting_started.heading": "Pierwsze kroki",
"hashtag.admin_moderation": "Otwórz interfejs moderacji #{name}",
"hashtag.browse": "Przeglądaj posty z #{hashtag}",
"hashtag.browse_from_account": "Przeglądaj posty od @{name} z #{hashtag}",
"hashtag.column_header.tag_mode.all": "i {additional}",
"hashtag.column_header.tag_mode.any": "lub {additional}",
"hashtag.column_header.tag_mode.none": "bez {additional}",
@ -374,7 +408,10 @@
"hashtag.counter_by_accounts": "{count, plural, one {{counter} osoba} few {{counter} osoby} many {{counter} osób} other {{counter} osób}}",
"hashtag.counter_by_uses": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}",
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}} dzisiaj",
"hashtag.feature": "Wyróżnij w profilu",
"hashtag.follow": "Obserwuj hasztag",
"hashtag.mute": "Wycisz #{hashtag}",
"hashtag.unfeature": "Nie wyróżniaj w profilu",
"hashtag.unfollow": "Przestań obserwować hashtag",
"hashtags.and_other": "…i {count, plural, other {jeszcze #}}",
"hints.profiles.followers_may_be_missing": "Niektórzy obserwujący ten profil mogą być niewidoczni.",
@ -383,6 +420,7 @@
"hints.profiles.see_more_followers": "Zobacz więcej obserwujących na {domain}",
"hints.profiles.see_more_follows": "Zobacz więcej obserwowanych na {domain}",
"hints.profiles.see_more_posts": "Zobacz więcej wpisów na {domain}",
"home.column_settings.show_quotes": "Pokaż cytaty",
"home.column_settings.show_reblogs": "Pokazuj podbicia",
"home.column_settings.show_replies": "Pokazuj odpowiedzi",
"home.hide_announcements": "Ukryj ogłoszenia",
@ -456,6 +494,8 @@
"keyboard_shortcuts.translate": "aby przetłumaczyć wpis",
"keyboard_shortcuts.unfocus": "Opuść pole tekstowe",
"keyboard_shortcuts.up": "Przesuń w górę na liście",
"learn_more_link.got_it": "Rozumiem",
"learn_more_link.learn_more": "Dowiedz się więcej",
"lightbox.close": "Zamknij",
"lightbox.next": "Następne",
"lightbox.previous": "Poprzednie",
@ -505,8 +545,10 @@
"mute_modal.you_wont_see_mentions": "Nie zobaczysz wpisów wzmiankujących tę osobę.",
"mute_modal.you_wont_see_posts": "Nie zobaczysz wpisów tej osoby, ale ona może widzieć twoje.",
"navigation_bar.about": "O serwerze",
"navigation_bar.account_settings": "Hasło i zabezpieczenia",
"navigation_bar.administration": "Administracja",
"navigation_bar.advanced_interface": "Otwórz w widoku zaawansowanym",
"navigation_bar.automated_deletion": "Automatyczne usuwanie postów",
"navigation_bar.blocks": "Zablokowani",
"navigation_bar.bookmarks": "Zakładki",
"navigation_bar.direct": "Wzmianki bezpośrednie",
@ -516,13 +558,21 @@
"navigation_bar.follow_requests": "Prośby o obserwowanie",
"navigation_bar.followed_tags": "Obserwowane hasztagi",
"navigation_bar.follows_and_followers": "Obserwowani i obserwujący",
"navigation_bar.import_export": "Import i eksport",
"navigation_bar.lists": "Listy",
"navigation_bar.logout": "Wyloguj",
"navigation_bar.moderation": "Moderacja",
"navigation_bar.more": "Więcej",
"navigation_bar.mutes": "Wyciszeni",
"navigation_bar.opened_in_classic_interface": "Wpisy, konta i inne określone strony są domyślnie otwierane w widoku klasycznym.",
"navigation_bar.preferences": "Ustawienia",
"navigation_bar.privacy_and_reach": "Prywatność i zasięg",
"navigation_bar.search": "Szukaj",
"navigation_bar.search_trends": "Szukaj / Na czasie",
"navigation_panel.collapse_followed_tags": "Zwiń menu obserwowanych hashtagów",
"navigation_panel.collapse_lists": "Zwiń menu listy",
"navigation_panel.expand_followed_tags": "Rozwiń menu obserwowanych hashtagów",
"navigation_panel.expand_lists": "Rozwiń menu listy",
"not_signed_in_indicator.not_signed_in": "Zaloguj się, aby uzyskać dostęp.",
"notification.admin.report": "{name} zgłosił {target}",
"notification.admin.report_account": "{name} zgłosił(a) {count, plural, one {1 wpis} few {# wpisy} other {# wpisów}} z {target} w kategorii {category}",
@ -749,6 +799,7 @@
"report_notification.categories.violation": "Naruszenie zasad",
"report_notification.categories.violation_sentence": "naruszenie zasad",
"report_notification.open": "Otwórz zgłoszenie",
"search.clear": "Wyczyść wyszukiwanie",
"search.no_recent_searches": "Brak ostatnich wyszukiwań",
"search.placeholder": "Szukaj",
"search.quick_action.account_search": "Profile pasujące do {x}",
@ -790,6 +841,8 @@
"status.bookmark": "Dodaj zakładkę",
"status.cancel_reblog_private": "Cofnij podbicie",
"status.cannot_reblog": "Ten wpis nie może zostać podbity",
"status.context.load_new_replies": "Dostępne są nowe odpowiedzi",
"status.context.loading": "Sprawdzanie kolejnych odpowiedzi",
"status.continued_thread": "Ciąg dalszy wątku",
"status.copy": "Skopiuj odnośnik do wpisu",
"status.delete": "Usuń",
@ -815,6 +868,10 @@
"status.mute_conversation": "Wycisz konwersację",
"status.open": "Rozszerz ten wpis",
"status.pin": "Przypnij do profilu",
"status.quote_error.filtered": "Ukryte z powodu jednego z Twoich filtrów",
"status.quote_error.not_available": "Post niedostępny",
"status.quote_error.pending_approval": "Post oczekujący",
"status.quote_post_author": "Zacytowano post @{name}",
"status.read_more": "Czytaj dalej",
"status.reblog": "Podbij",
"status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu",
@ -844,8 +901,13 @@
"subscribed_languages.save": "Zapisz zmiany",
"subscribed_languages.target": "Zmień subskrybowane języki dla {target}",
"tabs_bar.home": "Strona główna",
"tabs_bar.menu": "Menu",
"tabs_bar.notifications": "Powiadomienia",
"tabs_bar.publish": "Nowy post",
"tabs_bar.search": "Szukaj",
"terms_of_service.effective_as_of": "Obowiązuje od {date}",
"terms_of_service.title": "Warunki korzystania z usługi",
"terms_of_service.upcoming_changes_on": "Nadchodzące zmiany od {date}",
"time_remaining.days": "{number, plural, one {Pozostał # dzień} few {Pozostały # dni} many {Pozostało # dni} other {Pozostało # dni}}",
"time_remaining.hours": "{number, plural, one {Pozostała # godzina} few {Pozostały # godziny} many {Pozostało # godzin} other {Pozostało # godzin}}",
"time_remaining.minutes": "{number, plural, one {Pozostała # minuta} few {Pozostały # minuty} many {Pozostało # minut} other {Pozostało # minut}}",
@ -876,6 +938,12 @@
"video.expand": "Rozszerz film",
"video.fullscreen": "Pełny ekran",
"video.hide": "Ukryj film",
"video.mute": "Wycisz",
"video.pause": "Pauzuj",
"video.play": "Odtwórz"
"video.play": "Odtwórz",
"video.skip_backward": "Skocz do tyłu",
"video.skip_forward": "Skocz do przodu",
"video.unmute": "Wyłącz wyciszenie",
"video.volume_down": "Zmniejsz głośność",
"video.volume_up": "Zwiększ głośność"
}

View File

@ -498,6 +498,8 @@
"keyboard_shortcuts.translate": "bir gönderiyi çevirmek için",
"keyboard_shortcuts.unfocus": "Aramada bir gönderiye odaklanmamak için",
"keyboard_shortcuts.up": "Listede yukarıya çıkmak için",
"learn_more_link.got_it": "Anladım",
"learn_more_link.learn_more": "Daha fazla bilgi edin",
"lightbox.close": "Kapat",
"lightbox.next": "Sonraki",
"lightbox.previous": "Önceki",
@ -873,6 +875,11 @@
"status.open": "Bu gönderiyi genişlet",
"status.pin": "Profile sabitle",
"status.quote_error.filtered": "Bazı filtrelerinizden dolayı gizlenmiştir",
"status.quote_error.not_available": "Gönderi kullanılamıyor",
"status.quote_error.pending_approval": "Gönderi beklemede",
"status.quote_error.pending_approval_popout.body": "Fediverse genelinde paylaşılan alıntıların görüntülenmesi zaman alabilir, çünkü farklı sunucuların farklı protokolleri vardır.",
"status.quote_error.pending_approval_popout.title": "Bekleyen bir teklif mi var? Sakin olun.",
"status.quote_post_author": "@{name} adlı kullanıcının bir gönderisini alıntıladı",
"status.read_more": "Devamını okuyun",
"status.reblog": "Yeniden paylaş",
"status.reblog_private": "Özgün görünürlük ile yeniden paylaş",

View File

@ -45,7 +45,7 @@ const AccountRoleFactory = ImmutableRecord<AccountRoleShape>({
// Account
export interface AccountShape
extends Required<
Omit<ApiAccountJSON, 'emojis' | 'fields' | 'roles' | 'moved'>
Omit<ApiAccountJSON, 'emojis' | 'fields' | 'roles' | 'moved' | 'url'>
> {
emojis: ImmutableList<CustomEmoji>;
fields: ImmutableList<AccountField>;
@ -55,6 +55,7 @@ export interface AccountShape
note_plain: string | null;
hidden: boolean;
moved: string | null;
url: string;
}
export type Account = RecordOf<AccountShape>;
@ -148,8 +149,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
note_emojified: emojify(accountNote, emojiMap),
note_plain: unescapeHTML(accountNote),
url:
accountJSON.url.startsWith('http://') ||
accountJSON.url.startsWith('https://')
accountJSON.url?.startsWith('http://') ||
accountJSON.url?.startsWith('https://')
? accountJSON.url
: accountJSON.uri,
});

View File

@ -36,6 +36,7 @@ export type NotificationGroupFavourite =
export type NotificationGroupReblog = BaseNotificationWithStatus<'reblog'>;
export type NotificationGroupStatus = BaseNotificationWithStatus<'status'>;
export type NotificationGroupMention = BaseNotificationWithStatus<'mention'>;
export type NotificationGroupQuote = BaseNotificationWithStatus<'quote'>;
export type NotificationGroupPoll = BaseNotificationWithStatus<'poll'>;
export type NotificationGroupUpdate = BaseNotificationWithStatus<'update'>;
export type NotificationGroupFollow = BaseNotification<'follow'>;
@ -87,6 +88,7 @@ export type NotificationGroup =
| NotificationGroupReblog
| NotificationGroupStatus
| NotificationGroupMention
| NotificationGroupQuote
| NotificationGroupPoll
| NotificationGroupUpdate
| NotificationGroupFollow
@ -137,6 +139,7 @@ export function createNotificationGroupFromJSON(
case 'reblog':
case 'status':
case 'mention':
case 'quote':
case 'poll':
case 'update': {
const { status_id: statusId, ...groupWithoutStatus } = group;
@ -209,6 +212,7 @@ export function createNotificationGroupFromNotificationJSON(
case 'reblog':
case 'status':
case 'mention':
case 'quote':
case 'poll':
case 'update':
return {

View File

@ -20,5 +20,16 @@ export function loadPolyfills() {
loadIntlPolyfills(),
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- those properties might not exist in old browsers, even if they are always here in types
needsExtraPolyfills && importExtraPolyfills(),
loadEmojiPolyfills(),
]);
}
// In the case of no /v support, rely on the emojibase data.
async function loadEmojiPolyfills() {
if (!('unicodeSets' in RegExp.prototype)) {
emojiRegexPolyfill = (await import('emojibase-regex/emoji')).default;
}
}
// Null unless polyfill is needed.
export let emojiRegexPolyfill: RegExp | null = null;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m228-240 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T458-480L320-240h-92Zm360 0 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T818-480L680-240h-92Z"/></svg>

After

Width:  |  Height:  |  Size: 322 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m228-240 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T458-480L320-240h-92Zm360 0 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T818-480L680-240h-92ZM320-500q25 0 42.5-17.5T380-560q0-25-17.5-42.5T320-620q-25 0-42.5 17.5T260-560q0 25 17.5 42.5T320-500Zm360 0q25 0 42.5-17.5T740-560q0-25-17.5-42.5T680-620q-25 0-42.5 17.5T620-560q0 25 17.5 42.5T680-500Zm0-60Zm-360 0Z"/></svg>

After

Width:  |  Height:  |  Size: 538 B

View File

@ -17,9 +17,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
return reject_payload! if unsupported_object_type? || non_matching_uri_hosts?(@account.uri, object_uri) || tombstone_exists? || !related_to_local_activity?
with_redis_lock("create:#{object_uri}") do
Status.uncached do
return if delete_arrived_first?(object_uri) || poll_vote?
@status = find_existing_status
end
if @status.nil?
process_status
@ -64,6 +66,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
resolve_thread(@status)
resolve_unresolved_mentions(@status)
fetch_replies(@status)
fetch_and_verify_quote
distribute
forward_for_reply
end
@ -204,11 +207,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
@quote.status = status
@quote.save
embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @json['context'])
ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id], depth: @options[:depth])
rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] })
end
def process_tags
@ -378,6 +376,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
Rails.logger.warn "Error fetching replies: #{e}"
end
def fetch_and_verify_quote
return if @quote.nil?
embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @json['context'])
ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id], depth: @options[:depth])
rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] })
end
def conversation_from_uri(uri)
return nil if uri.nil?
return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri)

View File

@ -9,12 +9,32 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity
quoted_status = status_from_uri(object_uri)
return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable?
# For now, we don't support being quoted by external servers
if Mastodon::Feature.outgoing_quotes_enabled? && StatusPolicy.new(@account, quoted_status).quote?
accept_quote_request!(quoted_status)
else
reject_quote_request!(quoted_status)
end
end
private
def accept_quote_request!(quoted_status)
status = status_from_uri(@json['instrument'])
# TODO: import inlined quote post if possible
status ||= ActivityPub::FetchRemoteStatusService.new.call(@json['instrument'], on_behalf_of: @account.followers.local.first, request_id: @options[:request_id])
# TODO: raise if status is nil
# Sanity check
return unless status.quote.quoted_status == quoted_status
status.quote.ensure_quoted_access
status.quote.update!(activity_uri: @json['id'])
status.quote.accept!
json = Oj.dump(serialize_payload(status.quote, ActivityPub::AcceptQuoteRequestSerializer))
ActivityPub::DeliveryWorker.perform_async(json, quoted_status.account_id, @account.inbox_url)
end
def reject_quote_request!(quoted_status)
quote = Quote.new(
quoted_status: quoted_status,

View File

@ -32,8 +32,12 @@ class Fasp::Request
.send(verb, url, body:)
validate!(response)
@provider.delivery_failure_tracker.track_success!
response.parse if response.body.present?
rescue *::Mastodon::HTTP_CONNECTION_ERRORS
@provider.delivery_failure_tracker.track_failure!
raise
end
def request_headers(_verb, _url, body = '')

View File

@ -6,7 +6,7 @@ class NotificationMailer < ApplicationMailer
:routing
before_action :process_params
with_options only: %i(mention favourite reblog) do
with_options only: %i(mention favourite reblog quote) do
before_action :set_status
after_action :thread_by_conversation!
end
@ -27,6 +27,14 @@ class NotificationMailer < ApplicationMailer
end
end
def quote
return if @status.blank?
locale_for_account(@me) do
mail subject: default_i18n_subject(name: @status.account.acct)
end
end
def follow
locale_for_account(@me) do
mail subject: default_i18n_subject(name: @account.acct)

View File

@ -118,6 +118,10 @@ class Fasp::Provider < ApplicationRecord
save!
end
def delivery_failure_tracker
@delivery_failure_tracker ||= DeliveryFailureTracker.new(base_url, resolution: :minutes)
end
private
def create_keypair

View File

@ -30,6 +30,7 @@ class Notification < ApplicationRecord
'FollowRequest' => :follow_request,
'Favourite' => :favourite,
'Poll' => :poll,
'Quote' => :quote,
}.freeze
# Please update app/javascript/api_types/notification.ts if you change this

View File

@ -56,6 +56,12 @@ class Quote < ApplicationRecord
accepted? || !legacy?
end
def ensure_quoted_access
status.mentions.create!(account: quoted_account, silent: true)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
nil
end
def schedule_refresh_if_stale!
return unless quoted_status_id.present? && approval_uri.present? && updated_at <= BACKGROUND_REFRESH_INTERVAL.ago

View File

@ -43,6 +43,7 @@ class UserSettings
setting :reblog, default: false
setting :favourite, default: false
setting :mention, default: true
setting :quote, default: true
setting :follow_request, default: true
setting :report, default: true
setting :pending_account, default: true

View File

@ -43,7 +43,7 @@ class UsernameBlock < ApplicationRecord
def self.matches?(str, allow_with_approval: false)
normalized_str = str.downcase.gsub(Regexp.union(HOMOGLYPHS.keys), HOMOGLYPHS)
where(allow_with_approval: allow_with_approval).matches_exactly(normalized_str).or(matches_partially(normalized_str)).any?
matches_exactly(normalized_str).or(matches_partially(normalized_str)).where(allow_with_approval: allow_with_approval).any?
end
def to_log_human_identifier

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class ActivityPub::AcceptQuoteRequestSerializer < ActivityPub::Serializer
attributes :id, :type, :actor, :result
has_one :object, serializer: ActivityPub::QuoteRequestSerializer
def id
[ActivityPub::TagManager.instance.uri_for(object.quoted_account), '#accepts/quote_requests/', object.id].join
end
def type
'Accept'
end
def actor
ActivityPub::TagManager.instance.uri_for(object.quoted_account)
end
def result
ActivityPub::TagManager.instance.approval_uri_for(object)
end
end

View File

@ -23,6 +23,7 @@ class ActivityPub::QuoteRequestSerializer < ActivityPub::Serializer
end
def instrument
# TODO: inline object?
ActivityPub::TagManager.instance.uri_for(object.status)
end
end

View File

@ -65,7 +65,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
end
def url
ActivityPub::TagManager.instance.url_for(object)
ActivityPub::TagManager.instance.url_for(object) || ActivityPub::TagManager.instance.uri_for(object)
end
def uri

View File

@ -24,7 +24,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
end
def status_type?
[:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type)
[:favourite, :reblog, :status, :mention, :poll, :update, :quote].include?(object.type)
end
def report_type?

View File

@ -18,6 +18,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
@poll_changed = false
@quote_changed = false
@request_id = request_id
@quote = nil
# Only native types can be updated at the moment
return @status if !expected_type? || already_updated_more_recently?
@ -49,6 +50,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
create_edits!
end
fetch_and_verify_quote!(@quote, @status_parser.quote_uri) if @quote.present?
download_media_files!
queue_poll_notifications!
@ -312,10 +314,10 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
@quote_changed = true
end
@quote = quote
quote.save
fetch_and_verify_quote!(quote, quote_uri)
elsif @status.quote.present?
@quote = nil
@status.quote.destroy!
@quote_changed = true
end

View File

@ -93,14 +93,10 @@ class PostStatusService < BaseService
def attach_quote!(status)
return if @quoted_status.nil?
# NOTE: for now this is only for convenience in testing, as we don't support the request flow nor serialize quotes in ActivityPub
# we only support incoming quotes so far
status.quote = Quote.create(quoted_status: @quoted_status, status: status)
if @quoted_status.local? && StatusPolicy.new(@status.account, @quoted_status).quote?
# TODO: produce a QuoteAuthorization
status.quote.accept!
end
status.quote.ensure_quoted_access
status.quote.accept! if @quoted_status.local? && StatusPolicy.new(@status.account, @quoted_status).quote?
end
def safeguard_mentions!(status)

View File

@ -0,0 +1,16 @@
= content_for :heading do
= render 'application/mailer/heading',
image_url: frontend_asset_url('images/mailer-new/heading/boost.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' }
%tr
%td.email-body-padding-td
%table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
%tr
%td.email-inner-card-td
= render 'status', status: @status, time_zone: @me.user_time_zone
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
%tr
%td.email-padding-top-24
= render 'application/mailer/button', text: t('notification_mailer.mention.action'), url: web_url("@#{@status.account.pretty_acct}/#{@status.id}")

View File

@ -0,0 +1,5 @@
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
<%= raw t('notification_mailer.quote.body', name: @status.account.pretty_acct) %>
<%= render 'status', status: @status %>

View File

@ -18,6 +18,7 @@
= ff.input :'notification_emails.reblog', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.reblog')
= ff.input :'notification_emails.favourite', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.favourite')
= ff.input :'notification_emails.mention', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.mention')
= ff.input :'notification_emails.quote', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.quote')
.fields-group
= ff.input :always_send_emails, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_always_send_emails'), hint: I18n.t('simple_form.hints.defaults.setting_always_send_emails')

View File

@ -10,6 +10,6 @@ class FetchReplyWorker
batch = WorkerBatch.new(options.delete('batch_id')) if options['batch_id']
result = FetchRemoteStatusService.new.call(child_url, **options.symbolize_keys)
ensure
batch&.remove_job(jid, increment: result.present?)
batch&.remove_job(jid, increment: result&.previously_new_record?)
end
end

View File

@ -49,8 +49,14 @@ pl:
attributes:
reblog:
taken: status już istnieje
terms_of_service:
attributes:
effective_date:
too_soon: jest zbyt wcześnie, musi być później niż %{date}
user:
attributes:
date_of_birth:
below_limit: jest poniżej granicy wiekowej
email:
blocked: używa niedozwolonego dostawcy poczty elektronicznej
unreachable: wydaje się nie istnieć

View File

@ -1905,7 +1905,7 @@ da:
ownership: Andres indlæg kan ikke fastgøres
reblog: En fremhævelse kan ikke fastgøres
quote_policies:
followers: Kun dine følgere
followers: Kun egne følgere
nobody: Ingen
public: Alle
title: '%{name}: "%{quote}"'

View File

@ -1905,6 +1905,8 @@ el:
ownership: Δεν μπορείς να καρφιτσώσεις ανάρτηση κάποιου άλλου
reblog: Οι ενισχύσεις δεν καρφιτσώνονται
quote_policies:
followers: Μόνο οι ακόλουθοί σου
nobody: Κανένας
public: Όλοι
title: '%{name}: "%{quote}"'
visibilities:

View File

@ -1687,6 +1687,10 @@ en:
title: New mention
poll:
subject: A poll by %{name} has ended
quote:
body: 'Your post was quoted by %{name}:'
subject: "%{name} quoted your post"
title: New quote
reblog:
body: 'Your post was boosted by %{name}:'
subject: "%{name} boosted your post"

View File

@ -325,6 +325,8 @@ et:
create: Loo teadaanne
title: Uus teadaanne
preview:
disclaimer: Kuna kasutajd ei saa neist postiitustest keelduda, siis peaksid teavituskirjad keskenduma vaid olulistele teemadele nagi võimalik isiklike andmete leke või serveri tegevuse lõpetamine.
explanation_html: 'See e-kiri saadetakse <strong>%{display_count}-le kasutajale</strong>. E-kirjas sisaldub järgnev tekst:'
title: Info teavituse üle vaatamine
publish: Postita
published_msg: Teadaande avaldamine õnnestus!
@ -992,6 +994,7 @@ et:
explanation_html: Esitatud teenusetingimuste näidis on mõeldud ainult teavitamise eesmärgil ja seda ei tohiks tõlgendada kui juriidilist nõuannet mis tahes küsimuses. Palun konsulteeri olukorra ja konkreetsete juriidiliste küsimuste osas oma õigusnõustajaga.
title: Teenuse tingimuste seadistamine
history: Ajalugu
notified_on_html: 'Kasutajad on teavitatud: %{date}'
notify_users: Teata kasutajatele
preview:
explanation_html: 'See e-kiri saadetakse <strong>%{display_count}-le kasutajale</strong>, kes olid liitunud enne %{date}. E-kirjas sisaldub järgnev tekst:'

View File

@ -1905,6 +1905,8 @@ fo:
ownership: Postar hjá øðrum kunnu ikki festast
reblog: Ein stimbran kann ikki festast
quote_policies:
followers: Einans tey, ið fylgja tær
nobody: Eingin
public: Øll
title: '%{name}: "%{quote}"'
visibilities:

View File

@ -255,6 +255,7 @@ nan:
create_relay_html: "%{name} 建立中繼 %{target}"
create_unavailable_domain_html: "%{name} 停止送kàu域名 %{target}"
create_user_role_html: "%{name} 建立 %{target} 角色"
create_username_block_html: "%{name} 加添用者ê名包含 %{target} ê規則ah"
demote_user_html: "%{name} kā用者 %{target} 降級"
destroy_announcement_html: "%{name} kā公告 %{target} thâi掉ah"
destroy_canonical_email_block_html: "%{name} kā hash是 %{target} ê電子phue取消封鎖ah"
@ -268,6 +269,7 @@ nan:
destroy_status_html: "%{name} kā %{target} ê PO文thâi掉"
destroy_unavailable_domain_html: "%{name} 恢復送kàu域名 %{target}"
destroy_user_role_html: "%{name} thâi掉 %{target} 角色"
destroy_username_block_html: "%{name} thâi掉用者ê名包含 %{target} ê規則ah"
disable_2fa_user_html: "%{name} 停止使用者 %{target} 用雙因素驗證"
disable_custom_emoji_html: "%{name} kā 新ê emoji %{target} 停止使用ah"
disable_relay_html: "%{name} 停止使用中繼 %{target}"
@ -302,6 +304,7 @@ nan:
update_report_html: "%{name} 更新 %{target} ê檢舉"
update_status_html: "%{name} kā %{target} ê PO文更新"
update_user_role_html: "%{name} 更改 %{target} 角色"
update_username_block_html: "%{name} 更新用者ê名包含 %{target} ê規則ah"
deleted_account: thâi掉ê口座
empty: Tshuē無log。
filter_by_action: 照動作過濾
@ -639,6 +642,57 @@ nan:
other: "%{count} 篇筆記"
action_log: 審查日誌
action_taken_by: 操作由
actions:
delete_description_html: 受檢舉ê PO文ē thâi掉而且ē用tsi̍t ue̍h橫tsuā記錄幫tsān lí提升kâng tsi̍t ê用戶未來ê違規。
mark_as_sensitive_description_html: 受檢舉ê PO文內ê媒體ē標做敏感而且ē用tsi̍t ue̍h橫tsuā記錄幫tsān lí提升kâng tsi̍t ê用戶未來ê違規。
other_description_html: 看其他控制tsit ê口座ê所行kap自訂聯絡受檢舉ê口座ê選項。
resolve_description_html: Buē用行動控制受檢舉ê口座mā無用橫tsuā記錄而且tsit ê報告ē關掉。
silence_description_html: 本口座kan-ta ē hōo早前跟tuè ê á是手動tshiau ê看見大大限制看見ê範圍。設定隨時ē當回復。請關所有tuì tsit ê口座ê檢舉。
suspend_description_html: Tsit ê口座kap伊ê內容ē bē當用落尾ē thâi掉mā bē當hām伊互動。30 kang以內通回復。請關所有tuì tsit ê口座ê檢舉。
actions_description_html: 決定行siánn物行動來解決tsit ê檢舉。Nā lí tuì受檢舉ê口座採用處罰電子phue通知ē送予in除非選擇 <strong>Pùn-sò phue</strong> 類別。
actions_description_remote_html: 決定行siánn物行動來解決tsit ê檢舉。Tse kan-ta ē影響 <strong>lí ê</strong> 服侍器hām tsit ê遠距離服侍器聯絡kap處理伊ê內容ê方法。
actions_no_posts: Tsit份檢舉無beh thâi掉ê相關PO文
add_to_report: 加添其他ê內容kàu檢舉
already_suspended_badges:
local: 已經佇tsit ê服侍器停止權限ah
remote: 已經佇in ê服侍器停止權限ah
are_you_sure: Lí kám確定
assign_to_self: 分配hōo家kī
assigned: 分配管理者
by_target_domain: 受檢舉ê口座ê網域
cancel: 取消
category: 類別
category_description_html: Tsit ê 受檢舉ê口座kap/á是內容ē佇kap tsit ê口座ê聯絡內底引用。
comment:
none:
comment_description_html: 為著提供其他資訊,%{name} 寫:
confirm: 確認
confirm_action: 確認kā %{acct} 審核ê動作
created_at: 檢舉tī
delete_and_resolve: Thâi掉PO文
forwarded: 轉送ah
forwarded_replies_explanation: 本報告是tuì別站ê用者送ê關係別站ê內容。本報告轉hōo lí因為受檢舉ê內容是回應lí ê服侍器ê用者。
forwarded_to: 有轉送kàu %{domain}
mark_as_resolved: 標做「解決ah」
mark_as_sensitive: 標做敏感
mark_as_unresolved: 標做「無解決」
no_one_assigned: 無lâng
notes:
create: 加添筆記
create_and_resolve: 標「處理ah」留筆記
create_and_unresolve: 留筆記koh重開
delete: Thâi掉
placeholder: 描述有行siánn物行動á是其他關聯ê更新……
title: 筆記
notes_description_html: 檢視á是留筆記hōo別ê管理者kap未來ê家己
processed_msg: '檢舉 #%{id} 處理成功ah'
quick_actions_description_html: 緊行行動á是giú kàu下kha看檢舉ê內容
remote_user_placeholder: tuì %{instance} 來ê遠距離用者
reopen: 重頭phah開檢舉
report: '檢舉 #%{id}'
roles:
privileges:
manage_announcements: 管理公告
statuses:
language: 語言
trends:

View File

@ -196,6 +196,7 @@ pl:
create_relay: Utwórz przekaźnik
create_unavailable_domain: Utwórz niedostępną domenę
create_user_role: Utwórz rolę
create_username_block: Utwórz zasadę nazwy użytkownika
demote_user: Zdegraduj użytkownika
destroy_announcement: Usuń ogłoszenie
destroy_canonical_email_block: Usuń blokadę e-mail
@ -209,6 +210,7 @@ pl:
destroy_status: Usuń wpis
destroy_unavailable_domain: Usuń niedostępną domenę
destroy_user_role: Zlikwiduj rolę
destroy_username_block: Usuń zasadę nazwy użytkownika
disable_2fa_user: Wyłącz 2FA
disable_custom_emoji: Wyłącz niestandardowe emoji
disable_relay: Wyłącz przekaźnik
@ -243,6 +245,7 @@ pl:
update_report: Wiadomości or raporcie
update_status: Aktualizuj wpis
update_user_role: Aktualizuj rolę
update_username_block: Zaktualizuj zasadę nazwy użytkownika
actions:
approve_appeal_html: "%{name} zatwierdził(-a) odwołanie decyzji moderacyjnej od %{target}"
approve_user_html: "%{name} zatwierdził rejestrację od %{target}"
@ -261,6 +264,7 @@ pl:
create_relay_html: "%{name} utworzył przekaźnik %{target}"
create_unavailable_domain_html: "%{name} przestał(a) doręczać na domenę %{target}"
create_user_role_html: "%{name} utworzył rolę %{target}"
create_username_block_html: Użytkownik %{name} dodał zasadę dla nazw użytkowników zawierających %{target}
demote_user_html: "%{name} zdegradował(a) użytkownika %{target}"
destroy_announcement_html: "%{name} usunął(-ęła) ogłoszenie %{target}"
destroy_canonical_email_block_html: "%{name} odblokował(a) e-mail z hashem %{target}"
@ -274,6 +278,7 @@ pl:
destroy_status_html: "%{name} usunął(-ęła) wpis użytkownika %{target}"
destroy_unavailable_domain_html: "%{name} wznowił(a) doręczanie do domeny %{target}"
destroy_user_role_html: "%{name} usunął rolę %{target}"
destroy_username_block_html: Użytkownik %{name} usunął zasadę dla nazw użytkowników zawierających %{target}
disable_2fa_user_html: "%{name} wyłączył(a) uwierzytelnianie dwuskładnikowe użytkownikowi %{target}"
disable_custom_emoji_html: "%{name} wyłączył(a) emoji %{target}"
disable_relay_html: "%{name} wyłączył przekaźnik %{target}"
@ -308,6 +313,7 @@ pl:
update_report_html: "%{target} zaktualizowany przez %{name}"
update_status_html: "%{name} zaktualizował(a) wpis użytkownika %{target}"
update_user_role_html: "%{name} zmienił rolę %{target}"
update_username_block_html: Użytkownik %{name} zaktualizował zasadę dla nazw użytkowników zawierających %{target}
deleted_account: usunięte konto
empty: Nie znaleziono aktywności w dzienniku.
filter_by_action: Filtruj według działania
@ -315,6 +321,7 @@ pl:
title: Dziennik działań administracyjnych
unavailable_instance: "(domena niedostępna)"
announcements:
back: Powrót do ogłoszeń
destroyed_msg: Pomyślnie usunięto ogłoszenie!
edit:
title: Edytuj ogłoszenie
@ -323,6 +330,9 @@ pl:
new:
create: Utwórz ogłoszenie
title: Nowe ogłoszenie
preview:
explanation_html: 'Wiadomość e-mail zostanie wysłana do <strong>%{display_count} użytkowników</strong>. Otrzymają oni wiadomość o następującej treści:'
title: Podgląd powiadomienia
publish: Opublikuj
published_msg: Pomyślnie opublikowano ogłoszenie!
scheduled_for: Zaplanowano na %{time}
@ -491,6 +501,29 @@ pl:
new:
title: Importuj zablokowane domeny
no_file: Nie wybrano pliku
fasp:
debug:
callbacks:
created_at: 'Utworzono:'
delete: Usuń
ip: Adres IP
providers:
active: Aktywne
base_url: Podstawowy adres URL
delete: Usuń
edit: Edytuj dostawców
finish_registration: Zakończ rejestrację
name: Nazwa
providers: Dostawca
registration_requested: Wymagana rejestracja
registrations:
confirm: Zatwierdź
reject: Odrzuć
save: Zapisz
sign_in: Zaloguj się
status: Status
title: Dostawcy usług pomocniczych Fediverse (Fediverse Auxiliary Service Providers)
title: FASP
follow_recommendations:
description_html: "<strong>Polecane obserwacje pomagają nowym użytkownikom szybko odnaleźć interesujące treści</strong>. Jeżeli użytkownik nie wchodził w interakcje z innymi wystarczająco często, aby powstały spersonalizowane rekomendacje, polecane są te konta. Są one obliczane każdego dnia na podstawie kombinacji kont o największej liczbie niedawnej aktywności i największej liczbie lokalnych obserwatorów dla danego języka."
language: Dla języka
@ -565,6 +598,8 @@ pl:
all: Wszystkie
limited: Ograniczone
title: Moderacja
moderation_notes:
title: Notatki moderacyjne
private_comment: Prywatny komentarz
public_comment: Publiczny komentarz
purge: Wyczyść
@ -779,11 +814,16 @@ pl:
title: Role
rules:
add_new: Dodaj zasadę
add_translation: Dodaj tłumaczenie
delete: Usuń
description_html: Chociaż większość twierdzi, że przeczytała i zgadza się z warunkami korzystania z usługi, zwykle ludzie nie czytają ich, dopóki nie pojawi się problem. <strong>Ułatw użytkownikom szybkie przejrzenie zasad serwera, umieszczając je na prostej liście punktowanej.</strong> Postaraj się, aby poszczególne zasady były krótkie i proste, ale staraj się też nie dzielić ich na wiele oddzielnych elementów.
edit: Edytuj zasadę
empty: Jeszcze nie zdefiniowano zasad serwera.
move_down: Przenieś w dół
move_up: Przenieś w górę
title: Regulamin serwera
translation: Tłumaczenie
translations: Tłumaczenia
settings:
about:
manage_rules: Zarządzaj regułami serwera
@ -809,6 +849,7 @@ pl:
discovery:
follow_recommendations: Polecane konta
preamble: Prezentowanie interesujących treści ma kluczowe znaczenie dla nowych użytkowników, którzy mogą nie znać nikogo z Mastodona. Kontroluj, jak różne funkcje odkrywania działają na Twoim serwerze.
privacy: Prywatność
profile_directory: Katalog profilów
public_timelines: Publiczne osie czasu
publish_statistics: Publikuj statystyki
@ -1066,6 +1107,22 @@ pl:
other: Użyte przez %{count} osób w ciągu ostatniego tygodnia
title: Rekomendacje i Trendy
trending: Popularne
username_blocks:
add_new: Dodaj nową
comparison:
contains: Zawiera
equals: Równa się
contains_html: Zawiera %{string}
delete: Usuń
edit:
title: Edytuj zasadę nazwy użytkownika
matches_exactly_html: Równa się %{string}
new:
create: Dodaj zasadę
title: Utwórz zasadę nazwy użytkownika
not_permitted: Brak uprawnień
title: Zasady nazwy użytkownika
updated_msg: Pomyślnie zaktualizowano zasadę nazwy użytkownika
warning_presets:
add_new: Dodaj nowy
delete: Usuń
@ -1332,6 +1389,10 @@ pl:
basic_information: Podstawowe informacje
hint_html: "<strong>Dostosuj to, co ludzie widzą na Twoim profilu publicznym i obok Twoich wpisów.</strong> Inne osoby są bardziej skłonne obserwować Cię i wchodzić z Tobą w interakcje, gdy masz wypełniony profil i zdjęcie profilowe."
other: Inne
emoji_styles:
auto: Automatycznie
native: Natywny
twemoji: Twemoji
errors:
'400': Wysłane zgłoszenie jest nieprawidłowe lub uszkodzone.
'403': Nie masz uprawnień, aby wyświetlić tę stronę.
@ -1899,12 +1960,17 @@ pl:
edited_at_html: Edytowane %{date}
errors:
in_reply_not_found: Post, na który próbujesz odpowiedzieć, nie istnieje.
quoted_status_not_found: Wpis, który próbujesz zacytować, nie istnieje.
over_character_limit: limit %{max} znaków przekroczony
pin_errors:
direct: Nie możesz przypiąć wpisu, który jest widoczny tylko dla wspomnianych użytkowników
limit: Przekroczyłeś maksymalną liczbę przypiętych wpisów
ownership: Nie możesz przypiąć cudzego wpisu
reblog: Nie możesz przypiąć podbicia wpisu
quote_policies:
followers: Tylko obserwujący
nobody: Nikt
public: Wszyscy
title: '%{name}: "%{quote}"'
visibilities:
direct: Bezpośredni

View File

@ -1682,37 +1682,37 @@ ru:
notification_mailer:
admin:
report:
subject: "%{name} отправил жалобу"
subject: Поступила жалоба от %{name}
sign_up:
subject: "%{name} зарегистрирован"
subject: "%{name} зарегистрировался (-лась) на сервере"
favourite:
body: "%{name} добавил(а) ваш пост в избранное:"
subject: "%{name} добавил(а) ваш пост в избранное"
title: Понравившийся статус
title: Ваш пост добавили в избранное
follow:
body: "%{name} теперь подписан(а) на вас!"
subject: "%{name} теперь подписан(а) на вас"
title: Новый подписчик
follow_request:
action: Управление запросами на подписку
action: Перейти к запросам на подписку
body: "%{name} отправил(а) вам запрос на подписку"
subject: "%{name} хочет подписаться на вас"
title: Новый запрос на подписку
mention:
action: Ответить
body: 'Вас упомянул(а) %{name} в:'
body: "%{name} упомянул(а) вас:"
subject: "%{name} упомянул(а) вас"
title: Новое упоминание
poll:
subject: Опрос %{name} завершился
reblog:
body: 'Ваш пост был продвинут %{name}:'
body: "%{name} продвинул(а) ваш пост:"
subject: "%{name} продвинул(а) ваш пост"
title: Новое продвижение
title: Ваш пост продвинули
status:
subject: "%{name} только что запостил(а)"
subject: "%{name} опубликовал(а) новый пост"
update:
subject: "%{name} изменил(а) пост"
subject: "%{name} отредактировал(а) пост"
notifications:
email_events: События для уведомлений по электронной почте
email_events_hint: 'Выберите события, для которых вы хотели бы получать уведомления:'
@ -1891,17 +1891,17 @@ ru:
user_domain_block: Вы заблокировали %{target_name}
lost_followers: Потерянные подписчики
lost_follows: Потерянные подписки
preamble: Вы можете потерять подписчиков и последователей, если заблокируете домен или, если ваши модераторы решат приостановить работу удаленного сервера. Когда это произойдет, вы сможете загрузить списки разорванных отношений, чтобы проверить их и, возможно, импортировать на другой сервер.
preamble: Когда вы блокируете сервер или это делают модераторы вашего сервера, вы теряете подписчиков и перестаёте быть подписаны на пользователей с заблокированного сервера. После блокировки вы сможете скачать списки пользователей, отношения с которыми были разорваны, чтобы рассмотреть их или чтобы импортировать их на другом сервере.
purged: Информация об этом сервере была удалена администраторами вашего сервера.
type: Событие
statuses:
attached:
audio:
few: "%{count} аудиозаписи"
many: "%{count} аудиозаписей"
one: "%{count} аудиозапись"
other: "%{count} аудиозаписи"
description: 'Вложение: %{attached}'
few: "%{count} аудиофайла"
many: "%{count} аудиофайлов"
one: "%{count} аудиофайл"
other: "%{count} аудиофайлов"
description: Прикреплено %{attached}
image:
few: "%{count} изображения"
many: "%{count} изображений"
@ -1913,14 +1913,14 @@ ru:
one: "%{count} видео"
other: "%{count} видео"
boosted_from_html: Продвижение польз. %{acct_link}
content_warning: 'Спойлер: %{warning}'
content_warning: 'Предупреждение о содержании: %{warning}'
default_language: Тот же, что язык интерфейса
disallowed_hashtags:
few: 'содержались запрещённые хэштеги: %{tags}'
many: 'содержались запрещённые хэштеги: %{tags}'
one: 'содержался запрещённый хэштег: %{tags}'
other: 'содержались запрещённые хэштеги: %{tags}'
edited_at_html: Редактировано %{date}
edited_at_html: 'Дата последнего изменения: %{date}'
errors:
in_reply_not_found: Пост, на который вы пытаетесь ответить, не существует или удалён.
over_character_limit: превышен лимит символов (%{max})

View File

@ -56,7 +56,7 @@ da:
scopes: De API'er, som applikationen vil kunne tilgå. Vælges en topniveaudstrækning, vil detailvalg være unødvendige.
setting_aggregate_reblogs: Vis ikke nye fremhævelser for nyligt fremhævede indlæg (påvirker kun nyligt modtagne fremhævelser)
setting_always_send_emails: Normalt sendes ingen e-mailnotifikationer under aktivt brug af Mastodon
setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg, der oprettes med den næste Mastodon-version, men du kan vælge din præference som forberedelse.
setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg oprettet med den næste Mastodon-version, men egne præference kan vælges som forberedelse.
setting_default_sensitive: Sensitive medier er som standard skjult og kan vises med et klik
setting_display_media_default: Skjul medier med sensitiv-markering
setting_display_media_hide_all: Skjul altid medier

View File

@ -56,6 +56,7 @@ el:
scopes: Ποια API θα επιτρέπεται στην εφαρμογή να χρησιμοποιήσεις. Αν επιλέξεις κάποιο υψηλό εύρος εφαρμογής, δε χρειάζεται να επιλέξεις και το καθένα ξεχωριστά.
setting_aggregate_reblogs: Απόκρυψη των νέων αναρτήσεων για τις αναρτήσεις που έχουν ενισχυθεί πρόσφατα (επηρεάζει μόνο τις νέες ενισχύσεις)
setting_always_send_emails: Κανονικά οι ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου δεν θα αποστέλλονται όταν χρησιμοποιείτε ενεργά το Mastodon
setting_default_quote_policy: Αυτή η ρύθμιση θα τεθεί σε ισχύ μόνο για αναρτήσεις που δημιουργήθηκαν με την επόμενη έκδοση του Mastodon, αλλά μπορείτε να επιλέξετε την προτίμησή σας κατά την προετοιμασία.
setting_default_sensitive: Τα ευαίσθητα πολυμέσα είναι κρυμμένα και εμφανίζονται με ένα κλικ
setting_display_media_default: Απόκρυψη ευαίσθητων πολυμέσων
setting_display_media_hide_all: Μόνιμη απόκρυψη όλων των πολυμέσων

View File

@ -329,6 +329,7 @@ en:
follow_request: Someone requested to follow you
mention: Someone mentioned you
pending_account: New account needs review
quote: Someone quoted you
reblog: Someone boosted your post
report: New report is submitted
software_updates:

View File

@ -56,6 +56,7 @@ fo:
scopes: Hvørji API nýtsluskipanin fær atgongd til. Velur tú eitt vav á hægsta stigi, so er ikki neyðugt at velja tey einstøku.
setting_aggregate_reblogs: Vís ikki nýggjar stimbranir fyri postar, sum nýliga eru stimbraðir (ávirkar einans stimbranir, ið eru móttiknar fyri kortum)
setting_always_send_emails: Vanliga vera teldupostfráboðanir ikki sendar, tá tú virkin brúkar Mastodon
setting_default_quote_policy: Hendan stillingin verður bara virkin fyri postar, sum verða stovnaðir í næstu Mastodon útgávuni, men sum fyrireiking til tað, kanst tú velja tína stilling longu nú.
setting_default_sensitive: Viðkvæmar miðlafílur eru fjaldar og kunnu avdúkast við einum klikki
setting_display_media_default: Fjal miðlafílur, sum eru merktar sum viðkvæmar
setting_display_media_hide_all: Fjal altíð miðlafílur

View File

@ -221,6 +221,7 @@ pl:
setting_boost_modal: Pytaj o potwierdzenie przed podbiciem
setting_default_language: Język wpisów
setting_default_privacy: Widoczność wpisów
setting_default_quote_policy: Kto może cytować
setting_default_sensitive: Zawsze oznaczaj zawartość multimedialną jako wrażliwą
setting_delete_modal: Pytaj o potwierdzenie przed usunięciem wpisu
setting_disable_hover_cards: Wyłącz podgląd profilu po najechaniu
@ -229,6 +230,7 @@ pl:
setting_display_media_default: Domyślne
setting_display_media_hide_all: Ukryj wszystko
setting_display_media_show_all: Pokaż wszystko
setting_emoji_style: Styl emoji
setting_expand_spoilers: Zawsze rozwijaj wpisy oznaczone ostrzeżeniem o zawartości
setting_hide_network: Ukryj swoją sieć
setting_missing_alt_text_modal: Pokaż okno potwierdzenia przed opublikowaniem materiałów bez pomocniczego opisu obrazów
@ -266,6 +268,7 @@ pl:
favicon: Favicon
mascot: Własna ikona
media_cache_retention_period: Okres przechowywania pamięci podręcznej
min_age: Wymagany minimalny wiek
peers_api_enabled: Opublikuj listę odkrytych serwerów w API
profile_directory: Włącz katalog profilów
registrations_mode: Kto może się zarejestrować
@ -331,6 +334,7 @@ pl:
usable: Pozwól na umieszczanie tego hashtagu w lokalnych wpisach
terms_of_service:
changelog: Co się zmieniło?
effective_date: Data wejścia w życie
text: Warunki korzystania z usługi
terms_of_service_generator:
admin_email: Adres e-mail przeznaczony do celów prawnych
@ -341,7 +345,11 @@ pl:
dmca_email: Adres e-mail dla zgłoszeń naruszenia DMCA/praw autorskich
domain: Domena
jurisdiction: Jurysdykcja
min_age: Wiek minimalny
user:
date_of_birth_1i: Dzień
date_of_birth_2i: Miesiąc
date_of_birth_3i: Rok
role: Rola
time_zone: Strefa czasowa
user_role:
@ -350,6 +358,10 @@ pl:
name: Nazwa
permissions_as_keys: Uprawnienia
position: Priorytet
username_block:
allow_with_approval: Zezwól na rejestracje po zatwierdzeniu
comparison: Metoda porównania
username: Słowo do dopasowania
webhook:
events: Włączone zdarzenia
template: Szablon zawartości

View File

@ -56,6 +56,7 @@ tr:
scopes: Uygulamanın erişmesine izin verilen API'ler. Üst seviye bir kapsam seçtiyseniz, bireysel kapsam seçmenize gerek yoktur.
setting_aggregate_reblogs: Yakın zamanda teşvik edilmiş gönderiler için yeni teşvikleri göstermeyin (yalnızca yeni alınan teşvikleri etkiler)
setting_always_send_emails: Normalde, Mastodon'u aktif olarak kullanırken e-posta bildirimleri gönderilmeyecektir
setting_default_quote_policy: Bu ayar yalnızca bir sonraki Mastodon sürümüyle oluşturulan gönderiler için geçerli olacak, ancak hazırlık aşamasında tercihinizi seçebilirsiniz.
setting_default_sensitive: Hassas medya varsayılan olarak gizlidir ve bir tıklama ile gösterilebilir
setting_display_media_default: Hassas olarak işaretlenmiş medyayı gizle
setting_display_media_hide_all: Medyayı her zaman gizle
@ -159,6 +160,10 @@ tr:
name: Rolün, eğer rozet olarak görüntülenmesi ayarlandıysa kullanılacak herkese açık ismi
permissions_as_keys: Bu role sahip kullanıcıların şunlara erişimi var...
position: Belirli durumlarda çatışmayı çözmek için daha yüksek rol belirleyicidir. Bazı eylemler ancak daha düşük öncelikteki rollere uygulanabilir
username_block:
allow_with_approval: Kayıt işlemini tamamen engellemek yerine, eşleşen kayıtlar onayınızı gerektirecektir
comparison: Kısmi eşleşmeleri engellerken lütfen Scunthorpe Problemini aklınızda bulundurun
username: '"a" için "4" veya "e" için "3" gibi büyük/küçük harfe ve yaygın homogliflere bakılmaksızın eşleştirilecektir'
webhook:
events: Gönderilecek etkinlikleri seçin
template: Değişken değerleme kullanarak kendi JSON yükünüzü oluşturun. Varsayılan JSON için boş bırakın.
@ -370,6 +375,10 @@ tr:
name: Ad
permissions_as_keys: İzinler
position: Öncelik
username_block:
allow_with_approval: Onay ile kayıtlara izin ver
comparison: Karşılaştırma yöntemi
username: Eşleşecek kelime
webhook:
events: Etkin olaylar
template: Yük şablonu

View File

@ -1,7 +1,7 @@
---
tr:
about:
about_mastodon_html: Jetub Max<em>ücretsiz ve açık kaynaklı</em> bir sosyal ağdır. <em>Merkezi olmayan</em> yapısı sayesinde diğer ticari sosyal platformların aksine iletişimininizin tek bir firmada tutulmasının/yönetilmesinin önüne geçer. Güvendiğiniz bir sunucuyu seçerek oradaki kişilerle etkileşimde bulunabilirsiniz. Herkes kendi Jetub Max sunucusunu kurabilir ve sorunsuz bir şekilde Jetub Max<em>sosyal ağına</em> dahil edebilir!
about_mastodon_html: 'Geleceğin sosyal ağı: Reklam yok, kurumsal gözetim yok, etik tasarım ve merkeziyetsizlik! Mastodon ile verilerinizin sahibi olun!'
contact_missing: Ayarlanmadı
contact_unavailable: Bulunamadı
hosted_on: Mastodon %{domain} üzerinde barındırılıyor
@ -190,6 +190,7 @@ tr:
create_relay: Aktarıcı Oluştur
create_unavailable_domain: Mevcut Olmayan Alan Adı Oluştur
create_user_role: Rol Oluştur
create_username_block: Kullanıcı Adı Kuralı Oluştur
demote_user: Kullanıcıyı Düşür
destroy_announcement: Duyuru Sil
destroy_canonical_email_block: E-Posta Engelini Sil
@ -203,6 +204,7 @@ tr:
destroy_status: Durumu Sil
destroy_unavailable_domain: Mevcut Olmayan Alan Adı Sil
destroy_user_role: Rolü Kaldır
destroy_username_block: Kullanıcı Adı Kuralını Sil
disable_2fa_user: 2AD Kapat
disable_custom_emoji: Özel İfadeyi Devre Dışı Bırak
disable_relay: Aktarıcıyı Devre Dışı Bırak
@ -237,6 +239,7 @@ tr:
update_report: Raporu Güncelle
update_status: Durumu Güncelle
update_user_role: Rolü Güncelle
update_username_block: Kullanıcı Adı Kuralını Güncelle
actions:
approve_appeal_html: "%{name}, %{target} kullanıcısının yönetim kararına itirazını kabul etti"
approve_user_html: "%{name}, %{target} konumundan kaydı onayladı"
@ -255,6 +258,7 @@ tr:
create_relay_html: "%{name}, %{target} aktarıcısını oluşturdu"
create_unavailable_domain_html: "%{name}, %{target} alan adına teslimatı durdurdu"
create_user_role_html: "%{name}, %{target} rolünü oluşturdu"
create_username_block_html: "%{name}, %{target} içeren kullanıcı adları için kural ekledi"
demote_user_html: "%{name}, %{target} kullanıcısını düşürdü"
destroy_announcement_html: "%{name}, %{target} duyurusunu sildi"
destroy_canonical_email_block_html: "%{name}, %{target} karmasıyla e-posta engelini kaldırdı"
@ -268,6 +272,7 @@ tr:
destroy_status_html: "%{name}, %{target} kullanıcısının gönderisini kaldırdı"
destroy_unavailable_domain_html: "%{name}, %{target} alan adına teslimatı sürdürdü"
destroy_user_role_html: "%{name}, %{target} rolünü sildi"
destroy_username_block_html: "%{name}, %{target} içeren kullanıcı adları için kural silindi"
disable_2fa_user_html: "%{name}, %{target} kullanıcısının iki aşamalı doğrulama gereksinimini kapattı"
disable_custom_emoji_html: "%{name}, %{target} emojisini devre dışı bıraktı"
disable_relay_html: "%{name}, %{target} aktarıcısını devre dışı bıraktı"
@ -302,6 +307,7 @@ tr:
update_report_html: "%{name}, %{target} raporunu güncelledi"
update_status_html: "%{name}, %{target} kullanıcısının gönderisini güncelledi"
update_user_role_html: "%{name}, %{target} rolünü değiştirdi"
update_username_block_html: "%{name}, %{target} içeren kullanıcı adları için kural güncellendi"
deleted_account: hesap silindi
empty: Kayıt bulunamadı.
filter_by_action: Eyleme göre filtre
@ -1085,6 +1091,25 @@ tr:
other: Geçen hafta %{count} kişi tarafından kullanıldı
title: Öneriler ve Öne Çıkanlar
trending: Öne çıkanlar
username_blocks:
add_new: Yeni ekle
block_registrations: Kayıtları engelle
comparison:
contains: İçerir
equals: Eşit
contains_html: "%{string} içerir"
created_msg: Kullanıcı adı kuralı başarıyla oluşturuldu
delete: Sil
edit:
title: Kullanıcı adı kuralını düzenle
matches_exactly_html: "%{string} değerine eşittir"
new:
create: Kural oluştur
title: Yeni kullanıcı adı kuralı oluştur
no_username_block_selected: Hiçbir kullanıcı adı kuralı değiştirilmedi çünkü hiçbiri seçilmedi
not_permitted: İzin verilmiyor
title: Kullanıcı adı kuralları
updated_msg: Kullanıcı adı kuralı başarıyla güncellendi
warning_presets:
add_new: Yeni ekle
delete: Sil
@ -1880,6 +1905,8 @@ tr:
ownership: Başkasının gönderisi sabitlenemez
reblog: Bir gönderi sabitlenemez
quote_policies:
followers: Yalnızca takipçileriniz
nobody: Hiç kimse
public: Herkes
title: '%{name}: "%{quote}"'
visibilities:

View File

@ -69,6 +69,7 @@
"emoji-mart": "npm:emoji-mart-lazyload@latest",
"emojibase": "^16.0.0",
"emojibase-data": "^16.0.3",
"emojibase-regex": "^16.0.0",
"escape-html": "^1.0.3",
"fast-glob": "^3.3.3",
"fuzzysort": "^3.0.0",
@ -168,7 +169,7 @@
"eslint": "^9.23.0",
"eslint-import-resolver-typescript": "^4.2.5",
"eslint-plugin-formatjs": "^5.3.1",
"eslint-plugin-import": "~2.31.0",
"eslint-plugin-import": "~2.32.0",
"eslint-plugin-jsdoc": "^52.0.0",
"eslint-plugin-jsx-a11y": "~6.10.2",
"eslint-plugin-promise": "~7.2.1",

View File

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::Activity::QuoteRequest do
RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do
let(:sender) { Fabricate(:account, domain: 'example.com') }
let(:recipient) { Fabricate(:account) }
let(:quoted_post) { Fabricate(:status, account: recipient) }
@ -47,5 +47,44 @@ RSpec.describe ActivityPub::Activity::QuoteRequest do
end, recipient.id, sender.inbox_url)
end
end
context 'when trying to quote a quotable local status' do
let(:status_json) do
{
'@context': [
'https://www.w3.org/ns/activitystreams',
{
'@id': 'https://w3id.org/fep/044f#quote',
'@type': '@id',
},
{
'@id': 'https://w3id.org/fep/044f#quoteAuthorization',
'@type': '@id',
},
],
id: 'https://example.com/unknown-status',
type: 'Note',
summary: 'Show more',
content: 'Hello universe',
quote: ActivityPub::TagManager.instance.uri_for(quoted_post),
attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
}.deep_stringify_keys
end
before do
stub_request(:get, 'https://example.com/unknown-status').to_return(status: 200, body: Oj.dump(status_json), headers: { 'Content-Type': 'application/activity+json' })
quoted_post.update(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16)
end
it 'accepts the quote and sends an Accept activity' do
expect { subject.perform }
.to change { quoted_post.reload.quotes.accepted.count }.by(1)
.and enqueue_sidekiq_job(ActivityPub::DeliveryWorker)
.with(satisfying do |body|
outgoing_json = Oj.load(body)
outgoing_json['type'] == 'Accept' && %w(type id actor object instrument).all? { |key| json[key] == outgoing_json['object'][key] }
end, recipient.id, sender.inbox_url)
end
end
end
end

View File

@ -27,6 +27,14 @@ RSpec.describe Fasp::Request do
'Signature-Input' => /.+/,
})
end
it 'tracks that a successful connection was made' do
provider.delivery_failure_tracker.track_failure!
expect do
subject.send(method, '/test_path')
end.to change(provider.delivery_failure_tracker, :failures).from(1).to(0)
end
end
context 'when the response is not signed' do
@ -55,6 +63,21 @@ RSpec.describe Fasp::Request do
end
end
end
context 'when the request raises an error' do
before do
stub_request(method, 'https://reqprov.example.com/fasp/test_path')
.to_raise(HTTP::ConnectionError)
end
it "records the failure using the provider's delivery failure tracker" do
expect do
subject.send(method, '/test_path')
end.to raise_error(HTTP::ConnectionError)
expect(provider.delivery_failure_tracker.failures).to eq 1
end
end
end
describe '#get' do

View File

@ -51,6 +51,27 @@ RSpec.describe NotificationMailer do
it_behaves_like 'delivery without status'
end
describe 'quote' do
let(:quote) { Fabricate(:quote, state: :accepted, status: foreign_status, quoted_status: own_status) }
let(:notification) { Notification.create!(account: receiver.account, activity: quote) }
let(:mail) { prepared_mailer_for(own_status.account).quote }
it_behaves_like 'localized subject', 'notification_mailer.quote.subject', name: 'bob'
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('bob quoted your post'))
.and(have_body_text('Your post was quoted by bob'))
.and(have_body_text('The body of the foreign status'))
.and have_thread_headers
.and have_standard_headers('quote').for(receiver)
end
it_behaves_like 'delivery to non functional user'
it_behaves_like 'delivery without status'
end
describe 'follow' do
let(:follow) { sender.follow!(receiver.account) }
let(:notification) { Notification.create!(account: receiver.account, activity: follow) }

View File

@ -33,6 +33,12 @@ class NotificationMailerPreview < ActionMailer::Preview
mailer_for(activity.reblog.account, activity).reblog
end
# Preview this email at http://localhost:3000/rails/mailers/notification_mailer/quote
def quote
activity = Quote.first
mailer_for(activity.quoted_account, activity).quote
end
private
def mailer_for(account, activity)

View File

@ -206,4 +206,12 @@ RSpec.describe Fasp::Provider do
end
end
end
describe '#delivery_failure_tracker' do
subject { Fabricate(:fasp_provider) }
it 'returns a `DeliverFailureTracker` instance' do
expect(subject.delivery_failure_tracker).to be_a(DeliveryFailureTracker)
end
end
end

View File

@ -54,7 +54,7 @@ RSpec.describe WorkerBatch do
end
it 'persists the job IDs' do
expect(subject.jobs).to eq %w(foo bar)
expect(subject.jobs).to contain_exactly('foo', 'bar')
end
end
end
@ -67,7 +67,7 @@ RSpec.describe WorkerBatch do
end
it 'removes the job from pending jobs' do
expect(subject.jobs).to eq %w(bar baz)
expect(subject.jobs).to contain_exactly('bar', 'baz')
end
it 'decrements the number of pending jobs' do

View File

@ -29,6 +29,7 @@ RSpec.describe 'API Web Push Subscriptions' do
mention: false,
poll: true,
status: false,
quote: true,
},
},
}

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::AcceptQuoteRequestSerializer do
subject { serialized_record_json(record, described_class, adapter: ActivityPub::Adapter) }
describe 'serializing an object' do
let(:record) { Fabricate(:quote, state: :accepted) }
it 'returns expected attributes' do
expect(subject.deep_symbolize_keys)
.to include(
actor: eq(ActivityPub::TagManager.instance.uri_for(record.quoted_account)),
id: match("#accepts/quote_requests/#{record.id}"),
object: include(
type: 'QuoteRequest',
instrument: ActivityPub::TagManager.instance.uri_for(record.status),
object: ActivityPub::TagManager.instance.uri_for(record.quoted_status)
),
type: 'Accept'
)
end
end
end

911
yarn.lock

File diff suppressed because it is too large Load Diff