diff --git a/Gemfile.lock b/Gemfile.lock index 920209f6c3..7144931dba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/javascript/mastodon/components/callout/index.tsx b/app/javascript/mastodon/components/callout/index.tsx index e7ab410a9c..a9232ec3a7 100644 --- a/app/javascript/mastodon/components/callout/index.tsx +++ b/app/javascript/mastodon/components/callout/index.tsx @@ -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 = ({ onSecondary: secondaryAction, secondaryLabel, onClose, + extraContent, id, }) => { const intl = useIntl(); @@ -105,6 +107,8 @@ export const Callout: FC = ({ )} + {extraContent} + {onClose && ( { children?: never; id: string; icon: IconProp; + noFill?: boolean; } export const Icon: React.FC = ({ @@ -20,6 +21,7 @@ export const Icon: React.FC = ({ 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 = ({ return ( - {domain} - {children} + {children ?? domain} - + {isRedesignEnabled() && } @@ -166,7 +162,9 @@ export const AccountHeader: React.FC<{ {!suspendedOrHidden && (
- {me && account.id !== me && ( + {me && account.id !== me && isRedesignEnabled() ? ( + + ) : ( )} diff --git a/app/javascript/mastodon/features/account_timeline/components/account_name.tsx b/app/javascript/mastodon/features/account_timeline/components/account_name.tsx index 90ccf7486d..20bff5aeee 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_name.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_name.tsx @@ -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 ( -

- - - - @{username} - {isRedesignEnabled() && '@'} - - {!isRedesignEnabled() && '@'} - {domain} + if (!isRedesignEnabled()) { + return ( +

+ + + + @{username} + @{domain} - + + {account.locked && ( + + )} + +

+ ); + } + + return ( +
+

+ +

+

+ @{username}@{domain} - {isRedesignEnabled() && } + - {!isRedesignEnabled() && account.locked && ( - - )} - -

+

+
); }; diff --git a/app/javascript/mastodon/features/account_timeline/components/badges.tsx b/app/javascript/mastodon/features/account_timeline/components/badges.tsx index 1c5942d90d..1c7dca1ef5 100644 --- a/app/javascript/mastodon/features/account_timeline/components/badges.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/badges.tsx @@ -37,7 +37,12 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { let icon: ReactNode = undefined; if (isAdminBadge(role)) { icon = ( - + ); } badges.push( diff --git a/app/javascript/mastodon/features/account_timeline/components/fields.tsx b/app/javascript/mastodon/features/account_timeline/components/fields.tsx index ab29a8299e..d407c5010f 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields.tsx @@ -76,6 +76,7 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { id='verified' icon={IconVerified} className={classes.fieldIconVerified} + noFill /> )} diff --git a/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx b/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx index 103fffca50..5c29e77c11 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx @@ -81,6 +81,7 @@ export const AccountFieldsModal: FC<{ id='verified' icon={IconVerified} className={classes.fieldIconVerified} + noFill /> )} diff --git a/app/javascript/mastodon/features/account_timeline/components/menu.tsx b/app/javascript/mastodon/features/account_timeline/components/menu.tsx index ce98c61f76..80fd055cca 100644 --- a/app/javascript/mastodon/features/account_timeline/components/menu.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/menu.tsx @@ -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, diff --git a/app/javascript/mastodon/features/account_timeline/components/note.tsx b/app/javascript/mastodon/features/account_timeline/components/note.tsx new file mode 100644 index 0000000000..dd22d92e7b --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/note.tsx @@ -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 ( + + } + > + {relationship.note} + + ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss index 4bc64d05a9..028e2b41dc 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -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 { diff --git a/app/javascript/mastodon/features/account_timeline/modals/modals.module.css b/app/javascript/mastodon/features/account_timeline/modals/modals.module.css new file mode 100644 index 0000000000..cee0bc498a --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/modals/modals.module.css @@ -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; +} diff --git a/app/javascript/mastodon/features/account_timeline/modals/note_modal.tsx b/app/javascript/mastodon/features/account_timeline/modals/note_modal.tsx new file mode 100644 index 0000000000..0fa665a9cc --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/modals/note_modal.tsx @@ -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 ; + } + + return ( + + ); +}; + +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 = 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 ( + + + + + + + } + onClose={handleCancel} + confirm={intl.formatMessage(messages.save)} + onConfirm={handleSave} + updating={state === 'saving'} + disabled={!isDirty} + closeWhenConfirm={false} + noFocusButton + /> + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx index 04831ee1dd..a37e080d00 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx @@ -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} )} - {/* eslint-disable-next-line jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */} - + {/* eslint-enable */}
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 0e718747d9..0458bac93c 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -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 })), }; diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 53d90d3eab..94c244ea38 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -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": "هذا يتصرف بالضبط مثل النشر للعامة، باستثناء أن المنشور لن يظهر في الموجزات الحية أو في الوسوم أو في الإستكشاف، أو في نتائج بحث ماستدون، حتى وإن قمت بتفعيله على مستوى الحساب.", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 095fee741d..85ce20d024 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -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, нават калі Вы ўключылі бачнасць у пошуку ў наладах.", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index d9f9006d3e..3549c0ae2a 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -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, дори ако сте избрали да се публично видими на ниво акаунт.", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index eb144adfa0..0e9e9d33e6 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -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", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 1a25cd95d8..b74658a334 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -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.", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index b29ca9c352..a29a08a20c 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -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.", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 6c7223b38b..3eeb9cf6ea 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -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.", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index dbc4726c85..2bb186fb6f 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -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.", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index a866ddf284..61100ab78e 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -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.", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 8e5cf51d0d..72aa120c02 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -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, ακόμη και αν το έχεις επιλέξει για τον λογαριασμό σου.", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 5c5d992188..b0064179e9 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -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.", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 1d5b8f35cf..9fc147d3a8 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -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", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 23e26b4dd5..a8d960bc93 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -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", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 7e87e5c4fd..97a3971912 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -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.", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 5e66b7b067..22afd2d8f8 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -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.", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index b8ddab5199..a4f9c84d67 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -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": "Diapositiva {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.", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index c5dd1afa22..f8840d8779 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -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.", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 306e24e59f..fb50eff98b 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -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.", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 110f8a7153..9501be8b93 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -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": "درست مثل عمومی رفتار می‌کند؛ جز این که فرسته در برچسب‌ها یا خوراک‌های زنده، کشف یا جست‌وجوی ماستودون ظاهر نخواهد شد. حتا اگر کلیّت نمایه‌تان اجازه داده باشد.", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 16d7d5006d..f247403ca7 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -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.", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 51200d5a41..342c482f11 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -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.", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 111a0450ff..3e7c00d691 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -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.", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 50db4dfb4b..7a250a6c50 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -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.", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index ad272e270a..d44ae06986 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -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.", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index ef4f648770..861f7b04e7 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -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": "ההתנהגות דומה להודעה ציבורית, מלבד שההודעה לא תופיע בפיד החי המקומי או בתגיות, תגליות או חיפוש מסטודון, אפילו אם ביקשת שהחשבון כולו יהיה פומבי.", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index b405b5b095..4d0a9b0314 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -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.", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index ac8e9fe438..d9a2f5e844 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -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.", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index f1b2e5ff38..dd4d77ce51 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -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.", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index e9128ee8b1..35bd2fb5be 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -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.", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 3772255942..098d41ffca 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -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検索などに投稿が表示されない点で「公開」と異なります。また、アカウント設定で投稿の検索や表示を許可している場合でも、この公開範囲を設定した投稿は前述の機能には表示されません。", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 92850880aa..857c18821f 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -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": "공개와 똑같지만 게시물이 실시간 피드나 해시태그, 둘러보기, (계정 설정에서 허용했더라도) 마스토돈 검색에서 제외됩니다.", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 3b6957b0c2..154d4aeb42 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -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ą.", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index f30c31cf95..d73177da90 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -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.", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 4d7df2e529..31e0b68d9e 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -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âng,m̄-koh 就算lí佇口座設定phah開有關ê公開功能,PO文mā bē顯示佇即時ê動態、hashtag、探索kap Mastodon ê搜尋結果。", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 313a05cb95..0da945e937 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -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.", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 7bf3e42234..4b553ffc13 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -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.", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 7a037beb87..ee44d1c30a 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -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.", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index ad244780f9..c9e2e5eeb1 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -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.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 255719a455..9433c10c5f 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -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.", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 9a18842f76..7424a73f12 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -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, даже если вы разрешили поиск по своим постам в настройках профиля.", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index d3209da510..3c1112abef 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -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ú.", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 76c176fe75..810999622d 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -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 s’do 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ë.", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 9636c188ea..c4839f6c54 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -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.", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index a6fc469c2d..52887b8451 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -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 แม้ว่าคุณได้เลือกรับทั่วทั้งบัญชีก็ตาม", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 33431b8b44..bb9dd8fcda 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -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": "Slayt {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.", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index e2a7816df4..80702ff3f6 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -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, навіть якщо ви використовуєте облікові записи.", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 72e22a380d..30d0ee58db 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -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.", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 9667c7621d..5be74aa9a1 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -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 搜索页面中,即使你已全局开启了对应的发现设置。", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index fa3a536c05..95f66ccc09 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -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 搜尋中,即使您於帳戶設定中選擇加入。", diff --git a/app/javascript/material-icons/400-24px/help-fill.svg b/app/javascript/material-icons/400-24px/help-fill.svg new file mode 100644 index 0000000000..6fd48ca5dd --- /dev/null +++ b/app/javascript/material-icons/400-24px/help-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/help.svg b/app/javascript/material-icons/400-24px/help.svg new file mode 100644 index 0000000000..0f10691c55 --- /dev/null +++ b/app/javascript/material-icons/400-24px/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 6526b380df..b30509813f 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -275,7 +275,7 @@ height: 24px; aspect-ratio: 1; - path { + &:not(.icon--no-fill) path { fill: currentColor; } } diff --git a/app/models/concerns/account/counters.rb b/app/models/concerns/account/counters.rb index 536d5ca7bc..887cd07374 100644 --- a/app/models/concerns/account/counters.rb +++ b/app/models/concerns/account/counters.rb @@ -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 diff --git a/app/models/status.rb b/app/models/status.rb index 6d2eb55e52..8761487c33 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -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 diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb index 640d774272..a2a1256ec5 100644 --- a/app/serializers/activitypub/add_serializer.rb +++ b/app/serializers/activitypub/add_serializer.rb @@ -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 diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 1cdf0b4830..166db90e0b 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -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? diff --git a/app/services/create_collection_service.rb b/app/services/create_collection_service.rb index 10843cb967..008d4b5c09 100644 --- a/app/services/create_collection_service.rb +++ b/app/services/create_collection_service.rb @@ -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 diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 1c469f3763..b7b0d6b90e 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -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? diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 8d0986adcb..0c40e7b3a8 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -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! diff --git a/app/workers/local_notification_worker.rb b/app/workers/local_notification_worker.rb index e76572984d..7e9bd0c92b 100644 --- a/app/workers/local_notification_worker.rb +++ b/app/workers/local_notification_worker.rb @@ -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 diff --git a/config/locales/da.yml b/config/locales/da.yml index 25c7619cb7..5c28950e05 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -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 diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index 3c06421aee..93b134b3ea 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -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: Χρήση της προεπιλεγμένης γραμματοσειράς του συστήματος diff --git a/config/locales/sq.yml b/config/locales/sq.yml index bf2e7f7a60..159014d168 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -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 t’u aplikuar + patch: Hedhje në qarkullim arnimesh — ndreqje të metash dhe ndryshime të lehta për t’u aplikuar version: Version statuses: account: Autor diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb deleted file mode 100644 index aa557b67d8..0000000000 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ /dev/null @@ -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 diff --git a/spec/controllers/settings/aliases_controller_spec.rb b/spec/controllers/settings/aliases_controller_spec.rb deleted file mode 100644 index 4858c15298..0000000000 --- a/spec/controllers/settings/aliases_controller_spec.rb +++ /dev/null @@ -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 diff --git a/spec/models/concerns/account/counters_spec.rb b/spec/models/concerns/account/counters_spec.rb index bbbaa7d06c..17c63dd0e8 100644 --- a/spec/models/concerns/account/counters_spec.rb +++ b/spec/models/concerns/account/counters_spec.rb @@ -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 diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 6be93ecb70..bc12d1bece 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -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 diff --git a/spec/requests/oauth/authorizations_spec.rb b/spec/requests/oauth/authorizations_spec.rb new file mode 100644 index 0000000000..f17b58a002 --- /dev/null +++ b/spec/requests/oauth/authorizations_spec.rb @@ -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 diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/requests/oauth/authorized_applications_spec.rb similarity index 53% rename from spec/controllers/oauth/authorized_applications_controller_spec.rb rename to spec/requests/oauth/authorized_applications_spec.rb index 8d804476ee..397cac98db 100644 --- a/spec/controllers/oauth/authorized_applications_controller_spec.rb +++ b/spec/requests/oauth/authorized_applications_spec.rb @@ -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 diff --git a/spec/serializers/activitypub/add_serializer_spec.rb b/spec/serializers/activitypub/add_serializer_spec.rb index 3b3eaeb1b0..a5e1052fa0 100644 --- a/spec/serializers/activitypub/add_serializer_spec.rb +++ b/spec/serializers/activitypub/add_serializer_spec.rb @@ -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 diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 1a19cbb782..cceeda1bb1 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -258,6 +258,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do tag: [ { type: 'Hashtag', name: 'foo' }, { type: 'Hashtag', name: 'bar' }, + { type: 'Hashtag', name: '#2024' }, ], } end diff --git a/spec/services/create_collection_service_spec.rb b/spec/services/create_collection_service_spec.rb index f88a366a6c..8189b01fbe 100644 --- a/spec/services/create_collection_service_spec.rb +++ b/spec/services/create_collection_service_spec.rb @@ -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) diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index 9f48899560..79ecd06c8d 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -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 diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 9d9d4eed3d..9927fa9f04 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -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) diff --git a/spec/system/settings/aliases_spec.rb b/spec/system/settings/aliases_spec.rb new file mode 100644 index 0000000000..96d9461503 --- /dev/null +++ b/spec/system/settings/aliases_spec.rb @@ -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 diff --git a/spec/validators/date_of_birth_validator_spec.rb b/spec/validators/date_of_birth_validator_spec.rb index 33e69e811b..65b63db234 100644 --- a/spec/validators/date_of_birth_validator_spec.rb +++ b/spec/validators/date_of_birth_validator_spec.rb @@ -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 diff --git a/spec/validators/poll_expiration_validator_spec.rb b/spec/validators/poll_expiration_validator_spec.rb index 41b8c96211..ea726c0591 100644 --- a/spec/validators/poll_expiration_validator_spec.rb +++ b/spec/validators/poll_expiration_validator_spec.rb @@ -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 diff --git a/vite.config.mts b/vite.config.mts index 046722f325..6984ce3dd3 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -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; diff --git a/yarn.lock b/yarn.lock index 27b4c76088..f2f934ebff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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