mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-06 09:51:24 +00:00
Merge branch 'main' into feature/require-mfa-by-admin
This commit is contained in:
commit
7e1eeff268
|
@ -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
|
||||
|
|
|
@ -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)'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -37,7 +37,7 @@ export interface BaseApiAccountJSON {
|
|||
roles?: ApiAccountJSON[];
|
||||
statuses_count: number;
|
||||
uri: string;
|
||||
url: string;
|
||||
url?: string;
|
||||
username: string;
|
||||
moved?: ApiAccountJSON;
|
||||
suspended?: boolean;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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}';
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 ê通看見ê範圍轉送",
|
||||
|
|
|
@ -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ść"
|
||||
}
|
||||
|
|
|
@ -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ş",
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 |
1
app/javascript/material-icons/400-24px/format_quote.svg
Normal file
1
app/javascript/material-icons/400-24px/format_quote.svg
Normal 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 |
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = '')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -23,6 +23,7 @@ class ActivityPub::QuoteRequestSerializer < ActivityPub::Serializer
|
|||
end
|
||||
|
||||
def instrument
|
||||
# TODO: inline object?
|
||||
ActivityPub::TagManager.instance.uri_for(object.status)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
16
app/views/notification_mailer/quote.html.haml
Normal file
16
app/views/notification_mailer/quote.html.haml
Normal 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}")
|
5
app/views/notification_mailer/quote.text.erb
Normal file
5
app/views/notification_mailer/quote.text.erb
Normal 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 %>
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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ć
|
||||
|
|
|
@ -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}"'
|
||||
|
|
|
@ -1905,6 +1905,8 @@ el:
|
|||
ownership: Δεν μπορείς να καρφιτσώσεις ανάρτηση κάποιου άλλου
|
||||
reblog: Οι ενισχύσεις δεν καρφιτσώνονται
|
||||
quote_policies:
|
||||
followers: Μόνο οι ακόλουθοί σου
|
||||
nobody: Κανένας
|
||||
public: Όλοι
|
||||
title: '%{name}: "%{quote}"'
|
||||
visibilities:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:'
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: Μόνιμη απόκρυψη όλων των πολυμέσων
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,6 +29,7 @@ RSpec.describe 'API Web Push Subscriptions' do
|
|||
mention: false,
|
||||
poll: true,
|
||||
status: false,
|
||||
quote: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue
Block a user