Bump version to v4.3.0-alpha.4+glitch+cat+1.0.0

This commit is contained in:
Jeremy Kescher 2024-05-30 18:45:08 +02:00
commit cb080ca3ef
No known key found for this signature in database
GPG key ID: 80A419A7A613DFA4
99 changed files with 849 additions and 553 deletions

View file

@ -2,6 +2,7 @@ version: '3'
services:
app:
working_dir: /workspaces/mastodon/
build:
context: .
dockerfile: Dockerfile

View file

@ -176,12 +176,6 @@ Style/SafeNavigation:
Exclude:
- 'app/models/concerns/account/finder_concern.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode.
Style/StringConcatenation:
Exclude:
- 'config/initializers/paperclip.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: WordRegex.
# SupportedStyles: percent, brackets

View file

@ -5,6 +5,7 @@ All changes to Catstodon that aren't Mastodon or glitch-soc Mastodon changes wil
## [v4.3.0-alpha.3+glitch+cat+1.2.7] - 2024-05-29
- Upstream changes
- Includes security fixes announced in version 4.2.9!
- Add ability to disable the suspicious sign-in detector
- The CatCatNya~ production config has an IP retention period of one day. That will cause suspicious login emails to
be sent out simply because we don't have any known IP data. Therefore, this fix is being applied.

View file

@ -2,6 +2,61 @@
All notable changes to this project will be documented in this file.
## [4.2.9] - 2024-05-30
### Security
- Update dependencies
- Fix private mention filtering ([GHSA-5fq7-3p3j-9vrf](https://github.com/mastodon/mastodon/security/advisories/GHSA-5fq7-3p3j-9vrf))
- Fix password change endpoint not being rate-limited ([GHSA-q3rg-xx5v-4mxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-q3rg-xx5v-4mxh))
- Add hardening around rate-limit bypass ([GHSA-c2r5-cfqr-c553](https://github.com/mastodon/mastodon/security/advisories/GHSA-c2r5-cfqr-c553))
### Added
- Add rate-limit on OAuth application registration ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
- Add fallback redirection when getting a webfinger query `WEB_DOMAIN@WEB_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28592))
- Add `digest` attribute to `Admin::DomainBlock` entity in REST API ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29092))
### Removed
- Remove superfluous application-level caching in some controllers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29862))
- Remove aggressive OAuth application vacuuming ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
### Fixed
- Fix leaking Elasticsearch connections in Sidekiq processes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30450))
- Fix language of remote posts not being recognized when using unusual casing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30403))
- Fix off-by-one in `tootctl media` commands ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30306))
- Fix removal of allowed domains (in `LIMITED_FEDERATION_MODE`) not being recorded in the audit log ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30125))
- Fix not being able to block a subdomain of an already-blocked domain through the API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30119))
- Fix `Idempotency-Key` being ignored when scheduling a post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30084))
- Fix crash when supplying the `FFMPEG_BINARY` environment variable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30022))
- Fix improper email address validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29838))
- Fix results/query in `api/v1/featured_tags/suggestions` ([mjankowski](https://github.com/mastodon/mastodon/pull/29597))
- Fix unblocking internationalized domain names under certain conditions ([tribela](https://github.com/mastodon/mastodon/pull/29530))
- Fix admin account created by `mastodon:setup` not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29379))
- Fix reference to non-existent var in CLI maintenance command ([mjankowski](https://github.com/mastodon/mastodon/pull/28363))
## [4.2.8] - 2024-02-23
### Added
- Add hourly task to automatically require approval for new registrations in the absence of moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29318), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29355))
In order to prevent future abandoned Mastodon servers from being used for spam, harassment and other malicious activity, Mastodon will now automatically switch new user registrations to require moderator approval whenever they are left open and no activity (including non-moderation actions from apps) from any logged-in user with permission to access moderation reports has been detected in a full week.
When this happens, users with the permission to change server settings will receive an email notification.
This feature is disabled when `EMAIL_DOMAIN_ALLOWLIST` is used, and can also be disabled with `DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS=true`.
### Changed
- Change registrations to be closed by default on new installations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29280))
If you are running a server and never changed your registrations mode from the default, updating will automatically close your registrations.
Simply re-enable them through the administration interface or using `tootctl settings registrations open` if you want to enable them again.
### Fixed
- Fix processing of remote ActivityPub actors making use of `Link` objects as `Image` `url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29335))
- Fix link verifications when page size exceeds 1MB ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29358))
## [4.2.7] - 2024-02-16
### Fixed

View file

@ -237,7 +237,7 @@ GEM
tzinfo
excon (0.110.0)
fabrication (2.31.0)
faker (3.3.1)
faker (3.4.1)
i18n (>= 1.8.11, < 2)
faraday (1.10.3)
faraday-em_http (~> 1.0)
@ -431,7 +431,7 @@ GEM
mime-types-data (3.2024.0507)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.23.0)
minitest (5.23.1)
msgpack (1.7.2)
multi_json (1.15.0)
multipart-post (2.4.0)
@ -805,7 +805,7 @@ GEM
thor (>= 0.20, < 3.0)
simple-navigation (4.4.0)
activesupport (>= 2.3.2)
simple_form (5.3.0)
simple_form (5.3.1)
actionpack (>= 5.2)
activemodel (>= 5.2)
simplecov (0.22.0)

View file

@ -106,11 +106,11 @@ class Api::V1::AccountsController < Api::BaseController
end
def account_ids
Array(accounts_params[:ids]).uniq.map(&:to_i)
Array(accounts_params[:id]).uniq.map(&:to_i)
end
def accounts_params
params.permit(ids: [])
params.permit(id: [])
end
def account_params

View file

@ -38,15 +38,15 @@ class Api::V1::ConversationsController < Api::BaseController
def paginated_conversations
AccountConversation.where(account: current_account)
.includes(
account: :account_stat,
account: [:account_stat, user: :role],
last_status: [
:media_attachments,
:status_stat,
:tags,
{
preview_cards_status: :preview_card,
active_mentions: [account: :account_stat],
account: :account_stat,
preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } },
active_mentions: :account,
account: [:account_stat, user: :role],
},
]
)

View file

@ -143,11 +143,11 @@ class Api::V1::StatusesController < Api::BaseController
end
def status_ids
Array(statuses_params[:ids]).uniq.map(&:to_i)
Array(statuses_params[:id]).uniq.map(&:to_i)
end
def statuses_params
params.permit(ids: [])
params.permit(id: [])
end
def status_params

View file

@ -65,7 +65,7 @@ window.addEventListener('message', (e) => {
{
type: 'setHeight',
id: data.id,
height: document.getElementsByTagName('html')[0].scrollHeight,
height: document.getElementsByTagName('html')[0]?.scrollHeight,
},
'*',
);
@ -135,7 +135,7 @@ function loaded() {
);
};
const todayFormat = new IntlMessageFormat(
localeData['relative_format.today'] || 'Today at {time}',
localeData['relative_format.today'] ?? 'Today at {time}',
locale,
);
@ -288,13 +288,13 @@ function loaded() {
if (statusEl.dataset.spoiler === 'expanded') {
statusEl.dataset.spoiler = 'folded';
this.textContent = new IntlMessageFormat(
localeData['status.show_more'] || 'Show more',
localeData['status.show_more'] ?? 'Show more',
locale,
).format() as string;
} else {
statusEl.dataset.spoiler = 'expanded';
this.textContent = new IntlMessageFormat(
localeData['status.show_less'] || 'Show less',
localeData['status.show_less'] ?? 'Show less',
locale,
).format() as string;
}
@ -316,8 +316,8 @@ function loaded() {
const message =
statusEl.dataset.spoiler === 'expanded'
? localeData['status.show_less'] || 'Show less'
: localeData['status.show_more'] || 'Show more';
? localeData['status.show_less'] ?? 'Show less'
: localeData['status.show_more'] ?? 'Show more';
spoilerLink.textContent = new IntlMessageFormat(
message,
locale,

View file

@ -67,7 +67,9 @@ const fetchInteractionURLFailure = () => {
);
};
const isValidDomain = (value: string) => {
const isValidDomain = (value: unknown) => {
if (typeof value !== 'string') return false;
const url = new URL('https:///path');
url.hostname = value;
return url.hostname === value;
@ -124,6 +126,11 @@ const fromAcct = (acct: string) => {
const domain = segments[1];
const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`;
if (!domain) {
fetchInteractionURLFailure();
return;
}
axios
.get(`https://${domain}/.well-known/webfinger`, {
params: { resource: `acct:${acct}` },

View file

@ -63,8 +63,9 @@ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
<span
key={key}
style={{
position: direction * style.y > 0 ? 'absolute' : 'static',
transform: `translateY(${style.y * 100}%)`,
position:
direction * (style.y ?? 0) > 0 ? 'absolute' : 'static',
transform: `translateY(${(style.y ?? 0) * 100}%)`,
}}
>
{obfuscate ? (

View file

@ -52,7 +52,10 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
);
return Object.values(groups).map((tags) => {
if (tags.length === 1) return tags[0];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know that the array has at least one element
const firstTag = tags[0]!;
if (tags.length === 1) return firstTag;
// The best match is the one where we have the less difference between upper and lower case letter count
const best = minBy(tags, (tag) => {
@ -66,7 +69,7 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
return Math.abs(lowerCase - upperCase);
});
return best ?? tags[0];
return best ?? firstTag;
});
}

View file

@ -48,7 +48,7 @@ const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
const count = (
<FormattedNumber
value={rawNumber}
value={rawNumber ?? 0}
maximumFractionDigits={maxFractionDigits}
/>
);

View file

@ -65,7 +65,7 @@ window.addEventListener('message', (e) => {
{
type: 'setHeight',
id: data.id,
height: document.getElementsByTagName('html')[0].scrollHeight,
height: document.getElementsByTagName('html')[0]?.scrollHeight,
},
'*',
);
@ -135,7 +135,7 @@ function loaded() {
);
};
const todayFormat = new IntlMessageFormat(
localeData['relative_format.today'] || 'Today at {time}',
localeData['relative_format.today'] ?? 'Today at {time}',
locale,
);
@ -288,13 +288,13 @@ function loaded() {
if (statusEl.dataset.spoiler === 'expanded') {
statusEl.dataset.spoiler = 'folded';
this.textContent = new IntlMessageFormat(
localeData['status.show_more'] || 'Show more',
localeData['status.show_more'] ?? 'Show more',
locale,
).format() as string;
} else {
statusEl.dataset.spoiler = 'expanded';
this.textContent = new IntlMessageFormat(
localeData['status.show_less'] || 'Show less',
localeData['status.show_less'] ?? 'Show less',
locale,
).format() as string;
}
@ -316,8 +316,8 @@ function loaded() {
const message =
statusEl.dataset.spoiler === 'expanded'
? localeData['status.show_less'] || 'Show less'
: localeData['status.show_more'] || 'Show more';
? localeData['status.show_less'] ?? 'Show less'
: localeData['status.show_more'] ?? 'Show more';
spoilerLink.textContent = new IntlMessageFormat(
message,
locale,

View file

@ -67,7 +67,9 @@ const fetchInteractionURLFailure = () => {
);
};
const isValidDomain = (value: string) => {
const isValidDomain = (value: unknown) => {
if (typeof value !== 'string') return false;
const url = new URL('https:///path');
url.hostname = value;
return url.hostname === value;
@ -124,6 +126,11 @@ const fromAcct = (acct: string) => {
const domain = segments[1];
const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`;
if (!domain) {
fetchInteractionURLFailure();
return;
}
axios
.get(`https://${domain}/.well-known/webfinger`, {
params: { resource: `acct:${acct}` },

View file

@ -29,7 +29,10 @@ const emojis: Emojis = {};
// decompress
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
const [_filenameData, searchData] = shortCodesToEmojiData[shortCode];
const emojiData = shortCodesToEmojiData[shortCode];
if (!emojiData) return;
const [_filenameData, searchData] = emojiData;
const [native, short_names, search, unified] = searchData;
emojis[shortCode] = {

View file

@ -46,7 +46,10 @@ function processEmojiMapData(
Object.keys(shortCodesToEmojiData).forEach(
(shortCode: ShortCodesToEmojiDataKey) => {
if (shortCode === undefined) return;
const [filenameData, _searchData] = shortCodesToEmojiData[shortCode];
const emojiData = shortCodesToEmojiData[shortCode];
if (!emojiData) return;
const [filenameData, _searchData] = emojiData;
filenameData.forEach((emojiMapData) => {
processEmojiMapData(emojiMapData, shortCode);
});

View file

@ -11,8 +11,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
import { Avatar } from 'flavours/glitch/components/avatar';
import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon';
import { Permalink } from 'flavours/glitch/components/permalink';
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import { useBlurhash } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
@ -46,6 +48,20 @@ const addAutoPlay = html => {
return html;
};
const MoreFromAuthor = ({ author }) => (
<div className='more-from-author'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <Permalink href={author.get('url')} to={`/@${author.get('acct')}`}><Avatar account={author} size={16} /> {author.get('display_name')}</Permalink> }} />
</div>
);
MoreFromAuthor.propTypes = {
author: ImmutablePropTypes.map,
};
export default class Card extends PureComponent {
static propTypes = {
@ -126,6 +142,7 @@ export default class Card extends PureComponent {
const interactive = card.get('type') === 'video';
const language = card.get('language') || '';
const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive;
const showAuthor = !!card.get('author_account');
const description = (
<div className='status-card__content'>
@ -136,7 +153,7 @@ export default class Card extends PureComponent {
<strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong>
{card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description' lang={language}>{card.get('description')}</span>}
{!showAuthor && (card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description' lang={language}>{card.get('description')}</span>)}
</div>
);
@ -225,10 +242,14 @@ export default class Card extends PureComponent {
}
return (
<a href={card.get('url')} className={classNames('status-card', { expanded: largeImage })} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
<>
<a href={card.get('url')} className={classNames('status-card', { expanded: largeImage, bottomless: showAuthor })} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
{embed}
{description}
</a>
{showAuthor && <MoreFromAuthor author={card.get('author_account')} />}
</>
);
}

View file

@ -74,8 +74,9 @@ export const soundsMiddleware = (): Middleware<
if (isActionWithMetaSound(action)) {
const sound = action.meta.sound;
if (sound && Object.hasOwn(soundCache, sound)) {
play(soundCache[sound]);
if (sound) {
const s = soundCache[sound];
if (s) play(s);
}
}

View file

@ -89,21 +89,17 @@ type OnData<LoadDataResult, ReturnedData> = (
},
) => ReturnedData | DiscardLoadData | Promise<ReturnedData | DiscardLoadData>;
type ArgsType = Record<string, unknown> | undefined;
// Overload when there is no `onData` method, the payload is the `onData` result
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
thunkOptions?: AppThunkOptions,
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?:
@ -113,10 +109,7 @@ export function createDataLoadingThunk<
): ReturnType<typeof createThunk<Args, void>>;
// Overload when the `onData` method returns nothing, then the mayload is the `onData` result
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, void>,
@ -126,7 +119,7 @@ export function createDataLoadingThunk<
// Overload when there is an `onData` method returning something
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
Args extends ArgsType,
Returned,
>(
name: string,
@ -162,7 +155,7 @@ export function createDataLoadingThunk<
*/
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
Args extends ArgsType,
Returned,
>(
name: string,

View file

@ -4146,6 +4146,10 @@ input.glitch-setting-text {
border: 1px solid var(--background-border-color);
border-radius: 8px;
&.bottomless {
border-radius: 8px 8px 0 0;
}
&__actions {
bottom: 0;
inset-inline-start: 0;
@ -10830,3 +10834,42 @@ noscript {
}
}
}
.more-from-author {
font-size: 14px;
color: $darker-text-color;
background: var(--surface-background-color);
border: 1px solid var(--background-border-color);
border-top: 0;
border-radius: 0 0 8px 8px;
padding: 15px;
display: flex;
align-items: center;
gap: 8px;
.logo {
height: 16px;
color: $darker-text-color;
}
& > span {
display: flex;
align-items: center;
gap: 8px;
}
a {
display: inline-flex;
align-items: center;
gap: 4px;
font-weight: 500;
color: $primary-text-color;
text-decoration: none;
&:hover,
&:focus,
&:active {
color: $highlight-text-color;
}
}
}

View file

@ -48,8 +48,9 @@ export const AnimatedNumber: React.FC<Props> = ({ value }) => {
<span
key={key}
style={{
position: direction * style.y > 0 ? 'absolute' : 'static',
transform: `translateY(${style.y * 100}%)`,
position:
direction * (style.y ?? 0) > 0 ? 'absolute' : 'static',
transform: `translateY(${(style.y ?? 0) * 100}%)`,
}}
>
<ShortNumber value={data as number} />

View file

@ -52,7 +52,10 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
);
return Object.values(groups).map((tags) => {
if (tags.length === 1) return tags[0];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know that the array has at least one element
const firstTag = tags[0]!;
if (tags.length === 1) return firstTag;
// The best match is the one where we have the less difference between upper and lower case letter count
const best = minBy(tags, (tag) => {
@ -66,7 +69,7 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
return Math.abs(lowerCase - upperCase);
});
return best ?? tags[0];
return best ?? firstTag;
});
}

View file

@ -48,7 +48,7 @@ const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
const count = (
<FormattedNumber
value={rawNumber}
value={rawNumber ?? 0}
maximumFractionDigits={maxFractionDigits}
/>
);

View file

@ -29,7 +29,10 @@ const emojis: Emojis = {};
// decompress
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
const [_filenameData, searchData] = shortCodesToEmojiData[shortCode];
const emojiData = shortCodesToEmojiData[shortCode];
if (!emojiData) return;
const [_filenameData, searchData] = emojiData;
const [native, short_names, search, unified] = searchData;
emojis[shortCode] = {

View file

@ -46,7 +46,10 @@ function processEmojiMapData(
Object.keys(shortCodesToEmojiData).forEach(
(shortCode: ShortCodesToEmojiDataKey) => {
if (shortCode === undefined) return;
const [filenameData, _searchData] = shortCodesToEmojiData[shortCode];
const emojiData = shortCodesToEmojiData[shortCode];
if (!emojiData) return;
const [filenameData, _searchData] = emojiData;
filenameData.forEach((emojiMapData) => {
processEmojiMapData(emojiMapData, shortCode);
});

View file

@ -6,6 +6,8 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
@ -13,6 +15,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
import { Avatar } from 'mastodon/components/avatar';
import { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
@ -56,6 +59,20 @@ const addAutoPlay = html => {
return html;
};
const MoreFromAuthor = ({ author }) => (
<div className='more-from-author'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <Link to={`/@${author.get('acct')}`}><Avatar account={author} size={16} /> {author.get('display_name')}</Link> }} />
</div>
);
MoreFromAuthor.propTypes = {
author: ImmutablePropTypes.map,
};
export default class Card extends PureComponent {
static propTypes = {
@ -136,6 +153,7 @@ export default class Card extends PureComponent {
const interactive = card.get('type') === 'video';
const language = card.get('language') || '';
const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive;
const showAuthor = !!card.get('author_account');
const description = (
<div className='status-card__content'>
@ -146,7 +164,7 @@ export default class Card extends PureComponent {
<strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong>
{card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description' lang={language}>{card.get('description')}</span>}
{!showAuthor && (card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description' lang={language}>{card.get('description')}</span>)}
</div>
);
@ -235,10 +253,14 @@ export default class Card extends PureComponent {
}
return (
<a href={card.get('url')} className={classNames('status-card', { expanded: largeImage })} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
<>
<a href={card.get('url')} className={classNames('status-card', { expanded: largeImage, bottomless: showAuthor })} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
{embed}
{description}
</a>
{showAuthor && <MoreFromAuthor author={card.get('author_account')} />}
</>
);
}

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Mostra el perfil de totes maneres",
"limited_account_hint.title": "Aquest perfil l'han amagat els moderadors de {domain}.",
"link_preview.author": "Per {name}",
"link_preview.more_from_author": "Més de {name}",
"lists.account.add": "Afegeix a la llista",
"lists.account.remove": "Elimina de la llista",
"lists.delete": "Elimina la llista",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Show profile anyway",
"limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
"link_preview.author": "By {name}",
"link_preview.more_from_author": "More from {name}",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Mostrar perfil de todos modos",
"limited_account_hint.title": "Este perfil fue ocultado por los moderadores de {domain}.",
"link_preview.author": "Por {name}",
"link_preview.more_from_author": "Más de {name}",
"lists.account.add": "Agregar a lista",
"lists.account.remove": "Quitar de lista",
"lists.delete": "Eliminar lista",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Näytä profiili joka tapauksessa",
"limited_account_hint.title": "Palvelimen {domain} valvojat ovat piilottaneet tämän käyttäjätilin.",
"link_preview.author": "Julkaissut {name}",
"link_preview.more_from_author": "Lisää käyttäjältä {name}",
"lists.account.add": "Lisää listalle",
"lists.account.remove": "Poista listalta",
"lists.delete": "Poista lista",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Mostrar perfil igualmente",
"limited_account_hint.title": "Este perfil foi agochado pola moderación de {domain}.",
"link_preview.author": "Por {name}",
"link_preview.more_from_author": "Máis de {name}",
"lists.account.add": "Engadir á listaxe",
"lists.account.remove": "Eliminar da listaxe",
"lists.delete": "Eliminar listaxe",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Mostra comunque il profilo",
"limited_account_hint.title": "Questo profilo è stato nascosto dai moderatori di {domain}.",
"link_preview.author": "Di {name}",
"link_preview.more_from_author": "Altro da {name}",
"lists.account.add": "Aggiungi all'elenco",
"lists.account.remove": "Rimuovi dall'elenco",
"lists.delete": "Elimina elenco",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "그래도 프로필 보기",
"limited_account_hint.title": "이 프로필은 {domain}의 중재자에 의해 숨겨진 상태입니다.",
"link_preview.author": "{name}",
"link_preview.more_from_author": "{name} 더 둘러보기",
"lists.account.add": "리스트에 추가",
"lists.account.remove": "리스트에서 제거",
"lists.delete": "리스트 삭제",

View file

@ -466,6 +466,7 @@
"notification.follow_request": "{name} paprašė tave sekti",
"notification.mention": "{name} paminėjo tave",
"notification.moderation-warning.learn_more": "Sužinoti daugiau",
"notification.moderation_warning": "Gavai prižiūrėjimo įspėjimą",
"notification.moderation_warning.action_delete_statuses": "Kai kurie tavo įrašai buvo pašalintos.",
"notification.moderation_warning.action_disable": "Tavo paskyra buvo išjungta.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo įrašai buvo pažymėtos kaip jautrios.",

View file

@ -673,7 +673,7 @@
"search.quick_action.account_search": "Profiler som samsvarer med {x}",
"search.quick_action.go_to_account": "Gå til profil {x}",
"search.quick_action.go_to_hashtag": "Gå til emneknagg {x}",
"search.quick_action.open_url": "Åpne URL i Mastodon",
"search.quick_action.open_url": "Opne adressa i Mastodon",
"search.quick_action.status_search": "Innlegg som samsvarer med {x}",
"search.search_or_paste": "Søk eller lim inn URL",
"search_popout.full_text_search_disabled_message": "Ikkje tilgjengeleg på {domain}.",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Pokaż profil mimo to",
"limited_account_hint.title": "Ten profil został ukryty przez moderatorów {domain}.",
"link_preview.author": "{name}",
"link_preview.more_from_author": "Więcej od {name}",
"lists.account.add": "Dodaj do listy",
"lists.account.remove": "Usunąć z listy",
"lists.delete": "Usuń listę",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Exibir perfil mesmo assim",
"limited_account_hint.title": "Este perfil foi ocultado pelos moderadores do {domain}.",
"link_preview.author": "Por {name}",
"link_preview.more_from_author": "Mais de {name}",
"lists.account.add": "Adicionar à lista",
"lists.account.remove": "Remover da lista",
"lists.delete": "Excluir lista",
@ -474,6 +475,7 @@
"notification.follow_request": "{name} quer te seguir",
"notification.mention": "{name} te mencionou",
"notification.moderation-warning.learn_more": "Aprender mais",
"notification.moderation_warning": "Você recebeu um aviso de moderação",
"notification.moderation_warning.action_delete_statuses": "Algumas das suas publicações foram removidas.",
"notification.moderation_warning.action_disable": "Sua conta foi desativada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas de suas publicações foram marcadas por ter conteúdo sensível.",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Exibir perfil mesmo assim",
"limited_account_hint.title": "Este perfil foi ocultado pelos moderadores de {domain}.",
"link_preview.author": "Por {name}",
"link_preview.more_from_author": "Mais de {name}",
"lists.account.add": "Adicionar à lista",
"lists.account.remove": "Remover da lista",
"lists.delete": "Eliminar lista",

View file

@ -18,6 +18,7 @@
"account.edit_profile": "පැතිකඩ සංස්කරණය",
"account.enable_notifications": "@{name} පළ කරන විට මට දැනුම් දෙන්න",
"account.endorse": "පැතිකඩෙහි විශේෂාංගය",
"account.featured_tags.last_status_at": "අවසාන ලිපිය: {date}",
"account.featured_tags.last_status_never": "ලිපි නැත",
"account.follow": "අනුගමනය",
"account.followers": "අනුගාමිකයින්",
@ -104,6 +105,7 @@
"compose_form.poll.duration": "මත විමසීමේ කාලය",
"compose_form.poll.switch_to_multiple": "තේරීම් කිහිපයකට මත විමසුම වෙනස් කරන්න",
"compose_form.poll.switch_to_single": "තනි තේරීමකට මත විමසුම වෙනස් කරන්න",
"compose_form.publish": "ප්‍රකාශනය",
"compose_form.publish_form": "නව ලිපිය",
"compose_form.spoiler.marked": "අන්තර්ගත අවවාදය ඉවත් කරන්න",
"compose_form.spoiler.unmarked": "අන්තර්ගත අවවාදයක් එක් කරන්න",
@ -154,6 +156,7 @@
"empty_column.bookmarked_statuses": "ඔබ සතුව පොත්යොමු තබන ලද ලිපි කිසිවක් නැත. ඔබ පොත්යොමුවක් තබන විට, එය මෙහි දිස්වනු ඇත.",
"empty_column.domain_blocks": "අවහිර කරන ලද වසම් නැත.",
"empty_column.explore_statuses": "දැන් කිසිවක් නැඹුරු නොවේ. පසුව නැවත පරීක්ෂා කරන්න!",
"empty_column.favourited_statuses": "ඔබ සතුව ප්‍රියතම ලිපි කිසිවක් නැත. ඔබ යමකට ප්‍රිය කළ විට එය මෙහි පෙන්වනු ඇත.",
"empty_column.follow_requests": "ඔබට තවමත් අනුගමන ඉල්ලීම් ලැබී නැත. ඉල්ලීමක් ලැබුණු විට, එය මෙහි පෙන්වනු ඇත.",
"empty_column.home": "මුල් පිටුව හිස් ය! මෙය පිරවීමට බොහෝ පුද්ගලයින් අනුගමනය කරන්න.",
"empty_column.lists": "ඔබට තවමත් ලැයිස්තු කිසිවක් නැත. ඔබ එකක් සාදන විට, එය මෙහි පෙන්වනු ඇත.",
@ -205,6 +208,7 @@
"interaction_modal.on_this_server": "මෙම සේවාදායකයෙහි",
"interaction_modal.title.favourite": "{name}ගේ ලිපිය ප්‍රිය කරන්න",
"interaction_modal.title.follow": "{name} අනුගමනය",
"interaction_modal.title.reply": "{name}ගේ ලිපියට පිළිතුරු",
"intervals.full.days": "{number, plural, one {දවස් #} other {දවස් #}}",
"intervals.full.hours": "{number, plural, one {පැය #} other {පැය #}}",
"intervals.full.minutes": "{number, plural, one {විනාඩි #} other {විනාඩි #}}",
@ -239,6 +243,7 @@
"lists.delete": "ලැයිස්තුව මකන්න",
"lists.edit": "ලැයිස්තුව සංස්කරණය",
"lists.edit.submit": "සිරැසිය සංශෝධනය",
"lists.new.create": "එකතු",
"lists.new.title_placeholder": "නව ලැයිස්තුවේ සිරැසිය",
"lists.replies_policy.list": "ලැයිස්තුවේ සාමාජිකයින්",
"lists.replies_policy.none": "කිසිවෙක් නැත",
@ -266,6 +271,7 @@
"navigation_bar.search": "සොයන්න",
"navigation_bar.security": "ආරක්ෂාව",
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
"notification.favourite": "{name} ඔබගේ ලිපියට ප්‍රිය කළා",
"notification.follow": "{name} ඔබව අනුගමනය කළා",
"notification.mention": "{name} ඔබව සඳහන් කර ඇත",
"notification.own_poll": "ඔබගේ මත විමසුම නිමයි",
@ -395,6 +401,7 @@
"status.admin_status": "මෙම ලිපිය මැදිහත්කරණ අතුරුමුහුණතෙහි අරින්න",
"status.block": "@{name} අවහිර",
"status.bookmark": "පොත්යොමුවක්",
"status.copy": "ලිපියට සබැඳියේ පිටපතක්",
"status.delete": "මකන්න",
"status.detailed_status": "විස්තරාත්මක සංවාද දැක්ම",
"status.edit": "සංස්කරණය",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Vseeno pokaži profil",
"limited_account_hint.title": "Profil so moderatorji strežnika {domain} skrili.",
"link_preview.author": "Avtor_ica {name}",
"link_preview.more_from_author": "Več od {name}",
"lists.account.add": "Dodaj na seznam",
"lists.account.remove": "Odstrani s seznama",
"lists.delete": "Izbriši seznam",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "Vẫn cứ xem",
"limited_account_hint.title": "Người này đã bị ẩn bởi quản trị viên của {domain}.",
"link_preview.author": "Bởi {name}",
"link_preview.more_from_author": "Thêm từ {name}",
"lists.account.add": "Thêm vào danh sách",
"lists.account.remove": "Xóa khỏi danh sách",
"lists.delete": "Xóa danh sách",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "仍要显示个人资料",
"limited_account_hint.title": "此账号资料已被 {domain} 管理员隐藏。",
"link_preview.author": "由 {name}",
"link_preview.more_from_author": "查看 {name} 的更多内容",
"lists.account.add": "添加到列表",
"lists.account.remove": "从列表中移除",
"lists.delete": "删除列表",

View file

@ -414,6 +414,7 @@
"limited_account_hint.action": "一律顯示個人檔案",
"limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。",
"link_preview.author": "來自 {name}",
"link_preview.more_from_author": "來自 {name} 之更多內容",
"lists.account.add": "新增至列表",
"lists.account.remove": "自列表中移除",
"lists.delete": "刪除列表",

View file

@ -74,8 +74,9 @@ export const soundsMiddleware = (): Middleware<
if (isActionWithMetaSound(action)) {
const sound = action.meta.sound;
if (sound && Object.hasOwn(soundCache, sound)) {
play(soundCache[sound]);
if (sound) {
const s = soundCache[sound];
if (s) play(s);
}
}

View file

@ -89,21 +89,17 @@ type OnData<LoadDataResult, ReturnedData> = (
},
) => ReturnedData | DiscardLoadData | Promise<ReturnedData | DiscardLoadData>;
type ArgsType = Record<string, unknown> | undefined;
// Overload when there is no `onData` method, the payload is the `onData` result
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
thunkOptions?: AppThunkOptions,
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?:
@ -113,10 +109,7 @@ export function createDataLoadingThunk<
): ReturnType<typeof createThunk<Args, void>>;
// Overload when the `onData` method returns nothing, then the mayload is the `onData` result
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, void>,
@ -126,7 +119,7 @@ export function createDataLoadingThunk<
// Overload when there is an `onData` method returning something
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
Args extends ArgsType,
Returned,
>(
name: string,
@ -162,7 +155,7 @@ export function createDataLoadingThunk<
*/
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
Args extends ArgsType,
Returned,
>(
name: string,

View file

@ -3896,6 +3896,10 @@ $ui-header-logo-wordmark-width: 99px;
border: 1px solid var(--background-border-color);
border-radius: 8px;
&.bottomless {
border-radius: 8px 8px 0 0;
}
&__actions {
bottom: 0;
inset-inline-start: 0;
@ -10223,3 +10227,42 @@ noscript {
}
}
}
.more-from-author {
font-size: 14px;
color: $darker-text-color;
background: var(--surface-background-color);
border: 1px solid var(--background-border-color);
border-top: 0;
border-radius: 0 0 8px 8px;
padding: 15px;
display: flex;
align-items: center;
gap: 8px;
.logo {
height: 16px;
color: $darker-text-color;
}
& > span {
display: flex;
align-items: center;
gap: 8px;
}
a {
display: inline-flex;
align-items: center;
gap: 4px;
font-weight: 500;
color: $primary-text-color;
text-decoration: none;
&:hover,
&:focus,
&:active {
color: $highlight-text-color;
}
}
}

View file

@ -122,7 +122,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def process_status_params
@status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: @account.followers_url, object: @object)
attachment_ids = process_attachments.take(4).map(&:id)
attachment_ids = process_attachments.take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:id)
@params = {
uri: @status_parser.uri,
@ -278,7 +278,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
as_array(@object['attachment']).each do |attachment|
media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment)
next if media_attachment_parser.remote_url.blank? || media_attachments.size >= 4
next if media_attachment_parser.remote_url.blank? || media_attachments.size >= Status::MEDIA_ATTACHMENTS_LIMIT
begin
media_attachment = MediaAttachment.create(

View file

@ -195,6 +195,10 @@ class LinkDetailsExtractor
structured_data&.author_url
end
def author_account
opengraph_tag('fediverse:creator')
end
def embed_url
valid_url_or_nil(opengraph_tag('twitter:player:stream'))
end

View file

@ -1,10 +0,0 @@
# frozen_string_literal: true
class Vacuum::ApplicationsVacuum
def perform
Doorkeeper::Application.where(owner_id: nil)
.where.missing(:created_users, :access_tokens, :access_grants)
.where(created_at: ...1.day.ago)
.in_batches.delete_all
end
end

View file

@ -142,6 +142,8 @@ class Account < ApplicationRecord
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
scope :dormant, -> { joins(:account_stat).merge(AccountStat.without_recent_activity) }
scope :with_username, ->(value) { where arel_table[:username].lower.eq(value.to_s.downcase) }
scope :with_domain, ->(value) { where arel_table[:domain].lower.eq(value&.to_s&.downcase) }
after_update_commit :trigger_update_webhooks

View file

@ -3,7 +3,7 @@
class AccountSuggestions::SettingSource < AccountSuggestions::Source
def get(account, limit: DEFAULT_LIMIT)
if setting_enabled?
base_account_scope(account).where(setting_to_where_condition).limit(limit).pluck(:id).zip([key].cycle)
base_account_scope(account).merge(setting_to_where_condition).limit(limit).pluck(:id).zip([key].cycle)
else
[]
end
@ -25,11 +25,9 @@ class AccountSuggestions::SettingSource < AccountSuggestions::Source
def setting_to_where_condition
usernames_and_domains.map do |(username, domain)|
Arel::Nodes::Grouping.new(
Account.arel_table[:username].lower.eq(username.downcase).and(
Account.arel_table[:domain].lower.eq(domain&.downcase)
)
)
Account
.with_username(username)
.with_domain(domain)
end.reduce(:or)
end

View file

@ -25,42 +25,11 @@ module Account::FinderConcern
end
def find_remote(username, domain)
AccountFinder.new(username, domain).account
end
end
class AccountFinder
attr_reader :username, :domain
def initialize(username, domain)
@username = username
@domain = domain
end
def account
scoped_accounts.order(id: :asc).take
end
private
def scoped_accounts
Account.unscoped.tap do |scope|
scope.merge! with_usernames
scope.merge! matching_username
scope.merge! matching_domain
end
end
def with_usernames
Account.where.not(Account.arel_table[:username].lower.eq '')
end
def matching_username
Account.where(Account.arel_table[:username].lower.eq username.to_s.downcase)
end
def matching_domain
Account.where(Account.arel_table[:domain].lower.eq(domain.nil? ? nil : domain.to_s.downcase))
Account
.with_username(username)
.with_domain(domain)
.order(id: :asc)
.take
end
end
end

View file

@ -22,7 +22,7 @@ module User::LdapAuthenticable
safe_username = safe_username.gsub(keys, replacement)
end
resource = joins(:account).merge(Account.where(Account.arel_table[:username].lower.eq safe_username.downcase)).take
resource = joins(:account).merge(Account.with_username(safe_username)).take
if resource.blank?
resource = new(

View file

@ -32,6 +32,7 @@
# link_type :integer
# published_at :datetime
# image_description :string default(""), not null
# author_account_id :bigint(8)
#
class PreviewCard < ApplicationRecord
@ -54,6 +55,7 @@ class PreviewCard < ApplicationRecord
has_many :statuses, through: :preview_cards_statuses
has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
belongs_to :author_account, class_name: 'Account', optional: true
has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false

View file

@ -41,6 +41,8 @@ class Status < ApplicationRecord
include Status::SnapshotConcern
include Status::ThreadingConcern
MEDIA_ATTACHMENTS_LIMIT = 4
rate_limit by: :account, family: :statuses
self.discard_column = :deleted_at
@ -163,9 +165,9 @@ class Status < ApplicationRecord
:status_stat,
:tags,
:preloadable_poll,
preview_cards_status: [:preview_card],
preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } },
account: [:account_stat, user: :role],
active_mentions: { account: :account_stat },
active_mentions: :account,
reblog: [
:application,
:tags,
@ -173,11 +175,11 @@ class Status < ApplicationRecord
:conversation,
:status_stat,
:preloadable_poll,
preview_cards_status: [:preview_card],
preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } },
account: [:account_stat, user: :role],
active_mentions: { account: :account_stat },
active_mentions: :account,
],
thread: { account: :account_stat }
thread: :account
delegate :domain, to: :account, prefix: true

View file

@ -59,7 +59,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
statuses: {
max_characters: StatusLengthValidator::MAX_CHARS,
max_media_attachments: 4,
max_media_attachments: Status::MEDIA_ATTACHMENTS_LIMIT,
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
supported_mime_types: HtmlAwareFormatter::STATUS_MIME_TYPES,
},

View file

@ -8,6 +8,8 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer
:provider_url, :html, :width, :height,
:image, :image_description, :embed_url, :blurhash, :published_at
has_one :author_account, serializer: REST::AccountSerializer, if: -> { object.author_account.present? }
def url
object.original_url.presence || object.url
end

View file

@ -78,7 +78,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
statuses: {
max_characters: StatusLengthValidator::MAX_CHARS,
max_media_attachments: 4,
max_media_attachments: Status::MEDIA_ATTACHMENTS_LIMIT,
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
supported_mime_types: HtmlAwareFormatter::STATUS_MIME_TYPES,
},

View file

@ -73,7 +73,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
as_array(@json['attachment']).each do |attachment|
media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment)
next if media_attachment_parser.remote_url.blank? || @next_media_attachments.size > 4
next if media_attachment_parser.remote_url.blank? || @next_media_attachments.size > Status::MEDIA_ATTACHMENTS_LIMIT
begin
media_attachment = previous_media_attachments.find { |previous_media_attachment| previous_media_attachment.remote_url == media_attachment_parser.remote_url }

View file

@ -147,9 +147,12 @@ class FetchLinkCardService < BaseService
return if html.nil?
link_details_extractor = LinkDetailsExtractor.new(@url, @html, @html_charset)
provider = PreviewCardProvider.matching_domain(Addressable::URI.parse(link_details_extractor.canonical_url).normalized_host)
linked_account = ResolveAccountService.new.call(link_details_extractor.author_account, suppress_errors: true) if link_details_extractor.author_account.present? && provider&.trendable?
@card = PreviewCard.find_or_initialize_by(url: link_details_extractor.canonical_url) if link_details_extractor.canonical_url != @card.url
@card.assign_attributes(link_details_extractor.to_preview_card_attributes)
@card.author_account = linked_account
@card.save_with_optional_image! unless @card.title.blank? && @card.html.blank?
end
end

View file

@ -147,6 +147,9 @@ class NotifyService < BaseService
end
def statuses_that_mention_sender
# This queries private mentions from the recipient to the sender up in the thread.
# This allows up to 100 messages that do not match in the thread, allowing conversations
# involving multiple people.
Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @sender.id, depth_limit: 100])
WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS (
SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0
@ -154,16 +157,17 @@ class NotifyService < BaseService
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE s.id = :id
UNION ALL
SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1
FROM ancestors st
JOIN statuses s ON s.id = st.in_reply_to_id
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit
SELECT s.id, s.in_reply_to_id, m.id, ancestors.path || s.id, ancestors.depth + 1
FROM ancestors
JOIN statuses s ON s.id = ancestors.in_reply_to_id
/* early exit if we already have a mention matching our requirements */
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id AND s.account_id = :recipient_id
WHERE ancestors.mention_id IS NULL AND NOT s.id = ANY(path) AND ancestors.depth < :depth_limit
)
SELECT COUNT(*)
FROM ancestors st
JOIN statuses s ON s.id = st.id
WHERE st.mention_id IS NOT NULL AND s.visibility = 3
FROM ancestors
JOIN statuses s ON s.id = ancestors.id
WHERE ancestors.mention_id IS NOT NULL AND s.account_id = :recipient_id AND s.visibility = 3
SQL
end
end

View file

@ -146,9 +146,9 @@ class PostStatusService < BaseService
return
end
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present?
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > Status::MEDIA_ATTACHMENTS_LIMIT || @options[:poll].present?
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:to_i))
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?)
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?)

View file

@ -81,7 +81,7 @@ class ReportService < BaseService
# If the account making reports is remote, it is likely anonymized so we have to relax the requirements for attaching statuses.
domain = @source_account.domain.to_s.downcase
has_followers = @target_account.followers.where(Account.arel_table[:domain].lower.eq(domain)).exists?
has_followers = @target_account.followers.with_domain(domain).exists?
visibility = has_followers ? %i(public unlisted private) : %i(public unlisted)
scope = @target_account.statuses.with_discarded
scope.merge!(scope.where(visibility: visibility).or(scope.where('EXISTS (SELECT 1 FROM mentions m JOIN accounts a ON m.account_id = a.id WHERE lower(a.domain) = ?)', domain)))

View file

@ -70,9 +70,9 @@ class UpdateStatusService < BaseService
def validate_media!
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present?
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > Status::MEDIA_ATTACHMENTS_LIMIT || @options[:poll].present?
media_attachments = @status.account.media_attachments.where(status_id: [nil, @status.id]).where(scheduled_status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)).to_a
media_attachments = @status.account.media_attachments.where(status_id: [nil, @status.id]).where(scheduled_status_id: nil).where(id: @options[:media_ids].take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:to_i)).to_a
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media_attachments.size > 1 && media_attachments.find(&:audio_or_video?)
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if media_attachments.any?(&:not_processed?)

View file

@ -6,10 +6,7 @@ class UniqueUsernameValidator < ActiveModel::Validator
def validate(account)
return if account.username.blank?
normalized_username = account.username.downcase
normalized_domain = account.domain&.downcase
scope = Account.where(Account.arel_table[:username].lower.eq normalized_username).where(Account.arel_table[:domain].lower.eq normalized_domain)
scope = Account.with_username(account.username).with_domain(account.domain)
scope = scope.where.not(id: account.id) if account.persisted?
account.errors.add(:username, :taken) if scope.exists?

View file

@ -22,7 +22,6 @@ class Scheduler::VacuumScheduler
preview_cards_vacuum,
backups_vacuum,
access_tokens_vacuum,
applications_vacuum,
feeds_vacuum,
imports_vacuum,
]
@ -56,10 +55,6 @@ class Scheduler::VacuumScheduler
Vacuum::ImportsVacuum.new
end
def applications_vacuum
Vacuum::ApplicationsVacuum.new
end
def content_retention_policy
ContentRetentionPolicy.current
end

View file

@ -48,6 +48,7 @@ require_relative '../lib/chewy/strategy/bypass_with_warning'
require_relative '../lib/webpacker/manifest_extensions'
require_relative '../lib/webpacker/helper_extensions'
require_relative '../lib/rails/engine_extensions'
require_relative '../lib/action_dispatch/remote_ip_extensions'
require_relative '../lib/active_record/database_tasks_extensions'
require_relative '../lib/active_record/batches'
require_relative '../lib/simple_navigation/item_extensions'

View file

@ -13,7 +13,7 @@ end
Paperclip.interpolates :prefix_path do |attachment, _style|
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local?
'cache' + File::SEPARATOR
"cache#{File::SEPARATOR}"
else
''
end
@ -159,7 +159,7 @@ else
Paperclip::Attachment.default_options.merge!(
storage: :filesystem,
path: File.join(ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')), ':prefix_path:class', ':attachment', ':id_partition', ':style', ':filename'),
url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:prefix_url:class/:attachment/:id_partition/:style/:filename'
url: "#{ENV.fetch('PAPERCLIP_ROOT_URL', '/system')}/:prefix_url:class/:attachment/:id_partition/:style/:filename"
)
end

View file

@ -37,6 +37,10 @@ class Rack::Attack
authenticated_token&.id
end
def warden_user_id
@env['warden']&.user&.id
end
def unauthenticated?
!authenticated_user_id
end
@ -58,10 +62,6 @@ class Rack::Attack
end
end
Rack::Attack.safelist('allow from localhost') do |req|
req.remote_ip == '127.0.0.1' || req.remote_ip == '::1'
end
Rack::Attack.blocklist('deny from blocklist') do |req|
IpBlock.blocked?(req.remote_ip)
end
@ -105,6 +105,10 @@ class Rack::Attack
req.authenticated_user_id if (req.post? && req.path.match?(API_DELETE_REBLOG_REGEX)) || (req.delete? && req.path.match?(API_DELETE_STATUS_REGEX))
end
throttle('throttle_oauth_application_registrations/ip', limit: 5, period: 10.minutes) do |req|
req.throttleable_remote_ip if req.post? && req.path == '/api/v1/apps'
end
throttle('throttle_sign_up_attempts/ip', limit: 25, period: 5.minutes) do |req|
req.throttleable_remote_ip if req.post? && req.path_matches?('/auth')
end
@ -137,6 +141,10 @@ class Rack::Attack
req.session[:attempt_user_id] || req.params.dig('user', 'email').presence if req.post? && req.path_matches?('/auth/sign_in')
end
throttle('throttle_password_change/account', limit: 10, period: 10.minutes) do |req|
req.warden_user_id if req.put? || (req.patch? && req.path_matches?('/auth'))
end
self.throttled_responder = lambda do |request|
now = Time.now.utc
match_data = request.env['rack.attack.match_data']

View file

@ -282,6 +282,7 @@ ja:
update_custom_emoji_html: "%{name}さんがカスタム絵文字 %{target}を更新しました"
update_domain_block_html: "%{name}さんが%{target}のドメインブロックを更新しました"
update_ip_block_html: "%{name} さんがIP %{target} のルールを更新しました"
update_report_html: "%{name}さんが通報 %{target} を更新しました"
update_status_html: "%{name}さんが%{target}さんの投稿を更新しました"
update_user_role_html: "%{name}さんがロール『%{target}』を変更しました"
deleted_account: 削除されたアカウント
@ -939,6 +940,7 @@ ja:
delete: 削除
edit_preset: プリセット警告文を編集
empty: まだプリセット警告文が作成されていません。
title: プリセット警告文
webhooks:
add_new: エンドポイントを追加
delete: 削除

View file

@ -291,6 +291,7 @@ lt:
update_custom_emoji_html: "%{name} atnaujino jaustuką %{target}"
update_domain_block_html: "%{name} atnaujino domeno bloką %{target}"
update_ip_block_html: "%{name} pakeitė taisyklę IP %{target}"
update_report_html: "%{name} atnaujino ataskaitą %{target}"
update_status_html: "%{name} atnaujino įrašą %{target}"
update_user_role_html: "%{name} pakeitė %{target} vaidmenį"
deleted_account: ištrinta paskyra
@ -344,6 +345,8 @@ lt:
shortcode: Trumpas kodas
shortcode_hint: Bent du ženklai, tik raidiniai skaitmeniniai ženklai bei akcentai(_)
title: Asmeniniai jaustukai
uncategorized: Be kategorijos
unlist: Išbraukti iš sąrašo
unlisted: Neįtrauktas į sąrašą
update_failed_msg: Jaustukas negalėjo būti pakeistas
updated_msg: Jaustukas sėkmingai pakeistas!
@ -391,8 +394,16 @@ lt:
created_msg: Domenas buvo sėkmingai leistas federacijai.
destroyed_msg: Domenas buvo neleistas federacijai.
export: Eksportuoti
import: Importuoti
undo: Neleisti federavimo su domenu
domain_blocks:
add_new: Pridėti naują domeno bloką
confirm_suspension:
cancel: Atšaukti
confirm: Pristabdyti
permanent_action: Atšaukus pristabdymą jokie duomenys ar sąryšiai nebus atkurti.
preamble_html: Jūs pristabdysite <strong>%{domain}</strong> ir jo subdomenus.
remove_all_data: Taip iš serverio bus pašalintas visas šio domeno paskyrų turinys, medija ir profilio duomenys.
created_msg: Domeno užblokavimas nagrinėjamas
destroyed_msg: Domeno blokas pašalintas
domain: Domenas
@ -410,6 +421,7 @@ lt:
silence: Riboti
suspend: Pristabdyti
title: Naujos domeno blokas
private_comment: Privatus komentaras
public_comment: Viešas komentaras
public_comment_hint: Komentaras apie šį domeno apribojimą plačiajai visuomenei, jei įjungtas domenų apribojimų sąrašo reklamavimas.
reject_media: Atmesti medijos failus
@ -417,6 +429,7 @@ lt:
reject_reports: Atmesti ataskaitas
reject_reports_hint: Ignoruoti visus skundus, kurie siunčiami iš šio domeno. Neliečia užblokavimu
undo: Atkurti domeno bloką
view: Peržiūrėti domeno bloką
email_domain_blocks:
add_new: Pridėti naują
allow_registrations_with_approval: Leisti registracijas su patvirtinimu
@ -428,10 +441,16 @@ lt:
title: Naujas el pašto juodojo sąrašo įtraukimas
title: El pašto juodasis sąrašas
instances:
availability:
title: Prieinamumas
back_to_all: Visi
back_to_limited: Apribotas
back_to_warning: Įspėjimas
by_domain: Domenas
content_policies:
policy: Politika
reason: Viešoji priežastis
title: Turinio politika
delivery:
all: Visi
delivery_available: Pristatymas galimas
@ -624,6 +643,7 @@ lt:
add_new: Pridėti naują
delete: Ištrinti
edit_preset: Keisti įspėjimo nustatymus
title: Įspėjamieji numatytieji
webhooks:
description_html: "<strong>Webhook</strong> leidžia Mastodon siųsti <strong>realaus laiko pranešimus</strong> apie pasirinktus įvykius į tavo programą, kad programa galėtų <strong>automatiškai paleisti reakcijas</strong>."
events: Įvykiai

View file

@ -461,13 +461,13 @@ nn:
title: Importer domeneblokkeringar
no_file: Inga fil vald
follow_recommendations:
description_html: "<strong>Følgjeforslag hjelper nye brukarar å raskt finna interessant innhald</strong>. Om ein brukar ikkje har interagera nok med andre til å danne personlege følgjeforslag, vert disse kontiane føreslått i staden. Dei vert gjenkalkulert på dagleg basis ut frå ei blanding av dei konti med flest nylege engasjement og flest lokale følgjarar for eit gitt språk."
description_html: "<strong>Fylgjeforslag hjelper nye brukarar å finna interessant innhald raskt</strong>. Om ein brukar ikkje har samhandla nok med andre til å få tilpassa fylgjeforslag, blir desse kontoane føreslått i staden. Dei blir rekna ut på nytt kvar dag ut frå ei blanding av kva kontoar som har mykje nyleg aktivitet og høgast tal på fylgjarar på eit bestemt språk."
language: For språk
status: Status
suppress: Demp følgjeforslag
suppressed: Dempa
title: Følgjeforslag
unsuppress: Tilbakestill følgjeforslag
title: Fylgjeforslag
unsuppress: Nullstill fylgjeforslag
instances:
availability:
description_html:
@ -746,7 +746,7 @@ nn:
preamble: Tilpasse web-grensesnittet.
title: Utsjånad
branding:
preamble: Profileringa av tenaren din skil den frå andre tenarar i nettverket. Informasjonen kan bli vist ulike stadar, til dømes i Mastodon sitt web-grensesnitt, i eigne applikasjonar, i førehandsvisningar på andre nettsider, i meldingsappar og så bortetter. På grunn av dette er det best å halde informasjonen enkel, kort og treffande.
preamble: Profileringa av tenaren din skil den frå andre tenarar i nettverket. Informasjonen kan bli vist ulike stader, til dømes i Mastodon sitt web-grensesnitt, i eigne applikasjonar, i førehandsvisningar på andre nettsider, i meldingsappar og så bortetter. På grunn av dette er det best at denne informasjonen er enkel, kort og treffande.
title: Profilering
captcha_enabled:
desc_html: Dette baserer seg på eksterne skript frå hCaptcha, noko som kan vera eit tryggleiks- og personvernsproblem. <strong>I tillegg kan dette gjera registreringsprosessen monaleg mindre tilgjengeleg (særleg for folk med nedsett funksjonsevne)</strong>. Dette gjer at du bør du vurdera alternative tiltak, som til dømes godkjennings- eller invitasjonsbasert registrering.
@ -759,7 +759,7 @@ nn:
desc_html: Påverkar alle brukarar som ikkje har justert denne innstillinga sjølve
title: Ikkje la brukarar indekserast av søkjemotorar som standard
discovery:
follow_recommendations: Følgjeforslag
follow_recommendations: Fylgjeforslag
preamble: Å framheva interessant innhald er vitalt i mottakinga av nye brukarar som ikkje nødvendigvis kjenner nokon på Mastodon. Kontroller korleis oppdagingsfunksjonane på tenaren din fungerar.
profile_directory: Profilkatalog
public_timelines: Offentlege tidsliner
@ -1562,7 +1562,7 @@ nn:
activity: Kontoaktivitet
confirm_follow_selected_followers: Er du sikker på at du ynskjer å fylgja dei valde fylgjarane?
confirm_remove_selected_followers: Er du sikker på at du ynskjer å fjerna dei valde fylgjarane?
confirm_remove_selected_follows: Er du sikker på at du ynskjer å fjerna det valde følgjet?
confirm_remove_selected_follows: Er du sikker på at du ikkje vil fylgja desse?
dormant: I dvale
follow_failure: Greidde ikkje fylgja alle kontoane du valde.
follow_selected_followers: Følg valgte tilhengere

View file

@ -285,6 +285,7 @@ pt-BR:
update_custom_emoji_html: "%{name} atualizou o emoji %{target}"
update_domain_block_html: "%{name} atualizou o bloqueio de domínio de %{target}"
update_ip_block_html: "%{name} alterou a regra para o IP %{target}"
update_report_html: "%{name} atualizou o relatório %{target}"
update_status_html: "%{name} atualizou a publicação de %{target}"
update_user_role_html: "%{name} alterou o cargo %{target}"
deleted_account: conta excluída
@ -950,6 +951,7 @@ pt-BR:
delete: Excluir
edit_preset: Editar o aviso pré-definido
empty: Você ainda não definiu nenhuma predefinição de alerta.
title: Predefinições de aviso
webhooks:
add_new: Adicionar endpoint
delete: Excluir

View file

@ -87,7 +87,7 @@ si:
title: මැදිහත්කරණය
moderation_notes: මැදිහත්කරණ සටහන්
most_recent_activity: වඩාත්ම මෑත ක්රියාකාරිත්වය
most_recent_ip: මෑත අ.ජා.කෙ. (IP)
most_recent_ip: මෑත අ.ජා.කෙ.
no_account_selected: කිසිවක් තෝරා නොගත් බැවින් ගිණුම් කිසිවක් වෙනස් කර නැත
no_limits_imposed: සීමාවන් පනවා නැත
not_subscribed: දායක වී නැත
@ -160,7 +160,7 @@ si:
create_custom_emoji: අභිරුචි ඉමොජි සාදන්න
create_domain_allow: වසමකට ඉඩදීම සාදන්න
create_email_domain_block: ඊමේල් ඩොමේන් බ්ලොක් එකක් සාදන්න
create_ip_block: අ.ජා. කෙ. (IP) නීතියක් සාදන්න
create_ip_block: අ.ජා.කෙ. නීතියක් සාදන්න
create_unavailable_domain: ලබා ගත නොහැකි වසම සාදන්න
create_user_role: භූමිකාව සාදන්න
demote_user: පරිශීලකයා පහත් කරන්න
@ -1239,7 +1239,7 @@ si:
current_session: වත්මන් වාරය
description: "%{platform} හි %{browser}"
explanation: ඔබගේ මාස්ටඩන් ගිණුමට පිවිසීම සඳහා භාවිතා කර තිබෙන අතිරික්සු.
ip: අ.ජා. කෙ. (IP)
ip: අ.ජා.කෙ.
platforms:
adobe_air: ඇඩෝබි එයාර්
android: ඇන්ඩ්‍රොයිඩ්
@ -1399,7 +1399,7 @@ si:
details: 'ප්‍රවේශයට අදාළ විස්තර:'
explanation: ඔබගේ ගිණුමට නව අ.ජා.කෙ. (IP) ලිපිනයකින් ප්‍රවේශයක් අනාවරණය වී ඇත.
further_actions_html: මේ ඔබ නොවේ නම්, වහාම %{action}. ඔබගේ ගිණුම සුරක්‍ෂිතව තබා ගැනීමට ද්වි-සාධකය සබල කරන්න.
subject: ඔබගේ ගිණුමට නව අ.ජා.කෙ. (IP) ලිපිනයකින් ප්‍රවේශ වී ඇත
subject: ඔබගේ ගිණුමට නව අ.ජා.කෙ. ලිපිනයකින් ප්‍රවේශ වී ඇත
title: නව ප්‍රවේශයක්
warning:
appeal: අභියාචනයක් ඉදිරිපත් කරන්න

View file

@ -74,8 +74,10 @@ lt:
warn: Slėpti filtruojamą turinį po įspėjimu, paminint filtro pavadinimą
form_admin_settings:
activity_api_enabled: Vietinių paskelbtų įrašų, aktyvių naudotojų ir naujų registracijų skaičiai kas savaitę
app_icon: WEBP, PNG, GIF arba JPG. Pakeičia numatytąją programos piktogramą mobiliuosiuose įrenginiuose pasirinktine piktograma.
backups_retention_period: Naudotojai gali generuoti savo įrašų archyvus, kuriuos vėliau galės atsisiųsti. Nustačius teigiamą reikšmę, šie archyvai po nurodyto dienų skaičiaus bus automatiškai ištrinti iš saugyklos.
content_cache_retention_period: Visi įrašai iš kitų serverių (įskaitant pakėlimus ir atsakymus) bus ištrinti po nurodyto dienų skaičiaus, neatsižvelgiant į bet kokią vietinio naudotojo sąveiką su tais įrašais. Tai taikoma ir tiems įrašams, kuriuos vietinis naudotojas yra pažymėjęs kaip žymes ar mėgstamus. Privačios paminėjimai tarp naudotojų iš skirtingų instancijų taip pat bus prarastos ir jų bus neįmanoma atkurti. Šis nustatymas skirtas naudoti ypatingos paskirties instancijose, o įgyvendinus jį bendram naudojimui, pažeidžiami daugelio naudotojų lūkesčiai.
favicon: WEBP, PNG, GIF arba JPG. Pakeičia numatytąją Mastodon svetaines piktogramą pasirinktine piktograma.
mascot: Pakeičia išplėstinės žiniatinklio sąsajos iliustraciją.
media_cache_retention_period: Nuotolinių naudotojų įrašytų įrašų medijos failai talpinami tavo serveryje. Nustačius teigiamą reikšmę, medijos bus ištrinamos po nurodyto dienų skaičiaus. Jei medijos duomenų bus paprašyta po to, kai jie bus ištrinti, jie bus atsiųsti iš naujo, jei šaltinio turinys vis dar prieinamas. Dėl apribojimų, susijusių su nuorodų peržiūros kortelių apklausos dažnumu trečiųjų šalių svetainėse, rekomenduojama nustatyti šią reikšmę ne trumpesnę kaip 14 dienų, kitaip nuorodų peržiūros kortelės nebus atnaujinamos pagal pareikalavimą iki to laiko.
peers_api_enabled: Domenų pavadinimų sąrašas, su kuriais šis serveris susidūrė fediverse. Čia nėra duomenų apie tai, ar tu bendrauji su tam tikru serveriu, tik apie tai, kad tavo serveris apie jį žino. Tai naudojama tarnybose, kurios renka federacijos statistiką bendrąja prasme.

View file

@ -8,8 +8,8 @@ nn:
fields: Heimesida di, pronomen, alder, eller kva du måtte ynskje.
indexable: Dei offentlege innlegga dine kan dukka opp i søkjeresultat på Mastodon. Folk som har reagert på oinnlegga dine kan uansett søkja gjennom dei.
note: 'Du kan @nemne folk eller #emneknaggar.'
show_collections: Andre kan sjå kven du følgjer og kven som følgjer deg. Dei du følgjer kan alltid sjå at du følgjer dei.
unlocked: Alle kan følgje deg utan å måtte spørje om det. Vel bort om du vil gå gjennom førespurnadar om å følgje deg og seie ja eller nei.
show_collections: Andre kan sjå kven du fylgjer og kven som fylgjer deg. Dei du fylgjer kan alltid sjå at du fylgjer dei.
unlocked: Alle kan fylgja deg utan å måtta be om det. Vel bort dersom du vil gå gjennom førespurnader om å fylgja deg og seia ja eller nei til kvar av dei.
account_alias:
acct: Angi brukarnamn@domene til brukaren du ynskjer å flytta frå
account_migration:
@ -148,7 +148,7 @@ nn:
name: Merkelapp
value: Innhald
indexable: Ta med offentlege innlegg i søkjeresultat
show_collections: Vis følgjer og følgjare på profilen
show_collections: Vis dei du fylgjer og dei som fylgjer deg på profilen din
unlocked: Godta nye følgjare automatisk
account_alias:
acct: Brukarnamnet på den gamle kontoen

View file

@ -77,10 +77,15 @@ uk:
warn: Сховати відфільтрований вміст за попередженням, у якому вказано заголовок фільтра
form_admin_settings:
activity_api_enabled: Кількість локальних опублікованих дописів, активних і нових користувачів у тижневих розрізах
app_icon: WEBP, PNG, GIF або JPG. Замінює іконку програми за замовчуванням на мобільних пристроях на власну іконку.
backups_retention_period: Користувачі мають можливість створювати архіви своїх дописів, щоб завантажити їх пізніше. Якщо встановлено додатне значення, ці архіви будуть автоматично видалені з вашого сховища через вказану кількість днів.
bootstrap_timeline_accounts: Ці облікові записи будуть закріплені в топі пропозицій для нових користувачів.
closed_registrations_message: Показується, коли реєстрація закрита
content_cache_retention_period: Усі дописи з інших серверів (включно з коментарями та відповідями) будуть видалені через певну кількість днів, незважаючи на будь-яку локальну взаємодію користувачів з цими дописами. Сюди входять дописи, які локальний користувач позначив як закладки або вибране. Приватні згадки між користувачами з різних інстанцій також будуть втрачені і не підлягатимуть відновленню. Використання цього параметра призначено для екземплярів спеціального призначення і порушує багато очікувань користувачів, якщо його застосовано для загального використання.
custom_css: Ви можете застосувати користувацькі стилі у вебверсії Mastodon.
favicon: WEBP, PNG, GIF або JPG. Замінює стандартну піктограму Mastodon на власну піктограму.
mascot: Змінює ілюстрацію в розширеному вебінтерфейсі.
media_cache_retention_period: Медіафайли з дописів віддалених користувачів кешуються на вашому сервері. Якщо встановлено додатне значення, медіа буде видалено через вказану кількість днів. Якщо медіа-дані будуть запитані після видалення, вони будуть завантажені повторно, якщо вихідний вміст все ще доступний. Через обмеження на частоту опитування карток попереднього перегляду посилань на сторонніх сайтах, рекомендується встановити це значення не менше 14 днів, інакше картки попереднього перегляду посилань не будуть оновлюватися на вимогу раніше цього часу.
peers_api_enabled: Список доменів імен цього сервера з'явився у федівсесвіті. Сюди не входять дані чи ви пов'язані федерацією з цим сервером, а лише відомості, що вашому серверу відомо про нього. Його використовують служби, які збирають загальну статистику про федерації.
profile_directory: У каталозі профілів перераховані всі користувачі, які погодились бути видимими.
require_invite_text: Якщо реєстрація вимагає власноручного затвердження, зробіть текстове поле «Чому ви хочете приєднатися?» обов'язковим, а не додатковим

View file

@ -934,6 +934,7 @@ th:
delete: ลบ
edit_preset: แก้ไขคำเตือนที่ตั้งไว้ล่วงหน้า
empty: คุณยังไม่ได้กำหนดคำเตือนที่ตั้งไว้ล่วงหน้าใด ๆ
title: คำเตือนที่ตั้งไว้ล่วงหน้า
webhooks:
add_new: เพิ่มปลายทาง
delete: ลบ

View file

@ -291,6 +291,7 @@ uk:
update_custom_emoji_html: "%{name} оновлює емодзі %{target}"
update_domain_block_html: "%{name} оновлює блокування домену для %{target}"
update_ip_block_html: "%{name} змінює правило для IP %{target}"
update_report_html: "%{name} оновлений звіт %{target}"
update_status_html: "%{name} оновлює допис %{target}"
update_user_role_html: "%{name} змінює роль %{target}"
deleted_account: видалений обліковий запис
@ -984,6 +985,7 @@ uk:
delete: Видалити
edit_preset: Редагувати шаблон попередження
empty: Ви ще не визначили жодних попереджень.
title: Попереджувальні пресети
webhooks:
add_new: Додати кінцеву точку
delete: Видалити

View file

@ -934,6 +934,7 @@ vi:
delete: Xóa bỏ
edit_preset: Sửa mẫu có sẵn
empty: Bạn chưa thêm mẫu cảnh cáo nào cả.
title: Cảnh báo cài sẵn
webhooks:
add_new: Thêm endpoint
delete: Xóa bỏ

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
class AddAuthorAccountIdToPreviewCards < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
safety_assured { add_reference :preview_cards, :author_account, null: true, foreign_key: { to_table: 'accounts', on_delete: :nullify }, index: false }
add_index :preview_cards, :author_account_id, algorithm: :concurrently, where: 'author_account_id IS NOT NULL'
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_05_10_192043) do
ActiveRecord::Schema[7.1].define(version: 2024_05_22_041528) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -877,6 +877,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_10_192043) do
t.integer "link_type"
t.datetime "published_at"
t.string "image_description", default: "", null: false
t.bigint "author_account_id"
t.index ["author_account_id"], name: "index_preview_cards_on_author_account_id", where: "(author_account_id IS NOT NULL)"
t.index ["url"], name: "index_preview_cards_on_url", unique: true
end
@ -1367,6 +1369,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_10_192043) do
add_foreign_key "polls", "accounts", on_delete: :cascade
add_foreign_key "polls", "statuses", on_delete: :cascade
add_foreign_key "preview_card_trends", "preview_cards", on_delete: :cascade
add_foreign_key "preview_cards", "accounts", column: "author_account_id", on_delete: :nullify
add_foreign_key "report_notes", "accounts", on_delete: :cascade
add_foreign_key "report_notes", "reports", on_delete: :cascade
add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", name: "fk_bca45b75fd", on_delete: :nullify

View file

@ -55,7 +55,7 @@ services:
web:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.9
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@ -76,7 +76,7 @@ services:
streaming:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.9
restart: always
env_file: .env.production
command: node ./streaming
@ -94,7 +94,7 @@ services:
sidekiq:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.9
restart: always
env_file: .env.production
command: bundle exec sidekiq

View file

@ -0,0 +1,72 @@
# frozen_string_literal: true
# Mastodon is not made to be directly accessed without a reverse proxy.
# This monkey-patch prevents remote IP address spoofing when being accessed
# directly.
#
# See PR: https://github.com/rails/rails/pull/51610
# In addition to the PR above, it also raises an error if a request with
# `X-Forwarded-For` or `Client-Ip` comes directly from a client without
# going through a trusted proxy.
# rubocop:disable all -- This is a mostly vendored file
module ActionDispatch
class RemoteIp
module GetIpExtensions
def calculate_ip
# Set by the Rack web server, this is a single value.
remote_addr = ips_from(@req.remote_addr).last
# Could be a CSV list and/or repeated headers that were concatenated.
client_ips = ips_from(@req.client_ip).reverse!
forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
# `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
# are both set, it means that either:
#
# 1) This request passed through two proxies with incompatible IP header
# conventions.
#
# 2) The client passed one of `Client-Ip` or `X-Forwarded-For`
# (whichever the proxy servers weren't using) themselves.
#
# Either way, there is no way for us to determine which header is the right one
# after the fact. Since we have no idea, if we are concerned about IP spoofing
# we need to give up and explode. (If you're not concerned about IP spoofing you
# can turn the `ip_spoofing_check` option off.)
should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?! " \
"HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
# NOTE: Mastodon addition to make sure we don't get requests from a non-trusted client
if @check_ip && (forwarded_ips.last || client_ips.last) && !@proxies.any? { |proxy| proxy === remote_addr }
raise IpSpoofAttackError, "IP spoofing attack?! client #{remote_addr} is not a trusted proxy " \
"HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
# We assume these things about the IP headers:
#
# - X-Forwarded-For will be a list of IPs, one per proxy, or blank
# - Client-Ip is propagated from the outermost proxy, or is blank
# - REMOTE_ADDR will be the IP that made the request to Rack
ips = forwarded_ips + client_ips
ips.compact!
# If every single IP option is in the trusted list, return the IP that's
# furthest away
filter_proxies([remote_addr] + ips).first || ips.last || remote_addr
end
end
end
end
ActionDispatch::RemoteIp::GetIp.prepend(ActionDispatch::RemoteIp::GetIpExtensions)
# rubocop:enable all

View file

@ -8,6 +8,7 @@ class Mastodon::SidekiqMiddleware
rescue Mastodon::HostValidationError
# Do not retry
rescue => e
clean_up_elasticsearch_connections!
limit_backtrace_and_raise(e)
ensure
clean_up_sockets!
@ -25,6 +26,32 @@ class Mastodon::SidekiqMiddleware
clean_up_statsd_socket!
end
# This is a hack to immediately free up unused Elasticsearch connections.
#
# Indeed, Chewy creates one `Elasticsearch::Client` instance per thread,
# and each such client manages its long-lasting connection to
# Elasticsearch.
#
# As far as I know, neither `chewy`, `elasticsearch-transport` or even
# `faraday` provide a reliable way to immediately close a connection, and
# rely on the underlying object to be garbage-collected instead.
#
# Furthermore, `sidekiq` creates a new thread each time a job throws an
# exception, meaning that each failure will create a new connection, and
# the old one will only be closed on full garbage collection.
def clean_up_elasticsearch_connections!
return unless Chewy.enabled? && Chewy.current[:chewy_client].present?
Chewy.client.transport.transport.connections.each do |connection|
# NOTE: This bit of code is tailored for the HTTPClient Faraday adapter
connection.connection.app.instance_variable_get(:@client)&.reset_all
end
Chewy.current.delete(:chewy_client)
rescue
nil
end
def clean_up_redis_socket!
RedisConfiguration.pool.checkin if Thread.current[:redis]
Thread.current[:redis] = nil

View file

@ -17,7 +17,7 @@ module Mastodon
end
def default_prerelease
'alpha.3'
'alpha.4'
end
def prerelease
@ -25,7 +25,7 @@ module Mastodon
end
def catstodon_revision
'1.2.7'
'1.0.0'
end
def build_metadata

View file

@ -181,7 +181,7 @@
"eslint-plugin-import": "~2.29.0",
"eslint-plugin-jsdoc": "^48.0.0",
"eslint-plugin-jsx-a11y": "~6.8.0",
"eslint-plugin-promise": "~6.1.1",
"eslint-plugin-promise": "~6.2.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"husky": "^9.0.11",

View file

@ -56,7 +56,7 @@ describe Rack::Attack, type: :request do
end
def throttle_count
described_class.cache.read("#{counter_prefix}:#{throttle}:#{remote_ip}") || 0
described_class.cache.read("#{counter_prefix}:#{throttle}:#{discriminator}") || 0
end
def counter_prefix
@ -64,11 +64,12 @@ describe Rack::Attack, type: :request do
end
def increment_counter
described_class.cache.count("#{throttle}:#{remote_ip}", period)
described_class.cache.count("#{throttle}:#{discriminator}", period)
end
end
let(:remote_ip) { '1.2.3.5' }
let(:discriminator) { remote_ip }
describe 'throttle excessive sign-up requests by IP address' do
context 'when accessed through the website' do
@ -131,4 +132,48 @@ describe Rack::Attack, type: :request do
it_behaves_like 'throttled endpoint'
end
end
describe 'throttle excessive oauth application registration requests by IP address' do
let(:throttle) { 'throttle_oauth_application_registrations/ip' }
let(:limit) { 5 }
let(:period) { 10.minutes }
let(:path) { '/api/v1/apps' }
let(:params) do
{
client_name: 'Throttle Test',
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
scopes: 'read',
}
end
let(:request) { -> { post path, params: params, headers: { 'REMOTE_ADDR' => remote_ip } } }
it_behaves_like 'throttled endpoint'
end
describe 'throttle excessive password change requests by account' do
let(:user) { Fabricate(:user, email: 'user@host.example') }
let(:throttle) { 'throttle_password_change/account' }
let(:limit) { 10 }
let(:period) { 10.minutes }
let(:request) { -> { put path, headers: { 'REMOTE_ADDR' => remote_ip } } }
let(:path) { '/auth' }
let(:discriminator) { user.id }
before do
sign_in user, scope: :user
# Unfortunately, devise's `sign_in` helper causes the `session` to be
# loaded in the next request regardless of whether it's actually accessed
# by the client code.
#
# So, we make an extra query to clear issue a session cookie instead.
#
# A less resource-intensive way to deal with that would be to generate the
# session cookie manually, but this seems pretty involved.
get '/'
end
it_behaves_like 'throttled endpoint'
end
end

View file

@ -1,48 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Vacuum::ApplicationsVacuum do
subject { described_class.new }
describe '#perform' do
let!(:app_with_token) { Fabricate(:application, created_at: 1.month.ago) }
let!(:app_with_grant) { Fabricate(:application, created_at: 1.month.ago) }
let!(:app_with_signup) { Fabricate(:application, created_at: 1.month.ago) }
let!(:app_with_owner) { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) }
let!(:unused_app) { Fabricate(:application, created_at: 1.month.ago) }
let!(:recent_app) { Fabricate(:application, created_at: 1.hour.ago) }
before do
Fabricate(:access_token, application: app_with_token)
Fabricate(:access_grant, application: app_with_grant)
Fabricate(:user, created_by_application: app_with_signup)
subject.perform
end
it 'does not delete applications with valid access tokens' do
expect { app_with_token.reload }.to_not raise_error
end
it 'does not delete applications with valid access grants' do
expect { app_with_grant.reload }.to_not raise_error
end
it 'does not delete applications that were used to create users' do
expect { app_with_signup.reload }.to_not raise_error
end
it 'does not delete owned applications' do
expect { app_with_owner.reload }.to_not raise_error
end
it 'does not delete applications registered less than a day ago' do
expect { recent_app.reload }.to_not raise_error
end
it 'deletes unused applications' do
expect { unused_app.reload }.to raise_error ActiveRecord::RecordNotFound
end
end
end

View file

@ -8,13 +8,13 @@ describe '/api/v1/accounts' do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/accounts?ids[]=:id' do
describe 'GET /api/v1/accounts?id[]=:id' do
let(:account) { Fabricate(:account) }
let(:other_account) { Fabricate(:account) }
let(:scopes) { 'read:accounts' }
it 'returns expected response' do
get '/api/v1/accounts', headers: headers, params: { ids: [account.id, other_account.id, 123_123] }
get '/api/v1/accounts', headers: headers, params: { id: [account.id, other_account.id, 123_123] }
expect(response).to have_http_status(200)
expect(body_as_json).to contain_exactly(

View file

@ -9,13 +9,13 @@ describe '/api/v1/statuses' do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: client_app, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/statuses?ids[]=:id' do
describe 'GET /api/v1/statuses?id[]=:id' do
let(:status) { Fabricate(:status) }
let(:other_status) { Fabricate(:status) }
let(:scopes) { 'read:statuses' }
it 'returns expected response' do
get '/api/v1/statuses', headers: headers, params: { ids: [status.id, other_status.id, 123_123] }
get '/api/v1/statuses', headers: headers, params: { id: [status.id, other_status.id, 123_123] }
expect(response).to have_http_status(200)
expect(body_as_json).to contain_exactly(

View file

@ -45,7 +45,7 @@ describe 'Instances' do
),
statuses: include(
max_characters: StatusLengthValidator::MAX_CHARS,
max_media_attachments: 4 # TODO, move to constant somewhere
max_media_attachments: Status::MEDIA_ATTACHMENTS_LIMIT
),
polls: include(
max_options: PollValidator::MAX_OPTIONS

View file

@ -309,6 +309,19 @@ RSpec.describe NotifyService do
expect(subject.filter?).to be false
end
end
context 'when the sender is mentioned in an unrelated message chain' do
before do
original_status = Fabricate(:status, visibility: :direct)
intermediary_status = Fabricate(:status, visibility: :direct, thread: original_status)
notification.target_status.update(thread: intermediary_status)
Fabricate(:mention, status: original_status, account: notification.from_account)
end
it 'returns true' do
expect(subject.filter?).to be true
end
end
end
end
end

View file

@ -228,14 +228,15 @@ RSpec.describe PostStatusService do
expect(media.reload.status).to be_nil
end
it 'does not allow attaching more than 4 files' do
it 'does not allow attaching more files than configured limit' do
stub_const('Status::MEDIA_ATTACHMENTS_LIMIT', 1)
account = Fabricate(:account)
expect do
subject.call(
account,
text: 'test status update',
media_ids: Array.new(5) { Fabricate(:media_attachment, account: account) }.map(&:id)
media_ids: Array.new(2) { Fabricate(:media_attachment, account: account) }.map(&:id)
)
end.to raise_error(
Mastodon::ValidationError,

View file

@ -45,8 +45,8 @@ RSpec.describe StatusPinValidator do
end
end
context 'when pin.account.status_pins.count > 4 && pin.account.local?' do
let(:count) { 5 }
context 'when pin account is local and has too many pins' do
let(:count) { described_class::PIN_LIMIT + 1 }
let(:local) { true }
it 'calls errors.add' do

View file

@ -7,6 +7,8 @@
"allowJs": true,
"noEmit": true,
"strict": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"baseUrl": "./",

518
yarn.lock
View file

@ -1590,7 +1590,7 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-parser-algorithms@npm:^2.6.1, @csstools/css-parser-algorithms@npm:^2.6.3":
"@csstools/css-parser-algorithms@npm:^2.6.3":
version: 2.6.3
resolution: "@csstools/css-parser-algorithms@npm:2.6.3"
peerDependencies:
@ -1599,14 +1599,14 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-tokenizer@npm:^2.2.4, @csstools/css-tokenizer@npm:^2.3.1":
"@csstools/css-tokenizer@npm:^2.3.1":
version: 2.3.1
resolution: "@csstools/css-tokenizer@npm:2.3.1"
checksum: 10c0/fed6619fb5108e109d4dd10b0e967035a92793bae8fb84544e1342058b6df4e306d9d075623e2201fe88831b1ada797aea3546a8d12229d2d81cd7a5dfee4444
languageName: node
linkType: hard
"@csstools/media-query-list-parser@npm:^2.1.11, @csstools/media-query-list-parser@npm:^2.1.9":
"@csstools/media-query-list-parser@npm:^2.1.11":
version: 2.1.11
resolution: "@csstools/media-query-list-parser@npm:2.1.11"
peerDependencies:
@ -1982,7 +1982,7 @@ __metadata:
languageName: node
linkType: hard
"@csstools/selector-specificity@npm:^3.0.3, @csstools/selector-specificity@npm:^3.1.1":
"@csstools/selector-specificity@npm:^3.1.1":
version: 3.1.1
resolution: "@csstools/selector-specificity@npm:3.1.1"
peerDependencies:
@ -2007,10 +2007,10 @@ __metadata:
languageName: node
linkType: hard
"@dual-bundle/import-meta-resolve@npm:^4.0.0":
version: 4.0.0
resolution: "@dual-bundle/import-meta-resolve@npm:4.0.0"
checksum: 10c0/868b8314fc753b7767887108535afe3288de941d92bc8453164dbcb1abe886b171e338f6f7d02ff556256dee69c90e4ac6360e0c6a856a5ad7190274ab52de2e
"@dual-bundle/import-meta-resolve@npm:^4.1.0":
version: 4.1.0
resolution: "@dual-bundle/import-meta-resolve@npm:4.1.0"
checksum: 10c0/55069e550ee2710e738dd8bbd34aba796cede456287454b50c3be46fbef8695d00625677f3f41f5ffbec1174c0f57f314da9a908388bc9f8ad41a8438db884d9
languageName: node
linkType: hard
@ -2248,16 +2248,6 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/ecma402-abstract@npm:1.18.2":
version: 1.18.2
resolution: "@formatjs/ecma402-abstract@npm:1.18.2"
dependencies:
"@formatjs/intl-localematcher": "npm:0.5.4"
tslib: "npm:^2.4.0"
checksum: 10c0/87afb37dd937555e712ca85d5142a9083d617c491d1dddf8d660fdfb6186272d2bc75b78809b076388d26f016200c8bddbce73281fd707eb899da2bf3bc9b7ca
languageName: node
linkType: hard
"@formatjs/ecma402-abstract@npm:2.0.0":
version: 2.0.0
resolution: "@formatjs/ecma402-abstract@npm:2.0.0"
@ -2277,17 +2267,6 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/icu-messageformat-parser@npm:2.7.6":
version: 2.7.6
resolution: "@formatjs/icu-messageformat-parser@npm:2.7.6"
dependencies:
"@formatjs/ecma402-abstract": "npm:1.18.2"
"@formatjs/icu-skeleton-parser": "npm:1.8.0"
tslib: "npm:^2.4.0"
checksum: 10c0/9fc72c2075333a969601e2be4260638940b1abefd1a5fc15b93b0b10d2319c9df5778aa51fc2a173ce66ca5e8a47b4b64caca85a32d0eb6095e16e8d65cb4b00
languageName: node
linkType: hard
"@formatjs/icu-messageformat-parser@npm:2.7.8":
version: 2.7.8
resolution: "@formatjs/icu-messageformat-parser@npm:2.7.8"
@ -2299,16 +2278,6 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/icu-skeleton-parser@npm:1.8.0":
version: 1.8.0
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.0"
dependencies:
"@formatjs/ecma402-abstract": "npm:1.18.2"
tslib: "npm:^2.4.0"
checksum: 10c0/10956732d70cc67049d216410b5dc3ef048935d1ea2ae76f5755bb9d0243af37ddeabd5d140ddbf5f6c7047068c3d02a05f93c68a89cedfaf7488d5062885ea4
languageName: node
linkType: hard
"@formatjs/icu-skeleton-parser@npm:1.8.2":
version: 1.8.2
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.2"
@ -2381,26 +2350,6 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/ts-transformer@npm:3.13.12":
version: 3.13.12
resolution: "@formatjs/ts-transformer@npm:3.13.12"
dependencies:
"@formatjs/icu-messageformat-parser": "npm:2.7.6"
"@types/json-stable-stringify": "npm:^1.0.32"
"@types/node": "npm:14 || 16 || 17"
chalk: "npm:^4.0.0"
json-stable-stringify: "npm:^1.0.1"
tslib: "npm:^2.4.0"
typescript: "npm:5"
peerDependencies:
ts-jest: ">=27"
peerDependenciesMeta:
ts-jest:
optional: true
checksum: 10c0/68f72ee6379b87b7ef6340e118a5370cb2fa18cbbae08f5f3d10893803a52f0533e644002e0b5e9ffeded5b2f0aa9daad6adf8b487b10f5d2b61f9fb3fed0dbd
languageName: node
linkType: hard
"@formatjs/ts-transformer@npm:3.13.14":
version: 3.13.14
resolution: "@formatjs/ts-transformer@npm:3.13.14"
@ -2874,7 +2823,7 @@ __metadata:
eslint-plugin-import: "npm:~2.29.0"
eslint-plugin-jsdoc: "npm:^48.0.0"
eslint-plugin-jsx-a11y: "npm:~6.8.0"
eslint-plugin-promise: "npm:~6.1.1"
eslint-plugin-promise: "npm:~6.2.0"
eslint-plugin-react: "npm:^7.33.2"
eslint-plugin-react-hooks: "npm:^4.6.0"
exif-js: "npm:^2.3.0"
@ -3736,7 +3685,7 @@ __metadata:
languageName: node
linkType: hard
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8":
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8":
version: 7.0.15
resolution: "@types/json-schema@npm:7.0.15"
checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
@ -3979,12 +3928,12 @@ __metadata:
linkType: hard
"@types/react@npm:*, @types/react@npm:16 || 17 || 18, @types/react@npm:>=16.9.11, @types/react@npm:^18.2.7":
version: 18.3.2
resolution: "@types/react@npm:18.3.2"
version: 18.3.3
resolution: "@types/react@npm:18.3.3"
dependencies:
"@types/prop-types": "npm:*"
csstype: "npm:^3.0.2"
checksum: 10c0/9fb2f1fcf7e889ee4ea7c3c5978df595c66e770e5fd3a245dbdd2589b9b911524c11dab25a6275d8af4e336e4cb5fa850d447884b84c335a187a338c89df99ba
checksum: 10c0/fe455f805c5da13b89964c3d68060cebd43e73ec15001a68b34634604a78140e6fc202f3f61679b9d809dde6d7a7c2cb3ed51e0fd1462557911db09879b55114
languageName: node
linkType: hard
@ -4012,7 +3961,7 @@ __metadata:
languageName: node
linkType: hard
"@types/semver@npm:^7.5.0, @types/semver@npm:^7.5.8":
"@types/semver@npm:^7.5.0":
version: 7.5.8
resolution: "@types/semver@npm:7.5.8"
checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa
@ -4163,19 +4112,17 @@ __metadata:
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^7.0.0":
version: 7.8.0
resolution: "@typescript-eslint/eslint-plugin@npm:7.8.0"
version: 7.10.0
resolution: "@typescript-eslint/eslint-plugin@npm:7.10.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:7.8.0"
"@typescript-eslint/type-utils": "npm:7.8.0"
"@typescript-eslint/utils": "npm:7.8.0"
"@typescript-eslint/visitor-keys": "npm:7.8.0"
debug: "npm:^4.3.4"
"@typescript-eslint/scope-manager": "npm:7.10.0"
"@typescript-eslint/type-utils": "npm:7.10.0"
"@typescript-eslint/utils": "npm:7.10.0"
"@typescript-eslint/visitor-keys": "npm:7.10.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^5.3.1"
natural-compare: "npm:^1.4.0"
semver: "npm:^7.6.0"
ts-api-utils: "npm:^1.3.0"
peerDependencies:
"@typescript-eslint/parser": ^7.0.0
@ -4183,25 +4130,25 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/37ca22620d1834ff0baa28fa4b8fd92039a3903cb95748353de32d56bae2a81ce50d1bbaed27487eebc884e0a0f9387fcb0f1647593e4e6df5111ef674afa9f0
checksum: 10c0/bf3f0118ea5961c3eb01894678246458a329d82dda9ac7c2f5bfe77896410d05a08a4655e533bcb1ed2a3132ba6421981ec8c2ed0a3545779d9603ea231947ae
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^7.0.0":
version: 7.8.0
resolution: "@typescript-eslint/parser@npm:7.8.0"
version: 7.10.0
resolution: "@typescript-eslint/parser@npm:7.10.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:7.8.0"
"@typescript-eslint/types": "npm:7.8.0"
"@typescript-eslint/typescript-estree": "npm:7.8.0"
"@typescript-eslint/visitor-keys": "npm:7.8.0"
"@typescript-eslint/scope-manager": "npm:7.10.0"
"@typescript-eslint/types": "npm:7.10.0"
"@typescript-eslint/typescript-estree": "npm:7.10.0"
"@typescript-eslint/visitor-keys": "npm:7.10.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.56.0
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/0dd994c1b31b810c25e1b755b8d352debb7bf21a31f9a91acaec34acf4e471320bcceaa67cf64c110c0b8f5fac10a037dbabac6ec423e17adf037e59a7bce9c1
checksum: 10c0/4c4fbf43b5b05d75b766acb803d3dd078c6e080641a77f9e48ba005713466738ea4a71f0564fa3ce520988d65158d14c8c952ba01ccbc431ab4a05935db5ce6d
languageName: node
linkType: hard
@ -4215,22 +4162,22 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:7.8.0":
version: 7.8.0
resolution: "@typescript-eslint/scope-manager@npm:7.8.0"
"@typescript-eslint/scope-manager@npm:7.10.0":
version: 7.10.0
resolution: "@typescript-eslint/scope-manager@npm:7.10.0"
dependencies:
"@typescript-eslint/types": "npm:7.8.0"
"@typescript-eslint/visitor-keys": "npm:7.8.0"
checksum: 10c0/c253b98e96d4bf0375f473ca2c4d081726f1fd926cdfa65ee14c9ee99cca8eddb763b2d238ac365daa7246bef21b0af38180d04e56e9df7443c0e6f8474d097c
"@typescript-eslint/types": "npm:7.10.0"
"@typescript-eslint/visitor-keys": "npm:7.10.0"
checksum: 10c0/1d4f7ee137b95bd423b5a1b0d03251202dfc19bd8b6adfa5ff5df25fd5aa30e2d8ca50ab0d8d2e92441670ecbc2a82b3c2dbe39a4f268ec1ee1c1e267f7fd1d1
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:7.8.0":
version: 7.8.0
resolution: "@typescript-eslint/type-utils@npm:7.8.0"
"@typescript-eslint/type-utils@npm:7.10.0":
version: 7.10.0
resolution: "@typescript-eslint/type-utils@npm:7.10.0"
dependencies:
"@typescript-eslint/typescript-estree": "npm:7.8.0"
"@typescript-eslint/utils": "npm:7.8.0"
"@typescript-eslint/typescript-estree": "npm:7.10.0"
"@typescript-eslint/utils": "npm:7.10.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.3.0"
peerDependencies:
@ -4238,7 +4185,7 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/00f6315626b64f7dbc1f7fba6f365321bb8d34141ed77545b2a07970e59a81dbdf768c1e024225ea00953750d74409ddd8a16782fc4a39261e507c04192dacab
checksum: 10c0/55e9a6690f9cedb79d30abb1990b161affaa2684dac246b743223353812c9c1e3fd2d923c67b193c6a3624a07e1c82c900ce7bf5b6b9891c846f04cb480ebd9f
languageName: node
linkType: hard
@ -4249,10 +4196,10 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/types@npm:7.8.0, @typescript-eslint/types@npm:^7.2.0":
version: 7.8.0
resolution: "@typescript-eslint/types@npm:7.8.0"
checksum: 10c0/b2fdbfc21957bfa46f7d8809b607ad8c8b67c51821d899064d09392edc12f28b2318a044f0cd5d523d782e84e8f0558778877944964cf38e139f88790cf9d466
"@typescript-eslint/types@npm:7.10.0, @typescript-eslint/types@npm:^7.2.0":
version: 7.10.0
resolution: "@typescript-eslint/types@npm:7.10.0"
checksum: 10c0/f01d9330b93cc362ba7967ab5037396f64742076450e1f93139fa69cbe93a6ece3ed55d68ab780c9b7d07ef4a7c645da410305216a2cfc5dec7eba49ee65ab23
languageName: node
linkType: hard
@ -4275,12 +4222,12 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:7.8.0":
version: 7.8.0
resolution: "@typescript-eslint/typescript-estree@npm:7.8.0"
"@typescript-eslint/typescript-estree@npm:7.10.0":
version: 7.10.0
resolution: "@typescript-eslint/typescript-estree@npm:7.10.0"
dependencies:
"@typescript-eslint/types": "npm:7.8.0"
"@typescript-eslint/visitor-keys": "npm:7.8.0"
"@typescript-eslint/types": "npm:7.10.0"
"@typescript-eslint/visitor-keys": "npm:7.10.0"
debug: "npm:^4.3.4"
globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3"
@ -4290,24 +4237,21 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/1690b62679685073dcb0f62499f0b52b445b37ae6e12d02aa4acbafe3fb023cf999b01f714b6282e88f84fd934fe3e2eefb21a64455d19c348d22bbc68ca8e47
checksum: 10c0/6200695834c566e52e2fa7331f1a05019f7815969d8c1e1e237b85a99664d36f41ccc16384eff3f8582a0ecb75f1cc315b56ee9283b818da37f24fa4d42f1d7a
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:7.8.0":
version: 7.8.0
resolution: "@typescript-eslint/utils@npm:7.8.0"
"@typescript-eslint/utils@npm:7.10.0":
version: 7.10.0
resolution: "@typescript-eslint/utils@npm:7.10.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.15"
"@types/semver": "npm:^7.5.8"
"@typescript-eslint/scope-manager": "npm:7.8.0"
"@typescript-eslint/types": "npm:7.8.0"
"@typescript-eslint/typescript-estree": "npm:7.8.0"
semver: "npm:^7.6.0"
"@typescript-eslint/scope-manager": "npm:7.10.0"
"@typescript-eslint/types": "npm:7.10.0"
"@typescript-eslint/typescript-estree": "npm:7.10.0"
peerDependencies:
eslint: ^8.56.0
checksum: 10c0/31fb58388d15b082eb7bd5bce889cc11617aa1131dfc6950471541b3df64c82d1c052e2cccc230ca4ae80456d4f63a3e5dccb79899a8f3211ce36c089b7d7640
checksum: 10c0/6724471f94f2788f59748f7efa2a3a53ea910099993bee2fa5746ab5acacecdc9fcb110c568b18099ddc946ea44919ed394bff2bd055ba81fc69f5e6297b73bf
languageName: node
linkType: hard
@ -4338,13 +4282,13 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:7.8.0":
version: 7.8.0
resolution: "@typescript-eslint/visitor-keys@npm:7.8.0"
"@typescript-eslint/visitor-keys@npm:7.10.0":
version: 7.10.0
resolution: "@typescript-eslint/visitor-keys@npm:7.10.0"
dependencies:
"@typescript-eslint/types": "npm:7.8.0"
"@typescript-eslint/types": "npm:7.10.0"
eslint-visitor-keys: "npm:^3.4.3"
checksum: 10c0/5892fb5d9c58efaf89adb225f7dbbb77f9363961f2ff420b6b130bdd102dddd7aa8a16c46a5a71c19889d27b781e966119a89270555ea2cb5653a04d8994123d
checksum: 10c0/049e812bcd28869059d04c7bf3543bb55f5205f468b777439c4f120417fb856fb6024cb1d25291aa12556bd08e84f043a96d754ffb2cde37abb604d6f3c51634
languageName: node
linkType: hard
@ -5619,12 +5563,12 @@ __metadata:
languageName: node
linkType: hard
"braces@npm:^3.0.2, braces@npm:~3.0.2":
version: 3.0.2
resolution: "braces@npm:3.0.2"
"braces@npm:^3.0.3, braces@npm:~3.0.2":
version: 3.0.3
resolution: "braces@npm:3.0.3"
dependencies:
fill-range: "npm:^7.0.1"
checksum: 10c0/321b4d675791479293264019156ca322163f02dc06e3c4cab33bb15cd43d80b51efef69b0930cfde3acd63d126ebca24cd0544fa6f261e093a0fb41ab9dda381
fill-range: "npm:^7.1.1"
checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04
languageName: node
linkType: hard
@ -5927,13 +5871,6 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:5.3.0":
version: 5.3.0
resolution: "chalk@npm:5.3.0"
checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09
languageName: node
linkType: hard
"chalk@npm:^2.4.1, chalk@npm:^2.4.2":
version: 2.4.2
resolution: "chalk@npm:2.4.2"
@ -5965,6 +5902,13 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:~5.3.0":
version: 5.3.0
resolution: "chalk@npm:5.3.0"
checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09
languageName: node
linkType: hard
"char-regex@npm:^1.0.2":
version: 1.0.2
resolution: "char-regex@npm:1.0.2"
@ -6257,13 +6201,6 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:11.1.0":
version: 11.1.0
resolution: "commander@npm:11.1.0"
checksum: 10c0/13cc6ac875e48780250f723fb81c1c1178d35c5decb1abb1b628b3177af08a8554e76b2c0f29de72d69eef7c864d12613272a71fabef8047922bc622ab75a179
languageName: node
linkType: hard
"commander@npm:^2.20.0":
version: 2.20.3
resolution: "commander@npm:2.20.3"
@ -6278,6 +6215,13 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:~12.1.0":
version: 12.1.0
resolution: "commander@npm:12.1.0"
checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9
languageName: node
linkType: hard
"comment-parser@npm:1.4.1":
version: 1.4.1
resolution: "comment-parser@npm:1.4.1"
@ -6957,7 +6901,7 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4":
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:~4.3.4":
version: 4.3.4
resolution: "debug@npm:4.3.4"
dependencies:
@ -7835,11 +7779,11 @@ __metadata:
linkType: hard
"eslint-plugin-formatjs@npm:^4.10.1":
version: 4.13.1
resolution: "eslint-plugin-formatjs@npm:4.13.1"
version: 4.13.3
resolution: "eslint-plugin-formatjs@npm:4.13.3"
dependencies:
"@formatjs/icu-messageformat-parser": "npm:2.7.6"
"@formatjs/ts-transformer": "npm:3.13.12"
"@formatjs/icu-messageformat-parser": "npm:2.7.8"
"@formatjs/ts-transformer": "npm:3.13.14"
"@types/eslint": "npm:7 || 8"
"@types/picomatch": "npm:^2.3.0"
"@typescript-eslint/utils": "npm:^6.18.1"
@ -7851,7 +7795,7 @@ __metadata:
unicode-emoji-utils: "npm:^1.2.0"
peerDependencies:
eslint: 7 || 8
checksum: 10c0/ce18141dff84e8fe026127085c1a63279acb3a1bc0b70dc1ddce2fc65bb37d68ccf6d097231428745eda2caea42080e1c80a01a1895803155c15123a01bfeee3
checksum: 10c0/5e98f487a097189e3bdc64b678d19f4c83502c32d7c89a8959eda4ed9cb664bf16f13ad8871be89ca192cb39c1007d6a158c39bbf5b23c56962d949dbe9abfab
languageName: node
linkType: hard
@ -7883,8 +7827,8 @@ __metadata:
linkType: hard
"eslint-plugin-jsdoc@npm:^48.0.0":
version: 48.2.4
resolution: "eslint-plugin-jsdoc@npm:48.2.4"
version: 48.2.6
resolution: "eslint-plugin-jsdoc@npm:48.2.6"
dependencies:
"@es-joy/jsdoccomment": "npm:~0.43.0"
are-docs-informative: "npm:^0.0.2"
@ -7892,12 +7836,11 @@ __metadata:
debug: "npm:^4.3.4"
escape-string-regexp: "npm:^4.0.0"
esquery: "npm:^1.5.0"
is-builtin-module: "npm:^3.2.1"
semver: "npm:^7.6.0"
semver: "npm:^7.6.1"
spdx-expression-parse: "npm:^4.0.0"
peerDependencies:
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
checksum: 10c0/601c9d6ee41de56102c7813106ceb0b8b8342223670f7add010a8f89753c250cde4cc93e353e3911b7b29677f2634f3f4be45f27abb7a95c6fdbd058adfa3343
checksum: 10c0/9f01b3000aa31f17767786c62caf62f1e8c4b88bfef04b207d3b1de785be287cc2da3ad16ed32afacd5f6e6a9b76ebf3369069be416ce2228c44cd6d084fcd8f
languageName: node
linkType: hard
@ -7927,12 +7870,12 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-promise@npm:~6.1.1":
version: 6.1.1
resolution: "eslint-plugin-promise@npm:6.1.1"
"eslint-plugin-promise@npm:~6.2.0":
version: 6.2.0
resolution: "eslint-plugin-promise@npm:6.2.0"
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
checksum: 10c0/ec705741c110cd1cb4d702776e1c7f7fe60b671b71f706c88054ab443cf2767aae5a663928fb426373ba1095eaeda312a740a4f880546631f0e0727f298b3393
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
checksum: 10c0/5f42ee774023c089453ecb792076c64c6d0739ea6e9d6cdc9d6a63da5ba928c776e349d01cc110548f2c67045ec55343136aa7eb8b486e4ab145ac016c06a492
languageName: node
linkType: hard
@ -8175,23 +8118,6 @@ __metadata:
languageName: node
linkType: hard
"execa@npm:8.0.1":
version: 8.0.1
resolution: "execa@npm:8.0.1"
dependencies:
cross-spawn: "npm:^7.0.3"
get-stream: "npm:^8.0.1"
human-signals: "npm:^5.0.0"
is-stream: "npm:^3.0.0"
merge-stream: "npm:^2.0.0"
npm-run-path: "npm:^5.1.0"
onetime: "npm:^6.0.0"
signal-exit: "npm:^4.1.0"
strip-final-newline: "npm:^3.0.0"
checksum: 10c0/2c52d8775f5bf103ce8eec9c7ab3059909ba350a5164744e9947ed14a53f51687c040a250bda833f906d1283aa8803975b84e6c8f7a7c42f99dc8ef80250d1af
languageName: node
linkType: hard
"execa@npm:^1.0.0":
version: 1.0.0
resolution: "execa@npm:1.0.0"
@ -8224,6 +8150,23 @@ __metadata:
languageName: node
linkType: hard
"execa@npm:~8.0.1":
version: 8.0.1
resolution: "execa@npm:8.0.1"
dependencies:
cross-spawn: "npm:^7.0.3"
get-stream: "npm:^8.0.1"
human-signals: "npm:^5.0.0"
is-stream: "npm:^3.0.0"
merge-stream: "npm:^2.0.0"
npm-run-path: "npm:^5.1.0"
onetime: "npm:^6.0.0"
signal-exit: "npm:^4.1.0"
strip-final-newline: "npm:^3.0.0"
checksum: 10c0/2c52d8775f5bf103ce8eec9c7ab3059909ba350a5164744e9947ed14a53f51687c040a250bda833f906d1283aa8803975b84e6c8f7a7c42f99dc8ef80250d1af
languageName: node
linkType: hard
"exif-js@npm:^2.3.0":
version: 2.3.0
resolution: "exif-js@npm:2.3.0"
@ -8356,10 +8299,10 @@ __metadata:
languageName: node
linkType: hard
"fast-copy@npm:^3.0.0":
version: 3.0.1
resolution: "fast-copy@npm:3.0.1"
checksum: 10c0/a8310dbcc4c94ed001dc3e0bbc3c3f0491bb04e6c17163abe441a54997ba06cdf1eb532c2f05e54777c6f072c84548c23ef0ecd54665cd611be1d42f37eca258
"fast-copy@npm:^3.0.2":
version: 3.0.2
resolution: "fast-copy@npm:3.0.2"
checksum: 10c0/02e8b9fd03c8c024d2987760ce126456a0e17470850b51e11a1c3254eed6832e4733ded2d93316c82bc0b36aeb991ad1ff48d1ba95effe7add7c3ab8d8eb554a
languageName: node
linkType: hard
@ -8517,12 +8460,12 @@ __metadata:
languageName: node
linkType: hard
"fill-range@npm:^7.0.1":
version: 7.0.1
resolution: "fill-range@npm:7.0.1"
"fill-range@npm:^7.1.1":
version: 7.1.1
resolution: "fill-range@npm:7.1.1"
dependencies:
to-regex-range: "npm:^5.0.1"
checksum: 10c0/7cdad7d426ffbaadf45aeb5d15ec675bbd77f7597ad5399e3d2766987ed20bda24d5fac64b3ee79d93276f5865608bb22344a26b9b1ae6c4d00bd94bf611623f
checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018
languageName: node
linkType: hard
@ -9416,13 +9359,13 @@ __metadata:
languageName: node
linkType: hard
"http-proxy-agent@npm:^7.0.0":
version: 7.0.0
resolution: "http-proxy-agent@npm:7.0.0"
"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2":
version: 7.0.2
resolution: "http-proxy-agent@npm:7.0.2"
dependencies:
agent-base: "npm:^7.1.0"
debug: "npm:^4.3.4"
checksum: 10c0/a11574ff39436cee3c7bc67f259444097b09474605846ddd8edf0bf4ad8644be8533db1aa463426e376865047d05dc22755e638632819317c0c2f1b2196657c8
checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921
languageName: node
linkType: hard
@ -9466,13 +9409,13 @@ __metadata:
languageName: node
linkType: hard
"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2":
version: 7.0.2
resolution: "https-proxy-agent@npm:7.0.2"
"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.4":
version: 7.0.4
resolution: "https-proxy-agent@npm:7.0.4"
dependencies:
agent-base: "npm:^7.0.2"
debug: "npm:4"
checksum: 10c0/7735eb90073db087e7e79312e3d97c8c04baf7ea7ca7b013382b6a45abbaa61b281041a98f4e13c8c80d88f843785bcc84ba189165b4b4087b1e3496ba656d77
checksum: 10c0/bc4f7c38da32a5fc622450b6cb49a24ff596f9bd48dcedb52d2da3fa1c1a80e100fb506bd59b326c012f21c863c69b275c23de1a01d0b84db396822fdf25e52b
languageName: node
linkType: hard
@ -11034,36 +10977,36 @@ __metadata:
linkType: hard
"jsdom@npm:^24.0.0":
version: 24.0.0
resolution: "jsdom@npm:24.0.0"
version: 24.1.0
resolution: "jsdom@npm:24.1.0"
dependencies:
cssstyle: "npm:^4.0.1"
data-urls: "npm:^5.0.0"
decimal.js: "npm:^10.4.3"
form-data: "npm:^4.0.0"
html-encoding-sniffer: "npm:^4.0.0"
http-proxy-agent: "npm:^7.0.0"
https-proxy-agent: "npm:^7.0.2"
http-proxy-agent: "npm:^7.0.2"
https-proxy-agent: "npm:^7.0.4"
is-potential-custom-element-name: "npm:^1.0.1"
nwsapi: "npm:^2.2.7"
nwsapi: "npm:^2.2.10"
parse5: "npm:^7.1.2"
rrweb-cssom: "npm:^0.6.0"
rrweb-cssom: "npm:^0.7.0"
saxes: "npm:^6.0.0"
symbol-tree: "npm:^3.2.4"
tough-cookie: "npm:^4.1.3"
tough-cookie: "npm:^4.1.4"
w3c-xmlserializer: "npm:^5.0.0"
webidl-conversions: "npm:^7.0.0"
whatwg-encoding: "npm:^3.1.1"
whatwg-mimetype: "npm:^4.0.0"
whatwg-url: "npm:^14.0.0"
ws: "npm:^8.16.0"
ws: "npm:^8.17.0"
xml-name-validator: "npm:^5.0.0"
peerDependencies:
canvas: ^2.11.2
peerDependenciesMeta:
canvas:
optional: true
checksum: 10c0/7b35043d7af39ad6dcaef0fa5679d8c8a94c6c9b6cc4a79222b7c9987d57ab7150c50856684ae56b473ab28c7d82aec0fb7ca19dcbd4c3f46683c807d717a3af
checksum: 10c0/34eadd8a7ae20c1505abe7a0f3988b2f0881cce7e27d75c4f5224f440f81f8ac08f4f449695b0f4178f048ed1c1709f3594e9d3f2fe0406c28e8da6eddd44f5a
languageName: node
linkType: hard
@ -11253,10 +11196,10 @@ __metadata:
languageName: node
linkType: hard
"known-css-properties@npm:^0.30.0":
version: 0.30.0
resolution: "known-css-properties@npm:0.30.0"
checksum: 10c0/8b487a6b33487affcec41eb392ceb77acf4d093558dde5c88b5ea06b9a3c81781876d7cb09872e0518b9602f27c8f4112c9ac333e02c90a91c8fbd12e202ed48
"known-css-properties@npm:^0.31.0":
version: 0.31.0
resolution: "known-css-properties@npm:0.31.0"
checksum: 10c0/8e643cbed32d7733278ba215c43dfc38fc7e77d391f66b81f07228af97d69ce2cebba03a9bc1ac859479e162aea812e258b30f4c93cb7b7adfd0622a141d36da
languageName: node
linkType: hard
@ -11293,14 +11236,7 @@ __metadata:
languageName: node
linkType: hard
"lilconfig@npm:3.0.0":
version: 3.0.0
resolution: "lilconfig@npm:3.0.0"
checksum: 10c0/7f5ee7a658dc016cacf146815e8d88b06f06f4402823b8b0934e305a57a197f55ccc9c5cd4fb5ea1b2b821c8ccaf2d54abd59602a4931af06eabda332388d3e6
languageName: node
linkType: hard
"lilconfig@npm:^3.1.1":
"lilconfig@npm:^3.1.1, lilconfig@npm:~3.1.1":
version: 3.1.1
resolution: "lilconfig@npm:3.1.1"
checksum: 10c0/311b559794546894e3fe176663427326026c1c644145be9e8041c58e268aa9328799b8dfe7e4dd8c6a4ae305feae95a1c9e007db3569f35b42b6e1bc8274754c
@ -11315,36 +11251,36 @@ __metadata:
linkType: hard
"lint-staged@npm:^15.0.0":
version: 15.2.2
resolution: "lint-staged@npm:15.2.2"
version: 15.2.5
resolution: "lint-staged@npm:15.2.5"
dependencies:
chalk: "npm:5.3.0"
commander: "npm:11.1.0"
debug: "npm:4.3.4"
execa: "npm:8.0.1"
lilconfig: "npm:3.0.0"
listr2: "npm:8.0.1"
micromatch: "npm:4.0.5"
pidtree: "npm:0.6.0"
string-argv: "npm:0.3.2"
yaml: "npm:2.3.4"
chalk: "npm:~5.3.0"
commander: "npm:~12.1.0"
debug: "npm:~4.3.4"
execa: "npm:~8.0.1"
lilconfig: "npm:~3.1.1"
listr2: "npm:~8.2.1"
micromatch: "npm:~4.0.7"
pidtree: "npm:~0.6.0"
string-argv: "npm:~0.3.2"
yaml: "npm:~2.4.2"
bin:
lint-staged: bin/lint-staged.js
checksum: 10c0/a1ba6c7ee53e30a0f6ea9a351d95d3d0d2be916a41b561e22907e9ea513eb18cb3dbe65bff3ec13fad15777999efe56b2e2a95427e31d12a9b7e7948c3630ee2
checksum: 10c0/89c54489783510f86df15756659facade82e849c0cbfb564fe047b82be91c5d2b1b5608a4bfc5237bd7b9fd0e1206e66aa3e4f8cad3ac51e37a098b8492c2fa6
languageName: node
linkType: hard
"listr2@npm:8.0.1":
version: 8.0.1
resolution: "listr2@npm:8.0.1"
"listr2@npm:~8.2.1":
version: 8.2.1
resolution: "listr2@npm:8.2.1"
dependencies:
cli-truncate: "npm:^4.0.0"
colorette: "npm:^2.0.20"
eventemitter3: "npm:^5.0.1"
log-update: "npm:^6.0.0"
rfdc: "npm:^1.3.0"
rfdc: "npm:^1.3.1"
wrap-ansi: "npm:^9.0.0"
checksum: 10c0/b565d6ceb3a4c2dbe0c1735c0fd907afd0d6f89de21aced8e05187b2d88ca2f8f9ebc5d743885396a00f05f13146f6be744d098a56ce0402cf1cd131485a7ff1
checksum: 10c0/ac32cba8e5c79bcf0dbbb43c2fcc73e47902320c1fa1891074fefb3aa3dfaeef9c76348da22909f65334ba9bee1140bfc903e2f0c64427dd08ef4ba8f6b1dbd0
languageName: node
linkType: hard
@ -11786,16 +11722,6 @@ __metadata:
languageName: node
linkType: hard
"micromatch@npm:4.0.5, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5":
version: 4.0.5
resolution: "micromatch@npm:4.0.5"
dependencies:
braces: "npm:^3.0.2"
picomatch: "npm:^2.3.1"
checksum: 10c0/3d6505b20f9fa804af5d8c596cb1c5e475b9b0cd05f652c5b56141cf941bd72adaeb7a436fda344235cef93a7f29b7472efc779fcdb83b478eab0867b95cdeff
languageName: node
linkType: hard
"micromatch@npm:^3.0.4, micromatch@npm:^3.1.10, micromatch@npm:^3.1.4":
version: 3.1.10
resolution: "micromatch@npm:3.1.10"
@ -11817,6 +11743,16 @@ __metadata:
languageName: node
linkType: hard
"micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:~4.0.7":
version: 4.0.7
resolution: "micromatch@npm:4.0.7"
dependencies:
braces: "npm:^3.0.3"
picomatch: "npm:^2.3.1"
checksum: 10c0/58fa99bc5265edec206e9163a1d2cec5fabc46a5b473c45f4a700adce88c2520456ae35f2b301e4410fb3afb27e9521fb2813f6fc96be0a48a89430e0916a772
languageName: node
linkType: hard
"miller-rabin@npm:^4.0.0":
version: 4.0.1
resolution: "miller-rabin@npm:4.0.1"
@ -12363,10 +12299,10 @@ __metadata:
languageName: node
linkType: hard
"nwsapi@npm:^2.2.2, nwsapi@npm:^2.2.7":
version: 2.2.7
resolution: "nwsapi@npm:2.2.7"
checksum: 10c0/44be198adae99208487a1c886c0a3712264f7bbafa44368ad96c003512fed2753d4e22890ca1e6edb2690c3456a169f2a3c33bfacde1905cf3bf01c7722464db
"nwsapi@npm:^2.2.10, nwsapi@npm:^2.2.2":
version: 2.2.10
resolution: "nwsapi@npm:2.2.10"
checksum: 10c0/43dfa150387bd2a578e37556d0ae3330d5617f99e5a7b64e3400d4c2785620762aa6169caf8f5fbce17b7ef29c372060b602594320c374fba0a39da4163d77ed
languageName: node
linkType: hard
@ -13010,10 +12946,10 @@ __metadata:
languageName: node
linkType: hard
"picocolors@npm:^1.0.0":
version: 1.0.0
resolution: "picocolors@npm:1.0.0"
checksum: 10c0/20a5b249e331c14479d94ec6817a182fd7a5680debae82705747b2db7ec50009a5f6648d0621c561b0572703f84dbef0858abcbd5856d3c5511426afcb1961f7
"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1":
version: 1.0.1
resolution: "picocolors@npm:1.0.1"
checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400
languageName: node
linkType: hard
@ -13024,7 +12960,7 @@ __metadata:
languageName: node
linkType: hard
"pidtree@npm:0.6.0":
"pidtree@npm:~0.6.0":
version: 0.6.0
resolution: "pidtree@npm:0.6.0"
bin:
@ -13093,12 +13029,12 @@ __metadata:
linkType: hard
"pino-pretty@npm:^11.0.0":
version: 11.0.0
resolution: "pino-pretty@npm:11.0.0"
version: 11.1.0
resolution: "pino-pretty@npm:11.1.0"
dependencies:
colorette: "npm:^2.0.7"
dateformat: "npm:^4.6.3"
fast-copy: "npm:^3.0.0"
fast-copy: "npm:^3.0.2"
fast-safe-stringify: "npm:^2.1.1"
help-me: "npm:^5.0.0"
joycon: "npm:^3.1.1"
@ -13108,11 +13044,11 @@ __metadata:
pump: "npm:^3.0.0"
readable-stream: "npm:^4.0.0"
secure-json-parse: "npm:^2.4.0"
sonic-boom: "npm:^3.0.0"
sonic-boom: "npm:^4.0.1"
strip-json-comments: "npm:^3.1.1"
bin:
pino-pretty: bin.js
checksum: 10c0/d42213f3fdf19d92152b0a14683b2bb8443423739c81ab7c1181a5dac0e0ca7621d232c8264ece81edc01106ca2a8e165783daca0a902f0fde480027075d5540
checksum: 10c0/418be6f854b0d62c83c65e75b0969d5311792bfadeefbfe77d8a7f8c5ba26b8bea40f549222b5f500439f440eb4d6c2fa99d712bdd02881ebae7be3a0193b581
languageName: node
linkType: hard
@ -15224,10 +15160,10 @@ __metadata:
languageName: node
linkType: hard
"rfdc@npm:^1.3.0":
version: 1.3.0
resolution: "rfdc@npm:1.3.0"
checksum: 10c0/a17fd7b81f42c7ae4cb932abd7b2f677b04cc462a03619fb46945ae1ccae17c3bc87c020ffdde1751cbfa8549860a2883486fdcabc9b9de3f3108af32b69a667
"rfdc@npm:^1.3.1":
version: 1.3.1
resolution: "rfdc@npm:1.3.1"
checksum: 10c0/69f65e3ed30970f8055fac9fbbef9ce578800ca19554eab1dcbffe73a4b8aef536bc4248313889cf25e3b4e38b212c721eabe30856575bf2b2bc3d90f8ba93ef
languageName: node
linkType: hard
@ -15295,6 +15231,13 @@ __metadata:
languageName: node
linkType: hard
"rrweb-cssom@npm:^0.7.0":
version: 0.7.0
resolution: "rrweb-cssom@npm:0.7.0"
checksum: 10c0/278350b1f383f76db20e37394361b709740bd4f5f27f924e1c3c3fdd7112b2ae37ed9bc7cee63776f7df395b9b0f644d1f8be104990e3028d276a3288cd7e564
languageName: node
linkType: hard
"run-parallel@npm:^1.1.9":
version: 1.2.0
resolution: "run-parallel@npm:1.2.0"
@ -15511,14 +15454,12 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0":
version: 7.6.0
resolution: "semver@npm:7.6.0"
dependencies:
lru-cache: "npm:^6.0.0"
"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.1":
version: 7.6.2
resolution: "semver@npm:7.6.2"
bin:
semver: bin/semver.js
checksum: 10c0/fbfe717094ace0aa8d6332d7ef5ce727259815bd8d8815700853f4faf23aacbd7192522f0dc5af6df52ef4fa85a355ebd2f5d39f554bd028200d6cf481ab9b53
checksum: 10c0/97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c
languageName: node
linkType: hard
@ -15889,15 +15830,6 @@ __metadata:
languageName: node
linkType: hard
"sonic-boom@npm:^3.0.0":
version: 3.7.0
resolution: "sonic-boom@npm:3.7.0"
dependencies:
atomic-sleep: "npm:^1.0.0"
checksum: 10c0/57a3d560efb77f4576db111168ee2649c99e7869fda6ce0ec2a4e5458832d290ba58d74b073ddb5827d9a30f96d23cff79157993d919e1a6d5f28d8b6391c7f0
languageName: node
linkType: hard
"sonic-boom@npm:^4.0.1":
version: 4.0.1
resolution: "sonic-boom@npm:4.0.1"
@ -16204,7 +16136,7 @@ __metadata:
languageName: node
linkType: hard
"string-argv@npm:0.3.2":
"string-argv@npm:~0.3.2":
version: 0.3.2
resolution: "string-argv@npm:0.3.2"
checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82
@ -16528,14 +16460,14 @@ __metadata:
linkType: hard
"stylelint@npm:^16.0.2":
version: 16.5.0
resolution: "stylelint@npm:16.5.0"
version: 16.6.0
resolution: "stylelint@npm:16.6.0"
dependencies:
"@csstools/css-parser-algorithms": "npm:^2.6.1"
"@csstools/css-tokenizer": "npm:^2.2.4"
"@csstools/media-query-list-parser": "npm:^2.1.9"
"@csstools/selector-specificity": "npm:^3.0.3"
"@dual-bundle/import-meta-resolve": "npm:^4.0.0"
"@csstools/css-parser-algorithms": "npm:^2.6.3"
"@csstools/css-tokenizer": "npm:^2.3.1"
"@csstools/media-query-list-parser": "npm:^2.1.11"
"@csstools/selector-specificity": "npm:^3.1.1"
"@dual-bundle/import-meta-resolve": "npm:^4.1.0"
balanced-match: "npm:^2.0.0"
colord: "npm:^2.9.3"
cosmiconfig: "npm:^9.0.0"
@ -16552,16 +16484,16 @@ __metadata:
ignore: "npm:^5.3.1"
imurmurhash: "npm:^0.1.4"
is-plain-object: "npm:^5.0.0"
known-css-properties: "npm:^0.30.0"
known-css-properties: "npm:^0.31.0"
mathml-tag-names: "npm:^2.1.3"
meow: "npm:^13.2.0"
micromatch: "npm:^4.0.5"
normalize-path: "npm:^3.0.0"
picocolors: "npm:^1.0.0"
picocolors: "npm:^1.0.1"
postcss: "npm:^8.4.38"
postcss-resolve-nested-selector: "npm:^0.1.1"
postcss-safe-parser: "npm:^7.0.0"
postcss-selector-parser: "npm:^6.0.16"
postcss-selector-parser: "npm:^6.1.0"
postcss-value-parser: "npm:^4.2.0"
resolve-from: "npm:^5.0.0"
string-width: "npm:^4.2.3"
@ -16572,7 +16504,7 @@ __metadata:
write-file-atomic: "npm:^5.0.1"
bin:
stylelint: bin/stylelint.mjs
checksum: 10c0/9281693ff6c1918e07fdcf7a950531f79678a28261a0d5bd36ca2fcf524e53d7305158d20ba890f5dd01c0ff90c09a13453dce2fe6887f4c157d8c2c0acf3666
checksum: 10c0/acfb7983a0b71677d066b2aa570eefdac0a7be2e21351bac8884b8156deaeec19e53ad128ae7ae7933c79f6045f1de8d759ba06cfbc373b2711015860805a3e7
languageName: node
linkType: hard
@ -16973,15 +16905,15 @@ __metadata:
languageName: node
linkType: hard
"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.3":
version: 4.1.3
resolution: "tough-cookie@npm:4.1.3"
"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.4":
version: 4.1.4
resolution: "tough-cookie@npm:4.1.4"
dependencies:
psl: "npm:^1.1.33"
punycode: "npm:^2.1.1"
universalify: "npm:^0.2.0"
url-parse: "npm:^1.5.3"
checksum: 10c0/4fc0433a0cba370d57c4b240f30440c848906dee3180bb6e85033143c2726d322e7e4614abb51d42d111ebec119c4876ed8d7247d4113563033eebbc1739c831
checksum: 10c0/aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45
languageName: node
linkType: hard
@ -18395,7 +18327,7 @@ __metadata:
languageName: node
linkType: hard
"ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.16.0":
"ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.17.0":
version: 8.17.0
resolution: "ws@npm:8.17.0"
peerDependencies:
@ -18466,13 +18398,6 @@ __metadata:
languageName: node
linkType: hard
"yaml@npm:2.3.4":
version: 2.3.4
resolution: "yaml@npm:2.3.4"
checksum: 10c0/cf03b68f8fef5e8516b0f0b54edaf2459f1648317fc6210391cf606d247e678b449382f4bd01f77392538429e306c7cba8ff46ff6b37cac4de9a76aff33bd9e1
languageName: node
linkType: hard
"yaml@npm:^1.10.0":
version: 1.10.2
resolution: "yaml@npm:1.10.2"
@ -18480,6 +18405,15 @@ __metadata:
languageName: node
linkType: hard
"yaml@npm:~2.4.2":
version: 2.4.2
resolution: "yaml@npm:2.4.2"
bin:
yaml: bin.mjs
checksum: 10c0/280ddb2e43ffa7d91a95738e80c8f33e860749cdc25aa6d9e4d350a28e174fd7e494e4aa023108aaee41388e451e3dc1292261d8f022aabcf90df9c63d647549
languageName: node
linkType: hard
"yargs-parser@npm:^13.1.2":
version: 13.1.2
resolution: "yargs-parser@npm:13.1.2"