diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 97331f74ea..5d9917b399 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -2,6 +2,7 @@ version: '3' services: app: + working_dir: /workspaces/mastodon/ build: context: . dockerfile: Dockerfile diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index cdd0c5452d..9bc6b8c258 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index bf94a0d9bb..c4c4a8234a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/CHANGELOG_glitch.md b/CHANGELOG_glitch.md index a53790afaf..c9b24d6f15 100644 --- a/CHANGELOG_glitch.md +++ b/CHANGELOG_glitch.md @@ -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 diff --git a/Gemfile.lock b/Gemfile.lock index e05ff2d593..b00df42118 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index be7b302d3b..84b604b305 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -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 diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/conversations_controller.rb index a95c816e1c..a29b908555 100644 --- a/app/controllers/api/v1/conversations_controller.rb +++ b/app/controllers/api/v1/conversations_controller.rb @@ -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], }, ] ) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 3f138d28ba..9cbf9866d8 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -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 diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index d45927226c..40a9b7c0ca 100644 --- a/app/javascript/entrypoints/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -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, diff --git a/app/javascript/entrypoints/remote_interaction_helper.ts b/app/javascript/entrypoints/remote_interaction_helper.ts index d5834c6c3d..419571c896 100644 --- a/app/javascript/entrypoints/remote_interaction_helper.ts +++ b/app/javascript/entrypoints/remote_interaction_helper.ts @@ -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}` }, diff --git a/app/javascript/flavours/glitch/components/animated_number.tsx b/app/javascript/flavours/glitch/components/animated_number.tsx index 05a7e01898..e784d445d4 100644 --- a/app/javascript/flavours/glitch/components/animated_number.tsx +++ b/app/javascript/flavours/glitch/components/animated_number.tsx @@ -63,8 +63,9 @@ export const AnimatedNumber: React.FC = ({ value, obfuscate }) => { 0 ? 'absolute' : 'static', - transform: `translateY(${style.y * 100}%)`, + position: + direction * (style.y ?? 0) > 0 ? 'absolute' : 'static', + transform: `translateY(${(style.y ?? 0) * 100}%)`, }} > {obfuscate ? ( diff --git a/app/javascript/flavours/glitch/components/hashtag_bar.tsx b/app/javascript/flavours/glitch/components/hashtag_bar.tsx index ed5de7d3a5..1642ba6504 100644 --- a/app/javascript/flavours/glitch/components/hashtag_bar.tsx +++ b/app/javascript/flavours/glitch/components/hashtag_bar.tsx @@ -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; }); } diff --git a/app/javascript/flavours/glitch/components/short_number.tsx b/app/javascript/flavours/glitch/components/short_number.tsx index 74c3c5d75e..a0b523aaad 100644 --- a/app/javascript/flavours/glitch/components/short_number.tsx +++ b/app/javascript/flavours/glitch/components/short_number.tsx @@ -48,7 +48,7 @@ const ShortNumberCounter: React.FC = ({ value }) => { const count = ( ); diff --git a/app/javascript/flavours/glitch/entrypoints/public.tsx b/app/javascript/flavours/glitch/entrypoints/public.tsx index ab453ef119..5a8d5e08cb 100644 --- a/app/javascript/flavours/glitch/entrypoints/public.tsx +++ b/app/javascript/flavours/glitch/entrypoints/public.tsx @@ -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, diff --git a/app/javascript/flavours/glitch/entrypoints/remote_interaction_helper.ts b/app/javascript/flavours/glitch/entrypoints/remote_interaction_helper.ts index 8cb476f483..3bfc1fc139 100644 --- a/app/javascript/flavours/glitch/entrypoints/remote_interaction_helper.ts +++ b/app/javascript/flavours/glitch/entrypoints/remote_interaction_helper.ts @@ -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}` }, diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts b/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts index ffca1f8b06..806a3f8927 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts +++ b/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.ts @@ -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] = { diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts b/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts index 191419496f..d116c6c62c 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts +++ b/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.ts @@ -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); }); diff --git a/app/javascript/flavours/glitch/features/status/components/card.jsx b/app/javascript/flavours/glitch/features/status/components/card.jsx index 4c535b0a46..c6094d0beb 100644 --- a/app/javascript/flavours/glitch/features/status/components/card.jsx +++ b/app/javascript/flavours/glitch/features/status/components/card.jsx @@ -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 }) => ( +
+ + + + + {author.get('display_name')} }} /> +
+); + +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 = (
@@ -136,7 +153,7 @@ export default class Card extends PureComponent { {card.get('title')} - {card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')}} + {!showAuthor && (card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')})}
); @@ -225,10 +242,14 @@ export default class Card extends PureComponent { } return ( - - {embed} - {description} - + <> + + {embed} + {description} + + + {showAuthor && } + ); } diff --git a/app/javascript/flavours/glitch/store/middlewares/sounds.ts b/app/javascript/flavours/glitch/store/middlewares/sounds.ts index ab04f6c94b..ced4d910bf 100644 --- a/app/javascript/flavours/glitch/store/middlewares/sounds.ts +++ b/app/javascript/flavours/glitch/store/middlewares/sounds.ts @@ -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); } } diff --git a/app/javascript/flavours/glitch/store/typed_functions.ts b/app/javascript/flavours/glitch/store/typed_functions.ts index 0392f373c0..dae37e6225 100644 --- a/app/javascript/flavours/glitch/store/typed_functions.ts +++ b/app/javascript/flavours/glitch/store/typed_functions.ts @@ -89,21 +89,17 @@ type OnData = ( }, ) => ReturnedData | DiscardLoadData | Promise; +type ArgsType = Record | undefined; + // Overload when there is no `onData` method, the payload is the `onData` result -export function createDataLoadingThunk< - LoadDataResult, - Args extends Record, ->( +export function createDataLoadingThunk( name: string, loadData: (args: Args) => Promise, thunkOptions?: AppThunkOptions, ): ReturnType>; // Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty -export function createDataLoadingThunk< - LoadDataResult, - Args extends Record, ->( +export function createDataLoadingThunk( name: string, loadData: (args: Args) => Promise, onDataOrThunkOptions?: @@ -113,10 +109,7 @@ export function createDataLoadingThunk< ): ReturnType>; // Overload when the `onData` method returns nothing, then the mayload is the `onData` result -export function createDataLoadingThunk< - LoadDataResult, - Args extends Record, ->( +export function createDataLoadingThunk( name: string, loadData: (args: Args) => Promise, onDataOrThunkOptions?: AppThunkOptions | OnData, @@ -126,7 +119,7 @@ export function createDataLoadingThunk< // Overload when there is an `onData` method returning something export function createDataLoadingThunk< LoadDataResult, - Args extends Record, + Args extends ArgsType, Returned, >( name: string, @@ -162,7 +155,7 @@ export function createDataLoadingThunk< */ export function createDataLoadingThunk< LoadDataResult, - Args extends Record, + Args extends ArgsType, Returned, >( name: string, diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index e6ac7d2b4a..23c07f6a7b 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -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; + } + } +} diff --git a/app/javascript/mastodon/components/animated_number.tsx b/app/javascript/mastodon/components/animated_number.tsx index e98e30b242..6c1e0aaec1 100644 --- a/app/javascript/mastodon/components/animated_number.tsx +++ b/app/javascript/mastodon/components/animated_number.tsx @@ -48,8 +48,9 @@ export const AnimatedNumber: React.FC = ({ value }) => { 0 ? 'absolute' : 'static', - transform: `translateY(${style.y * 100}%)`, + position: + direction * (style.y ?? 0) > 0 ? 'absolute' : 'static', + transform: `translateY(${(style.y ?? 0) * 100}%)`, }} > diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index ed5de7d3a5..1642ba6504 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -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; }); } diff --git a/app/javascript/mastodon/components/short_number.tsx b/app/javascript/mastodon/components/short_number.tsx index 74c3c5d75e..a0b523aaad 100644 --- a/app/javascript/mastodon/components/short_number.tsx +++ b/app/javascript/mastodon/components/short_number.tsx @@ -48,7 +48,7 @@ const ShortNumberCounter: React.FC = ({ value }) => { const count = ( ); diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts index ffca1f8b06..806a3f8927 100644 --- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts +++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts @@ -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] = { diff --git a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts index 191419496f..d116c6c62c 100644 --- a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts +++ b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts @@ -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); }); diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index f47861f663..c2f5703b3c 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -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 }) => ( +
+ + + + + {author.get('display_name')} }} /> +
+); + +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 = (
@@ -146,7 +164,7 @@ export default class Card extends PureComponent { {card.get('title')} - {card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')}} + {!showAuthor && (card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')})}
); @@ -235,10 +253,14 @@ export default class Card extends PureComponent { } return ( - - {embed} - {description} - + <> + + {embed} + {description} + + + {showAuthor && } + ); } diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index ea67d217da..68429b093c 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -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", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 56e4612c1c..63298d59e3 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -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", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 2d42b3e949..4c30bfa25f 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -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", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index bae714b1d2..6c2162e52c 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -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", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 98cc313948..0847b8bf0d 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -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", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 8ab5db1b17..f66497e0a7 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -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", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index b340261479..7cd74fa501 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -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": "리스트 삭제", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 0f42e97fcf..5fc7d32869 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -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.", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 3316e7af81..3711cc0ae3 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -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}.", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index b763f740a2..6f67e8f74f 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -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ę", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1a6de08359..3c8f3cf416 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -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.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 70903065da..c389d4f4fc 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -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", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 4cb81a760c..ccbface05a 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -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": "සංස්කරණය", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 7806abc6b5..a8cce3202c 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -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", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 102f1c3b4b..56b2f7e52c 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -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", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index ab6fe0bd70..0f8bcae6f8 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -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": "删除列表", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index f00d62c076..1d20034db8 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -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": "刪除列表", diff --git a/app/javascript/mastodon/store/middlewares/sounds.ts b/app/javascript/mastodon/store/middlewares/sounds.ts index 720ee163e9..91407b1ec0 100644 --- a/app/javascript/mastodon/store/middlewares/sounds.ts +++ b/app/javascript/mastodon/store/middlewares/sounds.ts @@ -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); } } diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index 0392f373c0..dae37e6225 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -89,21 +89,17 @@ type OnData = ( }, ) => ReturnedData | DiscardLoadData | Promise; +type ArgsType = Record | undefined; + // Overload when there is no `onData` method, the payload is the `onData` result -export function createDataLoadingThunk< - LoadDataResult, - Args extends Record, ->( +export function createDataLoadingThunk( name: string, loadData: (args: Args) => Promise, thunkOptions?: AppThunkOptions, ): ReturnType>; // Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty -export function createDataLoadingThunk< - LoadDataResult, - Args extends Record, ->( +export function createDataLoadingThunk( name: string, loadData: (args: Args) => Promise, onDataOrThunkOptions?: @@ -113,10 +109,7 @@ export function createDataLoadingThunk< ): ReturnType>; // Overload when the `onData` method returns nothing, then the mayload is the `onData` result -export function createDataLoadingThunk< - LoadDataResult, - Args extends Record, ->( +export function createDataLoadingThunk( name: string, loadData: (args: Args) => Promise, onDataOrThunkOptions?: AppThunkOptions | OnData, @@ -126,7 +119,7 @@ export function createDataLoadingThunk< // Overload when there is an `onData` method returning something export function createDataLoadingThunk< LoadDataResult, - Args extends Record, + Args extends ArgsType, Returned, >( name: string, @@ -162,7 +155,7 @@ export function createDataLoadingThunk< */ export function createDataLoadingThunk< LoadDataResult, - Args extends Record, + Args extends ArgsType, Returned, >( name: string, diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 3c684c2908..83ca177189 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -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; + } + } +} diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 420027cf42..40d13d13e6 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -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( diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index 07776c3699..2e49d3fb4f 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -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 diff --git a/app/lib/vacuum/applications_vacuum.rb b/app/lib/vacuum/applications_vacuum.rb deleted file mode 100644 index ba88655f16..0000000000 --- a/app/lib/vacuum/applications_vacuum.rb +++ /dev/null @@ -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 diff --git a/app/models/account.rb b/app/models/account.rb index 69ff3a6c8d..f2d042a38d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -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 diff --git a/app/models/account_suggestions/setting_source.rb b/app/models/account_suggestions/setting_source.rb index 9f3cd7bd3d..6143481723 100644 --- a/app/models/account_suggestions/setting_source.rb +++ b/app/models/account_suggestions/setting_source.rb @@ -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 diff --git a/app/models/concerns/account/finder_concern.rb b/app/models/concerns/account/finder_concern.rb index a7acff1cbb..249a7b5fd1 100644 --- a/app/models/concerns/account/finder_concern.rb +++ b/app/models/concerns/account/finder_concern.rb @@ -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 diff --git a/app/models/concerns/user/ldap_authenticable.rb b/app/models/concerns/user/ldap_authenticable.rb index c8e9fa9348..fc1ee78d09 100644 --- a/app/models/concerns/user/ldap_authenticable.rb +++ b/app/models/concerns/user/ldap_authenticable.rb @@ -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( diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 9fe02bd168..11fdd9d88a 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -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 diff --git a/app/models/status.rb b/app/models/status.rb index 8b429fce94..afa58837f3 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -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 diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index c09c84cf33..318b4bb868 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -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, }, diff --git a/app/serializers/rest/preview_card_serializer.rb b/app/serializers/rest/preview_card_serializer.rb index 039262cd5f..7d4c99c2d1 100644 --- a/app/serializers/rest/preview_card_serializer.rb +++ b/app/serializers/rest/preview_card_serializer.rb @@ -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 diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb index 651d848cbb..858a6984d7 100644 --- a/app/serializers/rest/v1/instance_serializer.rb +++ b/app/serializers/rest/v1/instance_serializer.rb @@ -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, }, diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index fb2b33114e..1dbed27f28 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -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 } diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 36e866b6ce..900cb9863d 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -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 diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index e56562c0a5..1f01c2d48e 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -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 diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 64bf28bdff..71e44c23a6 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -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?) diff --git a/app/services/report_service.rb b/app/services/report_service.rb index fe546c383e..dea6df7b0a 100644 --- a/app/services/report_service.rb +++ b/app/services/report_service.rb @@ -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))) diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index b354a1b607..70f5959f21 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -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?) diff --git a/app/validators/unique_username_validator.rb b/app/validators/unique_username_validator.rb index 09c8fadb55..c417e2f696 100644 --- a/app/validators/unique_username_validator.rb +++ b/app/validators/unique_username_validator.rb @@ -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? diff --git a/app/workers/scheduler/vacuum_scheduler.rb b/app/workers/scheduler/vacuum_scheduler.rb index 1c9a2aabe3..c22d6f5f80 100644 --- a/app/workers/scheduler/vacuum_scheduler.rb +++ b/app/workers/scheduler/vacuum_scheduler.rb @@ -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 diff --git a/config/application.rb b/config/application.rb index 07b50ca036..6d6e91a5cc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -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' diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index b54fc6cf0c..5b9365a530 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -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 diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 1768e24cce..f120fb4f0d 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -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'] diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 4fb81ca61a..4001eee32e 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -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: 削除 diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 8e32ed07bd..77fcf42866 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -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 %{domain} 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: "Webhook leidžia Mastodon siųsti realaus laiko pranešimus apie pasirinktus įvykius į tavo programą, kad programa galėtų automatiškai paleisti reakcijas." events: Įvykiai diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 94efdcb15a..d82c92c262 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -461,13 +461,13 @@ nn: title: Importer domeneblokkeringar no_file: Inga fil vald follow_recommendations: - description_html: "Følgjeforslag hjelper nye brukarar å raskt finna interessant innhald. 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: "Fylgjeforslag hjelper nye brukarar å finna interessant innhald raskt. 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. I tillegg kan dette gjera registreringsprosessen monaleg mindre tilgjengeleg (særleg for folk med nedsett funksjonsevne). 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 diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 6b80edb24e..8d3b53f777 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -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 diff --git a/config/locales/si.yml b/config/locales/si.yml index 0f714ee146..85e242b630 100644 --- a/config/locales/si.yml +++ b/config/locales/si.yml @@ -66,7 +66,7 @@ si: inbox_url: එන ලිපි URL invite_request_text: එක්වීමට හේතුව invited_by: විසින් ආරාධනා කරන ලදී - ip: අ.ජා. කෙ. (IP) + ip: අ.ජා.කෙ. (IP) joined: එක් වූ දිනය location: all: සියල්ල @@ -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: පරිශීලකයා පහත් කරන්න @@ -473,7 +473,7 @@ si: new: title: නව අ.ජා.කෙ. නීතියක් සාදන්න no_ip_block_selected: IP රීති කිසිවක් තෝරා නොගත් බැවින් වෙනස් කර නැත - title: අ.ජා. කෙ. (IP) නීති + title: අ.ජා.කෙ. (IP) නීති relationships: title: "%{acct}හි සබඳතා" relays: @@ -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: අභියාචනයක් ඉදිරිපත් කරන්න diff --git a/config/locales/simple_form.lt.yml b/config/locales/simple_form.lt.yml index 789121be42..feec37ae00 100644 --- a/config/locales/simple_form.lt.yml +++ b/config/locales/simple_form.lt.yml @@ -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. diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml index a93a803221..a200e1206e 100644 --- a/config/locales/simple_form.nn.yml +++ b/config/locales/simple_form.nn.yml @@ -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 diff --git a/config/locales/simple_form.si.yml b/config/locales/simple_form.si.yml index eb41d263bc..a81ba27bb9 100644 --- a/config/locales/simple_form.si.yml +++ b/config/locales/simple_form.si.yml @@ -190,7 +190,7 @@ si: text: ඔබට එක් වීමට අවශ්‍ය ඇයි? ip_block: comment: අදහස - ip: අ.ජා. කෙ. (IP) + ip: අ.ජා.කෙ. (IP) severities: no_access: ප්‍රවේශය අවහිර කරන්න sign_up_requires_approval: ලියාපදිංචි වීම සීමා කරන්න diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml index 1d69f5c579..11337f2f61 100644 --- a/config/locales/simple_form.uk.yml +++ b/config/locales/simple_form.uk.yml @@ -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: Якщо реєстрація вимагає власноручного затвердження, зробіть текстове поле «Чому ви хочете приєднатися?» обов'язковим, а не додатковим diff --git a/config/locales/th.yml b/config/locales/th.yml index 3ca4f09733..bafcd30dee 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -934,6 +934,7 @@ th: delete: ลบ edit_preset: แก้ไขคำเตือนที่ตั้งไว้ล่วงหน้า empty: คุณยังไม่ได้กำหนดคำเตือนที่ตั้งไว้ล่วงหน้าใด ๆ + title: คำเตือนที่ตั้งไว้ล่วงหน้า webhooks: add_new: เพิ่มปลายทาง delete: ลบ diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 5baaa93870..c4f4a26380 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -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: Видалити diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 4265c1a33a..459d1bb0d9 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -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ỏ diff --git a/db/migrate/20240522041528_add_author_account_id_to_preview_cards.rb b/db/migrate/20240522041528_add_author_account_id_to_preview_cards.rb new file mode 100644 index 0000000000..a6e7a883da --- /dev/null +++ b/db/migrate/20240522041528_add_author_account_id_to_preview_cards.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index bb527dfc18..08b526b4d6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 3f2336f1d9..e7ae95ea7a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/lib/action_dispatch/remote_ip_extensions.rb b/lib/action_dispatch/remote_ip_extensions.rb new file mode 100644 index 0000000000..e5c48bf3c5 --- /dev/null +++ b/lib/action_dispatch/remote_ip_extensions.rb @@ -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 diff --git a/lib/mastodon/sidekiq_middleware.rb b/lib/mastodon/sidekiq_middleware.rb index 3a747afb63..c5f4d8da35 100644 --- a/lib/mastodon/sidekiq_middleware.rb +++ b/lib/mastodon/sidekiq_middleware.rb @@ -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 diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index fd091451ae..4c23afc252 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -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 diff --git a/package.json b/package.json index 71020e897d..793fe49e42 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/spec/config/initializers/rack/attack_spec.rb b/spec/config/initializers/rack/attack_spec.rb index e25b7dfde9..19de480898 100644 --- a/spec/config/initializers/rack/attack_spec.rb +++ b/spec/config/initializers/rack/attack_spec.rb @@ -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 diff --git a/spec/lib/vacuum/applications_vacuum_spec.rb b/spec/lib/vacuum/applications_vacuum_spec.rb deleted file mode 100644 index df5c860602..0000000000 --- a/spec/lib/vacuum/applications_vacuum_spec.rb +++ /dev/null @@ -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 diff --git a/spec/requests/api/v1/accounts_spec.rb b/spec/requests/api/v1/accounts_spec.rb index 55f8e1c6fa..3d9eb65019 100644 --- a/spec/requests/api/v1/accounts_spec.rb +++ b/spec/requests/api/v1/accounts_spec.rb @@ -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( diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 0b2d1f90cf..694861fb1c 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -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( diff --git a/spec/requests/api/v2/instance_spec.rb b/spec/requests/api/v2/instance_spec.rb index ba702359d4..064f92990a 100644 --- a/spec/requests/api/v2/instance_spec.rb +++ b/spec/requests/api/v2/instance_spec.rb @@ -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 diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 514f634d7f..6064d2b050 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -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 diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 18891bf118..11bf4c30ea 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -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, diff --git a/spec/validators/status_pin_validator_spec.rb b/spec/validators/status_pin_validator_spec.rb index d5109f990f..e50a952db8 100644 --- a/spec/validators/status_pin_validator_spec.rb +++ b/spec/validators/status_pin_validator_spec.rb @@ -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 diff --git a/tsconfig.json b/tsconfig.json index 2a63b07487..8ba4a6ede6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,8 @@ "allowJs": true, "noEmit": true, "strict": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, "esModuleInterop": true, "skipLibCheck": true, "baseUrl": "./", diff --git a/yarn.lock b/yarn.lock index fdde7727ac..3d2d371328 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"