Merge commit '7af5ba271d6f04831f000e9b815914375e42abeb' into glitch-soc/merge-upstream

This commit is contained in:
Claire 2026-01-27 18:03:27 +01:00
commit 5a45a3203a
96 changed files with 959 additions and 505 deletions

View File

@ -305,8 +305,8 @@ GEM
highline (3.1.2)
reline
hiredis (0.6.3)
hiredis-client (0.26.3)
redis-client (= 0.26.3)
hiredis-client (0.26.4)
redis-client (= 0.26.4)
hkdf (0.3.0)
htmlentities (4.3.4)
http (5.3.1)
@ -708,7 +708,7 @@ GEM
reline
redcarpet (3.6.1)
redis (4.8.1)
redis-client (0.26.3)
redis-client (0.26.4)
connection_pool
regexp_parser (2.11.3)
reline (0.6.3)

View File

@ -36,6 +36,7 @@ export interface CalloutProps {
secondaryLabel?: string;
onClose?: () => void;
id?: string;
extraContent?: ReactNode;
}
const variantClasses = {
@ -59,6 +60,7 @@ export const Callout: FC<CalloutProps> = ({
onSecondary: secondaryAction,
secondaryLabel,
onClose,
extraContent,
id,
}) => {
const intl = useIntl();
@ -105,6 +107,8 @@ export const Callout: FC<CalloutProps> = ({
)}
</div>
{extraContent}
{onClose && (
<IconButton
icon='close'

View File

@ -13,6 +13,7 @@
border-radius: 9999px;
width: 1rem;
height: 1rem;
margin-top: -2px;
}
.content {
@ -28,16 +29,12 @@
}
}
.icon + .content,
.wrapper:has(.close) .content {
margin-top: 2px;
}
.body {
flex-grow: 1;
h3 {
font-weight: 500;
margin-bottom: 5px;
}
}
@ -62,6 +59,12 @@
}
}
@media (prefers-reduced-motion: reduce) {
.action {
transition: none;
}
}
.close {
color: inherit;

View File

@ -13,6 +13,7 @@ interface Props extends React.SVGProps<SVGSVGElement> {
children?: never;
id: string;
icon: IconProp;
noFill?: boolean;
}
export const Icon: React.FC<Props> = ({
@ -20,6 +21,7 @@ export const Icon: React.FC<Props> = ({
icon: IconComponent,
className,
'aria-label': ariaLabel,
noFill = false,
...other
}) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@ -42,7 +44,12 @@ export const Icon: React.FC<Props> = ({
return (
<IconComponent
className={classNames('icon', `icon-${id}`, className)}
className={classNames(
'icon',
`icon-${id}`,
noFill && 'icon--no-fill',
className,
)}
title={title}
aria-hidden={ariaHidden}
aria-label={ariaLabel}

View File

@ -44,8 +44,7 @@ export const DomainPill: React.FC<{
aria-controls={accessibilityId}
type='button'
>
{domain}
{children}
{children ?? domain}
</button>
<Overlay

View File

@ -24,6 +24,7 @@ import { AccountHeaderFields } from './fields';
import { AccountInfo } from './info';
import { MemorialNote } from './memorial_note';
import { MovedNote } from './moved_note';
import { AccountNote as AccountNoteRedesign } from './note';
import { AccountNumberFields } from './number_fields';
import redesignClasses from './redesign.module.scss';
import { AccountTabs } from './tabs';
@ -142,12 +143,7 @@ export const AccountHeader: React.FC<{
isRedesignEnabled() && redesignClasses.nameWrapper,
)}
>
<AccountName
accountId={accountId}
className={classNames(
isRedesignEnabled() && redesignClasses.name,
)}
/>
<AccountName accountId={accountId} />
{isRedesignEnabled() && <AccountButtons accountId={accountId} />}
</div>
@ -166,7 +162,9 @@ export const AccountHeader: React.FC<{
{!suspendedOrHidden && (
<div className='account__header__extra'>
<div className='account__header__bio'>
{me && account.id !== me && (
{me && account.id !== me && isRedesignEnabled() ? (
<AccountNoteRedesign accountId={accountId} />
) : (
<AccountNote accountId={accountId} />
)}

View File

@ -6,7 +6,7 @@ import { DisplayName } from '@/mastodon/components/display_name';
import { Icon } from '@/mastodon/components/icon';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useAppSelector } from '@/mastodon/store';
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
import { DomainPill } from '../../account/components/domain_pill';
@ -14,10 +14,7 @@ import { isRedesignEnabled } from '../common';
import classes from './redesign.module.scss';
export const AccountName: FC<{ accountId: string; className?: string }> = ({
accountId,
className,
}) => {
export const AccountName: FC<{ accountId: string }> = ({ accountId }) => {
const intl = useIntl();
const account = useAccount(accountId);
const me = useAppSelector((state) => state.meta.get('me') as string);
@ -31,38 +28,52 @@ export const AccountName: FC<{ accountId: string; className?: string }> = ({
const [username = '', domain = localDomain] = account.acct.split('@');
return (
<h1 className={className}>
<DisplayName account={account} variant='simple' />
<small>
<span>
@{username}
{isRedesignEnabled() && '@'}
<span className='invisible'>
{!isRedesignEnabled() && '@'}
{domain}
if (!isRedesignEnabled()) {
return (
<h1>
<DisplayName account={account} variant='simple' />
<small>
<span>
@{username}
<span className='invisible'>@{domain}</span>
</span>
</span>
<DomainPill
username={username}
domain={domain}
isSelf={me === account.id}
/>
{account.locked && (
<Icon
id='lock'
icon={LockIcon}
aria-label={intl.formatMessage({
id: 'account.locked_info',
defaultMessage:
'This account privacy status is set to locked. The owner manually reviews who can follow them.',
})}
/>
)}
</small>
</h1>
);
}
return (
<div className={classes.name}>
<h1>
<DisplayName account={account} variant='simple' />
</h1>
<p className={classes.username}>
@{username}@{domain}
<DomainPill
username={username}
domain={domain}
isSelf={me === account.id}
className={(isRedesignEnabled() && classes.domainPill) || ''}
className={classes.domainPill}
>
{isRedesignEnabled() && <Icon id='info' icon={InfoIcon} />}
<Icon id='help' icon={HelpIcon} />
</DomainPill>
{!isRedesignEnabled() && account.locked && (
<Icon
id='lock'
icon={LockIcon}
aria-label={intl.formatMessage({
id: 'account.locked_info',
defaultMessage:
'This account privacy status is set to locked. The owner manually reviews who can follow them.',
})}
/>
)}
</small>
</h1>
</p>
</div>
);
};

View File

@ -37,7 +37,12 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
let icon: ReactNode = undefined;
if (isAdminBadge(role)) {
icon = (
<Icon icon={IconAdmin} id='badge-admin' className={classes.badgeIcon} />
<Icon
icon={IconAdmin}
id='badge-admin'
className={classes.badgeIcon}
noFill
/>
);
}
badges.push(

View File

@ -76,6 +76,7 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
id='verified'
icon={IconVerified}
className={classes.fieldIconVerified}
noFill
/>
)}
</>

View File

@ -81,6 +81,7 @@ export const AccountFieldsModal: FC<{
id='verified'
icon={IconVerified}
className={classes.fieldIconVerified}
noFill
/>
)}
</dd>

View File

@ -31,6 +31,8 @@ import {
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { isRedesignEnabled } from '../common';
const messages = defineMessages({
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
@ -55,6 +57,14 @@ const messages = defineMessages({
id: 'account.show_reblogs',
defaultMessage: 'Show boosts from @{name}',
},
addNote: {
id: 'account.add_note',
defaultMessage: 'Add a personal note',
},
editNote: {
id: 'account.edit_note',
defaultMessage: 'Edit personal note',
},
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: {
id: 'account.unendorse',
@ -187,7 +197,30 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
});
arr.push(null);
}
}
if (isRedesignEnabled()) {
arr.push({
text: intl.formatMessage(
relationship?.note ? messages.editNote : messages.addNote,
),
action: () => {
dispatch(
openModal({
modalType: 'ACCOUNT_NOTE',
modalProps: {
accountId: account.id,
},
}),
);
},
});
if (!relationship?.following) {
arr.push(null);
}
}
if (relationship?.following) {
arr.push({
text: intl.formatMessage(
relationship.endorsed ? messages.unendorse : messages.endorse,

View File

@ -0,0 +1,69 @@
import { useCallback, useEffect } from 'react';
import type { FC } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { fetchRelationships } from '@/mastodon/actions/accounts';
import { openModal } from '@/mastodon/actions/modal';
import { Callout } from '@/mastodon/components/callout';
import { IconButton } from '@/mastodon/components/icon_button';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import EditIcon from '@/material-icons/400-24px/edit_square.svg?react';
import classes from './redesign.module.scss';
const messages = defineMessages({
title: {
id: 'account.note.title',
defaultMessage: 'Personal note (visible only to you)',
},
editButton: {
id: 'account.note.edit_button',
defaultMessage: 'Edit',
},
});
export const AccountNote: FC<{ accountId: string }> = ({ accountId }) => {
const intl = useIntl();
const relationship = useAppSelector((state) =>
state.relationships.get(accountId),
);
const dispatch = useAppDispatch();
useEffect(() => {
if (!relationship) {
dispatch(fetchRelationships([accountId]));
}
}, [accountId, dispatch, relationship]);
const handleEdit = useCallback(() => {
dispatch(
openModal({
modalType: 'ACCOUNT_NOTE',
modalProps: { accountId },
}),
);
}, [accountId, dispatch]);
if (!relationship?.note) {
return null;
}
return (
<Callout
icon={false}
title={intl.formatMessage(messages.title)}
className={classes.note}
extraContent={
<IconButton
icon='edit'
iconComponent={EditIcon}
title={intl.formatMessage(messages.editButton)}
className={classes.noteEditButton}
onClick={handleEdit}
/>
}
>
{relationship.note}
</Callout>
);
};

View File

@ -7,20 +7,16 @@
flex-grow: 1;
font-size: 22px;
white-space: initial;
text-overflow: initial;
line-height: normal;
:global(.icon-info) {
margin-left: 2px;
width: 1em;
height: 1em;
align-self: center;
}
}
// Overrides .account__header__tabs__name h1 small
h1.name > small {
gap: 0;
.username {
display: flex;
font-size: 13px;
color: var(--color-text-secondary);
align-items: center;
user-select: all;
margin-top: 4px;
}
.domainPill {
@ -32,10 +28,20 @@ h1.name > small {
color: inherit;
font-size: 1em;
font-weight: initial;
margin-left: 2px;
width: 16px;
height: 16px;
transition: color 0.2s ease-in-out;
> svg {
width: 100%;
height: 100%;
}
&:hover,
&:global(.active) {
background: none;
color: inherit;
color: var(--color-text-brand-soft);
}
}
@ -53,10 +59,18 @@ h1.name > small {
svg.badgeIcon {
opacity: 1;
fill: revert-layer;
}
path {
fill: revert-layer;
.note {
margin-bottom: 16px;
}
.noteEditButton {
color: inherit;
svg {
width: 20px;
height: 20px;
}
}
@ -90,11 +104,6 @@ svg.badgeIcon {
.fieldIconVerified {
width: 1rem;
height: 1rem;
// Need to override .icon path.
path {
fill: revert-layer;
}
}
.fieldNumbersWrapper {

View File

@ -0,0 +1,21 @@
.noteCallout {
margin-bottom: 16px;
}
.noteInput {
min-height: 70px;
width: 100%;
padding: 8px;
border-radius: 8px;
box-sizing: border-box;
background: var(--color-bg-primary);
border: 1px solid var(--color-border-primary);
appearance: none;
resize: none;
margin-top: 4px;
}
.noteInput:focus-visible {
outline: var(--outline-focus-default);
outline-offset: 2px;
}

View File

@ -0,0 +1,160 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import type { ChangeEventHandler, FC } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { submitAccountNote } from '@/mastodon/actions/account_notes';
import { fetchRelationships } from '@/mastodon/actions/accounts';
import { Callout } from '@/mastodon/components/callout';
import { TextAreaField } from '@/mastodon/components/form_fields';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import type { Relationship } from '@/mastodon/models/relationship';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { ConfirmationModal } from '../../ui/components/confirmation_modals';
import classes from './modals.module.css';
const messages = defineMessages({
newTitle: {
id: 'account.node_modal.title',
defaultMessage: 'Add a personal note',
},
editTitle: {
id: 'account.node_modal.edit_title',
defaultMessage: 'Edit personal note',
},
save: {
id: 'account.node_modal.save',
defaultMessage: 'Save',
},
fieldLabel: {
id: 'account.node_modal.field_label',
defaultMessage: 'Personal Note',
},
errorUnknown: {
id: 'account.node_modal.error_unknown',
defaultMessage: 'Could not save the note',
},
});
export const AccountNoteModal: FC<{
accountId: string;
onClose: () => void;
}> = ({ accountId, onClose }) => {
const relationship = useAppSelector((state) =>
state.relationships.get(accountId),
);
const dispatch = useAppDispatch();
useEffect(() => {
if (!relationship) {
dispatch(fetchRelationships([accountId]));
}
}, [accountId, dispatch, relationship]);
if (!relationship) {
return <LoadingIndicator />;
}
return (
<InnerNodeModal
relationship={relationship}
accountId={accountId}
onClose={onClose}
/>
);
};
const InnerNodeModal: FC<{
relationship: Relationship;
accountId: string;
onClose: () => void;
}> = ({ relationship, accountId, onClose }) => {
// Set up the state.
const initialContents = relationship.note;
const [note, setNote] = useState(initialContents);
const [errorText, setErrorText] = useState('');
const [state, setState] = useState<'idle' | 'saving' | 'error'>('idle');
const isDirty = note !== initialContents;
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
(e) => {
if (state !== 'saving') {
setNote(e.target.value);
}
},
[state],
);
const intl = useIntl();
// Create an abort controller to cancel the request if the modal is closed.
const abortController = useRef(new AbortController());
const dispatch = useAppDispatch();
const handleSave = useCallback(() => {
if (state === 'saving' || !isDirty) {
return;
}
setState('saving');
dispatch(
submitAccountNote(
{ accountId, note },
{ signal: abortController.current.signal },
),
)
.then(() => {
setState('idle');
onClose();
})
.catch((err: unknown) => {
setState('error');
if (err instanceof Error) {
setErrorText(err.message);
} else {
setErrorText(intl.formatMessage(messages.errorUnknown));
}
});
}, [accountId, dispatch, intl, isDirty, note, onClose, state]);
const handleCancel = useCallback(() => {
abortController.current.abort();
onClose();
}, [onClose]);
return (
<ConfirmationModal
title={
initialContents
? intl.formatMessage(messages.editTitle)
: intl.formatMessage(messages.newTitle)
}
extraContent={
<>
<Callout className={classes.noteCallout}>
<FormattedMessage
id='account.node_modal.callout'
defaultMessage='Personal notes are visible only to you.'
/>
</Callout>
<TextAreaField
value={note}
onChange={handleChange}
label={intl.formatMessage(messages.fieldLabel)}
className={classes.noteInput}
hasError={state === 'error'}
hint={errorText}
// eslint-disable-next-line jsx-a11y/no-autofocus -- We want to focus here as it's a modal.
autoFocus
/>
</>
}
onClose={handleCancel}
confirm={intl.formatMessage(messages.save)}
onConfirm={handleSave}
updating={state === 'saving'}
disabled={!isDirty}
closeWhenConfirm={false}
noFocusButton
/>
);
};

View File

@ -19,6 +19,9 @@ export const ConfirmationModal: React.FC<
onConfirm: () => void;
closeWhenConfirm?: boolean;
extraContent?: React.ReactNode;
updating?: boolean;
disabled?: boolean;
noFocusButton?: boolean;
} & BaseConfirmationModalProps
> = ({
title,
@ -31,6 +34,9 @@ export const ConfirmationModal: React.FC<
onSecondary,
closeWhenConfirm = true,
extraContent,
updating,
disabled,
noFocusButton = false,
}) => {
const handleClick = useCallback(() => {
if (closeWhenConfirm) {
@ -74,16 +80,23 @@ export const ConfirmationModal: React.FC<
onClick={handleSecondary}
className='link-button'
type='button'
disabled={disabled}
>
{secondary}
</button>
</>
)}
{/* eslint-disable-next-line jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
<Button onClick={handleClick} autoFocus>
{/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
<Button
onClick={handleClick}
loading={updating}
disabled={disabled}
autoFocus={!noFocusButton}
>
{confirm}
</Button>
{/* eslint-enable */}
</div>
</div>
</div>

View File

@ -85,6 +85,7 @@ export const MODAL_COMPONENTS = {
'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal,
'ANNUAL_REPORT': AnnualReportModal,
'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }),
'ACCOUNT_NOTE': () => import('@/mastodon/features/account_timeline/modals/note_modal').then(module => ({ default: module.AccountNoteModal })),
'ACCOUNT_FIELDS': () => import('mastodon/features/account_timeline/components/fields_modal.tsx').then(module => ({ default: module.AccountFieldsModal })),
};

View File

@ -714,7 +714,6 @@
"privacy.private.short": "للمتابِعين",
"privacy.public.long": "أي شخص على أو خارج ماستدون",
"privacy.public.short": "للعامة",
"privacy.quote.anyone": "{visibility}، يمكن لأي شخص الاقتباس",
"privacy.quote.disabled": "{visibility}، الاقتباس معطل",
"privacy.quote.limited": "{visibility}، الاقتباسات محدودة",
"privacy.unlisted.additional": "هذا يتصرف بالضبط مثل النشر للعامة، باستثناء أن المنشور لن يظهر في الموجزات الحية أو في الوسوم أو في الإستكشاف، أو في نتائج بحث ماستدون، حتى وإن قمت بتفعيله على مستوى الحساب.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Падпісчыкі",
"privacy.public.long": "Усе, хто ёсць і каго няма ў Mastodon",
"privacy.public.short": "Публічны",
"privacy.quote.anyone": "{visibility}, усе могуць цытаваць",
"privacy.quote.anyone": "{visibility}, цытаты дазволеныя",
"privacy.quote.disabled": "{visibility}, цытаты адключаныя",
"privacy.quote.limited": "{visibility}, абмежаваныя цытаты",
"privacy.unlisted.additional": "Паводзіць сябе гэтак жа, як і публічны, за выключэннем таго, што допіс не будзе адлюстроўвацца ў жывой стужцы, хэштэгах, аглядзе або ў пошуку Mastodon, нават калі Вы ўключылі бачнасць у пошуку ў наладах.",

View File

@ -714,7 +714,6 @@
"privacy.private.short": "Последователи",
"privacy.public.long": "Всеки във и извън Mastodon",
"privacy.public.short": "Публично",
"privacy.quote.anyone": "{visibility}, всеки може да цитира",
"privacy.quote.disabled": "{visibility}, цитатите са изключени",
"privacy.quote.limited": "{visibility}, цитатите са ограничени",
"privacy.unlisted.additional": "Това действие е точно като публичното, с изключение на това, че публикацията няма да се появява в каналите на живо, хаштаговете, разглеждането или търсенето в Mastodon, дори ако сте избрали да се публично видими на ниво акаунт.",

View File

@ -559,7 +559,6 @@
"privacy.private.long": "Hoc'h heulierien·ezed hepken",
"privacy.private.short": "Heulierien",
"privacy.public.short": "Publik",
"privacy.quote.anyone": "{visibility}, n'eus forzh piv a c'hall menegiñ",
"privacy.quote.disabled": "{visibility}, menegoù diweredekaet",
"privacy_policy.last_updated": "Hizivadenn ziwezhañ {date}",
"privacy_policy.title": "Reolennoù Prevezded",

View File

@ -772,7 +772,6 @@
"privacy.private.short": "Seguidors",
"privacy.public.long": "Tothom dins o fora Mastodon",
"privacy.public.short": "Públic",
"privacy.quote.anyone": "{visibility}, qualsevol pot citar",
"privacy.quote.disabled": "{visibility}, cites desactivades",
"privacy.quote.limited": "{visibility}, cites limitades",
"privacy.unlisted.additional": "Es comporta igual que públic, excepte que la publicació no apareixerà als canals en directe o etiquetes, l'explora o a la cerca de Mastodon, fins i tot si ho heu activat a nivell de compte.",

View File

@ -790,7 +790,6 @@
"privacy.private.short": "Sledující",
"privacy.public.long": "Kdokoliv na Mastodonu i mimo něj",
"privacy.public.short": "Veřejné",
"privacy.quote.anyone": "{visibility}, kdokoliv může citovat",
"privacy.quote.disabled": "{visibility}, citování je zakázáno",
"privacy.quote.limited": "{visibility}, citování je omezené",
"privacy.unlisted.additional": "Chová se stejně jako veřejný, až na to, že se příspěvek neobjeví v živých kanálech nebo hashtazích, v objevování nebo vyhledávání na Mastodonu, a to i když je účet nastaven tak, aby se zde všude tyto příspěvky zobrazovaly.",

View File

@ -785,7 +785,6 @@
"privacy.private.short": "Dilynwyr",
"privacy.public.long": "Unrhyw un ar ac oddi ar Mastodon",
"privacy.public.short": "Cyhoeddus",
"privacy.quote.anyone": "{visibility}, gall unrhyw un ddyfynnu",
"privacy.quote.disabled": "{visibility}, dyfyniadau wedi'u hanalluogi",
"privacy.quote.limited": "{visibility}, dyfyniadau wedi'u cyfyngu",
"privacy.unlisted.additional": "Mae hwn yn ymddwyn yn union fel y cyhoeddus, ac eithrio na fydd y postiad yn ymddangos mewn ffrydiau byw neu hashnodau, archwilio, neu chwiliad Mastodon, hyd yn oed os ydych wedi eich cynnwys ar draws y cyfrif.",

View File

@ -577,7 +577,7 @@
"lists.add_to_list": "Føj til liste",
"lists.add_to_lists": "Føj {name} til lister",
"lists.create": "Opret",
"lists.create_a_list_to_organize": "Opret en ny liste til organisering af hjemmefeed",
"lists.create_a_list_to_organize": "Opret en ny liste til organisering af dit Hjem-feed",
"lists.create_list": "Opret liste",
"lists.delete": "Slet liste",
"lists.done": "Færdig",
@ -799,7 +799,7 @@
"privacy.private.short": "Følgere",
"privacy.public.long": "Alle på og udenfor Mastodon",
"privacy.public.short": "Offentlig",
"privacy.quote.anyone": "{visibility}, alle kan citere",
"privacy.quote.anyone": "{visibility}, citering tilladt",
"privacy.quote.disabled": "{visibility}, citering deaktiveret",
"privacy.quote.limited": "{visibility}, citering begrænset",
"privacy.unlisted.additional": "Dette svarer til offentlig, bortset fra at indlægget ikke vises i live-feeds eller hashtags, udforsk eller Mastodon-søgning, selvom du har tilvalgt dette for kontoen.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Follower",
"privacy.public.long": "Alle innerhalb und außerhalb von Mastodon",
"privacy.public.short": "Öffentlich",
"privacy.quote.anyone": "{visibility} alle dürfen zitieren",
"privacy.quote.anyone": "{visibility}, zitierte Beiträge erlaubt",
"privacy.quote.disabled": "{visibility} Zitieren deaktiviert",
"privacy.quote.limited": "{visibility} nur Follower",
"privacy.unlisted.additional": "Das Verhalten ist wie bei „Öffentlich“, jedoch gibt es einige Einschränkungen. Der Beitrag wird nicht in „Live-Feeds“, „Erkunden“, Hashtags oder über die Mastodon-Suchfunktion auffindbar sein selbst wenn die zugehörige Einstellung aktiviert wurde.",

View File

@ -60,7 +60,7 @@
"account.joined_long": "Έγινε μέλος {date}",
"account.joined_short": "Έγινε μέλος",
"account.languages": "Αλλαγή εγγεγραμμένων γλωσσών",
"account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου ελέχθηκε στις {date}",
"account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου ελέγχθηκε στις {date}",
"account.locked_info": "Η κατάσταση απορρήτου αυτού του λογαριασμού έχει ρυθμιστεί σε κλειδωμένη. Ο ιδιοκτήτης αξιολογεί χειροκίνητα ποιος μπορεί να τον ακολουθήσει.",
"account.media": "Πολυμέσα",
"account.mention": "Επισήμανε @{name}",
@ -799,7 +799,7 @@
"privacy.private.short": "Ακόλουθοι",
"privacy.public.long": "Όλοι εντός και εκτός του Mastodon",
"privacy.public.short": "Δημόσια",
"privacy.quote.anyone": "{visibility}, ο καθένας μπορεί να παραθέσει",
"privacy.quote.anyone": "{visibility}, παραθέσεις επιτρεπτές",
"privacy.quote.disabled": "{visibility}, παραθέσεις απενεργοποιημένες",
"privacy.quote.limited": "{visibility}, παραθέσεις περιορισμένες",
"privacy.unlisted.additional": "Αυτό συμπεριφέρεται ακριβώς όπως το δημόσιο, εκτός από το ότι η ανάρτηση δεν θα εμφανιστεί σε ζωντανές ροές ή ετικέτες, εξερεύνηση ή αναζήτηση στο Mastodon, ακόμη και αν το έχεις επιλέξει για τον λογαριασμό σου.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Followers",
"privacy.public.long": "Anyone on and off Mastodon",
"privacy.public.short": "Public",
"privacy.quote.anyone": "{visibility}, anyone can quote",
"privacy.quote.anyone": "{visibility}, quotes allowed",
"privacy.quote.disabled": "{visibility}, quotes disabled",
"privacy.quote.limited": "{visibility}, quotes limited",
"privacy.unlisted.additional": "This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.",

View File

@ -14,6 +14,7 @@
"about.powered_by": "Decentralized social media powered by {mastodon}",
"about.rules": "Server rules",
"account.account_note_header": "Personal note",
"account.add_note": "Add a personal note",
"account.add_or_remove_from_list": "Add or Remove from lists",
"account.badges.bot": "Automated",
"account.badges.group": "Group",
@ -27,6 +28,7 @@
"account.direct": "Privately mention @{name}",
"account.disable_notifications": "Stop notifying me when @{name} posts",
"account.domain_blocking": "Blocking domain",
"account.edit_note": "Edit personal note",
"account.edit_profile": "Edit profile",
"account.edit_profile_short": "Edit",
"account.enable_notifications": "Notify me when @{name} posts",
@ -72,6 +74,14 @@
"account.muting": "Muting",
"account.mutual": "You follow each other",
"account.no_bio": "No description provided.",
"account.node_modal.callout": "Personal notes are visible only to you.",
"account.node_modal.edit_title": "Edit personal note",
"account.node_modal.error_unknown": "Could not save the note",
"account.node_modal.field_label": "Personal Note",
"account.node_modal.save": "Save",
"account.node_modal.title": "Add a personal note",
"account.note.edit_button": "Edit",
"account.note.title": "Personal note (visible only to you)",
"account.open_original_page": "Open original page",
"account.posts": "Posts",
"account.posts_with_replies": "Posts and replies",

View File

@ -86,6 +86,7 @@
"account.unmute": "Malsilentigi @{name}",
"account.unmute_notifications_short": "Malsilentigu sciigojn",
"account.unmute_short": "Ne plu silentigi",
"account_fields_modal.close": "Fermi",
"account_note.placeholder": "Alklaku por aldoni noton",
"admin.dashboard.daily_retention": "Uzantoretenprocento laŭ tag post registro",
"admin.dashboard.monthly_retention": "Uzantoretenprocento laŭ monato post registro",
@ -112,6 +113,7 @@
"annual_report.announcement.action_dismiss": "Ne, dankon",
"annual_report.nav_item.badge": "Nova",
"annual_report.shared_page.donate": "Donaci",
"annual_report.summary.close": "Fermi",
"annual_report.summary.copy_link": "Kopii ligilon",
"annual_report.summary.highlighted_post.title": "Plej populara afiŝo",
"annual_report.summary.most_used_app.most_used_app": "plej uzita aplikaĵo",
@ -149,6 +151,9 @@
"closed_registrations_modal.find_another_server": "Trovi alian servilon",
"closed_registrations_modal.preamble": "Mastodon estas malcentraliza, do sendepende de tio, kie vi kreas vian konton, vi povos sekvi kaj komuniki kun ĉiuj ajn el ĉi tiu servilo. Vi eĉ povas mem starigi propran servilon!",
"closed_registrations_modal.title": "Registriĝi en Mastodon",
"collections.create_collection": "Krei kolekton",
"collections.delete_collection": "Forigi kolekton",
"collections.error_loading_collections": "Okazis eraro provante ŝargi viajn kolektojn.",
"column.about": "Pri",
"column.blocks": "Blokitaj uzantoj",
"column.bookmarks": "Legosignoj",
@ -179,6 +184,7 @@
"community.column_settings.local_only": "Nur loka",
"community.column_settings.media_only": "Nur aŭdovidaĵoj",
"community.column_settings.remote_only": "Nur fora",
"compose.error.blank_post": "Afiŝo ne povas esti malplena.",
"compose.language.change": "Ŝanĝi lingvon",
"compose.language.search": "Serĉi lingvojn...",
"compose.published.body": "Afiŝo publikigita.",
@ -213,6 +219,7 @@
"confirmations.delete_list.title": "Ĉu forigi liston?",
"confirmations.discard_draft.confirm": "Forĵetu kaj daŭrigu",
"confirmations.discard_draft.edit.cancel": "Daŭrigi redaktadon",
"confirmations.discard_draft.edit.message": "Daŭrigo forigos ĉiujn ŝanĝojn, kiujn vi faris al la afiŝo, kiun vi nun redaktas.",
"confirmations.discard_draft.edit.title": "Ĉu forĵeti ŝanĝojn al via afiŝo?",
"confirmations.discard_draft.post.cancel": "Daŭrigi malneton",
"confirmations.discard_draft.post.message": "Daŭrigo forigos la afiŝon, kiun vi nun verkas.",
@ -230,6 +237,8 @@
"confirmations.missing_alt_text.secondary": "Afiŝi ĉiuokaze",
"confirmations.missing_alt_text.title": "Ĉu aldoni alttekston?",
"confirmations.mute.confirm": "Silentigi",
"confirmations.private_quote_notify.confirm": "Publikigi afiŝon",
"confirmations.private_quote_notify.do_not_show_again": "Ne montru al mi ĉi tiun mesaĝon denove",
"confirmations.quiet_post_quote_info.dismiss": "Ne memorigu min denove",
"confirmations.quiet_post_quote_info.got_it": "Komprenite",
"confirmations.redraft.confirm": "Forigi kaj reskribi",
@ -336,6 +345,7 @@
"explore.trending_statuses": "Afiŝoj",
"explore.trending_tags": "Kradvortoj",
"featured_carousel.header": "{count, plural, one {Alpinglita afiŝo} other {Alpinglitaj afiŝoj}}",
"featured_carousel.slide": "Afiŝo {current, number} de {max, number}",
"filter_modal.added.context_mismatch_explanation": "Ĉi tiu filtrilkategorio ne kongruas kun la kunteksto en kiu vi akcesis ĉi tiun afiŝon. Se vi volas ke la afiŝo estas ankaŭ filtrita en ĉi tiu kunteksto, vi devus redakti la filtrilon.",
"filter_modal.added.context_mismatch_title": "Ne kongruas la kunteksto!",
"filter_modal.added.expired_explanation": "Ĉi tiu filtrilkategorio eksvalidiĝis, vu bezonos ŝanĝi la eksvaliddaton por ĝi.",
@ -378,6 +388,9 @@
"follow_suggestions.who_to_follow": "Kiun sekvi",
"followed_tags": "Sekvataj kradvortoj",
"footer.about": "Pri",
"footer.about_mastodon": "Pri Mastodon",
"footer.about_server": "Pri {domain}",
"footer.about_this_server": "Pri",
"footer.directory": "Profilujo",
"footer.get_app": "Akiri la apon",
"footer.keyboard_shortcuts": "Fulmoklavoj",
@ -710,7 +723,6 @@
"privacy.private.short": "Sekvantoj",
"privacy.public.long": "Ĉiujn ajn ĉe kaj ekster Mastodon",
"privacy.public.short": "Publika",
"privacy.quote.anyone": "{visibility}, iu ajn povas citi",
"privacy.quote.disabled": "{visibility}, malŝaltitaj citaĵoj",
"privacy.quote.limited": "{visibility}, limigitaj citaĵoj",
"privacy.unlisted.additional": "Ĉi tio kondutas ekzakte kiel publika, krom ke la afiŝo ne aperos en vivaj fluoj aŭ kradvortoj, esploro aŭ Mastodon-serĉo, eĉ se vi estas enskribita en la tuta konto.",
@ -835,6 +847,7 @@
"status.cancel_reblog_private": "Ne plu diskonigi",
"status.cannot_quote": "Vi ne rajtas citi ĉi tiun afiŝon",
"status.cannot_reblog": "Ĉi tiun afiŝon ne eblas diskonigi",
"status.context.show": "Montri",
"status.continued_thread": "Daŭrigis fadenon",
"status.copy": "Kopii la ligilon al la afiŝo",
"status.delete": "Forigi",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Seguidores",
"privacy.public.long": "Cualquier persona dentro y fuera de Mastodon",
"privacy.public.short": "Público",
"privacy.quote.anyone": "{visibility}, todos pueden citar",
"privacy.quote.anyone": "{visibility}, citas permitidas",
"privacy.quote.disabled": "{visibility}, citas deshabilitadas",
"privacy.quote.limited": "{visibility}, citas limitadas",
"privacy.unlisted.additional": "Esto se comporta exactamente igual que con la configuración de privacidad de mensaje «Público», excepto que el mensaje no aparecerá en las líneas temporales en vivo, ni en las etiquetas, ni en la línea temporal «Explorá», ni en la búsqueda de Mastodon; incluso si optaste por hacer tu cuenta visible.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Seguidores",
"privacy.public.long": "Cualquiera dentro y fuera de Mastodon",
"privacy.public.short": "Público",
"privacy.quote.anyone": "{visibility}, cualquiera puede citar",
"privacy.quote.anyone": "{visibility}, citas permitidas",
"privacy.quote.disabled": "{visibility}, citas desactivadas",
"privacy.quote.limited": "{visibility}, citas limitadas",
"privacy.unlisted.additional": "Esto se comporta exactamente igual que el público, excepto que el post no aparecerá en las cronologías en directo o en las etiquetas, la exploración o busquedas en Mastodon, incluso si está optado por activar la cuenta de usuario.",

View File

@ -187,6 +187,7 @@
"bundle_modal_error.close": "Cerrar",
"bundle_modal_error.message": "Ha habido algún error mientras cargábamos esta pantalla.",
"bundle_modal_error.retry": "Inténtalo de nuevo",
"callout.dismiss": "Descartar",
"carousel.current": "<sr>Diapositiva</sr> {current, number} / {max, number}",
"carousel.slide": "Diapositiva {current, number} de {max, number}",
"closed_registrations.other_server_instructions": "Como Mastodon es descentralizado, puedes crear una cuenta en otro servidor y seguir interactuando con este.",
@ -194,9 +195,16 @@
"closed_registrations_modal.find_another_server": "Buscar otro servidor",
"closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde crees tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso puedes alojarlo tú mismo!",
"closed_registrations_modal.title": "Registrarse en Mastodon",
"collections.create_a_collection_hint": "Crea una colección para recomendar o compartir tus cuentas favoritas con otros.",
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.",
"collections.no_collections_yet": "Aún no hay colecciones.",
"collections.view_collection": "Ver colección",
"column.about": "Acerca de",
"column.blocks": "Usuarios bloqueados",
"column.bookmarks": "Marcadores",
"column.collections": "Mis colecciones",
"column.community": "Cronología local",
"column.create_list": "Crear lista",
"column.direct": "Menciones privadas",
@ -791,7 +799,7 @@
"privacy.private.short": "Seguidores",
"privacy.public.long": "Visible por todo el mundo, dentro y fuera de Mastodon",
"privacy.public.short": "Pública",
"privacy.quote.anyone": "{visibility}, cualquiera puede citar",
"privacy.quote.anyone": "{visibility}, citas permitidas",
"privacy.quote.disabled": "{visibility}, citas deshabilitadas",
"privacy.quote.limited": "{visibility}, citas limitadas",
"privacy.unlisted.additional": "Se comporta exactamente igual que la visibilidad pública, excepto que la publicación no aparecerá en las cronologías públicas o en las etiquetas, la sección de Explorar o la búsqueda de Mastodon, incluso si has habilitado la opción de búsqueda en tu perfil.",

View File

@ -791,7 +791,6 @@
"privacy.private.short": "Jälgijad",
"privacy.public.long": "Nii kasutajad kui mittekasutajad",
"privacy.public.short": "Avalik",
"privacy.quote.anyone": "{visibility}, kõik võivad tsiteerida",
"privacy.quote.disabled": "{visibility}, tsiteerimine pole lubatud",
"privacy.quote.limited": "{visibility}, tsiteerimine on piiratud",
"privacy.unlisted.additional": "See on olemuselt küll avalik, aga postitus ei ilmu voogudes ega teemaviidetes, lehitsedes ega Mastodoni otsingus, isegi kui konto on seadistustes avalik.",

View File

@ -785,7 +785,6 @@
"privacy.private.short": "Jarraitzaileak",
"privacy.public.long": "Mastodonen dagoen edo ez dagoen edonor",
"privacy.public.short": "Publikoa",
"privacy.quote.anyone": "{visibility}, edonork aipa dezake",
"privacy.quote.disabled": "{visibility}, aipuak desgaituta",
"privacy.quote.limited": "{visibility}, aipuak mugatuta",
"privacy.unlisted.additional": "Aukera honek publiko modua bezala funtzionatzen du, baina argitalpena ez da agertuko zuzeneko jarioetan edo traoletan, \"Arakatu\" atalean edo Mastodonen bilaketan, nahiz eta kontua zabaltzeko onartu duzun.",

View File

@ -785,7 +785,6 @@
"privacy.private.short": "پی‌گیرندگان",
"privacy.public.long": "هرکسی در و بیرون از ماستودون",
"privacy.public.short": "عمومی",
"privacy.quote.anyone": "{visibility}، هرکسی می‌تواند نقل کند",
"privacy.quote.disabled": "{visibility}، نقل‌ها از کار افتاده",
"privacy.quote.limited": "{visibility}، نقل‌ها محدود شده",
"privacy.unlisted.additional": "درست مثل عمومی رفتار می‌کند؛ جز این که فرسته در برچسب‌ها یا خوراک‌های زنده، کشف یا جست‌وجوی ماستودون ظاهر نخواهد شد. حتا اگر کلیّت نمایه‌تان اجازه داده باشد.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Seuraajat",
"privacy.public.long": "Kuka tahansa Mastodonissa ja sen ulkopuolella",
"privacy.public.short": "Julkinen",
"privacy.quote.anyone": "{visibility}, kuka tahansa voi lainata",
"privacy.quote.anyone": "{visibility}, lainaukset sallittu",
"privacy.quote.disabled": "{visibility}, lainaukset poissa käytöstä",
"privacy.quote.limited": "{visibility}, lainauksia rajoitettu",
"privacy.unlisted.additional": "Tämä toimii muuten kuin julkinen, mutta julkaisut eivät näy livesyöte-, aihetunniste- tai selausnäkymissä eivätkä Mastodonin hakutuloksissa, vaikka ne olisivat käyttäjätililläsi yleisesti sallittuina.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Fylgjarar",
"privacy.public.long": "Øll í og uttanfyri Mastodon",
"privacy.public.short": "Alment",
"privacy.quote.anyone": "{visibility}, øll kunnu sitera",
"privacy.quote.anyone": "{visibility}, siteringar loyvdar",
"privacy.quote.disabled": "{visibility}, siteringar óvirknar",
"privacy.quote.limited": "{visibility}, siteringar avmarkaðar",
"privacy.unlisted.additional": "Hetta er júst sum almenn, tó verður posturin ikki vístur í samtíðarrásum ella frámerkjum, rannsakan ella Mastodon leitingum, sjálvt um valið er galdandi fyri alla kontuna.",

View File

@ -790,7 +790,6 @@
"privacy.private.short": "Leantóirí",
"privacy.public.long": "Duine ar bith ar agus amach Mastodon",
"privacy.public.short": "Poiblí",
"privacy.quote.anyone": "{visibility}, is féidir le duine ar bith lua",
"privacy.quote.disabled": "{visibility}, sleachta díchumasaithe",
"privacy.quote.limited": "{visibility}, sleachta teoranta",
"privacy.unlisted.additional": "Iompraíonn sé seo díreach mar a bheadh poiblí, ach amháin ní bheidh an postáil le feiceáil i bhfothaí beo nó i hashtags, in iniúchadh nó i gcuardach Mastodon, fiú má tá tú liostáilte ar fud an chuntais.",

View File

@ -786,7 +786,6 @@
"privacy.private.short": "Luchd-leantainn",
"privacy.public.long": "Duine sam bith taobh a-staigh no a-muigh Mhastodon",
"privacy.public.short": "Poblach",
"privacy.quote.anyone": "{visibility}, luaidh fosgailte",
"privacy.quote.disabled": "{visibility}, luaidh à comas",
"privacy.quote.limited": "{visibility}, luaidh cuingichte",
"privacy.unlisted.additional": "Tha seo coltach ris an fhaicsinneachd phoblach ach cha nochd am post air loidhnichean-ama an t-saoghail phoblaich, nan tagaichean hais no an rùrachaidh no ann an toraidhean luirg Mhastodon fiù s ma thug thu ro-aonta airson sin seachad.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Seguidoras",
"privacy.public.long": "Para todas dentro e fóra de Mastodon",
"privacy.public.short": "Público",
"privacy.quote.anyone": "{visibility}, calquera pode citar",
"privacy.quote.anyone": "{visibility}, permítese citar",
"privacy.quote.disabled": "{visibility}, citas desactivadas",
"privacy.quote.limited": "{visibility}, citas limitadas",
"privacy.unlisted.additional": "Do mesmo xeito que público, menos que a publicación non aparecerá nas cronoloxías en directo ou nos cancelos, en descubrir ou nas buscas de Mastodon, incluso se estivese establecido nas opcións xerais da conta.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "עוקבים",
"privacy.public.long": "כל הגולשים, מחוברים למסטודון או לא",
"privacy.public.short": "פומבי",
"privacy.quote.anyone": "{visibility}, הציטוט מותר לכל",
"privacy.quote.anyone": "{visibility}, רשות הציטוט נתונה",
"privacy.quote.disabled": "{visibility}, האפשרות לציטוט מכובה",
"privacy.quote.limited": "{visibility}, האפשרות לציטוט מוגבלת",
"privacy.unlisted.additional": "ההתנהגות דומה להודעה ציבורית, מלבד שההודעה לא תופיע בפיד החי המקומי או בתגיות, תגליות או חיפוש מסטודון, אפילו אם ביקשת שהחשבון כולו יהיה פומבי.",

View File

@ -797,7 +797,6 @@
"privacy.private.short": "Követők",
"privacy.public.long": "Bárki a Mastodonon és azon kívül",
"privacy.public.short": "Nyilvános",
"privacy.quote.anyone": "{visibility}, bárki idézheti",
"privacy.quote.disabled": "{visibility}, idézés letiltva",
"privacy.quote.limited": "{visibility}, idézés korlátozva",
"privacy.unlisted.additional": "Ez pontosan úgy viselkedik, mint a nyilvános, kivéve, hogy a bejegyzés nem jelenik meg élő hírfolyamokban, hashtagekben, felfedezésben vagy a Mastodonos keresésben, még akkor sem, ha ezt az egész fiókra engedélyezted.",

View File

@ -741,7 +741,6 @@
"privacy.private.short": "Sequitores",
"privacy.public.long": "Quicunque, sur Mastodon o non",
"privacy.public.short": "Public",
"privacy.quote.anyone": "{visibility}, omnes pote citar",
"privacy.quote.disabled": "{visibility}, citation disactivate",
"privacy.quote.limited": "{visibility}, citation limitate",
"privacy.unlisted.additional": "Isto es exactemente como public, excepte que le message non apparera in fluxos in vivo, in hashtags, in Explorar, o in le recerca de Mastodon, mesmo si tu ha optate pro render tote le conto discoperibile.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Fylgjendur",
"privacy.public.long": "Hver sem er, á og utan Mastodon",
"privacy.public.short": "Opinbert",
"privacy.quote.anyone": "{visibility}, hver sem er getur vitnað í færslu",
"privacy.quote.anyone": "{visibility}, tilvitnanir leyfðar",
"privacy.quote.disabled": "{visibility}, tilvitnanir eru óvirkar",
"privacy.quote.limited": "{visibility}, tilvitnanir eru takmarkaðar",
"privacy.unlisted.additional": "Þetta hegðar sér eins og opinber færsla, fyrir utan að færslan birtist ekki í beinum streymum eða myllumerkjum, né heldur í Mastodon-leitum jafnvel þótt þú hafir valið að falla undir slíkt í notandasniðinu þínu.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Follower",
"privacy.public.long": "Chiunque dentro e fuori Mastodon",
"privacy.public.short": "Pubblico",
"privacy.quote.anyone": "{visibility}, chiunque può citare",
"privacy.quote.anyone": "{visibility}, citazioni autorizzate",
"privacy.quote.disabled": "{visibility}, citazioni disabilitate",
"privacy.quote.limited": "{visibility}, citazioni limitate",
"privacy.unlisted.additional": "Si comporta esattamente come pubblico, tranne per il fatto che il post non verrà visualizzato nei feed live o negli hashtag, nell'esplorazione o nella ricerca Mastodon, anche se hai attivato l'attivazione a livello di account.",

View File

@ -729,7 +729,6 @@
"privacy.private.short": "フォロワー",
"privacy.public.long": "すべての人 (Mastodon以外も含む)",
"privacy.public.short": "公開",
"privacy.quote.anyone": "{visibility}、誰でも引用可能",
"privacy.quote.disabled": "{visibility}、引用不可",
"privacy.quote.limited": "{visibility}、引用は制限",
"privacy.unlisted.additional": "「公開」とほとんど同じですが、リアルタイムフィードやハッシュタグ、探索機能、Mastodon検索などに投稿が表示されない点で「公開」と異なります。また、アカウント設定で投稿の検索や表示を許可している場合でも、この公開範囲を設定した投稿は前述の機能には表示されません。",

View File

@ -782,7 +782,6 @@
"privacy.private.short": "팔로워",
"privacy.public.long": "마스토돈 내외 모두",
"privacy.public.short": "공개",
"privacy.quote.anyone": "{visibility}, 누구나 인용 가능",
"privacy.quote.disabled": "{visibility}, 인용 비활성화",
"privacy.quote.limited": "{visibility}, 제한된 인용",
"privacy.unlisted.additional": "공개와 똑같지만 게시물이 실시간 피드나 해시태그, 둘러보기, (계정 설정에서 허용했더라도) 마스토돈 검색에서 제외됩니다.",

View File

@ -747,7 +747,6 @@
"privacy.private.short": "Sekėjai",
"privacy.public.long": "Bet kas iš Mastodon ir ne Mastodon",
"privacy.public.short": "Vieša",
"privacy.quote.anyone": "{visibility}, kiekvienas gali cituoti",
"privacy.quote.disabled": "{visibility}, paminėjimai išjungti",
"privacy.quote.limited": "{visibility}, paminėjimai apriboti",
"privacy.unlisted.additional": "Tai veikia lygiai taip pat, kaip ir vieša, tik įrašas nebus rodomas tiesioginiuose srautuose, grotažymėse, naršyme ar Mastodon paieškoje, net jei esi įtraukęs (-usi) visą paskyrą.",

View File

@ -633,7 +633,6 @@
"privacy.private.short": "Sekotāji",
"privacy.public.long": "Jebkurš Mastodon platformā un ārpus tās",
"privacy.public.short": "Publisks",
"privacy.quote.anyone": "{visibility}, jebkurš var citēt",
"privacy.quote.disabled": "{visibility}, aizliegta citēšana",
"privacy.quote.limited": "{visibility}, ierobežota citēšana",
"privacy.unlisted.additional": "Šis uzvedas tieši kā publisks, izņemot to, ka ieraksts neparādīsies tiešraides barotnēs vai tēmturos, izpētē vai Mastodon meklēšanā, pat ja esi to norādījis visa konta ietvaros.",

View File

@ -786,7 +786,6 @@
"privacy.private.short": "跟tuè lí ê",
"privacy.public.long": "逐ê lâng無論佇Mastodon以內á是以外",
"privacy.public.short": "公開ê",
"privacy.quote.anyone": "{visibility}ta̍k ê lâng lóng ē當引用",
"privacy.quote.disabled": "{visibility}停止引用PO文",
"privacy.quote.limited": "{visibility}PO文引用受限",
"privacy.unlisted.additional": "Tse ê行為kap公開相siângm̄-koh 就算lí佇口座設定phah開有關ê公開功能PO文mā bē顯示佇即時ê動態、hashtag、探索kap Mastodon ê搜尋結果。",

View File

@ -799,7 +799,6 @@
"privacy.private.short": "Volgers",
"privacy.public.long": "Iedereen op Mastodon en daarbuiten",
"privacy.public.short": "Openbaar",
"privacy.quote.anyone": "{visibility}, iedereen mag citeren",
"privacy.quote.disabled": "{visibility}, citeren uitgeschakeld",
"privacy.quote.limited": "{visibility}, citeren beperkt",
"privacy.unlisted.additional": "Dit is vergelijkbaar met openbaar, behalve dat het bericht niet op openbare tijdlijnen, onder hashtags, verkennen of zoeken verschijnt, zelfs als je je account daarvoor hebt ingesteld.",

View File

@ -790,7 +790,6 @@
"privacy.private.short": "Fylgjarar",
"privacy.public.long": "Kven som helst på og av Mastodon",
"privacy.public.short": "Offentleg",
"privacy.quote.anyone": "{visibility}, alle kan sitera",
"privacy.quote.disabled": "{visibility}, ingen kan sitera",
"privacy.quote.limited": "{visibility}, avgrensa sitat",
"privacy.unlisted.additional": "Dette er akkurat som offentleg, bortsett frå at innlegga ikkje dukkar opp i direktestraumar eller emneknaggar, i oppdagingar eller Mastodon-søk, sjølv om du har sagt ja til at kontoen skal vera synleg.",

View File

@ -769,7 +769,6 @@
"privacy.private.short": "Obserwujący",
"privacy.public.long": "Każdy na i poza Mastodon",
"privacy.public.short": "Publiczny",
"privacy.quote.anyone": "{visibility}, każdy może cytować",
"privacy.quote.disabled": "{visibility}, cytaty wyłączone",
"privacy.quote.limited": "{visibility}, cytaty ograniczone",
"privacy.unlisted.additional": "Dostępny podobnie jak wpis publiczny, ale nie będzie widoczny w aktualnościach, hashtagach ani wyszukiwarce Mastodon, nawet jeśli twoje konto jest widoczne.",

View File

@ -785,7 +785,6 @@
"privacy.private.short": "Seguidores",
"privacy.public.long": "Qualquer um dentro ou fora do Mastodon",
"privacy.public.short": "Público",
"privacy.quote.anyone": "{visibility} Qualquer pessoa pode citar",
"privacy.quote.disabled": "{visibility} Citações desabilitadas",
"privacy.quote.limited": "{visibility} Citações limitadas",
"privacy.unlisted.additional": "Isso se comporta exatamente como público, exceto que a publicação não aparecerá nos _feeds ao vivo_ ou nas _hashtags_, explorar, ou barra de busca, mesmo que você seja escolhido em toda a conta.",

View File

@ -799,7 +799,6 @@
"privacy.private.short": "Seguidores",
"privacy.public.long": "Qualquer pessoa no Mastodon ou não",
"privacy.public.short": "Público",
"privacy.quote.anyone": "{visibility}, qualquer pessoa pode citar",
"privacy.quote.disabled": "{visibility}, citações desativadas",
"privacy.quote.limited": "{visibility}, citações limitadas",
"privacy.unlisted.additional": "Este comportamento é exatamente igual ao do público, exceto que a publicação não aparecerá em cronologias, nas etiquetas, ao explorar ou na pesquisa do Mastodon, mesmo que tenhas optado por participar em toda a tua conta.",

View File

@ -759,7 +759,6 @@
"privacy.private.short": "Для подписчиков",
"privacy.public.long": "Для кого угодно в интернете",
"privacy.public.short": "Публичный",
"privacy.quote.anyone": "{visibility}, цитировать разрешено всем",
"privacy.quote.disabled": "{visibility}, без возможности цитирования",
"privacy.quote.limited": "{visibility}, с ограничениями цитирования",
"privacy.unlisted.additional": "Похоже на «Публичный» за исключением того, что пост не появится ни в живых лентах, ни в лентах хештегов, ни в разделе «Актуальное», ни в поиске Mastodon, даже если вы разрешили поиск по своим постам в настройках профиля.",

View File

@ -665,7 +665,6 @@
"privacy.private.short": "Sledovatelia",
"privacy.public.long": "Ktokoľvek na Mastodone aj mimo neho",
"privacy.public.short": "Verejné",
"privacy.quote.anyone": "{visibility}, hocikto môže citovať",
"privacy.quote.disabled": "{visibility}, citovanie nepovolené",
"privacy.quote.limited": "{visibility}, citovanie obmedzené",
"privacy.unlisted.additional": "Presne ako verejné, s tým rozdielom, že sa príspevok nezobrazí v živých kanáloch, hashtagoch, objavovaní či vo vyhľadávaní na Mastodone, aj keď máte pre účet objaviteľnosť zapnutú.",

View File

@ -797,7 +797,7 @@
"privacy.private.short": "Ndjekës",
"privacy.public.long": "Cilido që hyn e del në Mastodon",
"privacy.public.short": "Publik",
"privacy.quote.anyone": "{visibility}, mund të citojë cilido",
"privacy.quote.anyone": "{visibility}, lejohen citimet",
"privacy.quote.disabled": "{visibility}, citimet janë çaktivizuar",
"privacy.quote.limited": "{visibility}, citime të kufizuara",
"privacy.unlisted.additional": "Ky sillet saktësisht si publik, vetëm se postimi sdo të shfaqet në prurje të drejtpërdrejta, ose në hashtag-ë, te eksploroni, apo kërkim në Mastodon, edhe kur keni zgjedhur të jetë për tërë llogarinë.",

View File

@ -797,7 +797,6 @@
"privacy.private.short": "Följare",
"privacy.public.long": "Alla på och utanför Mastodon",
"privacy.public.short": "Offentlig",
"privacy.quote.anyone": "{visibility}, vem som helst kan citera",
"privacy.quote.disabled": "{visibility}, citat inaktiverade",
"privacy.quote.limited": "{visibility}, citat begränsade",
"privacy.unlisted.additional": "Detta fungerar precis som offentlig, förutom att inlägget inte visas i liveflöden eller hashtaggar, utforska eller Mastodon-sökning, även om du har valt detta för hela kontot.",

View File

@ -708,7 +708,6 @@
"privacy.private.short": "ผู้ติดตาม",
"privacy.public.long": "ใครก็ตามที่อยู่ในและนอก Mastodon",
"privacy.public.short": "สาธารณะ",
"privacy.quote.anyone": "{visibility}, ใครก็ตามสามารถอ้างอิง",
"privacy.quote.disabled": "{visibility}, ปิดใช้งานการอ้างอิงแล้ว",
"privacy.quote.limited": "{visibility}, จำกัดการอ้างอิงอยู่",
"privacy.unlisted.additional": "สิ่งนี้ทำงานเหมือนกับสาธารณะทุกประการ ยกเว้นโพสต์จะไม่ปรากฏในฟีดสดหรือแฮชแท็ก, การสำรวจ หรือการค้นหา Mastodon แม้ว่าคุณได้เลือกรับทั่วทั้งบัญชีก็ตาม",

View File

@ -187,6 +187,7 @@
"bundle_modal_error.close": "Kapat",
"bundle_modal_error.message": "Bu ekran yüklenirken bir şeyler ters gitti.",
"bundle_modal_error.retry": "Tekrar deneyin",
"callout.dismiss": "Yoksay",
"carousel.current": "<sr>Slayt</sr> {current, number} / {max, number}",
"carousel.slide": "Slayt {current, number} / {max, number}",
"closed_registrations.other_server_instructions": "Mastodon merkeziyetsiz olduğu için, başka bir sunucuda bir hesap oluşturabilir ve bu sunucuyla etkileşimde bulunmaya devam edebilirsiniz.",
@ -798,7 +799,7 @@
"privacy.private.short": "Takipçiler",
"privacy.public.long": "Mastodon'da olan olmayan herkes",
"privacy.public.short": "Herkese açık",
"privacy.quote.anyone": "{visibility}, herkes alıntılayabilir",
"privacy.quote.anyone": "{visibility}, alıntı yapılabilir",
"privacy.quote.disabled": "{visibility}, alıntı yapılamaz",
"privacy.quote.limited": "{visibility}, sınırlı alıntı",
"privacy.unlisted.additional": "Bu neredeyse herkese açık gibi çalışır, tek farkı gönderi canlı akışlarda veya etiketlerde, keşfette, veya Mastodon aramasında gözükmez, hesap çapında öyle seçmiş olsanız bile.",

View File

@ -718,7 +718,6 @@
"privacy.private.short": "Підписники",
"privacy.public.long": "Усі з Mastodon",
"privacy.public.short": "Публічно",
"privacy.quote.anyone": "{visibility}, будь-хто може цитувати",
"privacy.quote.disabled": "{visibility}, цитування вимкнено",
"privacy.quote.limited": "{visibility}, цитування обмежено",
"privacy.unlisted.additional": "Має таку ж поведінку, як у людей, але повідомлення не з'являтимуться у стрічках або хештегах, оглядах, або пошуку Mastodon, навіть якщо ви використовуєте облікові записи.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "Người theo dõi",
"privacy.public.long": "Bất cứ ai",
"privacy.public.short": "Công khai",
"privacy.quote.anyone": "{visibility}, mọi người có thể trích dẫn",
"privacy.quote.anyone": "{visibility}, cho phép trích dẫn",
"privacy.quote.disabled": "{visibility}, tắt trích dẫn",
"privacy.quote.limited": "{visibility}, hạn chế trích dẫn",
"privacy.unlisted.additional": "Công khai, nhưng tút sẽ không hiện trong bảng tin, hashtag, khám phá hoặc tìm kiếm Mastodon, kể cả trong cài đặt tài khoản bạn chọn cho phép.",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "关注者",
"privacy.public.long": "所有 Mastodon 内外的人",
"privacy.public.short": "公开",
"privacy.quote.anyone": "{visibility}任何人都能引用",
"privacy.quote.anyone": "{visibility}允许嘟文引用",
"privacy.quote.disabled": "{visibility},禁用嘟文引用",
"privacy.quote.limited": "{visibility},嘟文引用受限",
"privacy.unlisted.additional": "此模式的行为与“公开”类似,只是嘟文不会出现在实时动态、话题、探索或 Mastodon 搜索页面中,即使你已全局开启了对应的发现设置。",

View File

@ -799,7 +799,7 @@
"privacy.private.short": "跟隨者",
"privacy.public.long": "所有人 (無論在 Mastodon 上與否)",
"privacy.public.short": "公開",
"privacy.quote.anyone": "{visibility}任何人皆可引用",
"privacy.quote.anyone": "{visibility}允許引用嘟文",
"privacy.quote.disabled": "{visibility},停用引用嘟文",
"privacy.quote.limited": "{visibility},受限的引用嘟文",
"privacy.unlisted.additional": "此與公開嘟文完全相同,但嘟文不會出現於即時內容或主題標籤、探索、及 Mastodon 搜尋中,即使您於帳戶設定中選擇加入。",

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>

After

Width:  |  Height:  |  Size: 580 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="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 676 B

View File

@ -275,7 +275,7 @@
height: 24px;
aspect-ratio: 1;
path {
&:not(.icon--no-fill) path {
fill: currentColor;
}
}

View File

@ -20,8 +20,8 @@ module Account::Counters
to: :account_stat
# @param [Symbol] key
def increment_count!(key)
update_count!(key, 1)
def increment_count!(key, status_created_at: nil)
update_count!(key, 1, status_created_at:)
end
# @param [Symbol] key
@ -31,11 +31,11 @@ module Account::Counters
# @param [Symbol] key
# @param [Integer] value
def update_count!(key, value)
def update_count!(key, value, status_created_at: nil)
raise ArgumentError, "Invalid key #{key}" unless ALLOWED_COUNTER_KEYS.include?(key)
raise ArgumentError, 'Do not call update_count! on dirty objects' if association(:account_stat).loaded? && account_stat&.changed? && account_stat.changed_attribute_names_to_save == %w(id)
result = updated_account_stat(key, value.to_i)
result = updated_account_stat(key, value.to_i, status_created_at:)
# Reload account_stat if it was loaded, taking into account newly-created unsaved records
if association(:account_stat).loaded?
@ -50,25 +50,27 @@ module Account::Counters
private
def updated_account_stat(key, value)
def updated_account_stat(key, value, status_created_at: nil)
status_created_at = Time.now.utc if status_created_at.nil? || status_created_at > Time.now.utc
AccountStat.upsert(
initial_values(key, value),
initial_values(key, value, status_created_at:),
on_duplicate: Arel.sql(
duplicate_values(key, value).join(', ')
duplicate_values(key, value, status_created_at:).join(', ')
),
unique_by: :account_id
)
end
def initial_values(key, value)
def initial_values(key, value, status_created_at: nil)
{ :account_id => id, key => [value, 0].max }.tap do |values|
values.merge!(last_status_at: Time.current) if key == :statuses_count
values.merge!(last_status_at: status_created_at) if key == :statuses_count
end
end
def duplicate_values(key, value)
def duplicate_values(key, value, status_created_at: nil)
["#{key} = (account_stats.#{key} + #{value})", 'updated_at = CURRENT_TIMESTAMP'].tap do |values|
values << 'last_status_at = CURRENT_TIMESTAMP' if key == :statuses_count && value.positive?
values << AccountStat.sanitize_sql_array(['last_status_at = GREATEST(account_stats.last_status_at, ?::timestamp)', status_created_at]) if key == :statuses_count && value.positive?
end
end

View File

@ -539,7 +539,7 @@ class Status < ApplicationRecord
def increment_counter_caches
return if direct_visibility?
account&.increment_count!(:statuses_count)
account&.increment_count!(:statuses_count, status_created_at: created_at)
reblog&.increment_count!(:reblogs_count) if reblog?
thread&.increment_count!(:replies_count) if in_reply_to_id.present? && distributable?
end

View File

@ -10,11 +10,13 @@ class ActivityPub::AddSerializer < ActivityPub::Serializer
end
def self.serializer_for(model, options)
case model.class.name
when 'Status'
case model
when Status
UriSerializer
when 'FeaturedTag'
when FeaturedTag
ActivityPub::HashtagSerializer
when Collection
ActivityPub::FeaturedCollectionSerializer
else
super
end
@ -38,6 +40,16 @@ class ActivityPub::AddSerializer < ActivityPub::Serializer
end
def target
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
case object
when Status, FeaturedTag
# Technically this is not correct, as tags have their own collection.
# But sadly we do not store the collection URI for tags anywhere so cannot
# handle `Add` activities to that properly (yet). The receiving code for
# this currently looks at the type of the contained objects to do the
# right thing.
ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured)
when Collection
ap_account_featured_collections_url(object.account_id)
end
end
end

View File

@ -204,7 +204,11 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
def update_tags!
previous_tags = @status.tags.to_a
current_tags = @status.tags = Tag.find_or_create_by_names(@raw_tags)
current_tags = @status.tags = @raw_tags.flat_map do |tag|
Tag.find_or_create_by_names([tag])
rescue ActiveRecord::RecordInvalid
[]
end
return unless @status.distributable?

View File

@ -8,11 +8,18 @@ class CreateCollectionService
build_items
@collection.save!
distribute_add_activity if Mastodon::Feature.collections_federation_enabled?
@collection
end
private
def distribute_add_activity
ActivityPub::AccountRawDistributionWorker.perform_async(activity_json, @account.id)
end
def build_items
return if @accounts_to_add.empty?
@ -23,4 +30,8 @@ class CreateCollectionService
@collection.collection_items.build(account: account_to_add)
end
end
def activity_json
ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::AddSerializer, adapter: ActivityPub::Adapter).to_json
end
end

View File

@ -81,9 +81,11 @@ class FanOutOnWriteService < BaseService
end
def notify_mentioned_accounts!
@status.active_mentions.where.not(id: @options[:silenced_account_ids] || []).joins(:account).merge(Account.local).select(:id, :account_id).reorder(nil).find_in_batches do |mentions|
@status.active_mentions.joins(:account).merge(Account.local).select(:id, :account_id).reorder(nil).find_in_batches do |mentions|
LocalNotificationWorker.push_bulk(mentions) do |mention|
[mention.account_id, mention.id, 'Mention', 'mention']
options = { 'silenced' => true } if @options[:silenced_account_ids]&.include?(mention.account_id)
[mention.account_id, mention.id, 'Mention', 'mention', options].compact
end
next unless update?

View File

@ -30,12 +30,13 @@ class NotifyService < BaseService
annual_report
).freeze
def initialize(notification)
def initialize(notification, **options)
@recipient = notification.account
@sender = notification.from_account
@notification = notification
@policy = NotificationPolicy.find_or_initialize_by(account: @recipient)
@from_staff = @sender.local? && @sender.user.present? && @sender.user_role&.bypass_block?(@recipient.user_role)
@options = options
end
private
@ -164,7 +165,7 @@ class NotifyService < BaseService
end
def blocked_by_limited_accounts_policy?
@policy.drop_limited_accounts? && @sender.silenced? && not_following?
@policy.drop_limited_accounts? && (@options[:silenced] || @sender.silenced?) && not_following?
end
end
@ -200,13 +201,14 @@ class NotifyService < BaseService
end
def filtered_by_limited_accounts_policy?
@policy.filter_limited_accounts? && @sender.silenced? && not_following?
@policy.filter_limited_accounts? && (@options[:silenced] || @sender.silenced?) && not_following?
end
end
def call(recipient, type, activity)
def call(recipient, type, activity, **options)
return if recipient.user.nil?
@options = options
@recipient = recipient
@activity = activity
@notification = Notification.new(account: @recipient, type: type, activity: @activity)
@ -236,11 +238,11 @@ class NotifyService < BaseService
private
def drop?
DropCondition.new(@notification).drop?
DropCondition.new(@notification, silenced: @options[:silenced]).drop?
end
def filter?
FilterCondition.new(@notification).filter?
FilterCondition.new(@notification, silenced: @options[:silenced]).filter?
end
def update_notification_request!

View File

@ -3,14 +3,9 @@
class LocalNotificationWorker
include Sidekiq::Worker
def perform(receiver_account_id, activity_id = nil, activity_class_name = nil, type = nil)
if activity_id.nil? && activity_class_name.nil?
activity = Mention.find(receiver_account_id)
receiver = activity.account
else
receiver = Account.find(receiver_account_id)
activity = activity_class_name.constantize.find(activity_id)
end
def perform(receiver_account_id, activity_id, activity_class_name, type = nil, options = {})
receiver = Account.find(receiver_account_id)
activity = activity_class_name.constantize.find(activity_id)
# For most notification types, only one notification should exist, and the older one is
# preferred. For updates, such as when a status is edited, the new notification
@ -23,7 +18,7 @@ class LocalNotificationWorker
return
end
NotifyService.new.call(receiver, type || activity_class_name.underscore, activity)
NotifyService.new.call(receiver, type || activity_class_name.underscore, activity, **options.symbolize_keys)
rescue ActiveRecord::RecordNotFound
true
end

View File

@ -1473,7 +1473,7 @@ da:
other: "%{count} individuelle indlæg skjult"
title: Filtre
new:
save: Gem nye filter
save: Gem nyt filter
title: Tilføj nyt filter
statuses:
back_to_filter: Tilbage til filter

View File

@ -241,8 +241,8 @@ el:
setting_boost_modal: Έλεγχος ορατότητας της ενίσχυσης
setting_color_scheme: Λειτουργία
setting_contrast: Αντίθεση
setting_default_language: Γλώσσα κατά την ανάρτηση
setting_default_privacy: Ορατότητα αναρτήσεων
setting_default_language: Γλώσσα ανάρτησης
setting_default_privacy: Ορατότητα ανάρτησης
setting_default_quote_policy: Ποιος μπορεί να παραθέσει
setting_default_sensitive: Σημείωση όλων των πολυμέσων ως ευαίσθητου περιεχομένου
setting_delete_modal: Προειδοποίηση πριν από τη διαγραφή μιας ανάρτησης
@ -255,7 +255,7 @@ el:
setting_emoji_style: Στυλ Emoji
setting_expand_spoilers: Μόνιμη ανάπτυξη των τουτ με προειδοποίηση περιεχομένου
setting_hide_network: Κρύψε τις διασυνδέσεις σου
setting_missing_alt_text_modal: Προειδοποίηση πριν από την ανάρτηση πολυμέσων χωρίς εναλλακτικό κείμενο
setting_missing_alt_text_modal: Προειδοποίηση πριν από τη δημοσίευση πολυμέσων χωρίς εναλλακτικό κείμενο
setting_quick_boosting: Ενεργοποίηση γρήγορης ενίσχυσης
setting_reduce_motion: Μείωση κίνησης κινουμένων στοιχείων
setting_system_font_ui: Χρήση της προεπιλεγμένης γραμματοσειράς του συστήματος

View File

@ -884,7 +884,7 @@ sq:
types:
major: Hedhje e rëndësishme në qarkullim
minor: Hedhje e vockël në qarkullim
patch: Hedhje në qarkullim arnimesh — ndreqje të meash dhe ndryshime të lehta për tu aplikuar
patch: Hedhje në qarkullim arnimesh — ndreqje të metash dhe ndryshime të lehta për tu aplikuar
version: Version
statuses:
account: Autor

View File

@ -1,76 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe OAuth::AuthorizationsController do
render_views
let(:app) { Doorkeeper::Application.create!(name: 'test', redirect_uri: 'http://localhost/', scopes: 'read') }
describe 'GET #new' do
subject do
get :new, params: { client_id: app.uid, response_type: 'code', redirect_uri: 'http://localhost/', scope: 'read' }
end
context 'when signed in' do
let!(:user) { Fabricate(:user) }
before do
sign_in user, scope: :user
end
it 'returns http success and private cache control headers' do
subject
expect(response)
.to have_http_status(200)
expect(response.headers['Cache-Control'])
.to include('private, no-store')
expect(response.parsed_body.at('body.modal-layout'))
.to be_present
expect(controller.stored_location_for(:user))
.to eq authorize_path_for(app)
end
context 'when app is already authorized' do
before do
Doorkeeper::AccessToken.find_or_create_for(
application: app,
resource_owner: user.id,
scopes: app.scopes,
expires_in: Doorkeeper.configuration.access_token_expires_in,
use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?
)
end
it 'redirects to callback' do
subject
expect(response).to redirect_to(/\A#{app.redirect_uri}/)
end
context 'with `force_login` param true' do
subject do
get :new, params: { client_id: app.uid, response_type: 'code', redirect_uri: 'http://localhost/', scope: 'read', force_login: 'true' }
end
it { is_expected.to have_http_status(:success) }
end
end
end
context 'when not signed in' do
it 'redirects' do
subject
expect(response)
.to redirect_to '/auth/sign_in'
expect(controller.stored_location_for(:user))
.to eq authorize_path_for(app)
end
end
def authorize_path_for(app)
"/oauth/authorize?client_id=#{app.uid}&redirect_uri=http%3A%2F%2Flocalhost%2F&response_type=code&scope=read"
end
end
end

View File

@ -1,71 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Settings::AliasesController do
render_views
let!(:user) { Fabricate(:user) }
let(:account) { user.account }
before do
sign_in user, scope: :user
end
describe 'GET #index' do
before do
get :index
end
it 'returns http success with private cache control headers', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.headers['Cache-Control']).to include('private, no-store')
end
end
describe 'POST #create' do
context 'with valid alias' do
before { stub_resolver }
it 'creates an alias for the user' do
expect do
post :create, params: { account_alias: { acct: 'new@example.com' } }
end.to change(AccountAlias, :count).by(1)
expect(response).to redirect_to(settings_aliases_path)
end
end
context 'with invalid alias' do
it 'does not create an alias for the user' do
expect do
post :create, params: { account_alias: { acct: 'format-wrong' } }
end.to_not change(AccountAlias, :count)
expect(response).to have_http_status(200)
end
end
end
describe 'DELETE #destroy' do
let(:account_alias) do
AccountAlias.new(account: user.account, acct: 'new@example.com').tap do |account_alias|
account_alias.save(validate: false)
end
end
it 'removes an alias' do
delete :destroy, params: { id: account_alias.id }
expect(response).to redirect_to(settings_aliases_path)
expect { account_alias.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
private
def stub_resolver
resolver = instance_double(ResolveAccountService, call: Fabricate(:account))
allow(ResolveAccountService).to receive(:new).and_return(resolver)
end
end

View File

@ -21,6 +21,28 @@ RSpec.describe Account::Counters do
expect(account.statuses_count).to eq increment_by
end
it 'updates last_status_at when discovering a new post' do
status_created_at = Time.now.utc
expect { account.increment_count!(:statuses_count, status_created_at:) }
.to(change { account.reload.last_status_at })
end
it 'does not update last_status_at when discovering an older post' do
account_stat = Fabricate(
:account_stat,
account: account,
last_status_at: 1.day.ago.utc,
statuses_count: 10
)
status_created_at = 2.days.ago.utc
expect { account.increment_count!(:statuses_count, status_created_at:) }
.to change { account_stat.reload.statuses_count }
.and(not_change { account_stat.reload.last_status_at })
end
end
describe '#decrement_count!' do

View File

@ -119,6 +119,7 @@ RSpec.configure do |config|
config.include SignedRequestHelpers, type: :request
config.include CommandLineHelpers, type: :cli
config.include SystemHelpers, type: :system
config.include Shoulda::Matchers::ActiveModel, type: :validator
# TODO: Remove when Devise fixes https://github.com/heartcombo/devise/issues/5705
config.before do

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'OAuth Authorizations' do
let(:application) { Fabricate :application, name: 'test', redirect_uri: 'http://localhost/', scopes: 'read' }
let(:params) { { client_id: application.uid, response_type: 'code', redirect_uri: 'http://localhost/', scope: 'read' } }
describe 'GET #new' do
subject { get oauth_authorization_path(params) }
context 'when signed in' do
let(:user) { Fabricate(:user) }
before { sign_in user }
it 'returns http success and private cache control headers' do
subject
expect(response)
.to have_http_status(:success)
expect(response.headers['Cache-Control'])
.to include('private, no-store')
expect(response.parsed_body.at('body.modal-layout'))
.to be_present
expect(controller.stored_location_for(:user))
.to eq authorize_path_for(application)
end
context 'when app is already authorized' do
before do
Doorkeeper::AccessToken.find_or_create_for(
application: application,
resource_owner: user.id,
scopes: application.scopes,
expires_in: Doorkeeper.configuration.access_token_expires_in,
use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?
)
end
it 'redirects to callback' do
subject
expect(response)
.to redirect_to(/\A#{application.redirect_uri}/)
end
context 'with `force_login` param true' do
subject do
get oauth_authorization_path(params.merge(force_login: 'true'))
end
it 'renders new page with success status' do
subject
expect(response)
.to have_http_status(:success)
expect(response.parsed_body.title)
.to match(I18n.t('doorkeeper.authorizations.new.title'))
end
end
end
end
context 'when not signed in' do
it 'redirects' do
subject
expect(response)
.to redirect_to(new_user_session_path)
expect(controller.stored_location_for(:user))
.to eq authorize_path_for(application)
end
end
def authorize_path_for(application)
"/oauth/authorize?client_id=#{application.uid}&redirect_uri=http%3A%2F%2Flocalhost%2F&response_type=code&scope=read"
end
end
end

View File

@ -2,21 +2,16 @@
require 'rails_helper'
RSpec.describe OAuth::AuthorizedApplicationsController do
render_views
describe 'GET #index' do
subject do
get :index
end
RSpec.describe 'OAuth Authorized Applications' do
describe 'GET /oauth/authorized_applications' do
subject { get oauth_authorized_applications_path }
context 'when signed in' do
before do
sign_in Fabricate(:user), scope: :user
end
before { sign_in Fabricate(:user) }
it 'returns http success with private cache control headers' do
subject
expect(response)
.to have_http_status(200)
expect(response.headers['Cache-Control'])
@ -40,29 +35,29 @@ RSpec.describe OAuth::AuthorizedApplicationsController do
end
end
describe 'DELETE #destroy' do
describe 'DELETE /oauth/authorized_applications/:id' do
subject { delete oauth_authorized_application_path(application) }
let!(:user) { Fabricate(:user) }
let!(:application) { Fabricate(:application) }
let!(:access_token) { Fabricate(:accessible_access_token, application: application, resource_owner_id: user.id) }
let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) }
let(:redis_pipeline_stub) { instance_double(Redis::PipelinedConnection, publish: nil) }
before do
sign_in user, scope: :user
allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub)
end
before { allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub) }
it 'revokes access tokens for the application and removes subscriptions and sends kill payload to streaming' do
post :destroy, params: { id: application.id }
context 'when signed in' do
before { sign_in user }
expect(Doorkeeper::AccessToken.where(application: application).first.revoked_at)
.to_not be_nil
expect(Web::PushSubscription.where(user: user).count)
.to eq(0)
expect { web_push_subscription.reload }
.to raise_error(ActiveRecord::RecordNotFound)
expect(redis_pipeline_stub)
.to have_received(:publish).with("timeline:access_token:#{access_token.id}", '{"event":"kill"}')
it 'revokes access tokens for the application and removes subscriptions and sends kill payload to streaming' do
expect { subject }
.to change { Doorkeeper::AccessToken.where(application:).first.reload.revoked_at }.from(nil).to(be_present)
.and change { Web::PushSubscription.where(user:).reload.count }.to(0)
expect { web_push_subscription.reload }
.to raise_error(ActiveRecord::RecordNotFound)
expect(redis_pipeline_stub)
.to have_received(:publish).with("timeline:access_token:#{access_token.id}", '{"event":"kill"}')
end
end
end
end

View File

@ -18,10 +18,38 @@ RSpec.describe ActivityPub::AddSerializer do
it { is_expected.to eq(ActivityPub::HashtagSerializer) }
end
context 'with a Collection model' do
let(:model) { Collection.new }
it { is_expected.to eq(ActivityPub::FeaturedCollectionSerializer) }
end
context 'with an Array' do
let(:model) { [] }
it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) }
end
end
describe '#target' do
subject { described_class.new(object).target }
context 'when object is a Status' do
let(:object) { Fabricate(:status) }
it { is_expected.to match(%r{/#{object.account_id}/collections/featured$}) }
end
context 'when object is a FeaturedTag' do
let(:object) { Fabricate(:featured_tag) }
it { is_expected.to match(%r{/#{object.account_id}/collections/featured$}) }
end
context 'when object is a Collection' do
let(:object) { Fabricate(:collection) }
it { is_expected.to match(%r{/#{object.account_id}/featured_collections$}) }
end
end
end

View File

@ -258,6 +258,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
tag: [
{ type: 'Hashtag', name: 'foo' },
{ type: 'Hashtag', name: 'bar' },
{ type: 'Hashtag', name: '#2024' },
],
}
end

View File

@ -29,6 +29,12 @@ RSpec.describe CreateCollectionService do
expect(collection).to be_local
end
it 'federates an `Add` activity', feature: :collections_federation do
subject.call(base_params, author)
expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job
end
context 'when given account ids' do
let(:accounts) do
Fabricate.times(2, :account)

View File

@ -45,7 +45,9 @@ RSpec.describe FanOutOnWriteService do
let(:visibility) { 'public' }
it 'adds status to home feed of author and followers and broadcasts', :inline_jobs do
subject.call(status)
expect { subject.call(status) }
.to change(bob.notifications, :count).by(1)
.and change(eve.notifications, :count).by(1)
expect(status.id)
.to be_in(home_feed_of(alice))
@ -58,6 +60,14 @@ RSpec.describe FanOutOnWriteService do
expect(redis).to have_received(:publish).with('timeline:public:local', anything)
expect(redis).to have_received(:publish).with('timeline:public:media', anything)
end
context 'with silenced_account_ids' do
it 'calls LocalNotificationWorker with the expected arguments' do
expect { subject.call(status, silenced_account_ids: [eve.id]) }
.to enqueue_sidekiq_job(LocalNotificationWorker).with(bob.id, anything, 'Mention', 'mention')
.and enqueue_sidekiq_job(LocalNotificationWorker).with(eve.id, anything, 'Mention', 'mention', { 'silenced' => true })
end
end
end
context 'when status is limited' do

View File

@ -224,13 +224,25 @@ RSpec.describe NotifyService do
end
end
context 'when sender is considered silenced through `silenced` option and recipient has a policy to ignore silenced accounts' do
subject { described_class.new(notification, silenced: true) }
before do
notification.account.create_notification_policy!(for_limited_accounts: :drop)
end
it 'returns true' do
expect(subject.drop?).to be true
end
end
context 'when sender is new and recipient has a default policy' do
it 'returns false' do
expect(subject.drop?).to be false
end
end
context 'when sender is new and recipient has a policy to ignore silenced accounts' do
context 'when sender is new and recipient has a policy to ignore new accounts' do
before do
notification.account.create_notification_policy!(for_new_accounts: :drop)
end
@ -240,7 +252,7 @@ RSpec.describe NotifyService do
end
end
context 'when sender is new and followed and recipient has a policy to ignore silenced accounts' do
context 'when sender is new and followed and recipient has a policy to ignore new accounts' do
before do
notification.account.create_notification_policy!(for_new_accounts: :drop)
notification.account.follow!(notification.from_account)
@ -300,6 +312,34 @@ RSpec.describe NotifyService do
end
end
context 'when sender is considered silenced through the `silenced` option' do
subject { described_class.new(notification, silenced: true) }
it 'returns true' do
expect(subject.filter?).to be true
end
context 'when recipient follows sender' do
before do
notification.account.follow!(notification.from_account)
end
it 'returns false' do
expect(subject.filter?).to be false
end
end
context 'when recipient is allowing limited accounts' do
before do
notification.account.create_notification_policy!(for_limited_accounts: :accept)
end
it 'returns false' do
expect(subject.filter?).to be false
end
end
end
context 'when recipient is filtering not-followed senders' do
before do
Fabricate(:notification_policy, account: notification.account, filter_not_following: true)

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Settings aliases page' do
let!(:user) { Fabricate(:user) }
let(:account) { user.account }
before { sign_in user }
describe 'Viewing aliases' do
it 'shows index page with private cache control headers' do
visit settings_aliases_path
# View index page
expect(page)
.to have_content(I18n.t('settings.aliases'))
.and have_private_cache_control
end
end
describe 'Creating an alias' do
context 'with valid alias value' do
before { stub_resolver }
it 'creates an alias for the user' do
visit settings_aliases_path
fill_in 'account_alias_acct',
with: 'new@host.example'
expect { submit_form }
.to change(AccountAlias, :count).by(1)
expect(page)
.to have_content(I18n.t('aliases.created_msg'))
end
end
context 'with invalid value' do
it 'does not create an alias for the user' do
visit settings_aliases_path
fill_in 'account_alias_acct',
with: 'invalid-value'
expect { submit_form }
.to not_change(AccountAlias, :count)
expect(page)
.to have_content(I18n.t('settings.aliases'))
end
end
def submit_form
click_on I18n.t('aliases.add_new')
end
end
describe 'Removing an alias' do
let!(:account_alias) do
AccountAlias.new(account: user.account, acct: 'new@example.com').tap do |account_alias|
account_alias.save(validate: false)
end
end
it 'removes an alias' do
visit settings_aliases_path
expect { click_on I18n.t('aliases.remove') }
.to change(AccountAlias, :count).by(-1)
expect(page)
.to have_content(I18n.t('settings.aliases'))
.and have_content(I18n.t('aliases.deleted_msg'))
expect { account_alias.reload }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
private
def stub_resolver
resolver = instance_double(ResolveAccountService, call: Fabricate(:account))
allow(ResolveAccountService).to receive(:new).and_return(resolver)
end
end

View File

@ -3,49 +3,25 @@
require 'rails_helper'
RSpec.describe DateOfBirthValidator do
let(:record_class) do
Class.new do
include ActiveModel::Validations
subject { Fabricate.build :user }
attr_accessor :date_of_birth
before { Setting.min_age = 16 }
validates :date_of_birth, date_of_birth: true
end
context 'with an invalid date' do
let(:invalid_date) { '76.830.10' }
it { is_expected.to_not allow_values(invalid_date).for(:date_of_birth) }
end
let(:record) { record_class.new }
context 'with a date below the age limit' do
let(:too_young) { 13.years.ago }
before do
Setting.min_age = 16
it { is_expected.to_not allow_values(too_young).for(:date_of_birth).with_message(:below_limit) }
end
describe '#validate_each' do
context 'with an invalid date' do
it 'adds errors' do
record.date_of_birth = '76.830.10'
context 'with a date above the age limit' do
let(:old_enough) { 16.years.ago }
expect(record).to_not be_valid
expect(record.errors.first.attribute).to eq(:date_of_birth)
expect(record.errors.first.type).to eq(:invalid)
end
end
context 'with a date below age limit' do
it 'adds errors' do
record.date_of_birth = 13.years.ago.strftime('%d.%m.%Y')
expect(record).to_not be_valid
expect(record.errors.first.attribute).to eq(:date_of_birth)
expect(record.errors.first.type).to eq(:below_limit)
end
end
context 'with a date above age limit' do
it 'does not add errors' do
record.date_of_birth = 16.years.ago.strftime('%d.%m.%Y')
expect(record).to be_valid
end
end
it { is_expected.to allow_values(old_enough).for(:date_of_birth) }
end
end

View File

@ -3,35 +3,23 @@
require 'rails_helper'
RSpec.describe PollExpirationValidator do
describe '#validate' do
before do
validator.validate(poll)
end
subject { Fabricate.build :poll }
let(:validator) { described_class.new }
let(:poll) { instance_double(Poll, options: options, expires_at: expires_at, errors: errors) }
let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
let(:options) { %w(foo bar) }
let(:expires_at) { 1.day.from_now }
context 'when poll expires in far future' do
let(:far_future) { 6.months.from_now }
it 'has no errors' do
expect(errors).to_not have_received(:add)
end
it { is_expected.to_not allow_value(far_future).for(:expires_at).with_message(I18n.t('polls.errors.duration_too_long')) }
end
context 'when the poll expires in 5 min from now' do
let(:expires_at) { 5.minutes.from_now }
context 'when poll expires in far past' do
let(:past_date) { 6.days.ago }
it 'has no errors' do
expect(errors).to_not have_received(:add)
end
end
it { is_expected.to_not allow_value(past_date).for(:expires_at).with_message(I18n.t('polls.errors.duration_too_short')) }
end
context 'when the poll expires in the past' do
let(:expires_at) { 5.minutes.ago }
context 'when poll expires in medium future' do
let(:allowed_future) { 10.minutes.from_now }
it 'has errors' do
expect(errors).to have_received(:add)
end
end
it { is_expected.to allow_value(allowed_future).for(:expires_at) }
end
end

View File

@ -214,7 +214,10 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => {
svgr(),
// Old library types need to be converted
optimizeLodashImports() as PluginOption,
!!process.env.ANALYZE_BUNDLE_SIZE && (visualizer() as PluginOption),
!!process.env.ANALYZE_BUNDLE_SIZE &&
(visualizer({
template: process.env.CI ? 'raw-data' : 'treemap',
}) as PluginOption),
MastodonNameLookup(),
],
} satisfies UserConfig;

214
yarn.lock
View File

@ -1265,10 +1265,10 @@ __metadata:
languageName: node
linkType: hard
"@csstools/color-helpers@npm:^6.0.0":
version: 6.0.0
resolution: "@csstools/color-helpers@npm:6.0.0"
checksum: 10c0/784447fa6ba2f5fec30f8676c48f9f66bff30a7a16582e3c3b3e9aa2574df1ac4e5f8e7455dfa6d991fd84ceceb22ff4016e2ea3a8116e76772b3972dbab7bec
"@csstools/color-helpers@npm:^6.0.1":
version: 6.0.1
resolution: "@csstools/color-helpers@npm:6.0.1"
checksum: 10c0/866844267d5aa5a02fe9d54f6db6fc18f6306595edb03664cc8ef15c99d3e6f3b42eb1a413c98bafa5b2dc0d8e0193da9b3bcc9d6a04f5de74cbd44935e74b3c
languageName: node
linkType: hard
@ -1305,16 +1305,16 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-color-parser@npm:^4.0.0":
version: 4.0.0
resolution: "@csstools/css-color-parser@npm:4.0.0"
"@csstools/css-color-parser@npm:^4.0.1":
version: 4.0.1
resolution: "@csstools/css-color-parser@npm:4.0.1"
dependencies:
"@csstools/color-helpers": "npm:^6.0.0"
"@csstools/color-helpers": "npm:^6.0.1"
"@csstools/css-calc": "npm:^3.0.0"
peerDependencies:
"@csstools/css-parser-algorithms": ^4.0.0
"@csstools/css-tokenizer": ^4.0.0
checksum: 10c0/bbfa3855bd53d5f31e2e9d40d8bc7b1143c7efd3dd6fa43c0ef2222a66bdc94f7ffedc8932a4eb4f10c5cbc38d1ed110fda99240037e0a2dae61c6b25527b0a2
checksum: 10c0/c46be5b9f5c0ef3cd25b47a71bd2a4d1c4856b123ecba4abe8eaa0688d3fc47f58fa67ea281d6b9efca4b9fdfa88fb045c51d0f9b8c612a56bd546d38260b138
languageName: node
linkType: hard
@ -1376,18 +1376,18 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-alpha-function@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/postcss-alpha-function@npm:2.0.1"
"@csstools/postcss-alpha-function@npm:^2.0.2":
version: 2.0.2
resolution: "@csstools/postcss-alpha-function@npm:2.0.2"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/7e38e245eb4a4d76dcaafd9ee1f6d1cdfc8fceabf835dd1615fbb9fb0944224d29c8d601e55db4b3ba1dd37a541c8568fd130106171ca11b012eb512ff1ec7a9
checksum: 10c0/2ffb1d4c6db63f9b841b8de18a0baf1b37bc321632af4d8dc0bba0c9472980ffb9c3c94a4b3445eedcc986399791fd2bc5e682b676d5a021d4db3e2602ad892f
languageName: node
linkType: hard
@ -1403,63 +1403,63 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-color-function-display-p3-linear@npm:^2.0.0":
version: 2.0.0
resolution: "@csstools/postcss-color-function-display-p3-linear@npm:2.0.0"
"@csstools/postcss-color-function-display-p3-linear@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/postcss-color-function-display-p3-linear@npm:2.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/c2d007dfa7500b6b54bca3a43f92f272cbdd8acaa9d4924eafa320c448f1cee77e7c00a229d5a6ffcc5211664b1dcfd7d54bb6622f2e7956e21ac51ea883165c
checksum: 10c0/cc6b79d8a19338f930f68bf4bac31bcbf38842068ed5779193979b5e2041a7b12386e4e992065cbf898c537ac6887ac530651f2164aaf77ced92d1bc4dd4ae50
languageName: node
linkType: hard
"@csstools/postcss-color-function@npm:^5.0.0":
version: 5.0.0
resolution: "@csstools/postcss-color-function@npm:5.0.0"
"@csstools/postcss-color-function@npm:^5.0.1":
version: 5.0.1
resolution: "@csstools/postcss-color-function@npm:5.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/b01b0e86df5cca222b9859601a4da18ee7a3bade52d6c7fada739cc6c6a4741173f0dccce05f4b307776bc8c0e156923930ce869faae9231e59aa2abab522581
checksum: 10c0/c6d28aae458a2f914367b3fa87ac300366bcf44c57ed7a6c6ebcdaec4d42901b7892a867b01d23af0edc0798035e205a7e590ccc3f288c632d8ed5ed9219455b
languageName: node
linkType: hard
"@csstools/postcss-color-mix-function@npm:^4.0.0":
version: 4.0.0
resolution: "@csstools/postcss-color-mix-function@npm:4.0.0"
"@csstools/postcss-color-mix-function@npm:^4.0.1":
version: 4.0.1
resolution: "@csstools/postcss-color-mix-function@npm:4.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/60fa7fbc5e97dc295fd01a90e866d3923bfe47a6e2e3317f01ec8ab0aca687901ef569cdda867780b3aa37a02efa44531bfa0d0d602b90803333baa4cfbf6063
checksum: 10c0/5137bb1fe2037609366e6282000ed1305151988a6968e336e9599cfed020495f9bf523d2eab64f83d74089f15a30d26d376a49c32ae3a3df16b3846d8690e477
languageName: node
linkType: hard
"@csstools/postcss-color-mix-variadic-function-arguments@npm:^2.0.0":
version: 2.0.0
resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:2.0.0"
"@csstools/postcss-color-mix-variadic-function-arguments@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:2.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/1206543ec6472f9dd7b67d3311128b66e3be2c6f1bdf9da1b6e4b4f9d69e9388e2896128bb3d1834d825ae4de9455524d7749501a60be740c52c0f7b67287263
checksum: 10c0/b4cd04a4650ba1b3340d99e5d020e860cef481d4ba4ecf1300ffb20863778a3376742c2b1209f14f0b13f1c0c63c7f3d84d1df812ef2a26ea53934f2f676d1df
languageName: node
linkType: hard
@ -1477,18 +1477,18 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-contrast-color-function@npm:^3.0.0":
version: 3.0.0
resolution: "@csstools/postcss-contrast-color-function@npm:3.0.0"
"@csstools/postcss-contrast-color-function@npm:^3.0.1":
version: 3.0.1
resolution: "@csstools/postcss-contrast-color-function@npm:3.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/7dec6e3bac9d45ae22b5dfcaec9ce8894a1ad80d1e62eb58634eebaa4a02a4f43f5b795a65224442b90b2285c0a0e1d7e80d9528fc1480348ab9a03bf9be2fe0
checksum: 10c0/00d17c77aae78953782cb51988fa76c00bb25316a59aba7a70c11142554f49ad48ce37963f10689fd908a98484d16e9531e808e5d2df60ae26aa6509a0c6b4f5
languageName: node
linkType: hard
@ -1517,46 +1517,46 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-gamut-mapping@npm:^3.0.0":
version: 3.0.0
resolution: "@csstools/postcss-gamut-mapping@npm:3.0.0"
"@csstools/postcss-gamut-mapping@npm:^3.0.1":
version: 3.0.1
resolution: "@csstools/postcss-gamut-mapping@npm:3.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/da138bee13c3af9e25962a425c70e242581722881fa52faa090dbb62d22adeced112d3589304651c759bae5fc0060eefb7eb62114798e96d3925bb01febc5a99
checksum: 10c0/a94337b13ed0332e2a80d07424e07bd726339476ce0ac009d00238c39b007cc7f2d58aeb48fe04d366a3015d7032f41bcc6a223f2f108574dcbfff7e53f694b0
languageName: node
linkType: hard
"@csstools/postcss-gradients-interpolation-method@npm:^6.0.0":
version: 6.0.0
resolution: "@csstools/postcss-gradients-interpolation-method@npm:6.0.0"
"@csstools/postcss-gradients-interpolation-method@npm:^6.0.1":
version: 6.0.1
resolution: "@csstools/postcss-gradients-interpolation-method@npm:6.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/33da282f65480d1a4e8a2ce336bf5e0948d47e9fdab0847d3c9e839ae640cb4af96defdcf4cb3d59e43019dec83a5e48fcd0fb96be927c43c7369e8be316fc79
checksum: 10c0/c930cfba3168d7d2016f7b30489fe3e8e6433ef2cc922635fb088946495439d7a4505ca65841897da60760c2962e0a112a15c0f67845e7f0ac9377e2736cc8c8
languageName: node
linkType: hard
"@csstools/postcss-hwb-function@npm:^5.0.0":
version: 5.0.0
resolution: "@csstools/postcss-hwb-function@npm:5.0.0"
"@csstools/postcss-hwb-function@npm:^5.0.1":
version: 5.0.1
resolution: "@csstools/postcss-hwb-function@npm:5.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/7f3e6d46531334621cc2c525df5c76cf916a53d0d9c82008b97b4b6b42f1b07b5729b4c3a8e5ba7b77c51ca44bbd216f83549e978f1aaf75e060335f754aacb2
checksum: 10c0/3951f49fdf081133bc339011e909fd60a2e36618a2ee20acf00baa0e1ebc6d5e30e988f5d70a4017c5247ef699d77e55ea9bb32cd7eb7a3683d2fc0f202429aa
languageName: node
linkType: hard
@ -1709,29 +1709,29 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-normalize-display-values@npm:^5.0.0":
version: 5.0.0
resolution: "@csstools/postcss-normalize-display-values@npm:5.0.0"
"@csstools/postcss-normalize-display-values@npm:^5.0.1":
version: 5.0.1
resolution: "@csstools/postcss-normalize-display-values@npm:5.0.1"
dependencies:
postcss-value-parser: "npm:^4.2.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/d43a2658604bdd037ab1516bffadea4db3224390838f5096fccdfbae12a2db08e56ca576724a0279968e6af39419e5fe2963632754dd0956fda2d01b848cf97e
checksum: 10c0/16b53d11db7ea6a48e1e43e06eb76d4d2b5c2c9af43b4f7e4d0d03345aa5f935a625d321d38a607739bfb59f6cc24b0506983dc5c599a7b795626ada33bba68f
languageName: node
linkType: hard
"@csstools/postcss-oklab-function@npm:^5.0.0":
version: 5.0.0
resolution: "@csstools/postcss-oklab-function@npm:5.0.0"
"@csstools/postcss-oklab-function@npm:^5.0.1":
version: 5.0.1
resolution: "@csstools/postcss-oklab-function@npm:5.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/af3413b1667be101f39b15b9528a57ff764e5abcf80578590b95c27c42a493ed9dcd2927834371d211b025c5d5df103cded6f2def98c9c79301f76ca379d7dbf
checksum: 10c0/ef5298c499fda9854f93d8ab1416cdb2bdb9120bca14ac7f97ea867821777aa67219c7784ffd70c793b02ba3ca3988a1c5f4fbd8be43f5d97cae822556847fe2
languageName: node
linkType: hard
@ -1780,18 +1780,18 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-relative-color-syntax@npm:^4.0.0":
version: 4.0.0
resolution: "@csstools/postcss-relative-color-syntax@npm:4.0.0"
"@csstools/postcss-relative-color-syntax@npm:^4.0.1":
version: 4.0.1
resolution: "@csstools/postcss-relative-color-syntax@npm:4.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/52f21acc66a0aa591d99173b9813f0b9f375de462bc657e909916ed176590b8d530a2eff83e71da488e2c8f476f0397d4a79bb7b7ef13664176d9e744a49510a
checksum: 10c0/101cb35ac950f45f21cb5aa853b57e304178d366390036e5390eda0ebcc21032f096d77863f6d978233f3b52b02c77acb6c394a965260ad41d1f49ddc0015aae
languageName: node
linkType: hard
@ -1855,15 +1855,15 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-text-decoration-shorthand@npm:^5.0.0":
version: 5.0.0
resolution: "@csstools/postcss-text-decoration-shorthand@npm:5.0.0"
"@csstools/postcss-text-decoration-shorthand@npm:^5.0.1":
version: 5.0.1
resolution: "@csstools/postcss-text-decoration-shorthand@npm:5.0.1"
dependencies:
"@csstools/color-helpers": "npm:^6.0.0"
"@csstools/color-helpers": "npm:^6.0.1"
postcss-value-parser: "npm:^4.2.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/0d1dc47da13adc1f0863017a3448f3a79774f00f1952fc2bfc09404b00b03d125b54dedce036e0e5c73a6f2a64cdabe702e9bc03dc6b88d3b71a6424cd0cec80
checksum: 10c0/e9a6f4d02a4584bf227783296eb4016542d70345152c2c0f1012146376eaf7e3efa7e27d8eab6856857612b8d682ea1085b5d027ad6f05406e0adee7b6244c33
languageName: node
linkType: hard
@ -5545,13 +5545,13 @@ __metadata:
linkType: hard
"axios@npm:^1.4.0":
version: 1.13.2
resolution: "axios@npm:1.13.2"
version: 1.13.3
resolution: "axios@npm:1.13.3"
dependencies:
follow-redirects: "npm:^1.15.6"
form-data: "npm:^4.0.4"
proxy-from-env: "npm:^1.1.0"
checksum: 10c0/e8a42e37e5568ae9c7a28c348db0e8cf3e43d06fcbef73f0048669edfe4f71219664da7b6cc991b0c0f01c28a48f037c515263cb79be1f1ae8ff034cd813867b
checksum: 10c0/86f0770624d9f14a3f8f8738c8b8f7f7fbb7b0d4ad38757db1de2d71007a0311bc597661c5ff4b4a9ee6350c6956a7282e3a281fcdf7b5b32054e35a8801e2ce
languageName: node
linkType: hard
@ -10683,8 +10683,8 @@ __metadata:
linkType: hard
"pino@npm:^10.0.0":
version: 10.2.1
resolution: "pino@npm:10.2.1"
version: 10.3.0
resolution: "pino@npm:10.3.0"
dependencies:
"@pinojs/redact": "npm:^0.4.0"
atomic-sleep: "npm:^1.0.0"
@ -10699,7 +10699,7 @@ __metadata:
thread-stream: "npm:^4.0.0"
bin:
pino: bin.js
checksum: 10c0/2eaed48bb7fb8865e27ac6d6709383f5c117f1e59c818734c7cc22b362e9aa5846a0547e7fd9cde64088a3b48aa314e1dab07ee16da8dc3b87897970eb56843e
checksum: 10c0/cfbbc7dfaa2df2aa2dce728d751aa4b5b7ab973b2cd4bfff57868567563ef0c1c021f22932769141d535c72662390e09a0190e44f4413496dbe5e3c672816308
languageName: node
linkType: hard
@ -10774,18 +10774,18 @@ __metadata:
languageName: node
linkType: hard
"postcss-color-functional-notation@npm:^8.0.0":
version: 8.0.0
resolution: "postcss-color-functional-notation@npm:8.0.0"
"postcss-color-functional-notation@npm:^8.0.1":
version: 8.0.1
resolution: "postcss-color-functional-notation@npm:8.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/4b23633475b57f5d0076340ab4f434506891b5a25254d077215c08d08bbdab369484488bbc249595618730afea3546329e5633cc9e119f31c756b1c224dd0300
checksum: 10c0/e5f1ec677f752344bbb9a65b011465d08d1fe5a2cf02eea2b337e472a555bf137030b96b42b833bfa07ffe9afbf76b9f5cd2ac05dbf5df7d66c785b8fb9a365a
languageName: node
linkType: hard
@ -10932,18 +10932,18 @@ __metadata:
languageName: node
linkType: hard
"postcss-lab-function@npm:^8.0.0":
version: 8.0.0
resolution: "postcss-lab-function@npm:8.0.0"
"postcss-lab-function@npm:^8.0.1":
version: 8.0.1
resolution: "postcss-lab-function@npm:8.0.1"
dependencies:
"@csstools/css-color-parser": "npm:^4.0.0"
"@csstools/css-color-parser": "npm:^4.0.1"
"@csstools/css-parser-algorithms": "npm:^4.0.0"
"@csstools/css-tokenizer": "npm:^4.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/utilities": "npm:^3.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/8805a853016c5919afd95f7cbee9ba624f398d25c81a2a7987edc0d0031fee08732b02f64cb51f537f364b0de07fc837bb4bd375e0ea96a489860c59a94c0590
checksum: 10c0/f1fd9d7cd99c1b87247c87de9d5f462c12ad9bcfe922837f37ecf20c43893ef863aa3dc35fd90fed69a5120a1379e8f952ca3cc2112faecbc8196192d15d805b
languageName: node
linkType: hard
@ -11070,22 +11070,22 @@ __metadata:
linkType: hard
"postcss-preset-env@npm:^11.0.0":
version: 11.1.1
resolution: "postcss-preset-env@npm:11.1.1"
version: 11.1.2
resolution: "postcss-preset-env@npm:11.1.2"
dependencies:
"@csstools/postcss-alpha-function": "npm:^2.0.1"
"@csstools/postcss-alpha-function": "npm:^2.0.2"
"@csstools/postcss-cascade-layers": "npm:^6.0.0"
"@csstools/postcss-color-function": "npm:^5.0.0"
"@csstools/postcss-color-function-display-p3-linear": "npm:^2.0.0"
"@csstools/postcss-color-mix-function": "npm:^4.0.0"
"@csstools/postcss-color-mix-variadic-function-arguments": "npm:^2.0.0"
"@csstools/postcss-color-function": "npm:^5.0.1"
"@csstools/postcss-color-function-display-p3-linear": "npm:^2.0.1"
"@csstools/postcss-color-mix-function": "npm:^4.0.1"
"@csstools/postcss-color-mix-variadic-function-arguments": "npm:^2.0.1"
"@csstools/postcss-content-alt-text": "npm:^3.0.0"
"@csstools/postcss-contrast-color-function": "npm:^3.0.0"
"@csstools/postcss-contrast-color-function": "npm:^3.0.1"
"@csstools/postcss-exponential-functions": "npm:^3.0.0"
"@csstools/postcss-font-format-keywords": "npm:^5.0.0"
"@csstools/postcss-gamut-mapping": "npm:^3.0.0"
"@csstools/postcss-gradients-interpolation-method": "npm:^6.0.0"
"@csstools/postcss-hwb-function": "npm:^5.0.0"
"@csstools/postcss-gamut-mapping": "npm:^3.0.1"
"@csstools/postcss-gradients-interpolation-method": "npm:^6.0.1"
"@csstools/postcss-hwb-function": "npm:^5.0.1"
"@csstools/postcss-ic-unit": "npm:^5.0.0"
"@csstools/postcss-initial": "npm:^3.0.0"
"@csstools/postcss-is-pseudo-class": "npm:^6.0.0"
@ -11099,19 +11099,19 @@ __metadata:
"@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^4.0.0"
"@csstools/postcss-mixins": "npm:^1.0.0"
"@csstools/postcss-nested-calc": "npm:^5.0.0"
"@csstools/postcss-normalize-display-values": "npm:^5.0.0"
"@csstools/postcss-oklab-function": "npm:^5.0.0"
"@csstools/postcss-normalize-display-values": "npm:^5.0.1"
"@csstools/postcss-oklab-function": "npm:^5.0.1"
"@csstools/postcss-position-area-property": "npm:^2.0.0"
"@csstools/postcss-progressive-custom-properties": "npm:^5.0.0"
"@csstools/postcss-property-rule-prelude-list": "npm:^2.0.0"
"@csstools/postcss-random-function": "npm:^3.0.0"
"@csstools/postcss-relative-color-syntax": "npm:^4.0.0"
"@csstools/postcss-relative-color-syntax": "npm:^4.0.1"
"@csstools/postcss-scope-pseudo-class": "npm:^5.0.0"
"@csstools/postcss-sign-functions": "npm:^2.0.0"
"@csstools/postcss-stepped-value-functions": "npm:^5.0.0"
"@csstools/postcss-syntax-descriptor-syntax-production": "npm:^2.0.0"
"@csstools/postcss-system-ui-font-family": "npm:^2.0.0"
"@csstools/postcss-text-decoration-shorthand": "npm:^5.0.0"
"@csstools/postcss-text-decoration-shorthand": "npm:^5.0.1"
"@csstools/postcss-trigonometric-functions": "npm:^5.0.0"
"@csstools/postcss-unset-value": "npm:^5.0.0"
autoprefixer: "npm:^10.4.23"
@ -11122,7 +11122,7 @@ __metadata:
cssdb: "npm:^8.7.0"
postcss-attribute-case-insensitive: "npm:^8.0.0"
postcss-clamp: "npm:^4.1.0"
postcss-color-functional-notation: "npm:^8.0.0"
postcss-color-functional-notation: "npm:^8.0.1"
postcss-color-hex-alpha: "npm:^11.0.0"
postcss-color-rebeccapurple: "npm:^11.0.0"
postcss-custom-media: "npm:^12.0.0"
@ -11135,7 +11135,7 @@ __metadata:
postcss-font-variant: "npm:^5.0.0"
postcss-gap-properties: "npm:^7.0.0"
postcss-image-set-function: "npm:^8.0.0"
postcss-lab-function: "npm:^8.0.0"
postcss-lab-function: "npm:^8.0.1"
postcss-logical: "npm:^9.0.0"
postcss-nesting: "npm:^14.0.0"
postcss-opacity-percentage: "npm:^3.0.0"
@ -11147,7 +11147,7 @@ __metadata:
postcss-selector-not: "npm:^9.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/ed9e0b07c82368defe1ab7f776e8b163610c9db3a79e36cf95291f65959d912261aa8c4b94734bba047fe875fcf3eb6e0dd71dec93640dfd94578cba33833c5a
checksum: 10c0/01cf7e5bc399e9c7521a8fdb6ef20763addd16c9e0fdf57ab6d3fa8da9ddfbc937db36bf6ea0d55492f2c788bb5ec3b43f40eb91ee1abf78622a30fbc812107f
languageName: node
linkType: hard