diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f991036add..b3b1d97a24 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -4,10 +4,6 @@ FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye # Install Rails # RUN gem install rails webdrivers -# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service -# The value is a comma-separated list of allowed domains -ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev,.preview.app.github.dev,.app.github.dev" - ARG NODE_VERSION="16" RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1" diff --git a/.devcontainer/codespaces/devcontainer.json b/.devcontainer/codespaces/devcontainer.json new file mode 100644 index 0000000000..ca9156fdaa --- /dev/null +++ b/.devcontainer/codespaces/devcontainer.json @@ -0,0 +1,49 @@ +{ + "name": "Mastodon on GitHub Codespaces", + "dockerComposeFile": "../docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + + "features": { + "ghcr.io/devcontainers/features/sshd:1": {} + }, + + "runServices": ["app", "db", "redis"], + + "forwardPorts": [3000, 4000], + + "portsAttributes": { + "3000": { + "label": "web", + "onAutoForward": "notify" + }, + "4000": { + "label": "stream", + "onAutoForward": "silent" + } + }, + + "otherPortsAttributes": { + "onAutoForward": "silent" + }, + + "remoteEnv": { + "LOCAL_DOMAIN": "${localEnv:CODESPACE_NAME}-3000.app.github.dev", + "LOCAL_HTTPS": "true", + "STREAMING_API_BASE_URL": "https://${localEnv:CODESPACE_NAME}-4000.app.github.dev", + "DISABLE_FORGERY_REQUEST_PROTECTION": "true", + "ES_ENABLED": "", + "LIBRE_TRANSLATE_ENDPOINT": "" + }, + + "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", + "postCreateCommand": ".devcontainer/post-create.sh", + "waitFor": "postCreateCommand", + + "customizations": { + "vscode": { + "settings": {}, + "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ce14169aae..fa8d6542c1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "name": "Mastodon", + "name": "Mastodon on local machine", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", @@ -8,13 +8,23 @@ "ghcr.io/devcontainers/features/sshd:1": {} }, - "runServices": ["app", "db", "redis"], - "forwardPorts": [3000, 4000], - "containerEnv": { - "ES_ENABLED": "", - "LIBRE_TRANSLATE_ENDPOINT": "" + "portsAttributes": { + "3000": { + "label": "web", + "onAutoForward": "notify", + "requireLocalPort": true + }, + "4000": { + "label": "stream", + "onAutoForward": "silent", + "requireLocalPort": true + } + }, + + "otherPortsAttributes": { + "onAutoForward": "silent" }, "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index a2658ea8ba..20aecd71d6 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -25,6 +25,7 @@ services: command: sleep infinity ports: - '127.0.0.1:3000:3000' + - '127.0.0.1:3035:3035' - '127.0.0.1:4000:4000' networks: - external_network diff --git a/.github/workflows/build-container-image.yml b/.github/workflows/build-container-image.yml index 8e9c747664..a1aeddf201 100644 --- a/.github/workflows/build-container-image.yml +++ b/.github/workflows/build-container-image.yml @@ -76,8 +76,6 @@ jobs: if: ${{ inputs.push_to_images != '' }} with: images: ${{ inputs.push_to_images }} - # Only tag with latest when ran against the latest stable branch - # This needs to be updated after each minor version release flavor: ${{ inputs.flavor }} tags: ${{ inputs.tags }} labels: ${{ inputs.labels }} @@ -85,7 +83,9 @@ jobs: - uses: docker/build-push-action@v4 with: context: . - build-args: MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }} MASTODON_VERSION_METADATA=${{ inputs.version_metadata }} + build-args: | + MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }} + MASTODON_VERSION_METADATA=${{ inputs.version_metadata }} platforms: ${{ inputs.platforms }} provenance: false builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }} diff --git a/.github/workflows/build-releases.yml b/.github/workflows/build-releases.yml index b408174688..f739a69d9a 100644 --- a/.github/workflows/build-releases.yml +++ b/.github/workflows/build-releases.yml @@ -17,6 +17,8 @@ jobs: push_to_images: | tootsuite/mastodon ghcr.io/mastodon/mastodon + # Only tag with latest when ran against the latest stable branch + # This needs to be updated after each minor version release flavor: | latest=${{ startsWith(github.ref, 'refs/tags/v4.1.') }} tags: | diff --git a/Gemfile.lock b/Gemfile.lock index 87d52bf7f5..09226e474b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,7 +109,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.4) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) airbrussh (1.4.1) @@ -124,8 +124,8 @@ GEM attr_required (1.0.1) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.793.0) - aws-sdk-core (3.180.3) + aws-partitions (1.809.0) + aws-sdk-core (3.181.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) @@ -133,8 +133,8 @@ GEM aws-sdk-kms (1.71.0) aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.132.1) - aws-sdk-core (~> 3, >= 3.179.0) + aws-sdk-s3 (1.133.0) + aws-sdk-core (~> 3, >= 3.181.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) aws-sigv4 (1.6.0) @@ -203,7 +203,7 @@ GEM activesupport cbor (0.5.9.6) charlock_holmes (0.7.7) - chewy (7.3.3) + chewy (7.3.4) activesupport (>= 5.2) elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch-dsl @@ -324,7 +324,7 @@ GEM ruby-progressbar (~> 1.4) globalid (1.1.0) activesupport (>= 5.0) - haml (6.1.1) + haml (6.1.2) temple (>= 0.8.2) thor tilt @@ -333,7 +333,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.49.3) + haml_lint (0.50.0) haml (>= 4.0, < 6.2) parallel (~> 1.10) rainbow @@ -482,7 +482,7 @@ GEM nokogiri (1.15.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) - oj (3.16.0) + oj (3.16.1) omniauth (2.1.1) hashie (>= 3.4.6) rack (>= 2.2.3) @@ -519,7 +519,7 @@ GEM parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) - pg (1.5.3) + pg (1.5.4) pghero (3.3.3) activerecord (>= 6) posix-spawn (0.3.15) @@ -733,7 +733,7 @@ GEM net-ssh (>= 2.8.0) stackprof (0.2.25) statsd-ruby (1.5.0) - stoplight (3.0.1) + stoplight (3.0.2) redlock (~> 1.0) strong_migrations (0.8.0) activerecord (>= 5.2) @@ -797,7 +797,7 @@ GEM webfinger (1.2.0) activesupport httpclient (>= 2.4) - webmock (3.18.1) + webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/Procfile.dev b/Procfile.dev index ba04fb661b..fbb2c2de23 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,4 +1,4 @@ web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq stream: env PORT=4000 yarn run start -webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0 +webpack: bin/webpack-dev-server diff --git a/app/chewy/accounts_index.rb b/app/chewy/accounts_index.rb index 0b48777054..c854cdf01c 100644 --- a/app/chewy/accounts_index.rb +++ b/app/chewy/accounts_index.rb @@ -31,12 +31,13 @@ class AccountsIndex < Chewy::Index analyzer: { natural: { - tokenizer: 'uax_url_email', + tokenizer: 'standard', filter: %w( - english_possessive_stemmer lowercase asciifolding cjk_width + elision + english_possessive_stemmer english_stop english_stemmer ), diff --git a/app/chewy/public_statuses_index.rb b/app/chewy/public_statuses_index.rb index abc4738398..29726d3db3 100644 --- a/app/chewy/public_statuses_index.rb +++ b/app/chewy/public_statuses_index.rb @@ -20,13 +20,19 @@ class PublicStatusesIndex < Chewy::Index }, analyzer: { - content: { + verbatim: { tokenizer: 'uax_url_email', + filter: %w(lowercase), + }, + + content: { + tokenizer: 'standard', filter: %w( - english_possessive_stemmer lowercase asciifolding cjk_width + elision + english_possessive_stemmer english_stop english_stemmer ), @@ -74,13 +80,20 @@ class PublicStatusesIndex < Chewy::Index english_stemmer ), }, + sudachi_analyzer: { + tokenizer: 'sudachi_tokenizer', + type: 'custom', filter: %w( + english_possessive_stemmer + lowercase + asciifolding + cjk_width + english_stop + english_stemmer my_posfilter sudachi_normalizedform ), - type: 'custom', - tokenizer: 'sudachi_tokenizer', }, }, tokenizer: { @@ -101,7 +114,7 @@ class PublicStatusesIndex < Chewy::Index .includes(:media_attachments, :preloadable_poll, :preview_cards) root date_detection: false do - field(:id, type: 'keyword') + field(:id, type: 'long') field(:account_id, type: 'long') field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'content') } field(:language, type: 'keyword') diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb index 47ee737c12..ca119e3779 100644 --- a/app/chewy/statuses_index.rb +++ b/app/chewy/statuses_index.rb @@ -19,13 +19,19 @@ class StatusesIndex < Chewy::Index }, }, analyzer: { - content: { + verbatim: { tokenizer: 'uax_url_email', + filter: %w(lowercase), + }, + + content: { + tokenizer: 'standard', filter: %w( - english_possessive_stemmer lowercase asciifolding cjk_width + elision + english_possessive_stemmer english_stop english_stemmer ), @@ -61,6 +67,11 @@ class StatusesIndex < Chewy::Index }, }, analyzer: { + verbatim: { + tokenizer: 'uax_url_email', + filter: %w(lowercase), + }, + content: { tokenizer: 'uax_url_email', filter: %w( @@ -72,13 +83,20 @@ class StatusesIndex < Chewy::Index english_stemmer ), }, + sudachi_analyzer: { + tokenizer: 'sudachi_tokenizer', + type: 'custom', filter: %w( + english_possessive_stemmer + lowercase + asciifolding + cjk_width + english_stop + english_stemmer my_posfilter sudachi_normalizedform ), - type: 'custom', - tokenizer: 'sudachi_tokenizer', }, }, tokenizer: { @@ -93,50 +111,30 @@ class StatusesIndex < Chewy::Index settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: PRODUCTION_SETTINGS - # We do not use delete_if option here because it would call a method that we - # expect to be called with crutches without crutches, causing n+1 queries - index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll, :preview_cards) - - crutch :mentions do |collection| - data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local, silent: false).pluck(:status_id, :account_id) - data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } - end - - crutch :favourites do |collection| - data = ::Favourite.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id) - data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } - end - - crutch :emoji_reactions do |collection| - data = ::EmojiReaction.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id) - data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } - end - - crutch :status_references do |collection| - data = ::StatusReference.joins(:status).where(target_status_id: collection.map(&:id), status: { account: Account.local }).pluck(:target_status_id, :account_id) - data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } - end - - crutch :reblogs do |collection| - data = ::Status.where(reblog_of_id: collection.map(&:id)).where(account: Account.local).pluck(:reblog_of_id, :account_id) - data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } - end - - crutch :bookmarks do |collection| - data = ::Bookmark.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id) - data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } - end - - crutch :votes do |collection| - data = ::PollVote.joins(:poll).where(poll: { status_id: collection.map(&:id) }).where(account: Account.local).pluck(:status_id, :account_id) - data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } - end + index_scope ::Status.unscoped.kept.without_reblogs.includes( + :media_attachments, + :preview_cards, + :local_mentioned, + :local_favorited, + :local_reblogged, + :local_bookmarked, + :local_emoji_reacted, + :local_referenced, + preloadable_poll: :local_voters + ), + delete_if: lambda { |status| + if status.searchability == 'direct' + status.searchable_by.empty? + else + status.searchability == 'limited' ? status.domain.present? : false + end + } root date_detection: false do - field(:id, type: 'keyword') + field(:id, type: 'long') field(:account_id, type: 'long') - field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'content') } - field(:searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }) + field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'sudachi_analyzer') } + field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by }) field(:searchability, type: 'keyword', value: ->(status) { status.compute_searchability }) field(:language, type: 'keyword') field(:domain, type: 'keyword', value: ->(status) { status.account.domain || '' }) diff --git a/app/controllers/api/v1/statuses/translations_controller.rb b/app/controllers/api/v1/statuses/translations_controller.rb index 540b17d009..ec5ea5b85b 100644 --- a/app/controllers/api/v1/statuses/translations_controller.rb +++ b/app/controllers/api/v1/statuses/translations_controller.rb @@ -8,7 +8,15 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController before_action :set_translation rescue_from TranslationService::NotConfiguredError, with: :not_found - rescue_from TranslationService::UnexpectedResponseError, TranslationService::QuotaExceededError, TranslationService::TooManyRequestsError, with: :service_unavailable + rescue_from TranslationService::UnexpectedResponseError, with: :service_unavailable + + rescue_from TranslationService::QuotaExceededError do + render json: { error: I18n.t('translation.errors.quota_exceeded') }, status: 503 + end + + rescue_from TranslationService::TooManyRequestsError do + render json: { error: I18n.t('translation.errors.too_many_requests') }, status: 503 + end def create render json: @translation, serializer: REST::TranslationSerializer diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index 9cd7b99046..a79d65c124 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Api::V1::Timelines::TagController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth? before_action :load_tag after_action :insert_pagination_headers, unless: -> { @statuses.empty? } @@ -12,6 +13,10 @@ class Api::V1::Timelines::TagController < Api::BaseController private + def require_auth? + !Setting.timeline_preview + end + def load_tag @tag = Tag.find_normalized(params[:id]) end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 1d27c92c8c..b0c4fff8bc 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -119,6 +119,8 @@ module SignatureVerification private def fail_with!(message, **options) + Rails.logger.warn { "Signature verification failed: #{message}" } + @signature_verification_failure_reason = { error: message }.merge(options) @signed_request_actor = nil end diff --git a/app/controllers/settings/privacy_extra_controller.rb b/app/controllers/settings/privacy_extra_controller.rb new file mode 100644 index 0000000000..54cedf2c4b --- /dev/null +++ b/app/controllers/settings/privacy_extra_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Settings::PrivacyExtraController < Settings::BaseController + before_action :set_account + + def show; end + + def update + if UpdateAccountService.new.call(@account, account_params.except(:settings)) + current_user.update!(settings_attributes: account_params[:settings]) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) + redirect_to settings_privacy_extra_path, notice: I18n.t('generic.changes_saved_msg') + else + render :show + end + end + + private + + def account_params + params.require(:account).permit(settings: UserSettings.keys) + end + + def set_account + @account = current_account + end +end diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index e7841e178a..a5f6729f41 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -1,11 +1,16 @@ -import api from '../api'; +import api, { getLinks } from '../api'; +import { fetchRelationships } from './accounts'; import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer'; export const REBLOG_REQUEST = 'REBLOG_REQUEST'; export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; export const REBLOG_FAIL = 'REBLOG_FAIL'; +export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST'; +export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS'; +export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL'; + export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST'; export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; export const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; @@ -34,6 +39,10 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST'; export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; +export const FAVOURITES_EXPAND_REQUEST = 'FAVOURITES_EXPAND_REQUEST'; +export const FAVOURITES_EXPAND_SUCCESS = 'FAVOURITES_EXPAND_SUCCESS'; +export const FAVOURITES_EXPAND_FAIL = 'FAVOURITES_EXPAND_FAIL'; + export const STATUS_REFERENCES_FETCH_REQUEST = 'STATUS_REFERENCES_FETCH_REQUEST'; export const STATUS_REFERENCES_FETCH_SUCCESS = 'STATUS_REFERENCES_FETCH_SUCCESS'; export const STATUS_REFERENCES_FETCH_FAIL = 'STATUS_REFERENCES_FETCH_FAIL'; @@ -374,8 +383,10 @@ export function fetchReblogs(id) { dispatch(fetchReblogsRequest(id)); api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); - dispatch(fetchReblogsSuccess(id, response.data)); + dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => { dispatch(fetchReblogsFail(id, error)); }); @@ -389,17 +400,62 @@ export function fetchReblogsRequest(id) { }; } -export function fetchReblogsSuccess(id, accounts) { +export function fetchReblogsSuccess(id, accounts, next) { return { type: REBLOGS_FETCH_SUCCESS, id, accounts, + next, }; } export function fetchReblogsFail(id, error) { return { type: REBLOGS_FETCH_FAIL, + id, + error, + }; +} + +export function expandReblogs(id) { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'reblogged_by', id, 'next']); + if (url === null) { + return; + } + + dispatch(expandReblogsRequest(id)); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(importFetchedAccounts(response.data)); + dispatch(expandReblogsSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => dispatch(expandReblogsFail(id, error))); + }; +} + +export function expandReblogsRequest(id) { + return { + type: REBLOGS_EXPAND_REQUEST, + id, + }; +} + +export function expandReblogsSuccess(id, accounts, next) { + return { + type: REBLOGS_EXPAND_SUCCESS, + id, + accounts, + next, + }; +} + +export function expandReblogsFail(id, error) { + return { + type: REBLOGS_EXPAND_FAIL, + id, error, }; } @@ -409,8 +465,10 @@ export function fetchFavourites(id) { dispatch(fetchFavouritesRequest(id)); api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); - dispatch(fetchFavouritesSuccess(id, response.data)); + dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); }).catch(error => { dispatch(fetchFavouritesFail(id, error)); }); @@ -424,17 +482,62 @@ export function fetchFavouritesRequest(id) { }; } -export function fetchFavouritesSuccess(id, accounts) { +export function fetchFavouritesSuccess(id, accounts, next) { return { type: FAVOURITES_FETCH_SUCCESS, id, accounts, + next, }; } export function fetchFavouritesFail(id, error) { return { type: FAVOURITES_FETCH_FAIL, + id, + error, + }; +} + +export function expandFavourites(id) { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'favourited_by', id, 'next']); + if (url === null) { + return; + } + + dispatch(expandFavouritesRequest(id)); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(importFetchedAccounts(response.data)); + dispatch(expandFavouritesSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => dispatch(expandFavouritesFail(id, error))); + }; +} + +export function expandFavouritesRequest(id) { + return { + type: FAVOURITES_EXPAND_REQUEST, + id, + }; +} + +export function expandFavouritesSuccess(id, accounts, next) { + return { + type: FAVOURITES_EXPAND_SUCCESS, + id, + accounts, + next, + }; +} + +export function expandFavouritesFail(id, error) { + return { + type: FAVOURITES_EXPAND_FAIL, + id, error, }; } diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index 674c481b81..d45a6e20eb 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -10,8 +10,8 @@ import { groupBy, minBy } from 'lodash'; import { getStatusContent } from './status_content'; -// About two lines on desktop -const VISIBLE_HASHTAGS = 7; +// Fit on a single line on desktop +const VISIBLE_HASHTAGS = 3; // Those types are not correct, they need to be replaced once this part of the state is typed export type TagLike = Record<{ name: string }>; @@ -210,7 +210,7 @@ const HashtagBar: React.FC<{ const revealedHashtags = expanded ? hashtags - : hashtags.slice(0, VISIBLE_HASHTAGS - 1); + : hashtags.slice(0, VISIBLE_HASHTAGS); return (
diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index f4ae93f80d..55447a6197 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -241,7 +241,12 @@ class StatusContent extends PureComponent { const renderReadMore = this.props.onClick && status.get('collapsed'); const contentLocale = intl.locale.replace(/[_-].*/, ''); const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); - const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && status.get('language') && targetLanguages?.includes(contentLocale); + const renderTranslate = this.props.onTranslate && + this.context.identity.signedIn && + (['public', 'unlisted'].includes(status.get('visibility')) || status.getIn(['account', 'other_settings', 'translatable_private'])) && + status.get('search_index').trim().length > 0 && + status.get('language') && + targetLanguages?.includes(contentLocale); const content = { __html: statusContent ?? getStatusContent(status) }; const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; diff --git a/app/javascript/mastodon/features/antennas/index.jsx b/app/javascript/mastodon/features/antennas/index.jsx index bac6105a50..a575527fce 100644 --- a/app/javascript/mastodon/features/antennas/index.jsx +++ b/app/javascript/mastodon/features/antennas/index.jsx @@ -12,7 +12,6 @@ import { createSelector } from 'reselect'; import { fetchAntennas } from 'mastodon/actions/antennas'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; -import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import ScrollableList from 'mastodon/components/scrollable_list'; import ColumnLink from 'mastodon/features/ui/components/column_link'; @@ -23,6 +22,8 @@ import NewAntennaForm from './components/new_antenna_form'; const messages = defineMessages({ heading: { id: 'column.antennas', defaultMessage: 'Antennas' }, subheading: { id: 'antennas.subheading', defaultMessage: 'Your antennas' }, + insert_list: { id: 'antennas.insert_list', defaultMessage: 'List' }, + insert_home: { id: 'antennas.insert_home', defaultMessage: 'Home' }, }); const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => { @@ -77,14 +78,8 @@ class Antennas extends ImmutablePureComponent { bindToDocument={!multiColumn} > {antennas.map(antenna => ( - - - {antenna.get('accounts_count')} - {antenna.get('domains_count')} - {antenna.get('tags_count')} - {antenna.get('keywords_count')} - - + ))} diff --git a/app/javascript/mastodon/features/bookmark_category_adder/components/bookmark_category.jsx b/app/javascript/mastodon/features/bookmark_category_adder/components/bookmark_category.jsx index f9fdb6b8e6..b28c58f303 100644 --- a/app/javascript/mastodon/features/bookmark_category_adder/components/bookmark_category.jsx +++ b/app/javascript/mastodon/features/bookmark_category_adder/components/bookmark_category.jsx @@ -55,7 +55,7 @@ class BookmarkCategory extends ImmutablePureComponent {
- + {bookmarkCategory.get('title')}
diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx index 682f8d3c8c..5ae6ee1090 100644 --- a/app/javascript/mastodon/features/compose/components/search.jsx +++ b/app/javascript/mastodon/features/compose/components/search.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl'; import classNames from 'classnames'; @@ -45,6 +45,17 @@ class Search extends PureComponent { options: [], }; + defaultOptions = [ + { label: <>has: , action: e => { e.preventDefault(); this._insertText('has:') } }, + { label: <>is: , action: e => { e.preventDefault(); this._insertText('is:') } }, + { label: <>language: , action: e => { e.preventDefault(); this._insertText('language:') } }, + { label: <>from: , action: e => { e.preventDefault(); this._insertText('from:') } }, + { label: <>domain: , action: e => { e.preventDefault(); this._insertText('domain:') } }, + { label: <>before: , action: e => { e.preventDefault(); this._insertText('before:') } }, + { label: <>during: , action: e => { e.preventDefault(); this._insertText('during:') } }, + { label: <>after: , action: e => { e.preventDefault(); this._insertText('after:') } }, + ]; + setRef = c => { this.searchForm = c; }; @@ -70,7 +81,7 @@ class Search extends PureComponent { handleKeyDown = (e) => { const { selectedOption } = this.state; - const options = this._getOptions(); + const options = this._getOptions().concat(this.defaultOptions); switch(e.key) { case 'Escape': @@ -100,11 +111,9 @@ class Search extends PureComponent { if (selectedOption === -1) { this._submit(); } else if (options.length > 0) { - options[selectedOption].action(); + options[selectedOption].action(e); } - this._unfocus(); - break; case 'Delete': if (selectedOption > -1 && options.length > 0) { @@ -147,6 +156,7 @@ class Search extends PureComponent { router.history.push(`/tags/${query}`); onClickSearchResult(query, 'hashtag'); + this._unfocus(); }; handleAccountClick = () => { @@ -157,6 +167,7 @@ class Search extends PureComponent { router.history.push(`/@${query}`); onClickSearchResult(query, 'account'); + this._unfocus(); }; handleURLClick = () => { @@ -164,6 +175,7 @@ class Search extends PureComponent { const { value, onOpenURL } = this.props; onOpenURL(value, router.history); + this._unfocus(); }; handleStatusSearch = () => { @@ -182,6 +194,8 @@ class Search extends PureComponent { } else if (search.get('type') === 'hashtag') { router.history.push(`/tags/${search.get('q')}`); } + + this._unfocus(); }; handleForgetRecentSearchClick = search => { @@ -194,6 +208,18 @@ class Search extends PureComponent { document.querySelector('.ui').parentElement.focus(); } + _insertText (text) { + const { value, onChange } = this.props; + + if (value === '') { + onChange(text); + } else if (value[value.length - 1] === ' ') { + onChange(`${value}${text}`); + } else { + onChange(`${value} ${text}`); + } + } + _submit (type) { const { onSubmit, openInRoute } = this.props; const { router } = this.context; @@ -203,6 +229,8 @@ class Search extends PureComponent { if (openInRoute) { router.history.push('/search'); } + + this._unfocus(); } _getOptions () { @@ -325,6 +353,16 @@ class Search extends PureComponent {
)} + +

+ +
+ {this.defaultOptions.map(({ key, label, action }, i) => ( + + ))} +
); diff --git a/app/javascript/mastodon/features/favourites/index.jsx b/app/javascript/mastodon/features/favourites/index.jsx index bfde78708e..b8ba948728 100644 --- a/app/javascript/mastodon/features/favourites/index.jsx +++ b/app/javascript/mastodon/features/favourites/index.jsx @@ -8,7 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { fetchFavourites } from 'mastodon/actions/interactions'; +import { debounce } from 'lodash'; + +import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions'; import ColumnHeader from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; @@ -21,7 +23,9 @@ const messages = defineMessages({ }); const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]), + accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'items']), + hasMore: !!state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'next']), + isLoading: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'isLoading'], true), }); class Favourites extends ImmutablePureComponent { @@ -30,6 +34,8 @@ class Favourites extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, + hasMore: PropTypes.bool, + isLoading: PropTypes.bool, multiColumn: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -40,18 +46,16 @@ class Favourites extends ImmutablePureComponent { } } - UNSAFE_componentWillReceiveProps (nextProps) { - if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchFavourites(nextProps.params.statusId)); - } - } - handleRefresh = () => { this.props.dispatch(fetchFavourites(this.props.params.statusId)); }; + handleLoadMore = debounce(() => { + this.props.dispatch(expandFavourites(this.props.params.statusId)); + }, 300, { leading: true }); + render () { - const { intl, accountIds, multiColumn } = this.props; + const { intl, accountIds, hasMore, isLoading, multiColumn } = this.props; if (!accountIds) { return ( @@ -75,6 +79,9 @@ class Favourites extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/reblogs/index.jsx b/app/javascript/mastodon/features/reblogs/index.jsx index 8bcef863f2..0c4e6dbb93 100644 --- a/app/javascript/mastodon/features/reblogs/index.jsx +++ b/app/javascript/mastodon/features/reblogs/index.jsx @@ -8,9 +8,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; +import { debounce } from 'lodash'; + import { Icon } from 'mastodon/components/icon'; -import { fetchReblogs } from '../../actions/interactions'; +import { fetchReblogs, expandReblogs } from '../../actions/interactions'; import ColumnHeader from '../../components/column_header'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; @@ -22,7 +24,9 @@ const messages = defineMessages({ }); const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]), + accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'items']), + hasMore: !!state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'next']), + isLoading: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'isLoading'], true), }); class Reblogs extends ImmutablePureComponent { @@ -31,6 +35,8 @@ class Reblogs extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, + hasMore: PropTypes.bool, + isLoading: PropTypes.bool, multiColumn: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -39,20 +45,18 @@ class Reblogs extends ImmutablePureComponent { if (!this.props.accountIds) { this.props.dispatch(fetchReblogs(this.props.params.statusId)); } - } - - UNSAFE_componentWillReceiveProps(nextProps) { - if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchReblogs(nextProps.params.statusId)); - } - } + }; handleRefresh = () => { this.props.dispatch(fetchReblogs(this.props.params.statusId)); }; + handleLoadMore = debounce(() => { + this.props.dispatch(expandReblogs(this.props.params.statusId)); + }, 300, { leading: true }); + render () { - const { intl, accountIds, multiColumn } = this.props; + const { intl, accountIds, hasMore, isLoading, multiColumn } = this.props; if (!accountIds) { return ( @@ -76,6 +80,9 @@ class Reblogs extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index c5980461e9..3047d1e1c8 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -23,11 +23,13 @@ const messages = defineMessages({ mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, + cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favorite' }, bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, + bookmark_category: { id: 'status.bookmark_category', defaultMessage: 'Bookmark category' }, more: { id: 'status.more', defaultMessage: 'More' }, mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, @@ -114,6 +116,10 @@ class ActionBar extends PureComponent { } }; + handleBookmarkCategoryAdderClick = () => { + this.props.onBookmarkCategoryAdder(this.props.status); + }; + handleDeleteClick = () => { this.props.onDelete(this.props.status, this.context.router.history); }; @@ -237,11 +243,12 @@ class ActionBar extends PureComponent { if (signedIn) { menu.push(null); - menu.push({ text: intl.formatMessage(messages.reblog), action: this.handleReblogForceModalClick }); + menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogForceModalClick }); if (publicStatus) { menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference }); } + menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick }); if (writtenByMe) { if (pinnableStatus) { diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index 224d5d2bb7..74110966c5 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -34,6 +34,7 @@ const messages = defineMessages({ about: { id: 'navigation_bar.about', defaultMessage: 'About' }, search: { id: 'navigation_bar.search', defaultMessage: 'Search' }, advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' }, + openedInClassicInterface: { id: 'navigation_bar.opened_in_classic_interface', defaultMessage: 'Posts, accounts, and other specific pages are opened by default in the classic web interface.' }, }); class NavigationPanel extends Component { @@ -70,12 +71,17 @@ class NavigationPanel extends Component {
- {transientSingleColumn && ( - - {intl.formatMessage(messages.advancedInterface)} - + {transientSingleColumn ? ( +
+ {intl.formatMessage(messages.openedInClassicInterface)} + {" "} + + {intl.formatMessage(messages.advancedInterface)} + +
+ ) : ( +
)} -
{signedIn && ( diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index ff070f7329..1dbcf52a20 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -421,6 +421,7 @@ "navigation_bar.lists": "Lists", "navigation_bar.logout": "Logout", "navigation_bar.mutes": "Muted users", + "navigation_bar.opened_in_classic_interface": "Posts, accounts, and other specific pages are opened by default in the classic web interface.", "navigation_bar.personal": "Personal", "navigation_bar.pins": "Pinned posts", "navigation_bar.preferences": "Preferences", @@ -617,8 +618,12 @@ "searchability.public.short": "Public", "searchability.unlisted.long": "Your followers and reactionners can find", "searchability.unlisted.short": "Followers and reactionners", + "search_popout.language_code": "ISO language code", + "search_popout.options": "Search options", "search_popout.quick_actions": "Quick actions", "search_popout.recent": "Recent searches", + "search_popout.specific_date": "specific date", + "search_popout.user": "user", "search_results.accounts": "Profiles", "search_results.all": "All", "search_results.hashtags": "Hashtags", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 2bef3bb4b3..116ed66d03 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -409,6 +409,7 @@ "navigation_bar.lists": "Listes", "navigation_bar.logout": "Déconnexion", "navigation_bar.mutes": "Comptes masqués", + "navigation_bar.opened_in_classic_interface": "Les messages, les comptes et les pages spécifiques sont ouvertes dans l’interface classique.", "navigation_bar.personal": "Personnel", "navigation_bar.pins": "Messages épinglés", "navigation_bar.preferences": "Préférences", diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index 99d91dbc77..edc83bd3d4 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -45,8 +45,18 @@ import { BLOCKS_EXPAND_FAIL, } from '../actions/blocks'; import { + REBLOGS_FETCH_REQUEST, REBLOGS_FETCH_SUCCESS, + REBLOGS_FETCH_FAIL, + REBLOGS_EXPAND_REQUEST, + REBLOGS_EXPAND_SUCCESS, + REBLOGS_EXPAND_FAIL, + FAVOURITES_FETCH_REQUEST, FAVOURITES_FETCH_SUCCESS, + FAVOURITES_FETCH_FAIL, + FAVOURITES_EXPAND_REQUEST, + FAVOURITES_EXPAND_SUCCESS, + FAVOURITES_EXPAND_FAIL, EMOJI_REACTIONS_FETCH_SUCCESS, STATUS_REFERENCES_FETCH_SUCCESS, } from '../actions/interactions'; @@ -138,9 +148,25 @@ export default function userLists(state = initialState, action) { case FOLLOWING_EXPAND_FAIL: return state.setIn(['following', action.id, 'isLoading'], false); case REBLOGS_FETCH_SUCCESS: - return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id))); + return normalizeList(state, ['reblogged_by', action.id], action.accounts, action.next); + case REBLOGS_EXPAND_SUCCESS: + return appendToList(state, ['reblogged_by', action.id], action.accounts, action.next); + case REBLOGS_FETCH_REQUEST: + case REBLOGS_EXPAND_REQUEST: + return state.setIn(['reblogged_by', action.id, 'isLoading'], true); + case REBLOGS_FETCH_FAIL: + case REBLOGS_EXPAND_FAIL: + return state.setIn(['reblogged_by', action.id, 'isLoading'], false); case FAVOURITES_FETCH_SUCCESS: - return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id))); + return normalizeList(state, ['favourited_by', action.id], action.accounts, action.next); + case FAVOURITES_EXPAND_SUCCESS: + return appendToList(state, ['favourited_by', action.id], action.accounts, action.next); + case FAVOURITES_FETCH_REQUEST: + case FAVOURITES_EXPAND_REQUEST: + return state.setIn(['favourited_by', action.id, 'isLoading'], true); + case FAVOURITES_FETCH_FAIL: + case FAVOURITES_EXPAND_FAIL: + return state.setIn(['favourited_by', action.id, 'isLoading'], false); case EMOJI_REACTIONS_FETCH_SUCCESS: return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts)); case STATUS_REFERENCES_FETCH_SUCCESS: diff --git a/app/javascript/mastodon/test_setup.js b/app/javascript/mastodon/test_setup.js index 666127af39..7b0828bfa8 100644 --- a/app/javascript/mastodon/test_setup.js +++ b/app/javascript/mastodon/test_setup.js @@ -1 +1 @@ -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom'; diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx index 1d917d60ee..ae4a7410e1 100644 --- a/app/javascript/packs/public.jsx +++ b/app/javascript/packs/public.jsx @@ -7,8 +7,6 @@ import { defineMessages } from 'react-intl'; import { delegate } from '@rails/ujs'; import axios from 'axios'; -import escapeTextContentForBrowser from 'escape-html'; -import { createBrowserHistory } from 'history'; import { throttle } from 'lodash'; import { start } from '../mastodon/common'; @@ -48,23 +46,6 @@ window.addEventListener('message', e => { function loaded() { const { messages: localeData } = getLocale(); - const scrollToDetailedStatus = () => { - const history = createBrowserHistory(); - const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status'); - const location = history.location; - - if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) { - detailedStatuses[0].scrollIntoView(); - history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true }); - } - }; - - const getEmojiAnimationHandler = (swapTo) => { - return ({ target }) => { - target.src = target.getAttribute(swapTo); - }; - }; - const locale = document.documentElement.lang; const dateTimeFormat = new Intl.DateTimeFormat(locale, { @@ -158,27 +139,21 @@ function loaded() { const root = createRoot(content); root.render(); document.body.appendChild(content); - scrollToDetailedStatus(); }) .catch(error => { console.error(error); - scrollToDetailedStatus(); }); - } else { - scrollToDetailedStatus(); } - delegate(document, '#user_account_attributes_username', 'input', throttle(() => { - const username = document.getElementById('user_account_attributes_username'); - - if (username.value && username.value.length > 0) { - axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => { - username.setCustomValidity(formatMessage(messages.usernameTaken)); + delegate(document, '#user_account_attributes_username', 'input', throttle(({ target }) => { + if (target.value && target.value.length > 0) { + axios.get('/api/v1/accounts/lookup', { params: { acct: target.value } }).then(() => { + target.setCustomValidity(formatMessage(messages.usernameTaken)); }).catch(() => { - username.setCustomValidity(''); + target.setCustomValidity(''); }); } else { - username.setCustomValidity(''); + target.setCustomValidity(''); } }, 500, { leading: false, trailing: true })); @@ -196,9 +171,6 @@ function loaded() { } }); - delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); - delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); - delegate(document, '.status__content__spoiler-link', 'click', function() { const statusEl = this.parentNode.parentNode; @@ -220,17 +192,6 @@ function loaded() { }); } -delegate(document, '#account_display_name', 'input', ({ target }) => { - const name = document.querySelector('.card .display-name strong'); - if (name) { - if (target.value) { - name.innerHTML = emojify(escapeTextContentForBrowser(target.value)); - } else { - name.textContent = target.dataset.default; - } - } -}); - delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => { const avatar = document.getElementById(target.id + '-preview'); const [file] = target.files || []; @@ -239,33 +200,6 @@ delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => { avatar.src = url; }); -const getProfileAvatarAnimationHandler = (swapTo) => { - //animate avatar gifs on the profile page when moused over - return ({ target }) => { - const swapSrc = target.getAttribute(swapTo); - //only change the img source if autoplay is off and the image src is actually different - if(target.getAttribute('data-autoplay') !== 'true' && target.src !== swapSrc) { - target.src = swapSrc; - } - }; -}; - -delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnimationHandler('data-original')); - -delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static')); - -delegate(document, '#account_locked', 'change', ({ target }) => { - const lock = document.querySelector('.card .display-name i'); - - if (lock) { - if (target.checked) { - delete lock.dataset.hidden; - } else { - lock.dataset.hidden = 'true'; - } - } -}); - delegate(document, '.input-copy input', 'click', ({ target }) => { target.focus(); target.select(); @@ -325,6 +259,9 @@ delegate(document, '.sidebar__toggle__icon', 'keydown', e => { } }); +delegate(document, '.custom-emoji', 'mouseover', ({ target }) => target.src = target.getAttribute('data-original')); +delegate(document, '.custom-emoji', 'mouseout', ({ target }) => target.src = target.getAttribute('data-static')); + // Empty the honeypot fields in JS in case something like an extension // automatically filled them. delegate(document, '#registration_new_user,#new_user', 'submit', () => { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 1d3476b0ea..ba644aebc8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2441,6 +2441,7 @@ $ui-header-height: 55px; .filter-form { display: flex; + flex-wrap: wrap; } .autosuggest-textarea__textarea { @@ -3330,6 +3331,22 @@ $ui-header-height: 55px; border-color: $ui-highlight-color; } +.switch-to-advanced { + color: $classic-primary-color; + background-color: $classic-base-color; + padding: 15px; + border-radius: 4px; + margin-top: 4px; + margin-bottom: 12px; + font-size: 13px; + line-height: 18px; + + .switch-to-advanced__toggle { + color: $ui-button-tertiary-color; + font-weight: bold; + } +} + .column-link { background: lighten($ui-base-color, 8%); color: $primary-text-color; @@ -5204,6 +5221,12 @@ a.status-card { } &__menu { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + &__message { color: $dark-text-color; padding: 0 10px; @@ -9655,19 +9678,24 @@ noscript { display: flex; flex-wrap: wrap; font-size: 14px; + line-height: 18px; gap: 4px; + color: $darker-text-color; a { display: inline-flex; - color: $dark-text-color; + color: inherit; text-decoration: none; - &:hover { - text-decoration: none; - - span { - text-decoration: underline; - } + &:hover span { + text-decoration: underline; } } + + .link-button { + color: inherit; + font-size: inherit; + line-height: inherit; + padding: 0; + } } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 4ecea8c856..045d62a72b 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -1186,14 +1186,14 @@ code { } li:first-child .label { - left: auto; inset-inline-start: 0; + inset-inline-end: auto; text-align: start; transform: none; } li:last-child .label { - left: auto; + inset-inline-start: auto; inset-inline-end: 0; text-align: end; transform: none; diff --git a/app/lib/importer/accounts_index_importer.rb b/app/lib/importer/accounts_index_importer.rb index fd869c3960..d8b9190275 100644 --- a/app/lib/importer/accounts_index_importer.rb +++ b/app/lib/importer/accounts_index_importer.rb @@ -4,10 +4,10 @@ class Importer::AccountsIndexImporter < Importer::BaseImporter def import! scope.includes(:account_stat).find_in_batches(batch_size: @batch_size) do |tmp| in_work_unit(tmp) do |accounts| - bulk = Chewy::Index::Import::BulkBuilder.new(index, to_index: accounts).bulk_body + bulk = build_bulk_body(accounts) - indexed = bulk.count { |entry| entry[:index] } - deleted = bulk.count { |entry| entry[:delete] } + indexed = bulk.size + deleted = 0 Chewy::Index::Import::BulkRequest.new(index).perform(bulk) diff --git a/app/lib/importer/base_importer.rb b/app/lib/importer/base_importer.rb index cc1b7b44d7..a21557d303 100644 --- a/app/lib/importer/base_importer.rb +++ b/app/lib/importer/base_importer.rb @@ -68,6 +68,14 @@ class Importer::BaseImporter protected + def build_bulk_body(to_import) + # Specialize `Chewy::Index::Import::BulkBuilder#bulk_body` to avoid a few + # inefficiencies, as none of our fields or join fields and we do not need + # `BulkBuilder`'s versatility. + crutches = Chewy::Index::Crutch::Crutches.new index, to_import + to_import.map { |object| { index: { _id: object.id, data: index.compose(object, crutches, fields: []) } } } + end + def in_work_unit(...) work_unit = Concurrent::Promises.future_on(@executor, ...) diff --git a/app/lib/importer/instances_index_importer.rb b/app/lib/importer/instances_index_importer.rb index 7318b51b5d..ebdceb72ed 100644 --- a/app/lib/importer/instances_index_importer.rb +++ b/app/lib/importer/instances_index_importer.rb @@ -4,10 +4,10 @@ class Importer::InstancesIndexImporter < Importer::BaseImporter def import! index.adapter.default_scope.find_in_batches(batch_size: @batch_size) do |tmp| in_work_unit(tmp) do |instances| - bulk = Chewy::Index::Import::BulkBuilder.new(index, to_index: instances).bulk_body + bulk = build_bulk_body(instances) - indexed = bulk.count { |entry| entry[:index] } - deleted = bulk.count { |entry| entry[:delete] } + indexed = bulk.size + deleted = 0 Chewy::Index::Import::BulkRequest.new(index).perform(bulk) diff --git a/app/lib/importer/public_statuses_index_importer.rb b/app/lib/importer/public_statuses_index_importer.rb index 8e36e36f90..ebaac3794f 100644 --- a/app/lib/importer/public_statuses_index_importer.rb +++ b/app/lib/importer/public_statuses_index_importer.rb @@ -2,24 +2,15 @@ class Importer::PublicStatusesIndexImporter < Importer::BaseImporter def import! - indexable_statuses_scope.find_in_batches(batch_size: @batch_size) do |batch| - in_work_unit(batch.map(&:status_id)) do |status_ids| + scope.select(:id).find_in_batches(batch_size: @batch_size) do |batch| + in_work_unit(batch.pluck(:id)) do |status_ids| bulk = ActiveRecord::Base.connection_pool.with_connection do - Chewy::Index::Import::BulkBuilder.new(index, to_index: Status.includes(:media_attachments, :preloadable_poll).where(id: status_ids)).bulk_body + build_bulk_body(index.adapter.default_scope.where(id: status_ids)) end - indexed = 0 + indexed = bulk.size deleted = 0 - bulk.map! do |entry| - if entry[:index] - indexed += 1 - else - deleted += 1 - end - entry - end - Chewy::Index::Import::BulkRequest.new(index).perform(bulk) [indexed, deleted] @@ -35,7 +26,7 @@ class Importer::PublicStatusesIndexImporter < Importer::BaseImporter PublicStatusesIndex end - def indexable_statuses_scope - Status.indexable.select('"statuses"."id", COALESCE("statuses"."reblog_of_id", "statuses"."id") AS status_id') + def scope + Status.indexable end end diff --git a/app/lib/importer/statuses_index_importer.rb b/app/lib/importer/statuses_index_importer.rb index b0721c2e02..08ad3e3797 100644 --- a/app/lib/importer/statuses_index_importer.rb +++ b/app/lib/importer/statuses_index_importer.rb @@ -13,32 +13,25 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter scope.find_in_batches(batch_size: @batch_size) do |tmp| in_work_unit(tmp.map(&:status_id)) do |status_ids| - bulk = ActiveRecord::Base.connection_pool.with_connection do - Chewy::Index::Import::BulkBuilder.new(index, to_index: Status.includes(:media_attachments, :preloadable_poll).where(id: status_ids)).bulk_body - end - - indexed = 0 deleted = 0 - # We can't use the delete_if proc to do the filtering because delete_if - # is called before rendering the data and we need to filter based - # on the results of the filter, so this filtering happens here instead - bulk.map! do |entry| - new_entry = if entry[:index] && entry.dig(:index, :data, 'searchable_by').blank? - { delete: entry[:index].except(:data) } - else - entry - end - - if new_entry[:index] - indexed += 1 - else - deleted += 1 + bulk = ActiveRecord::Base.connection_pool.with_connection do + to_index = index.adapter.default_scope.where(id: status_ids) + crutches = Chewy::Index::Crutch::Crutches.new index, to_index + to_index.map do |object| + # This is unlikely to happen, but the post may have been + # un-interacted with since it was queued for indexing + if object.searchable_by.empty? + deleted += 1 + { delete: { _id: object.id } } + else + { index: { _id: object.id, data: index.compose(object, crutches, fields: []) } } + end end - - new_entry end + indexed = bulk.size - deleted + Chewy::Index::Import::BulkRequest.new(index).perform(bulk) [indexed, deleted] diff --git a/app/lib/importer/tags_index_importer.rb b/app/lib/importer/tags_index_importer.rb index 77710ed7de..067fd8cd2d 100644 --- a/app/lib/importer/tags_index_importer.rb +++ b/app/lib/importer/tags_index_importer.rb @@ -4,10 +4,10 @@ class Importer::TagsIndexImporter < Importer::BaseImporter def import! index.adapter.default_scope.find_in_batches(batch_size: @batch_size) do |tmp| in_work_unit(tmp) do |tags| - bulk = Chewy::Index::Import::BulkBuilder.new(index, to_index: tags).bulk_body + bulk = build_bulk_body(tags) - indexed = bulk.count { |entry| entry[:index] } - deleted = bulk.count { |entry| entry[:delete] } + indexed = bulk.size + deleted = 0 Chewy::Index::Import::BulkRequest.new(index).perform(bulk) diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb index 7071bb6804..ebdd1d189f 100644 --- a/app/lib/inline_renderer.rb +++ b/app/lib/inline_renderer.rb @@ -12,6 +12,9 @@ class InlineRenderer when :status serializer = REST::StatusSerializer preload_associations_for_status + when :status_internal + serializer = REST::StatusInternalSerializer + preload_associations_for_status when :notification serializer = REST::NotificationSerializer when :emoji_reaction diff --git a/app/lib/plain_text_formatter.rb b/app/lib/plain_text_formatter.rb index 8eac730bec..d1ff6808b2 100644 --- a/app/lib/plain_text_formatter.rb +++ b/app/lib/plain_text_formatter.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class PlainTextFormatter - include ActionView::Helpers::TextHelper - NEWLINE_TAGS_RE = %r{(
|
|

)+} attr_reader :text, :local @@ -18,7 +16,10 @@ class PlainTextFormatter if local? text else - html_entities.decode(strip_tags(insert_newlines)).chomp + node = Nokogiri::HTML.fragment(insert_newlines) + # Elements that are entirely removed with our Sanitize config + node.xpath('.//iframe|.//math|.//noembed|.//noframes|.//noscript|.//plaintext|.//script|.//style|.//svg|.//xmp').remove + node.text.chomp end end @@ -27,8 +28,4 @@ class PlainTextFormatter def insert_newlines text.gsub(NEWLINE_TAGS_RE) { |match| "#{match}\n" } end - - def html_entities - HTMLEntities.new - end end diff --git a/app/lib/search_query_parser.rb b/app/lib/search_query_parser.rb index 15956d4cfd..1c57b9b024 100644 --- a/app/lib/search_query_parser.rb +++ b/app/lib/search_query_parser.rb @@ -6,10 +6,10 @@ class SearchQueryParser < Parslet::Parser rule(:colon) { str(':') } rule(:space) { match('\s').repeat(1) } rule(:operator) { (str('+') | str('-')).as(:operator) } - rule(:prefix) { (term >> colon).as(:prefix) } + rule(:prefix) { term >> colon } rule(:shortcode) { (colon >> term >> colon.maybe).as(:shortcode) } rule(:phrase) { (quote >> (term >> space.maybe).repeat >> quote).as(:phrase) } - rule(:clause) { (prefix.maybe >> operator.maybe >> (phrase | term | shortcode)).as(:clause) } + rule(:clause) { (operator.maybe >> prefix.maybe.as(:prefix) >> (phrase | term | shortcode)).as(:clause) | prefix.as(:clause) | quote.as(:junk) } rule(:query) { (clause >> space.maybe).repeat.as(:query) } root(:query) end diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index 2131ee290a..8ab60405df 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -1,46 +1,32 @@ # frozen_string_literal: true class SearchQueryTransformer < Parslet::Transform + SUPPORTED_PREFIXES = %w( + has + is + language + from + before + after + during + ).freeze + class Query - attr_reader :should_clauses, :must_not_clauses, :must_clauses, :filter_clauses + attr_reader :must_not_clauses, :must_clauses, :filter_clauses def initialize(clauses) - grouped = clauses.chunk(&:operator).to_h - @should_clauses = grouped.fetch(:should, []) + grouped = clauses.compact.chunk(&:operator).to_h @must_not_clauses = grouped.fetch(:must_not, []) @must_clauses = grouped.fetch(:must, []) @filter_clauses = grouped.fetch(:filter, []) end def apply(search) - should_clauses.each { |clause| search = search.query.should(clause_to_query(clause)) } - must_clauses.each { |clause| search = search.query.must(clause_to_query(clause)) } - must_not_clauses.each { |clause| search = search.query.must_not(clause_to_query(clause)) } - filter_clauses.each { |clause| search = search.filter(**clause_to_filter(clause)) } + must_clauses.each { |clause| search = search.query.must(clause.to_query) } + must_not_clauses.each { |clause| search = search.query.must_not(clause.to_query) } + filter_clauses.each { |clause| search = search.filter(**clause.to_query) } search.query.minimum_should_match(1) end - - private - - def clause_to_query(clause) - case clause - when TermClause - { match_phrase: { text: { query: clause.term } } } - when PhraseClause - { match_phrase: { text: { query: clause.phrase } } } - else - raise "Unexpected clause type: #{clause}" - end - end - - def clause_to_filter(clause) - case clause - when PrefixClause - { clause.type => { clause.filter => clause.term } } - else - raise "Unexpected clause type: #{clause}" - end - end end class Operator @@ -59,29 +45,39 @@ class SearchQueryTransformer < Parslet::Transform end class TermClause - attr_reader :prefix, :operator, :term + attr_reader :operator, :term - def initialize(prefix, operator, term) - @prefix = prefix + def initialize(operator, term) @operator = Operator.symbol(operator) @term = term end + + def to_query + # { multi_match: { type: 'most_fields', query: @term, fields: ['text', 'text.stemmed'], operator: 'and' } } + { match_phrase: { text: { query: @phrase } } } + end end class PhraseClause - attr_reader :prefix, :operator, :phrase + attr_reader :operator, :phrase - def initialize(prefix, operator, phrase) - @prefix = prefix + def initialize(operator, phrase) @operator = Operator.symbol(operator) @phrase = phrase end + + def to_query + { match_phrase: { text: { query: @phrase } } } + end end class PrefixClause - attr_reader :type, :filter, :operator, :term + attr_reader :operator, :prefix, :term - def initialize(prefix, term) + def initialize(prefix, operator, term, options = {}) + @prefix = prefix + @negated = operator == '-' + @options = options @operator = :filter case prefix @@ -92,31 +88,45 @@ class SearchQueryTransformer < Parslet::Transform when 'language' @filter = :language @type = :term - @term = term + @term = language_code_from_term(term) when 'from' @filter = :account_id @type = :term @term = account_id_from_term(term) + when 'domain' + @filter = :domain + @type = :term + @term = domain_from_term(term) when 'before' @filter = :created_at @type = :range - @term = { lt: term } + @term = { lt: term, time_zone: @options[:current_account]&.user_time_zone || 'UTC' } when 'after' @filter = :created_at @type = :range - @term = { gt: term } + @term = { gt: term, time_zone: @options[:current_account]&.user_time_zone || 'UTC' } when 'during' @filter = :created_at @type = :range - @term = { gte: term, lte: term } + @term = { gte: term, lte: term, time_zone: @options[:current_account]&.user_time_zone || 'UTC' } else - raise Mastodon::SyntaxError + raise "Unknown prefix: #{prefix}" + end + end + + def to_query + if @negated + { bool: { must_not: { @type => { @filter => @term } } } } + else + { @type => { @filter => @term } } end end private def account_id_from_term(term) + return @options[:current_account]&.id || -1 if term == 'me' + username, domain = term.gsub(/\A@/, '').split('@') domain = nil if TagManager.instance.local_domain?(domain) account = Account.find_remote(username, domain) @@ -125,24 +135,54 @@ class SearchQueryTransformer < Parslet::Transform # an ID that does not exist account&.id || -1 end + + def domain_from_term(term) + return '' if %w(local me).include?(term) + + term + end + + def language_code_from_term(term) + language_code = term + + return language_code if LanguagesHelper::SUPPORTED_LOCALES.key?(language_code.to_sym) + + language_code = term.downcase + + return language_code if LanguagesHelper::SUPPORTED_LOCALES.key?(language_code.to_sym) + + language_code = term.split(/[_-]/).first.downcase + + return language_code if LanguagesHelper::SUPPORTED_LOCALES.key?(language_code.to_sym) + + term + end end rule(clause: subtree(:clause)) do prefix = clause[:prefix][:term].to_s if clause[:prefix] operator = clause[:operator]&.to_s - if clause[:prefix] - PrefixClause.new(prefix, clause[:term].to_s) + if clause[:prefix] && SUPPORTED_PREFIXES.include?(prefix) + PrefixClause.new(prefix, operator, clause[:term].to_s, current_account: current_account) + elsif clause[:prefix] + TermClause.new(operator, "#{prefix} #{clause[:term]}") elsif clause[:term] - TermClause.new(prefix, operator, clause[:term].to_s) + TermClause.new(operator, clause[:term].to_s) elsif clause[:shortcode] - TermClause.new(prefix, operator, ":#{clause[:term]}:") + TermClause.new(operator, ":#{clause[:term]}:") elsif clause[:phrase] - PhraseClause.new(prefix, operator, clause[:phrase].is_a?(Array) ? clause[:phrase].map { |p| p[:term].to_s }.join(' ') : clause[:phrase].to_s) + PhraseClause.new(operator, clause[:phrase].is_a?(Array) ? clause[:phrase].map { |p| p[:term].to_s }.join(' ') : clause[:phrase].to_s) else raise "Unexpected clause type: #{clause}" end end - rule(query: sequence(:clauses)) { Query.new(clauses) } + rule(junk: subtree(:junk)) do + nil + end + + rule(query: sequence(:clauses)) do + Query.new(clauses) + end end diff --git a/app/models/account.rb b/app/models/account.rb index d2538bb065..b94f208635 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -319,6 +319,17 @@ class Account < ApplicationRecord user&.setting_noai || (settings.present? && settings['noai']) || false end + def translatable_private? + user&.setting_translatable_private || (settings.present? && settings['translatable_private']) || false + end + + def link_preview? + return user.setting_link_preview if local? && user.present? + return settings['link_preview'] if settings.present? && settings.key?('link_preview') + + true + end + def public_statuses_count hide_statuses_count? ? 0 : statuses_count end @@ -384,10 +395,16 @@ class Account < ApplicationRecord 'hide_statuses_count' => hide_statuses_count?, 'hide_following_count' => hide_following_count?, 'hide_followers_count' => hide_followers_count?, - 'emoji_reaction_must_following' => emoji_reactions_must_following?, - 'emoji_reaction_must_follower' => emoji_reactions_must_follower?, - 'emoji_reaction_deny_from_all' => emoji_reactions_deny_from_all?, + 'translatable_private' => translatable_private?, + 'link_preview' => link_preview?, } + if Setting.enable_block_emoji_reaction_settings + config = config.merge({ + 'emoji_reaction_must_following' => emoji_reactions_must_following?, + 'emoji_reaction_must_follower' => emoji_reactions_must_follower?, + 'emoji_reaction_deny_from_all' => emoji_reactions_deny_from_all?, + }) + end config = config.merge(settings) if settings.present? config end diff --git a/app/models/concerns/account_statuses_search.rb b/app/models/concerns/account_statuses_search.rb index 563a871051..fa9238e6ef 100644 --- a/app/models/concerns/account_statuses_search.rb +++ b/app/models/concerns/account_statuses_search.rb @@ -31,8 +31,8 @@ module AccountStatusesSearch def add_to_public_statuses_index! return unless Chewy.enabled? - statuses.indexable.find_in_batches do |batch| - PublicStatusesIndex.import(query: batch) + statuses.without_reblogs.where(visibility: :public).find_in_batches do |batch| + PublicStatusesIndex.import(batch) end end diff --git a/app/models/concerns/has_user_settings.rb b/app/models/concerns/has_user_settings.rb index dfb37c3517..62c617f731 100644 --- a/app/models/concerns/has_user_settings.rb +++ b/app/models/concerns/has_user_settings.rb @@ -68,7 +68,7 @@ module HasUserSettings end def setting_emoji_reaction_streaming_notify_impl2 - settings['emoji_reaction_streaming_notify_impl2'] + false end def setting_unfollow_modal @@ -99,6 +99,14 @@ module HasUserSettings settings['noai'] end + def setting_translatable_private + settings['translatable_private'] + end + + def setting_link_preview + settings['link_preview'] + end + def setting_hide_statuses_count settings['hide_statuses_count'] end diff --git a/app/models/concerns/status_search_concern.rb b/app/models/concerns/status_search_concern.rb index ddb69126b2..69f1263f81 100644 --- a/app/models/concerns/status_search_concern.rb +++ b/app/models/concerns/status_search_concern.rb @@ -7,30 +7,22 @@ module StatusSearchConcern scope :indexable, -> { without_reblogs.where(visibility: [:public, :login], searchability: nil).joins(:account).where(account: { indexable: true }) } end - def searchable_by(preloaded = nil) - ids = [] + def searchable_by + @searchable_by ||= begin + ids = [] - ids << account_id if local? + ids << account_id if local? - if preloaded.nil? - ids += mentions.joins(:account).merge(Account.local).active.pluck(:account_id) - ids += favourites.joins(:account).merge(Account.local).pluck(:account_id) - ids += emoji_reactions.joins(:account).merge(Account.local).pluck(:account_id) - ids += references.joins(:account).merge(Account.local).pluck(:account_id) - ids += reblogs.joins(:account).merge(Account.local).pluck(:account_id) - ids += bookmarks.joins(:account).merge(Account.local).pluck(:account_id) - ids += poll.votes.joins(:account).merge(Account.local).pluck(:account_id) if poll.present? - else - ids += preloaded.mentions[id] || [] - ids += preloaded.favourites[id] || [] - ids += preloaded.emoji_reactions[id] || [] - ids += preloaded.status_references[id] || [] - ids += preloaded.reblogs[id] || [] - ids += preloaded.bookmarks[id] || [] - ids += preloaded.votes[id] || [] + ids += local_mentioned.pluck(:id) + ids += local_favorited.pluck(:id) + ids += local_reblogged.pluck(:id) + ids += local_bookmarked.pluck(:id) + ids += local_emoji_reacted.pluck(:id) + ids += local_referenced.pluck(:id) + ids += preloadable_poll.local_voters.pluck(:id) if preloadable_poll.present? + + ids.uniq end - - ids.uniq end def searchable_text diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 5a19942e88..f443d08ca6 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -101,7 +101,10 @@ class CustomFilter < ApplicationRecord next if filter.exclude_follows && following next if filter.exclude_localusers && status.account.local? - match = rules[:keywords].match(status.proper.searchable_text) if rules[:keywords].present? + if rules[:keywords].present? + match = rules[:keywords].match(status.proper.searchable_text) + match = rules[:keywords].match(status.proper.references.pluck(:text).join("\n\n")) if match.nil? && status.proper.references.exists? + end keyword_matches = [match.to_s] unless match.nil? status_matches = [status.id, status.reblog_of_id].compact & rules[:status_ids] if rules[:status_ids].present? diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index df48df8160..197ee777fe 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -49,6 +49,7 @@ class MediaAttachment < ApplicationRecord MAX_VIDEO_MATRIX_LIMIT = 8_294_400 # 3840x2160px MAX_VIDEO_FRAME_RATE = 120 + MAX_VIDEO_FRAMES = 36_000 # Approx. 5 minutes at 120 fps IMAGE_FILE_EXTENSIONS = %w(.jpg .jpeg .png .gif .webp .heic .heif .avif).freeze VIDEO_FILE_EXTENSIONS = %w(.webm .mp4 .m4v .mov).freeze @@ -103,17 +104,14 @@ class MediaAttachment < ApplicationRecord convert_options: { output: { 'loglevel' => 'fatal', - 'movflags' => 'faststart', - 'pix_fmt' => 'yuv420p', - 'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'', - 'vsync' => 'cfr', + 'preset' => 'veryfast', + 'movflags' => 'faststart', # Move metadata to start of file so playback can begin before download finishes + 'pix_fmt' => 'yuv420p', # Ensure color space for cross-browser compatibility 'c:v' => 'h264', - 'maxrate' => '1300K', - 'bufsize' => '1300K', - 'b:v' => '1300K', - 'frames:v' => 60 * 60 * 3, - 'crf' => 18, + 'c:a' => 'aac', + 'b:a' => '192k', 'map_metadata' => '-1', + 'frames:v' => MAX_VIDEO_FRAMES, }.freeze, }.freeze, }.freeze @@ -140,7 +138,7 @@ class MediaAttachment < ApplicationRecord convert_options: { output: { 'loglevel' => 'fatal', - :vf => 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease', + :vf => 'scale=\'min(640\, iw):min(640\, ih)\':force_original_aspect_ratio=decrease', }.freeze, }.freeze, format: 'png', diff --git a/app/models/poll.rb b/app/models/poll.rb index 74a77978b9..efa625eb5b 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -28,6 +28,7 @@ class Poll < ApplicationRecord has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :delete_all has_many :voters, -> { group('accounts.id') }, through: :votes, class_name: 'Account', source: :account + has_many :local_voters, -> { group('accounts.id').merge(Account.local) }, through: :votes, class_name: 'Account', source: :account has_many :notifications, as: :activity, dependent: :destroy diff --git a/app/models/status.rb b/app/models/status.rb index e19f224fc8..011e961848 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -87,6 +87,14 @@ class Status < ApplicationRecord has_many :bookmark_category_relationships, class_name: 'BookmarkCategoryStatus', inverse_of: :status, dependent: :destroy has_many :joined_bookmark_categories, class_name: 'BookmarkCategory', through: :bookmark_category_relationships, source: :bookmark_category + # Those associations are used for the private search index + has_many :local_mentioned, -> { merge(Account.local) }, through: :active_mentions, source: :account + has_many :local_favorited, -> { merge(Account.local) }, through: :favourites, source: :account + has_many :local_reblogged, -> { merge(Account.local) }, through: :reblogs, source: :account + has_many :local_bookmarked, -> { merge(Account.local) }, through: :bookmarks, source: :account + has_many :local_emoji_reacted, -> { merge(Account.local) }, through: :emoji_reactions, source: :account + has_many :local_referenced, -> { merge(Account.local) }, through: :referenced_by_statuses, source: :account + has_and_belongs_to_many :tags has_and_belongs_to_many :preview_cards diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index 32bfa563cd..802d706191 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -12,6 +12,8 @@ class UserSettings setting :theme, default: -> { ::Setting.theme } setting :noindex, default: -> { ::Setting.noindex } setting :noai, default: true + setting :translatable_private, default: false + setting :link_preview, default: true setting :bio_markdown, default: false setting :discoverable_local, default: false setting :hide_statuses_count, default: false diff --git a/app/serializers/rest/status_internal_serializer.rb b/app/serializers/rest/status_internal_serializer.rb new file mode 100644 index 0000000000..7a2cbf5746 --- /dev/null +++ b/app/serializers/rest/status_internal_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::StatusInternalSerializer < REST::StatusSerializer + attributes :reference_texts + + def reference_texts + object.references.pluck(:text) + end +end diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb index 3ca3441169..b67cd2771a 100644 --- a/app/serializers/webfinger_serializer.rb +++ b/app/serializers/webfinger_serializer.rb @@ -18,18 +18,31 @@ class WebfingerSerializer < ActiveModel::Serializer end def links - if object.instance_actor? - [ - { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: about_more_url(instance_actor: true) }, - { rel: 'self', type: 'application/activity+json', href: instance_actor_url }, - { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, - ] - else - [ - { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) }, - { rel: 'self', type: 'application/activity+json', href: account_url(object) }, - { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, - ] + [ + { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: profile_page_href }, + { rel: 'self', type: 'application/activity+json', href: self_href }, + { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, + ].tap do |x| + x << { rel: 'http://webfinger.net/rel/avatar', type: object.avatar.content_type, href: full_asset_url(object.avatar_original_url) } if show_avatar? end end + + private + + def show_avatar? + media_present = object.avatar.present? && object.avatar.content_type.present? + + # Show avatar only if an instance shows profiles to logged out users + allowed_by_config = ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] != 'true' && !Rails.configuration.x.limited_federation_mode + + media_present && allowed_by_config + end + + def profile_page_href + object.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(object) + end + + def self_href + object.instance_actor? ? instance_actor_url : account_url(object) + end end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 94f9612725..865589dacd 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -195,7 +195,7 @@ class FanOutOnWriteService < BaseService end def rendered_status - @rendered_status ||= InlineRenderer.render(@status, nil, :status) + @rendered_status ||= InlineRenderer.render(@status, nil, :status_internal) end def update? diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 790cf44548..8323c6cdfe 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -20,7 +20,7 @@ class FetchLinkCardService < BaseService @status = status @original_url = parse_urls - return if @original_url.nil? || @status.preview_cards.any? + return if @original_url.nil? || @status.preview_cards.any? || !@status.account.link_preview? @url = @original_url.to_s diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 9404168c2b..22624dc913 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -92,9 +92,10 @@ class PostStatusService < BaseService end def load_circle - return unless @options[:visibility] == 'circle' + return unless @options[:visibility] == 'circle' || (@options[:visibility] == 'limited' && @options[:circle_id].present?) @circle = @options[:circle_id].present? && Circle.find(@options[:circle_id]) + @limited_scope = :circle raise ArgumentError if @circle.nil? || @circle.account_id != @account.id end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index a8f6d0dca4..af76dcc275 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class SearchService < BaseService - def call(query, account, limit, options = {}) - @query = query&.strip + QUOTE_EQUIVALENT_CHARACTERS = /[“”„«»「」『』《》]/ + def call(query, account, limit, options = {}) + @query = query&.strip&.gsub(QUOTE_EQUIVALENT_CHARACTERS, '"') @account = account @options = options @limit = limit.to_i @@ -19,7 +20,7 @@ class SearchService < BaseService results.merge!(url_resource_results) unless url_resource.nil? || @offset.positive? || (@options[:type].present? && url_resource_symbol != @options[:type].to_sym) elsif @query.present? results[:accounts] = perform_accounts_search! if account_searchable? - results[:statuses] = perform_statuses_search! if full_text_searchable? + results[:statuses] = perform_statuses_search! if status_searchable? results[:hashtags] = perform_hashtags_search! if hashtag_searchable? end end @@ -82,18 +83,16 @@ class SearchService < BaseService url_resource.class.name.downcase.pluralize.to_sym end - def full_text_searchable? - return false unless Chewy.enabled? - - statuses_search? && !@account.nil? && !(@query.include?('@') && !@query.include?(' ')) + def status_searchable? + Chewy.enabled? && status_search? && @account.present? end def account_searchable? - account_search? && !(@query.include?('@') && @query.include?(' ')) + account_search? end def hashtag_searchable? - hashtag_search? && !@query.include?('@') + hashtag_search? end def account_search? @@ -104,7 +103,7 @@ class SearchService < BaseService @options[:type].blank? || @options[:type] == 'hashtags' end - def statuses_search? + def status_search? @options[:type].blank? || @options[:type] == 'statuses' end end diff --git a/app/services/statuses_search_service.rb b/app/services/statuses_search_service.rb index 4f2e783345..b79d3ede6e 100644 --- a/app/services/statuses_search_service.rb +++ b/app/services/statuses_search_service.rb @@ -140,6 +140,6 @@ class StatusesSearchService < BaseService end def parsed_query - SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query)) + SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query), current_account: @account) end end diff --git a/app/services/translate_status_service.rb b/app/services/translate_status_service.rb index c2b40433ed..4bc99c8b3c 100644 --- a/app/services/translate_status_service.rb +++ b/app/services/translate_status_service.rb @@ -30,7 +30,7 @@ class TranslateStatusService < BaseService end def permitted? - return false unless @status.distributable? && TranslationService.configured? + return false unless (@status.distributable? || @status.account.translatable_private?) && TranslationService.configured? languages[@status.language]&.include?(@target_language) end diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml index 99c3e0b2bc..7bdce84c23 100644 --- a/app/views/settings/preferences/other/show.html.haml +++ b/app/views/settings/preferences/other/show.html.haml @@ -31,19 +31,16 @@ = ff.input :stay_privacy, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_stay_privacy') .fields-group - = ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false + = ff.input :public_post_to_unlisted, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_public_post_to_unlisted'), hint: I18n.t('simple_form.hints.defaults.setting_public_post_to_unlisted') .fields-group = ff.input :disallow_unlisted_public_searchability, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_disallow_unlisted_public_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_disallow_unlisted_public_searchability') - .fields-group - = ff.input :public_post_to_unlisted, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_public_post_to_unlisted'), hint: I18n.t('simple_form.hints.defaults.setting_public_post_to_unlisted') - .fields-group = ff.input :default_sensitive, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_default_sensitive'), hint: I18n.t('simple_form.hints.defaults.setting_default_sensitive') .fields-group - = ff.input :emoji_reaction_streaming_notify_impl2, as: :boolean, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_emoji_reaction_streaming_notify_impl2'), hint: I18n.t('simple_form.hints.defaults.setting_emoji_reaction_streaming_notify_impl2') + = ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false %h4= t 'preferences.public_timelines' diff --git a/app/views/settings/privacy/show.html.haml b/app/views/settings/privacy/show.html.haml index 0bed511a8c..619429392e 100644 --- a/app/views/settings/privacy/show.html.haml +++ b/app/views/settings/privacy/show.html.haml @@ -21,6 +21,9 @@ .fields-group = ff.input :discoverable_local, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.discoverable_local'), hint: I18n.t('simple_form.hints.defaults.discoverable_local') + .fields-group + = ff.input :noai, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_noai'), hint: I18n.t('simple_form.hints.defaults.setting_noai') + .fields-group = f.input :dissubscribable, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.dissubscribable') @@ -54,15 +57,5 @@ .fields-group = ff.input :show_application, wrapper: :with_label - %h4= t 'privacy.stop_deliver' - - %p.lead= t('privacy.stop_deliver_hint_html') - - .fields-group - = ff.input :reject_public_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_public_unlisted_subscription') - - .fields-group - = ff.input :reject_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_unlisted_subscription'), hint: I18n.t('simple_form.hints.defaults.setting_reject_unlisted_subscription') - .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/settings/privacy_extra/show.html.haml b/app/views/settings/privacy_extra/show.html.haml new file mode 100644 index 0000000000..53c11f9cdd --- /dev/null +++ b/app/views/settings/privacy_extra/show.html.haml @@ -0,0 +1,36 @@ +- content_for :page_title do + = t('privacy_extra.title') + +- content_for :heading do + %h2= t('settings.profile') + = render partial: 'settings/shared/profile_navigation' + += simple_form_for @account, url: settings_privacy_extra_path, html: { method: :put } do |f| + = render 'shared/error_messages', object: @account + + %p.lead= t('privacy_extra.hint_html') + + %h4= t('privacy_extra.post_processing') + + %p.lead= t('privacy_extra.post_processing_hint_html') + + = f.simple_fields_for :settings, current_user.settings do |ff| + .fields-group + = ff.input :translatable_private, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_translatable_private') + + .fields-group + = ff.input :link_preview, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_link_preview') + + %h4= t 'privacy_extra.stop_deliver' + + %p.lead= t('privacy_extra.stop_deliver_hint_html') + + = f.simple_fields_for :settings, current_user.settings do |ff| + .fields-group + = ff.input :reject_public_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_public_unlisted_subscription') + + .fields-group + = ff.input :reject_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_unlisted_subscription'), hint: I18n.t('simple_form.hints.defaults.setting_reject_unlisted_subscription') + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/settings/shared/_profile_navigation.html.haml b/app/views/settings/shared/_profile_navigation.html.haml index 3a657c5bde..fb7e80e023 100644 --- a/app/views/settings/shared/_profile_navigation.html.haml +++ b/app/views/settings/shared/_profile_navigation.html.haml @@ -3,5 +3,6 @@ :ruby primary.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_path primary.item :privacy, safe_join([fa_icon('lock fw'), t('privacy.title')]), settings_privacy_path + primary.item :privacy_extra, safe_join([fa_icon('lock fw'), t('privacy_extra.title')]), settings_privacy_extra_path primary.item :verification, safe_join([fa_icon('check fw'), t('verification.verification')]), settings_verification_path primary.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_path diff --git a/app/workers/add_to_public_statuses_index_worker.rb b/app/workers/add_to_public_statuses_index_worker.rb index 409e5e7086..80d921eab0 100644 --- a/app/workers/add_to_public_statuses_index_worker.rb +++ b/app/workers/add_to_public_statuses_index_worker.rb @@ -2,13 +2,20 @@ class AddToPublicStatusesIndexWorker include Sidekiq::Worker + include DatabaseHelper + + sidekiq_options queue: 'pull' def perform(account_id) - account = Account.find(account_id) + with_primary do + @account = Account.find(account_id) + end - return unless account.indexable? + return unless @account.indexable? - account.add_to_public_statuses_index! + with_read_replica do + @account.add_to_public_statuses_index! + end rescue ActiveRecord::RecordNotFound true end diff --git a/app/workers/scheduler/indexing_scheduler.rb b/app/workers/scheduler/indexing_scheduler.rb index 6c770d5a8f..1b09730c7d 100644 --- a/app/workers/scheduler/indexing_scheduler.rb +++ b/app/workers/scheduler/indexing_scheduler.rb @@ -3,6 +3,7 @@ class Scheduler::IndexingScheduler include Sidekiq::Worker include Redisable + include DatabaseHelper sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i @@ -15,7 +16,10 @@ class Scheduler::IndexingScheduler indexes.each do |type| with_redis do |redis| redis.sscan_each("chewy:queue:#{type.name}", count: SCAN_BATCH_SIZE).each_slice(IMPORT_BATCH_SIZE) do |ids| - type.import!(ids) + with_read_replica do + type.import!(ids) + end + redis.srem("chewy:queue:#{type.name}", ids) end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 31a3962458..9a6637bdb9 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -35,6 +35,8 @@ Rails.application.configure do config.cache_store = :null_store end + config.action_controller.forgery_protection_origin_check = ENV['DISABLE_FORGERY_REQUEST_PROTECTION'].nil? + ActiveSupport::Logger.new(STDOUT).tap do |logger| logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index 2bdf45964f..b4f26c9567 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -41,7 +41,8 @@ Rails.application.config.content_security_policy do |p| p.worker_src :self, :blob, assets_host if Rails.env.development? - webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{Webpacker.dev_server.host_with_port}" } + webpacker_public_host = ENV.fetch('WEBPACKER_DEV_SERVER_PUBLIC', Webpacker.config.dev_server[:public]) + webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{webpacker_public_host}" } p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host, google_host, google_host2, google_host3 diff --git a/config/locales/en.yml b/config/locales/en.yml index 90b47fe818..9ac7bce983 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1619,6 +1619,13 @@ en: search_hint_html: Control how you want to be found. Do you want people to find you by what you've publicly posted about? Do you want people outside Mastodon to find your profile when searching the web? Please mind that total exclusion from all search engines cannot be guaranteed for public information. stop_deliver: Stop delivering title: Privacy and reach + privacy_extra: + hint_html: These settings are kmyblue original. You will receive additional privacy benefits by doing this setting. + post_processing_hint_html: 投稿された情報に対して、システムが追加で行うことができる操作を制御します。これらには、第三者のサイトへあなたの投稿に関する情報の送信を伴う設定も含まれます。 + post_processing: 投稿の処理 + stop_deliver: 配送停止 + stop_deliver_hint_html: Mastodonの投稿を、他のソフトウェアでは自由に検索することができます。Mastodon内で行ったプライバシーの設定は無視され、あなたの投稿が意図しない人に見つかるおそれがあります。ここでは、他のサーバーやソフトウェアであなたの投稿が見つからないようにする設定が可能です。ただしリスクは伴います。 + title: Privacy extra settings privacy_policy: title: Privacy Policy reactions: @@ -1844,6 +1851,10 @@ en: default: "%b %d, %Y, %H:%M" month: "%b %Y" time: "%H:%M" + translation: + errors: + quota_exceeded: The server-wide usage quota for the translation service has been exceeded. + too_many_requests: There have been too many requests to the translation service recently. two_factor_authentication: add: Add disable: Disable 2FA diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1962d9af0d..3a88918add 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1592,9 +1592,14 @@ ja: reach_hint_html: Control whether you want to be discovered and followed by new people. Do you want your posts to appear on the Explore screen? Do you want other people to see you in their follow recommendations? Do you want to accept all new followers automatically, or have granular control over each one? search: Search search_hint_html: Control how you want to be found. Do you want people to find you by what you've publicly posted about? Do you want people outside Mastodon to find your profile when searching the web? Please mind that total exclusion from all search engines cannot be guaranteed for public information. + title: Privacy and reach + privacy_extra: + hint_html: これらはkmyblue独自のプライバシー設定項目です。この機能を利用することで、あなたは追加の恩恵を受けることができます。なおこれらの設定の一部は他のサーバーにも送信されますが、kmyblue以外で対応が確認されているソフトウェアは現在確認できていません。他のサーバーではこれらの設定は無視されること、ご了承ください。 + post_processing_hint_html: 投稿された情報に対して、システムが追加で行うことができる操作を制御します。これらには、第三者のサイトへあなたの投稿に関する情報の送信を伴う設定も含まれます。 + post_processing: 投稿の処理 stop_deliver: 配送停止 stop_deliver_hint_html: Mastodonの投稿を、他のソフトウェアでは自由に検索することができます。Mastodon内で行ったプライバシーの設定は無視され、あなたの投稿が意図しない人に見つかるおそれがあります。ここでは、他のサーバーやソフトウェアであなたの投稿が見つからないようにする設定が可能です。ただしリスクは伴います。 - title: Privacy and reach + title: プライバシー追加設定 privacy_policy: title: プライバシーポリシー reactions: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 88d9ded9d5..bf2d957c72 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -242,6 +242,7 @@ en: setting_hide_network: Hide your social graph setting_hide_recent_emojis: Hide recent emojis setting_hide_statuses_count: Hide statuses count + setting_link_preview: Generate post link preview card setting_noai: Set noai meta tags setting_public_post_to_unlisted: Convert public post to public unlisted if not using Web app setting_reduce_motion: Reduce motion in animations @@ -253,6 +254,7 @@ en: setting_stop_emoji_reaction_streaming: Disable stamp streamings setting_system_font_ui: Use system's default font setting_theme: Site theme + setting_translatable_private: Allow other users translation of your private posts setting_trends: Show today's trends setting_unfollow_modal: Show confirmation dialog before unfollowing someone setting_unsafe_limited_distribution: Send limit posts with unsafe way to other servers diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 05e27f62a3..a1c5c825a7 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -251,6 +251,7 @@ ja: setting_hide_network: 繋がりを隠す setting_hide_recent_emojis: 絵文字ピッカーで最近使用した絵文字を隠す(リアクションデッキのみを表示する) setting_hide_statuses_count: 投稿数を隠す + setting_link_preview: リンクのプレビューを生成する setting_stay_privacy: 投稿時に公開範囲を保存する setting_noai: 自分のコンテンツのAI学習利用に対して不快感を表明する setting_public_post_to_unlisted: サードパーティから公開範囲「公開」で投稿した場合、「ローカル公開」に変更する @@ -263,6 +264,7 @@ ja: setting_stop_emoji_reaction_streaming: スタンプのストリーミングを停止する setting_system_font_ui: システムのデフォルトフォントを使う setting_theme: サイトテーマ + setting_translatable_private: 非公開投稿の翻訳を許可する setting_trends: 本日のトレンドタグを表示する setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する setting_unsafe_limited_distribution: 安全でない方法で限定投稿を他サーバーに配信する (非推奨) diff --git a/config/routes/settings.rb b/config/routes/settings.rb index 888fa9ecb5..64c7a5eb5b 100644 --- a/config/routes/settings.rb +++ b/config/routes/settings.rb @@ -62,6 +62,7 @@ namespace :settings do resource :migration, only: [:show, :create] resource :verification, only: :show resource :privacy, only: [:show, :update], controller: 'privacy' + resource :privacy_extra, only: [:show, :update], controller: 'privacy_extra' namespace :migration do resource :redirect, only: [:new, :create, :destroy] diff --git a/config/webpacker.yml b/config/webpacker.yml index 6fd0fa1a0c..f8462e53a0 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -58,7 +58,7 @@ development: # Reference: https://webpack.js.org/configuration/dev-server/ dev_server: https: false - host: localhost + host: 0.0.0.0 port: 3035 public: localhost:3035 hmr: false diff --git a/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb b/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb index c35ad80028..3e9ab134b7 100644 --- a/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb +++ b/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb @@ -15,10 +15,22 @@ class AddUniqueIndexOnPreviewCardsStatuses < ActiveRecord::Migration[6.1] private + def supports_concurrent_reindex? + @supports_concurrent_reindex ||= begin + version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i + version >= 12_000 + end + end + def deduplicate_and_reindex! deduplicate_preview_cards! - safety_assured { execute 'REINDEX INDEX CONCURRENTLY preview_cards_statuses_pkey' } + if supports_concurrent_reindex? + safety_assured { execute 'REINDEX INDEX CONCURRENTLY preview_cards_statuses_pkey' } + else + remove_index :preview_cards_statuses, name: :preview_cards_statuses_pkey + add_index :preview_cards_statuses, [:status_id, :preview_card_id], name: :preview_cards_statuses_pkey, algorithm: :concurrently, unique: true + end rescue ActiveRecord::RecordNotUnique retry end diff --git a/dist/nginx.conf b/dist/nginx.conf index 39fa58e50d..5bb9903864 100644 --- a/dist/nginx.conf +++ b/dist/nginx.conf @@ -36,7 +36,11 @@ server { server_name example.com; ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; + + # You can use https://ssl-config.mozilla.org/ to generate your cipher set. + # We recommend their "Intermediate" level. + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; diff --git a/lib/paperclip/transcoder.rb b/lib/paperclip/transcoder.rb index 0f2e30f7d5..b88cf662c2 100644 --- a/lib/paperclip/transcoder.rb +++ b/lib/paperclip/transcoder.rb @@ -4,6 +4,9 @@ module Paperclip # This transcoder is only to be used for the MediaAttachment model # to check when uploaded videos are actually gifv's class Transcoder < Paperclip::Processor + # This is the H.264 "High" value taken from https://www.dr-lex.be/info-stuff/videocalc.html + BITS_PER_PIXEL = 0.11 + def initialize(file, options = {}, attachment = nil) super @@ -38,8 +41,11 @@ module Paperclip @output_options['vframes'] = 1 when 'mp4' unless eligible_to_passthrough?(metadata) - @output_options['acodec'] = 'aac' - @output_options['strict'] = 'experimental' + bitrate = (metadata.width * metadata.height * 30 * BITS_PER_PIXEL) / 1_000 + + @output_options['b:v'] = "#{bitrate}k" + @output_options['maxrate'] = "#{bitrate + 192}k" + @output_options['bufsize'] = "#{bitrate * 5}k" if high_vfr?(metadata) @output_options['vsync'] = 'vfr' diff --git a/package.json b/package.json index 6966b57c94..e6a880f262 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ }, "devDependencies": { "@formatjs/cli": "^6.1.1", - "@testing-library/jest-dom": "^5.16.5", + "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", "@types/babel__core": "^7.20.1", "@types/emoji-mart": "^3.0.9", diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb index 7189110833..1c60798fcf 100644 --- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/tag_controller_spec.rb @@ -5,36 +5,66 @@ require 'rails_helper' describe Api::V1::Timelines::TagController do render_views - let(:user) { Fabricate(:user) } + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') } before do allow(controller).to receive(:doorkeeper_token) { token } end - context 'with a user context' do - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } + describe 'GET #show' do + subject do + get :show, params: { id: 'test' } + end - describe 'GET #show' do - before do - PostStatusService.new.call(user.account, text: 'It is a #test') + before do + PostStatusService.new.call(user.account, text: 'It is a #test') + end + + context 'when the instance allows public preview' do + context 'when the user is not authenticated' do + let(:token) { nil } + + it 'returns http success', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(response.headers['Link'].links.size).to eq(2) + end end - it 'returns http success' do - get :show, params: { id: 'test' } - expect(response).to have_http_status(200) - expect(response.headers['Link'].links.size).to eq(2) + context 'when the user is authenticated' do + it 'returns http success', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(response.headers['Link'].links.size).to eq(2) + end end end - end - context 'without a user context' do - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil) } + context 'when the instance does not allow public preview' do + before do + Form::AdminSettings.new(timeline_preview: false).save + end - describe 'GET #show' do - it 'returns http success' do - get :show, params: { id: 'test' } - expect(response).to have_http_status(200) - expect(response.headers['Link']).to be_nil + context 'when the user is not authenticated' do + let(:token) { nil } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + + context 'when the user is authenticated' do + it 'returns http success', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(response.headers['Link'].links.size).to eq(2) + end end end end diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb index 8dc0f329b6..20770a7211 100644 --- a/spec/controllers/well_known/webfinger_controller_spec.rb +++ b/spec/controllers/well_known/webfinger_controller_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' describe WellKnown::WebfingerController do + include RoutingHelper + render_views describe 'GET #show' do @@ -167,5 +169,67 @@ describe WellKnown::WebfingerController do expect(response).to have_http_status(400) end end + + context 'when an account has an avatar' do + let(:alice) { Fabricate(:account, username: 'alice', avatar: attachment_fixture('attachment.jpg')) } + let(:resource) { alice.to_webfinger_s } + + it 'returns avatar in response' do + perform_show! + + avatar_link = get_avatar_link(body_as_json) + expect(avatar_link).to_not be_nil + expect(avatar_link[:type]).to eq alice.avatar.content_type + expect(avatar_link[:href]).to eq full_asset_url(alice.avatar) + end + + context 'with limited federation mode' do + before do + allow(Rails.configuration.x).to receive(:limited_federation_mode).and_return(true) + end + + it 'does not return avatar in response' do + perform_show! + + avatar_link = get_avatar_link(body_as_json) + expect(avatar_link).to be_nil + end + end + + context 'when enabling DISALLOW_UNAUTHENTICATED_API_ACCESS' do + around do |example| + ClimateControl.modify DISALLOW_UNAUTHENTICATED_API_ACCESS: 'true' do + example.run + end + end + + it 'does not return avatar in response' do + perform_show! + + avatar_link = get_avatar_link(body_as_json) + expect(avatar_link).to be_nil + end + end + end + + context 'when an account does not have an avatar' do + let(:alice) { Fabricate(:account, username: 'alice', avatar: nil) } + let(:resource) { alice.to_webfinger_s } + + before do + perform_show! + end + + it 'does not return avatar in response' do + avatar_link = get_avatar_link(body_as_json) + expect(avatar_link).to be_nil + end + end + end + + private + + def get_avatar_link(json) + json[:links].find { |link| link[:rel] == 'http://webfinger.net/rel/avatar' } end end diff --git a/spec/lib/search_query_parser_spec.rb b/spec/lib/search_query_parser_spec.rb new file mode 100644 index 0000000000..66b0e8f9e2 --- /dev/null +++ b/spec/lib/search_query_parser_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'parslet/rig/rspec' + +describe SearchQueryParser do + let(:parser) { described_class.new } + + context 'with term' do + it 'consumes "hello"' do + expect(parser.term).to parse('hello') + end + end + + context 'with prefix' do + it 'consumes "foo:"' do + expect(parser.prefix).to parse('foo:') + end + end + + context 'with operator' do + it 'consumes "+"' do + expect(parser.operator).to parse('+') + end + + it 'consumes "-"' do + expect(parser.operator).to parse('-') + end + end + + context 'with shortcode' do + it 'consumes ":foo:"' do + expect(parser.shortcode).to parse(':foo:') + end + end + + context 'with phrase' do + it 'consumes "hello world"' do + expect(parser.phrase).to parse('"hello world"') + end + end + + context 'with clause' do + it 'consumes "foo"' do + expect(parser.clause).to parse('foo') + end + + it 'consumes "-foo"' do + expect(parser.clause).to parse('-foo') + end + + it 'consumes "foo:bar"' do + expect(parser.clause).to parse('foo:bar') + end + + it 'consumes "-foo:bar"' do + expect(parser.clause).to parse('-foo:bar') + end + + it 'consumes \'foo:"hello world"\'' do + expect(parser.clause).to parse('foo:"hello world"') + end + + it 'consumes \'-foo:"hello world"\'' do + expect(parser.clause).to parse('-foo:"hello world"') + end + + it 'consumes "foo:"' do + expect(parser.clause).to parse('foo:') + end + + it 'consumes \'"\'' do + expect(parser.clause).to parse('"') + end + end + + context 'with query' do + it 'consumes "hello -world"' do + expect(parser.query).to parse('hello -world') + end + + it 'consumes \'foo "hello world"\'' do + expect(parser.query).to parse('foo "hello world"') + end + + it 'consumes "foo:bar hello"' do + expect(parser.query).to parse('foo:bar hello') + end + + it 'consumes \'"hello" world "\'' do + expect(parser.query).to parse('"hello" world "') + end + + it 'consumes "foo:bar bar: hello"' do + expect(parser.query).to parse('foo:bar bar: hello') + end + end +end diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb index 953f9acb2f..17f06d2833 100644 --- a/spec/lib/search_query_transformer_spec.rb +++ b/spec/lib/search_query_transformer_spec.rb @@ -3,16 +3,57 @@ require 'rails_helper' describe SearchQueryTransformer do - describe 'initialization' do - let(:parser) { SearchQueryParser.new.parse('query') } + subject { described_class.new.apply(parser, current_account: nil) } - it 'sets attributes' do - transformer = described_class.new.apply(parser) + let(:parser) { SearchQueryParser.new.parse(query) } - expect(transformer.should_clauses.first).to be_nil - expect(transformer.must_clauses.first).to be_a(SearchQueryTransformer::TermClause) - expect(transformer.must_not_clauses.first).to be_nil - expect(transformer.filter_clauses.first).to be_nil + context 'with "hello world"' do + let(:query) { 'hello world' } + + it 'transforms clauses' do + expect(subject.must_clauses.map(&:term)).to match_array %w(hello world) + expect(subject.must_not_clauses).to be_empty + expect(subject.filter_clauses).to be_empty + end + end + + context 'with "hello -world"' do + let(:query) { 'hello -world' } + + it 'transforms clauses' do + expect(subject.must_clauses.map(&:term)).to match_array %w(hello) + expect(subject.must_not_clauses.map(&:term)).to match_array %w(world) + expect(subject.filter_clauses).to be_empty + end + end + + context 'with "hello is:reply"' do + let(:query) { 'hello is:reply' } + + it 'transforms clauses' do + expect(subject.must_clauses.map(&:term)).to match_array %w(hello) + expect(subject.must_not_clauses).to be_empty + expect(subject.filter_clauses.map(&:term)).to match_array %w(reply) + end + end + + context 'with "foo: bar"' do + let(:query) { 'foo: bar' } + + it 'transforms clauses' do + expect(subject.must_clauses.map(&:term)).to match_array %w(foo bar) + expect(subject.must_not_clauses).to be_empty + expect(subject.filter_clauses).to be_empty + end + end + + context 'with "foo:bar"' do + let(:query) { 'foo:bar' } + + it 'transforms clauses' do + expect(subject.must_clauses.map(&:term)).to contain_exactly('foo bar') + expect(subject.must_not_clauses).to be_empty + expect(subject.filter_clauses).to be_empty end end end diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index 123bd4f560..cb69af5f54 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -83,15 +83,6 @@ describe SearchService, type: :service do expect(Tag).to have_received(:search_for).with('tag', 10, 0, exclude_unreviewed: nil) expect(results).to eq empty_results.merge(hashtags: [tag]) end - - it 'does not include tag when starts with @ character' do - query = '@username' - allow(Tag).to receive(:search_for) - - results = subject.call(query, nil, 10) - expect(Tag).to_not have_received(:search_for) - expect(results).to eq empty_results - end end end end diff --git a/streaming/index.js b/streaming/index.js index 7bd3ddb0a7..8b16bfe533 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -763,6 +763,11 @@ const startServer = async () => { const listener = message => { const { event, payload } = message; + // reference_texts property is not working if ProcessReferencesWorker is + // used on PostStatusService and so on. (Asynchronous processing) + const reference_texts = payload.reference_texts || []; + delete payload.reference_texts; + // Streaming only needs to apply filtering to some channels and only to // some events. This is because majority of the filtering happens on the // Ruby on Rails side when producing the event for streaming. @@ -908,7 +913,7 @@ const startServer = async () => { if (req.cachedFilters) { const status = payload; // TODO: Calculate searchableContent in Ruby on Rails: - const searchableContent = ([status.spoiler_text || '', status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); + const searchableContent = ([status.spoiler_text || '', status.content, ...(reference_texts || [])].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); const searchableTextContent = JSDOM.fragment(searchableContent).textContent; const now = new Date(); diff --git a/yarn.lock b/yarn.lock index 09ee606799..2ffc5b1c45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,7 +29,7 @@ jsonpointer "^5.0.0" leven "^3.1.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.10": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== @@ -44,7 +44,15 @@ dependencies: "@babel/highlight" "^7.22.5" -"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": +"@babel/code-frame@^7.22.5": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== @@ -113,7 +121,7 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.22.11": +"@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz#4078686740459eeb4af3494a273ac09148dfb213" integrity sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ== @@ -128,21 +136,6 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz#dd2612d59eac45588021ac3d6fa976d08f4e95a3" - integrity sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - semver "^6.3.1" - "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" @@ -292,7 +285,16 @@ "@babel/traverse" "^7.22.11" "@babel/types" "^7.22.11" -"@babel/highlight@^7.22.10", "@babel/highlight@^7.22.5": +"@babel/highlight@^7.22.10", "@babel/highlight@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" + integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== + dependencies: + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/highlight@^7.22.5": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== @@ -306,11 +308,16 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55" integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ== -"@babel/parser@^7.22.11", "@babel/parser@^7.22.5": +"@babel/parser@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.11.tgz#becf8ee33aad2a35ed5607f521fe6e72a615f905" integrity sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g== +"@babel/parser@^7.22.5": + version "7.22.14" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.14.tgz#c7de58e8de106e88efca42ce17f0033209dfd245" + integrity sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" @@ -487,10 +494,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-generator-functions@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz#45946cd17f915b10e65c29b8ed18a0a50fc648c8" - integrity sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g== +"@babel/plugin-transform-async-generator-functions@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.11.tgz#dbe3b1ff5a52e2e5edc4b19a60d325a675ed2649" + integrity sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" @@ -528,12 +535,12 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-class-static-block@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" - integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== +"@babel/plugin-transform-class-static-block@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" + integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -582,10 +589,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dynamic-import@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" - integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== +"@babel/plugin-transform-dynamic-import@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" + integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" @@ -598,10 +605,10 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-export-namespace-from@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" - integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== +"@babel/plugin-transform-export-namespace-from@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" + integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" @@ -622,10 +629,10 @@ "@babel/helper-function-name" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-json-strings@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" - integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== +"@babel/plugin-transform-json-strings@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" + integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" @@ -637,10 +644,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-logical-assignment-operators@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" - integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== +"@babel/plugin-transform-logical-assignment-operators@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" + integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -669,22 +676,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-commonjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" - integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== - dependencies: - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" - -"@babel/plugin-transform-modules-systemjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" - integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== +"@babel/plugin-transform-modules-systemjs@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz#3386be5875d316493b517207e8f1931d93154bb1" + integrity sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA== dependencies: "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.5" @@ -711,7 +709,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.3", "@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.22.3": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg== @@ -719,21 +717,21 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" - integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== +"@babel/plugin-transform-numeric-separator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" + integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" - integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== +"@babel/plugin-transform-object-rest-spread@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.11.tgz#dbbb06ce783cd994a8f430d8cefa553e9b42ca62" + integrity sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw== dependencies: - "@babel/compat-data" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.10" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.22.5" @@ -746,18 +744,18 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.5" -"@babel/plugin-transform-optional-catch-binding@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" - integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== +"@babel/plugin-transform-optional-catch-binding@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" + integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.22.10", "@babel/plugin-transform-optional-chaining@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz#076d28a7e074392e840d4ae587d83445bac0372a" - integrity sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g== +"@babel/plugin-transform-optional-chaining@^7.22.12", "@babel/plugin-transform-optional-chaining@^7.22.5": + version "7.22.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.12.tgz#d7ebf6a88cd2f4d307b0e000ab630acd8124b333" + integrity sha512-7XXCVqZtyFWqjDsYDY4T45w4mlx1rf7aOgkc/Ww76xkgBiOlmjPkx36PBLHa1k1rwWvVgYMPsbuVnIamx2ZQJw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -778,13 +776,13 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-property-in-object@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" - integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== +"@babel/plugin-transform-private-property-in-object@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" + integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" @@ -948,9 +946,9 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.22.4": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.10.tgz#3263b9fe2c8823d191d28e61eac60a79f9ce8a0f" - integrity sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A== + version "7.22.14" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.14.tgz#1cbb468d899f64fa71c53446f13b7ff8c0005cc1" + integrity sha512-daodMIoVo+ol/g+//c/AH+szBkFj4STQUikvBijRGL72Ph+w+AMTSh55DUETe8KJlPlDT1k/mp7NBfOuiWmoig== dependencies: "@babel/compat-data" "^7.22.9" "@babel/helper-compilation-targets" "^7.22.10" @@ -978,41 +976,41 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.10" + "@babel/plugin-transform-async-generator-functions" "^7.22.11" "@babel/plugin-transform-async-to-generator" "^7.22.5" "@babel/plugin-transform-block-scoped-functions" "^7.22.5" "@babel/plugin-transform-block-scoping" "^7.22.10" "@babel/plugin-transform-class-properties" "^7.22.5" - "@babel/plugin-transform-class-static-block" "^7.22.5" + "@babel/plugin-transform-class-static-block" "^7.22.11" "@babel/plugin-transform-classes" "^7.22.6" "@babel/plugin-transform-computed-properties" "^7.22.5" "@babel/plugin-transform-destructuring" "^7.22.10" "@babel/plugin-transform-dotall-regex" "^7.22.5" "@babel/plugin-transform-duplicate-keys" "^7.22.5" - "@babel/plugin-transform-dynamic-import" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.11" "@babel/plugin-transform-exponentiation-operator" "^7.22.5" - "@babel/plugin-transform-export-namespace-from" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.11" "@babel/plugin-transform-for-of" "^7.22.5" "@babel/plugin-transform-function-name" "^7.22.5" - "@babel/plugin-transform-json-strings" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.11" "@babel/plugin-transform-literals" "^7.22.5" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" "@babel/plugin-transform-member-expression-literals" "^7.22.5" "@babel/plugin-transform-modules-amd" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.11" + "@babel/plugin-transform-modules-systemjs" "^7.22.11" "@babel/plugin-transform-modules-umd" "^7.22.5" "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" "@babel/plugin-transform-new-target" "^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" - "@babel/plugin-transform-numeric-separator" "^7.22.5" - "@babel/plugin-transform-object-rest-spread" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-numeric-separator" "^7.22.11" + "@babel/plugin-transform-object-rest-spread" "^7.22.11" "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.10" + "@babel/plugin-transform-optional-catch-binding" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.22.12" "@babel/plugin-transform-parameters" "^7.22.5" "@babel/plugin-transform-private-methods" "^7.22.5" - "@babel/plugin-transform-private-property-in-object" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" "@babel/plugin-transform-property-literals" "^7.22.5" "@babel/plugin-transform-regenerator" "^7.22.10" "@babel/plugin-transform-reserved-words" "^7.22.5" @@ -1026,7 +1024,7 @@ "@babel/plugin-transform-unicode-regex" "^7.22.5" "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "0.1.6-no-external-plugins" - "@babel/types" "^7.22.10" + "@babel/types" "^7.22.11" babel-plugin-polyfill-corejs2 "^0.4.5" babel-plugin-polyfill-corejs3 "^0.8.3" babel-plugin-polyfill-regenerator "^0.5.2" @@ -1125,7 +1123,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.3.3": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03" integrity sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg== @@ -1143,7 +1141,7 @@ "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" -"@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.22.5": +"@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.22.5", "@babel/types@^7.4.4": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.11.tgz#0e65a6a1d4d9cbaa892b2213f6159485fe632ea2" integrity sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg== @@ -1287,9 +1285,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" - integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== + version "4.8.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.0.tgz#11195513186f68d42fbf449f9a7136b2c0c92005" + integrity sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg== "@eslint/eslintrc@^2.1.2": version "2.1.2" @@ -1306,10 +1304,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d" - integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og== +"@eslint/js@8.48.0": + version "8.48.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb" + integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw== "@floating-ui/core@^1.3.1": version "1.3.1" @@ -1538,12 +1536,12 @@ "@types/node" "*" jest-mock "^29.6.2" -"@jest/expect-utils@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.2.tgz#1b97f290d0185d264dd9fdec7567a14a38a90534" - integrity sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg== +"@jest/expect-utils@^29.6.2", "@jest/expect-utils@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.4.tgz#17c7dfe6cec106441f218b0aff4b295f98346679" + integrity sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg== dependencies: - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" "@jest/expect@^29.6.2": version "29.6.2" @@ -1605,10 +1603,10 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" - integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: "@sinclair/typebox" "^0.27.8" @@ -1662,12 +1660,12 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" - integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== +"@jest/types@^29.6.1", "@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^29.6.0" + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -2022,14 +2020,13 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.5": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz#5e97c8f9a15ccf4656da00fecab505728de81e0c" - integrity sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg== +"@testing-library/jest-dom@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.0.0.tgz#d2ba5a3fd13724d5966b3f8cd24d2cedcab4fa76" + integrity sha512-Ye2R3+/oM27jir8CzYPmuWdavTaKwNZcu0d22L9pO/vnOYE0wmrtpw79TQJa8H6gV8/i7yd+pLaqeLlA0rTMfg== dependencies: "@adobe/css-tools" "^4.0.1" "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" @@ -2225,7 +2222,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@^29.5.2": +"@types/jest@^29.5.2": version "29.5.3" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.3.tgz#7a35dc0044ffb8b56325c6802a4781a626b05777" integrity sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA== @@ -2288,9 +2285,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*": - version "20.4.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.9.tgz#c7164e0f8d3f12dfae336af0b1f7fdec8c6b204f" - integrity sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ== + version "20.5.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.7.tgz#4b8ecac87fbefbc92f431d09c30e176fc0a7c377" + integrity sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA== "@types/node@14 || 16 || 17": version "17.0.45" @@ -2476,9 +2473,9 @@ "@types/react" "*" "@types/react@*", "@types/react@16 || 17 || 18", "@types/react@>=16.9.11", "@types/react@^18.0.26", "@types/react@^18.2.7": - version "18.2.20" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.20.tgz#1605557a83df5c8a2cc4eeb743b3dfc0eb6aaeb2" - integrity sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw== + version "18.2.21" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.21.tgz#774c37fd01b522d0b91aed04811b58e4e0514ed9" + integrity sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2545,13 +2542,6 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== -"@types/testing-library__jest-dom@^5.9.1": - version "5.14.9" - resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz#0fb1e6a0278d87b6737db55af5967570b67cb466" - integrity sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw== - dependencies: - "@types/jest" "*" - "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" @@ -2618,15 +2608,15 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^6.0.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.0.tgz#53428b616f7d80fe879f45a08f11cc0f0b62cf13" - integrity sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg== + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.1.tgz#bc0c6f000134b53c304ad0bec4ee4753cd3e89d2" + integrity sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.4.0" - "@typescript-eslint/type-utils" "6.4.0" - "@typescript-eslint/utils" "6.4.0" - "@typescript-eslint/visitor-keys" "6.4.0" + "@typescript-eslint/scope-manager" "6.4.1" + "@typescript-eslint/type-utils" "6.4.1" + "@typescript-eslint/utils" "6.4.1" + "@typescript-eslint/visitor-keys" "6.4.1" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -2635,31 +2625,31 @@ ts-api-utils "^1.0.1" "@typescript-eslint/parser@^6.0.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.4.0.tgz#47e7c6e22ff1248e8675d95f488890484de67600" - integrity sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg== + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.4.1.tgz#85ad550bf4ac4aa227504f1becb828f8e46c44e3" + integrity sha512-610G6KHymg9V7EqOaNBMtD1GgpAmGROsmfHJPXNLCU9bfIuLrkdOygltK784F6Crboyd5tBFayPB7Sf0McrQwg== dependencies: - "@typescript-eslint/scope-manager" "6.4.0" - "@typescript-eslint/types" "6.4.0" - "@typescript-eslint/typescript-estree" "6.4.0" - "@typescript-eslint/visitor-keys" "6.4.0" + "@typescript-eslint/scope-manager" "6.4.1" + "@typescript-eslint/types" "6.4.1" + "@typescript-eslint/typescript-estree" "6.4.1" + "@typescript-eslint/visitor-keys" "6.4.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.4.0.tgz#3048e4262ba3eafa4e2e69b08912d9037ec646ae" - integrity sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig== +"@typescript-eslint/scope-manager@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz#4b073a30be2dbe603e44e9ae0cff7e1d3ed19278" + integrity sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A== dependencies: - "@typescript-eslint/types" "6.4.0" - "@typescript-eslint/visitor-keys" "6.4.0" + "@typescript-eslint/types" "6.4.1" + "@typescript-eslint/visitor-keys" "6.4.1" -"@typescript-eslint/type-utils@6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.4.0.tgz#c8ac92716ed6a9d5443aa3e342910355b0796ba0" - integrity sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg== +"@typescript-eslint/type-utils@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.4.1.tgz#fa21cb13016c8d6f352fe9b2d6c9ab6edc2d1857" + integrity sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA== dependencies: - "@typescript-eslint/typescript-estree" "6.4.0" - "@typescript-eslint/utils" "6.4.0" + "@typescript-eslint/typescript-estree" "6.4.1" + "@typescript-eslint/utils" "6.4.1" debug "^4.3.4" ts-api-utils "^1.0.1" @@ -2668,10 +2658,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA== -"@typescript-eslint/types@6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.4.0.tgz#5b109a59a805f0d8d375895e42d9e5f0037f66ee" - integrity sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg== +"@typescript-eslint/types@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.4.1.tgz#b2c61159f46dda210fed9f117f5d027f65bb5c3b" + integrity sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg== "@typescript-eslint/typescript-estree@5.59.0": version "5.59.0" @@ -2686,30 +2676,30 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.0.tgz#3c58d20632db93fec3d6ab902acbedf593d37276" - integrity sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA== +"@typescript-eslint/typescript-estree@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.1.tgz#91ff88101c710adb0f70a317f2f65efa9441da45" + integrity sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg== dependencies: - "@typescript-eslint/types" "6.4.0" - "@typescript-eslint/visitor-keys" "6.4.0" + "@typescript-eslint/types" "6.4.1" + "@typescript-eslint/visitor-keys" "6.4.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.4.0.tgz#23e996b693603c5924b1fbb733cc73196256baa5" - integrity sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw== +"@typescript-eslint/utils@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.4.1.tgz#81bf62ff0c3119a26c19fab683582e29450717bc" + integrity sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.4.0" - "@typescript-eslint/types" "6.4.0" - "@typescript-eslint/typescript-estree" "6.4.0" + "@typescript-eslint/scope-manager" "6.4.1" + "@typescript-eslint/types" "6.4.1" + "@typescript-eslint/typescript-estree" "6.4.1" semver "^7.5.4" "@typescript-eslint/visitor-keys@5.59.0": @@ -2720,12 +2710,12 @@ "@typescript-eslint/types" "5.59.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.0.tgz#96a426cdb1add28274abd7a34aefe27f8b7d51ef" - integrity sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA== +"@typescript-eslint/visitor-keys@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.1.tgz#e3ccf7b8d42e625946ac5094ed92a405fb4115e0" + integrity sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ== dependencies: - "@typescript-eslint/types" "6.4.0" + "@typescript-eslint/types" "6.4.1" eslint-visitor-keys "^3.4.1" "@webassemblyjs/ast@1.9.0": @@ -2931,16 +2921,16 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^8.0.4, acorn@^8.8.2: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - -acorn@^8.1.0, acorn@^8.8.1, acorn@^8.9.0: +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.8.1, acorn@^8.9.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.8.2: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -3362,9 +3352,9 @@ axe-core@^4.6.2: integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== axios@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" - integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== + version "1.5.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.0.tgz#f02e4af823e2e46a9768cfc74691fdd0517ea267" + integrity sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -3950,9 +3940,9 @@ caniuse-lite@^1.0.30001502: integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA== caniuse-lite@^1.0.30001517: - version "1.0.30001522" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz#44b87a406c901269adcdb834713e23582dd71856" - integrity sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg== + version "1.0.30001524" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz#1e14bce4f43c41a7deaeb5ebfe86664fe8dadb80" + integrity sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA== caniuse-lite@^1.0.30001520: version "1.0.30001520" @@ -4353,11 +4343,11 @@ copy-descriptor@^0.1.0: integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== core-js-compat@^3.31.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.0.tgz#f41574b6893ab15ddb0ac1693681bd56c8550a90" - integrity sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw== + version "3.32.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.1.tgz#55f9a7d297c0761a8eb1d31b593e0f5b6ffae964" + integrity sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA== dependencies: - browserslist "^4.21.9" + browserslist "^4.21.10" core-js@^2.5.0: version "2.6.12" @@ -4939,10 +4929,10 @@ detect-passive-events@^2.0.3: dependencies: detect-it "^4.0.1" -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diffie-hellman@^5.0.0: version "5.0.3" @@ -5110,9 +5100,9 @@ electron-to-chromium@^1.4.428: integrity sha512-/g3UyNDmDd6ebeWapmAoiyy+Sy2HyJ+/X8KyvNeHfKRFfHaA2W8oF5fxD5F3tjBDcjpwo0iek6YNgxNXDBoEtA== electron-to-chromium@^1.4.477: - version "1.4.500" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.500.tgz#7dd05fdfbe02ed34b9f6099cfe01407b473d5af7" - integrity sha512-P38NO8eOuWOKY1sQk5yE0crNtrjgjJj6r3NrbIKtG18KzCHmHE2Bt+aQA7/y0w3uYsHWxDa6icOohzjLJ4vJ4A== + version "1.4.505" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.505.tgz#00571ade5975b58413f0f56a665b065bfc29cdfc" + integrity sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ== elliptic@^6.5.3: version "6.5.4" @@ -5545,14 +5535,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.41.0: - version "8.47.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.47.0.tgz#c95f9b935463fb4fad7005e626c7621052e90806" - integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q== + version "8.48.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155" + integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "^8.47.0" + "@eslint/js" "8.48.0" "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -5751,7 +5741,18 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect@^29.0.0, expect@^29.6.2: +expect@^29.0.0: + version "29.6.4" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.4.tgz#a6e6f66d4613717859b2fe3da98a739437b6f4b8" + integrity sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA== + dependencies: + "@jest/expect-utils" "^29.6.4" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.6.4" + jest-message-util "^29.6.3" + jest-util "^29.6.3" + +expect@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521" integrity sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA== @@ -6007,14 +6008,15 @@ findup-sync@^3.0.0: resolve-dir "^1.0.1" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" + integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== dependencies: - flatted "^3.1.0" + flatted "^3.2.7" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: +flatted@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== @@ -6244,9 +6246,9 @@ glob-parent@^6.0.2: is-glob "^4.0.3" glob@^10.2.5, glob@^10.2.6: - version "10.3.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.3.tgz#8360a4ffdd6ed90df84aa8d52f21f452e86a123b" - integrity sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw== + version "10.3.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.4.tgz#c85c9c7ab98669102b6defda76d35c5b1ef9766f" + integrity sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ== dependencies: foreground-child "^3.1.0" jackspeak "^2.0.3" @@ -7349,9 +7351,9 @@ iterator.prototype@^1.1.0: reflect.getprototypeof "^1.0.3" jackspeak@^2.0.3: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.1.tgz#655e8cf025d872c9c03d3eb63e8f0c024fef16a6" - integrity sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw== + version "2.3.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.1.tgz#ce2effa4c458e053640e61938865a5b5fae98456" + integrity sha512-4iSY3Bh1Htv+kLhiiZunUhQ+OYXIn0ze3ulq8JeWrFKmhPAJSySV2+kdtRh2pGcCeF0s6oR8Oc+pYZynJj4t8A== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -7447,15 +7449,15 @@ jest-config@^29.6.2: slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" - integrity sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA== +jest-diff@^29.6.2, jest-diff@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.4.tgz#85aaa6c92a79ae8cd9a54ebae8d5b6d9a513314a" + integrity sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw== dependencies: chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.6.2" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.6.3" jest-docblock@^29.4.3: version "29.4.3" @@ -7501,10 +7503,10 @@ jest-environment-node@^29.6.2: jest-mock "^29.6.2" jest-util "^29.6.2" -jest-get-type@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" - integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.4.3, jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== jest-haste-map@^29.6.2: version "29.6.2" @@ -7533,28 +7535,28 @@ jest-leak-detector@^29.6.2: jest-get-type "^29.4.3" pretty-format "^29.6.2" -jest-matcher-utils@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535" - integrity sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ== +jest-matcher-utils@^29.6.2, jest-matcher-utils@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz#327db7ababea49455df3b23e5d6109fe0c709d24" + integrity sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ== dependencies: chalk "^4.0.0" - jest-diff "^29.6.2" - jest-get-type "^29.4.3" - pretty-format "^29.6.2" + jest-diff "^29.6.4" + jest-get-type "^29.6.3" + pretty-format "^29.6.3" -jest-message-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.2.tgz#af7adc2209c552f3f5ae31e77cf0a261f23dc2bb" - integrity sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ== +jest-message-util@^29.6.2, jest-message-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.3.tgz#bce16050d86801b165f20cfde34dc01d3cf85fbf" + integrity sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.6.2" + pretty-format "^29.6.3" slash "^3.0.0" stack-utils "^2.0.3" @@ -7681,12 +7683,12 @@ jest-snapshot@^29.6.2: pretty-format "^29.6.2" semver "^7.5.3" -jest-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" - integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== +jest-util@^29.6.2, jest-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.3.tgz#e15c3eac8716440d1ed076f09bc63ace1aebca63" + integrity sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" @@ -7860,6 +7862,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -7951,6 +7958,13 @@ keycode@^2.1.7: resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff" integrity sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg== +keyv@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== + dependencies: + json-buffer "3.0.1" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -8101,6 +8115,16 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + lodash.get@^4.0: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -8111,6 +8135,11 @@ lodash.has@^4.0: resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" integrity sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g== +lodash.invokemap@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62" + integrity sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w== + lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" @@ -8136,6 +8165,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.pullall@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba" + integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -8151,6 +8185,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== + lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -8193,9 +8232,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" "lru-cache@^9.1.1 || ^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" - integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== lz-string@^1.5.0: version "1.5.0" @@ -8532,9 +8571,9 @@ minipass@^5.0.0: integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== "minipass@^5.0.0 || ^6.0.2 || ^7.0.0": - version "7.0.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e" - integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA== + version "7.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" + integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== minizlib@^2.1.1: version "2.1.2" @@ -9631,9 +9670,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.2.15, postcss@^8.4.24, postcss@^8.4.25: - version "8.4.28" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.28.tgz#c6cc681ed00109072816e1557f889ef51cf950a5" - integrity sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw== + version "8.4.29" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" + integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -9719,12 +9758,12 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^29.0.0, pretty-format@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47" - integrity sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg== +pretty-format@^29.0.0, pretty-format@^29.6.2, pretty-format@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7" + integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw== dependencies: - "@jest/schemas" "^29.6.0" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -10941,14 +10980,14 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -sirv@^1.0.7: - version "1.0.19" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" - integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== +sirv@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" + integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== dependencies: "@polka/url" "^1.0.0-next.20" mrmime "^1.0.0" - totalist "^1.0.0" + totalist "^3.0.0" sisteransi@^1.0.5: version "1.0.5" @@ -11381,7 +11420,6 @@ stringz@^2.1.0: char-regex "^1.0.2" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11868,10 +11906,10 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -totalist@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" - integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== tough-cookie@^4.1.2: version "4.1.3" @@ -12429,19 +12467,26 @@ webpack-assets-manifest@^4.0.6: webpack-sources "^1.0" webpack-bundle-analyzer@^4.8.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz#fc093c4ab174fd3dcbd1c30b763f56d10141209d" - integrity sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw== + version "4.9.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d" + integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w== dependencies: "@discoveryjs/json-ext" "0.5.7" acorn "^8.0.4" acorn-walk "^8.0.0" - chalk "^4.1.0" commander "^7.2.0" + escape-string-regexp "^4.0.0" gzip-size "^6.0.0" - lodash "^4.17.20" + is-plain-object "^5.0.0" + lodash.debounce "^4.0.8" + lodash.escape "^4.0.1" + lodash.flatten "^4.4.0" + lodash.invokemap "^4.6.0" + lodash.pullall "^4.2.0" + lodash.uniqby "^4.7.0" opener "^1.5.2" - sirv "^1.0.7" + picocolors "^1.0.0" + sirv "^2.0.3" ws "^7.3.1" webpack-cli@^3.3.12: