diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index f991036add4a3d..b3b1d97a241b7b 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 00000000000000..ca9156fdaa4bf1
--- /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 ce14169aae82a8..fa8d6542c18aec 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 a2658ea8ba16f5..20aecd71d6839f 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 1b15d19885d99b..a1aeddf201b04d 100644
--- a/.github/workflows/build-container-image.yml
+++ b/.github/workflows/build-container-image.yml
@@ -8,7 +8,9 @@ on:
type: boolean
push_to_images:
type: string
- version_suffix:
+ version_prerelease:
+ type: string
+ version_metadata:
type: string
flavor:
type: string
@@ -74,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 }}
@@ -83,7 +83,9 @@ jobs:
- uses: docker/build-push-action@v4
with:
context: .
- build-args: MASTODON_VERSION_SUFFIX=${{ inputs.version_suffix }}
+ 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-nightly.yml b/.github/workflows/build-nightly.yml
index a3ae671d67b852..2600cf924b4838 100644
--- a/.github/workflows/build-nightly.yml
+++ b/.github/workflows/build-nightly.yml
@@ -16,9 +16,9 @@ jobs:
env:
TZ: Etc/UTC
run: |
- echo mastodon_version_suffix=nightly.$(date +'%Y-%m-%d')>> $GITHUB_OUTPUT
+ echo mastodon_version_prerelease=nightly.$(date +'%Y-%m-%d')>> $GITHUB_OUTPUT
outputs:
- suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }}
+ prerelease: ${{ steps.version_vars.outputs.mastodon_version_prerelease }}
build-image:
needs: compute-suffix
@@ -28,8 +28,7 @@ jobs:
use_native_arm64_builder: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon
- # The `-` is important here, result will be v4.1.2-nightly.2022-03-05
- version_suffix: -${{ needs.compute-suffix.outputs.suffix }}
+ version_prerelease: ${{ needs.compute-suffix.outputs.prerelease }}
labels: |
org.opencontainers.image.description=Nightly build image used for testing purposes
flavor: |
@@ -37,5 +36,5 @@ jobs:
tags: |
type=raw,value=edge
type=raw,value=nightly
- type=schedule,pattern=${{ needs.compute-suffix.outputs.suffix }}
+ type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit
diff --git a/.github/workflows/build-push-pr.yml b/.github/workflows/build-push-pr.yml
index f78e5ebada6f95..cf11d66533c895 100644
--- a/.github/workflows/build-push-pr.yml
+++ b/.github/workflows/build-push-pr.yml
@@ -21,9 +21,9 @@ jobs:
uses: actions/checkout@v3
- id: version_vars
run: |
- echo mastodon_version_suffix=+pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
+ echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
outputs:
- suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }}
+ metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
build-image:
needs: compute-suffix
@@ -33,7 +33,7 @@ jobs:
use_native_arm64_builder: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/mastodon
- version_suffix: ${{ needs.compute-suffix.outputs.suffix }}
+ version_metadata: ${{ needs.compute-suffix.outputs.metadata }}
flavor: |
latest=auto
tags: |
diff --git a/.github/workflows/build-releases.yml b/.github/workflows/build-releases.yml
index fa923e96063ba7..b9728f6a230883 100644
--- a/.github/workflows/build-releases.yml
+++ b/.github/workflows/build-releases.yml
@@ -16,6 +16,8 @@ jobs:
use_native_arm64_builder: false
push_to_images: |
ghcr.io/${{ github.repository_owner }}/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/CHANGELOG.md b/CHANGELOG.md
index 107dfaca3f29d2..fe66adc08a3532 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -101,7 +101,7 @@ The following changelog entries focus on changes visible to users, administrator
- **Change translation feature to cover Content Warnings, poll options and media descriptions** ([c960657](https://github.com/mastodon/mastodon/pull/24175), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25251), [c960657](https://github.com/mastodon/mastodon/pull/26168), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26452))
- **Change account search to match by text when opted-in** ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25599), [Gargron](https://github.com/mastodon/mastodon/pull/26378))
- **Change import feature to be clearer, less error-prone and more reliable** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21054), [mgmn](https://github.com/mastodon/mastodon/pull/24874))
-- **Change local and federated timelines to be in a single “Live feeds” column** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25641), [Gargron](https://github.com/mastodon/mastodon/pull/25683), [mgmn](https://github.com/mastodon/mastodon/pull/25694), [Plastikmensch](https://github.com/mastodon/mastodon/pull/26247))
+- **Change local and federated timelines to be tabs of a single “Live feeds” column** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25641), [Gargron](https://github.com/mastodon/mastodon/pull/25683), [mgmn](https://github.com/mastodon/mastodon/pull/25694), [Plastikmensch](https://github.com/mastodon/mastodon/pull/26247))
- **Change user archive export to be faster and more reliable, and export `.zip` archives instead of `.tar.gz` ones** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23360), [TheEssem](https://github.com/mastodon/mastodon/pull/25034))
- **Change `mastodon-streaming` systemd unit files to be templated** ([e-nomem](https://github.com/mastodon/mastodon/pull/24751))
- **Change `statsd` integration to disable sidekiq metrics by default** ([mjankowski](https://github.com/mastodon/mastodon/pull/25265), [mjankowski](https://github.com/mastodon/mastodon/pull/25336), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26310))
@@ -189,6 +189,7 @@ The following changelog entries focus on changes visible to users, administrator
- **Fix log-in flow when involving both OAuth and external authentication** ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24073))
- **Fix broken links in account gallery** ([c960657](https://github.com/mastodon/mastodon/pull/24218))
- **Fix blocking subdomains of an already-blocked domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26392))
+- **Fix migration handler not updating lists** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24808))
- Fix uploading of video files for which `ffprobe` reports `0/0` average framerate ([NicolaiSoeborg](https://github.com/mastodon/mastodon/pull/26500))
- Fix cached posts including stale stats ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26409))
- Fix adding column with default value taking longer on Postgres >= 11 ([Gargron](https://github.com/mastodon/mastodon/pull/26375))
diff --git a/Dockerfile b/Dockerfile
index cb5096581cb67c..cdabc4c7c826d5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -42,8 +42,8 @@ RUN apt-get update && \
FROM node:${NODE_VERSION}
# Use those args to specify your own version flags & suffixes
-ARG MASTODON_VERSION_FLAGS=""
-ARG MASTODON_VERSION_SUFFIX=""
+ARG MASTODON_VERSION_PRERELEASE=""
+ARG MASTODON_VERSION_METADATA=""
ARG UID="991"
ARG GID="991"
@@ -89,8 +89,8 @@ ENV RAILS_ENV="production" \
NODE_ENV="production" \
RAILS_SERVE_STATIC_FILES="true" \
BIND="0.0.0.0" \
- MASTODON_VERSION_FLAGS="${MASTODON_VERSION_FLAGS}" \
- MASTODON_VERSION_SUFFIX="${MASTODON_VERSION_SUFFIX}"
+ MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
+ MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}"
# Set the run user
USER mastodon
diff --git a/Gemfile.lock b/Gemfile.lock
index f801b1b5600327..4e30c42222af64 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)
@@ -731,7 +731,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)
@@ -795,7 +795,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 ba04fb661b344e..fbb2c2de23c547 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 1f8571c09d3b7b..00db257ac711db 100644
--- a/app/chewy/accounts_index.rb
+++ b/app/chewy/accounts_index.rb
@@ -21,12 +21,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
),
@@ -62,6 +63,6 @@ class AccountsIndex < Chewy::Index
field(:last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at })
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
- field(:text, type: 'text', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }
+ field(:text, type: 'text', analyzer: 'verbatim', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }
end
end
diff --git a/app/chewy/public_statuses_index.rb b/app/chewy/public_statuses_index.rb
new file mode 100644
index 00000000000000..5c68a136580092
--- /dev/null
+++ b/app/chewy/public_statuses_index.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+class PublicStatusesIndex < Chewy::Index
+ settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
+ filter: {
+ english_stop: {
+ type: 'stop',
+ stopwords: '_english_',
+ },
+
+ english_stemmer: {
+ type: 'stemmer',
+ language: 'english',
+ },
+
+ english_possessive_stemmer: {
+ type: 'stemmer',
+ language: 'possessive_english',
+ },
+ },
+
+ analyzer: {
+ verbatim: {
+ tokenizer: 'uax_url_email',
+ filter: %w(lowercase),
+ },
+
+ content: {
+ tokenizer: 'standard',
+ filter: %w(
+ lowercase
+ asciifolding
+ cjk_width
+ elision
+ english_possessive_stemmer
+ english_stop
+ english_stemmer
+ ),
+ },
+ },
+ }
+
+ index_scope ::Status.unscoped
+ .kept
+ .indexable
+ .includes(:media_attachments, :preloadable_poll, :preview_cards)
+
+ root date_detection: false do
+ field(:id, type: 'long')
+ field(:account_id, type: 'long')
+ field(:text, type: 'text', analyzer: 'verbatim', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'content') }
+ field(:language, type: 'keyword')
+ field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
+ field(:created_at, type: 'date')
+ end
+end
diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
index 9f680efa52d5e3..2be7e45250c363 100644
--- a/app/chewy/statuses_index.rb
+++ b/app/chewy/statuses_index.rb
@@ -1,31 +1,38 @@
# frozen_string_literal: true
class StatusesIndex < Chewy::Index
- include FormattingHelper
-
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: {
english_stop: {
type: 'stop',
stopwords: '_english_',
},
+
english_stemmer: {
type: 'stemmer',
language: 'english',
},
+
english_possessive_stemmer: {
type: 'stemmer',
language: 'possessive_english',
},
},
+
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
),
@@ -33,43 +40,15 @@ class StatusesIndex < Chewy::Index
},
}
- # 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)
-
- 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 :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, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
root date_detection: false do
- field :id, type: 'long'
- field :account_id, type: 'long'
-
- field :text, type: 'text', value: ->(status) { status.searchable_text } do
- field :stemmed, type: 'text', analyzer: 'content'
- end
-
- field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
+ field(:id, type: 'long')
+ field(:account_id, type: 'long')
+ field(:text, type: 'text', analyzer: 'verbatim', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'content') }
+ field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by })
+ field(:language, type: 'keyword')
+ field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
+ field(:created_at, type: 'date')
end
end
diff --git a/app/controllers/admin/software_updates_controller.rb b/app/controllers/admin/software_updates_controller.rb
new file mode 100644
index 00000000000000..52d8cb41e6dd94
--- /dev/null
+++ b/app/controllers/admin/software_updates_controller.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Admin
+ class SoftwareUpdatesController < BaseController
+ before_action :check_enabled!
+
+ def index
+ authorize :software_update, :index?
+ @software_updates = SoftwareUpdate.all.sort_by(&:gem_version)
+ end
+
+ private
+
+ def check_enabled!
+ not_found unless SoftwareUpdate.check_enabled?
+ end
+ end
+end
diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb
index 7c7d70fd32c53f..76ba75824518c2 100644
--- a/app/controllers/api/v1/accounts/credentials_controller.rb
+++ b/app/controllers/api/v1/accounts/credentials_controller.rb
@@ -30,6 +30,7 @@ def account_params
:bot,
:discoverable,
:hide_collections,
+ :indexable,
fields_attributes: [:name, :value]
)
end
diff --git a/app/controllers/api/v1/statuses/translations_controller.rb b/app/controllers/api/v1/statuses/translations_controller.rb
index 540b17d0092c13..ec5ea5b85b0d1a 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 9cd7b990467f0b..a79d65c124b73c 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 @@ def show
private
+ def require_auth?
+ !Setting.timeline_preview
+ end
+
def load_tag
@tag = Tag.find_normalized(params[:id])
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c57031da3c26ca..4d7805abade91f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
include DomainControlHelper
include ThemingConcern
include DatabaseHelper
+ include AuthorizedFetchHelper
helper_method :current_account
helper_method :current_session
@@ -53,10 +54,6 @@ def raise_not_found
private
- def authorized_fetch_mode?
- ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.limited_federation_mode
- end
-
def public_fetch_mode?
!authorized_fetch_mode?
end
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index 1d27c92c8c34d1..b0c4fff8bc2de8 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -119,6 +119,8 @@ def request_body
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_controller.rb b/app/controllers/settings/privacy_controller.rb
index c2648eedd89bf7..1102c89fad5f47 100644
--- a/app/controllers/settings/privacy_controller.rb
+++ b/app/controllers/settings/privacy_controller.rb
@@ -18,7 +18,7 @@ def update
private
def account_params
- params.require(:account).permit(:discoverable, :unlocked, :show_collections, settings: UserSettings.keys)
+ params.require(:account).permit(:discoverable, :unlocked, :indexable, :show_collections, settings: UserSettings.keys)
end
def set_account
diff --git a/app/helpers/authorized_fetch_helper.rb b/app/helpers/authorized_fetch_helper.rb
new file mode 100644
index 00000000000000..ce87526e6abb8b
--- /dev/null
+++ b/app/helpers/authorized_fetch_helper.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module AuthorizedFetchHelper
+ def authorized_fetch_mode?
+ ENV.fetch('AUTHORIZED_FETCH') { Setting.authorized_fetch } == 'true' || Rails.configuration.x.limited_federation_mode
+ end
+
+ def authorized_fetch_overridden?
+ ENV.key?('AUTHORIZED_FETCH') || Rails.configuration.x.limited_federation_mode
+ end
+end
diff --git a/app/javascript/core/public.js b/app/javascript/core/public.js
deleted file mode 100644
index 01b4157f8cd707..00000000000000
--- a/app/javascript/core/public.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// This file will be loaded on public pages, regardless of theme.
-
-import 'packs/public-path';
-
-import { delegate } from '@rails/ujs';
-
-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_header', 'change', ({ target }) => {
- const header = document.querySelector('.card .card__img img');
- const [file] = target.files || [];
- const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
-
- header.src = url;
-});
diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js
index 0155d933e46594..d9a8ea4cfd4220 100644
--- a/app/javascript/core/settings.js
+++ b/app/javascript/core/settings.js
@@ -2,21 +2,6 @@
import 'packs/public-path';
import { delegate } from '@rails/ujs';
-import escapeTextContentForBrowser from 'escape-html';
-
-
-import emojify from '../mastodon/features/emoji/emoji';
-
-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 = name.textContent = target.dataset.default;
- }
- }
-});
delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => {
const avatar = document.getElementById(target.id + '-preview');
@@ -26,18 +11,6 @@ delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => {
avatar.src = url;
});
-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();
diff --git a/app/javascript/core/theme.yml b/app/javascript/core/theme.yml
index 20912e28d04a78..1b2bfb98f1449f 100644
--- a/app/javascript/core/theme.yml
+++ b/app/javascript/core/theme.yml
@@ -13,8 +13,8 @@ pack:
mailer:
filename: mailer.js
stylesheet: true
- modal: public.js
- public: public.js
+ modal:
+ public:
settings: settings.js
sign_up:
share:
diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js
index 6b8864a039d92b..095fb3155e41a4 100644
--- a/app/javascript/flavours/glitch/actions/interactions.js
+++ b/app/javascript/flavours/glitch/actions/interactions.js
@@ -1,11 +1,16 @@
-import api from '../api';
+import api, { getLinks } from '../api';
+import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus } 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';
@@ -26,6 +31,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 PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS';
export const PIN_FAIL = 'PIN_FAIL';
@@ -259,8 +268,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));
});
@@ -274,17 +285,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,
};
}
@@ -294,8 +350,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));
});
@@ -309,17 +367,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/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx
index d3d432ae05b661..36abc699304bd6 100644
--- a/app/javascript/flavours/glitch/components/status.jsx
+++ b/app/javascript/flavours/glitch/components/status.jsx
@@ -792,6 +792,7 @@ class Status extends ImmutablePureComponent {
tabIndex={0}
data-featured={featured ? 'true' : null}
aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}
+ data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}
>
{!muted && prepend}
diff --git a/app/javascript/flavours/glitch/features/compose/components/search.jsx b/app/javascript/flavours/glitch/features/compose/components/search.jsx
index 2f1b46e5d9ffc2..e83c1761ec1dfd 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search.jsx
+++ b/app/javascript/flavours/glitch/features/compose/components/search.jsx
@@ -1,11 +1,7 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
-import {
- injectIntl,
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl';
import classNames from 'classnames';
@@ -52,6 +48,16 @@ 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: <>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;
};
@@ -100,7 +106,7 @@ class Search extends PureComponent {
handleKeyDown = (e) => {
const { selectedOption } = this.state;
- const options = this._getOptions();
+ const options = searchEnabled ? this._getOptions().concat(this.defaultOptions) : this._getOptions();
switch(e.key) {
case 'Escape':
@@ -131,10 +137,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) {
@@ -161,6 +166,7 @@ class Search extends PureComponent {
router.history.push(`/tags/${query}`);
onClickSearchResult(query, 'hashtag');
+ this._unfocus();
};
handleAccountClick = () => {
@@ -171,6 +177,7 @@ class Search extends PureComponent {
router.history.push(`/@${query}`);
onClickSearchResult(query, 'account');
+ this._unfocus();
};
handleURLClick = () => {
@@ -178,6 +185,7 @@ class Search extends PureComponent {
const { onOpenURL } = this.props;
onOpenURL(router.history);
+ this._unfocus();
};
handleStatusSearch = () => {
@@ -196,6 +204,8 @@ class Search extends PureComponent {
} else if (search.get('type') === 'hashtag') {
router.history.push(`/tags/${search.get('q')}`);
}
+
+ this._unfocus();
};
handleForgetRecentSearchClick = search => {
@@ -208,6 +218,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;
@@ -217,6 +239,8 @@ class Search extends PureComponent {
if (openInRoute) {
router.history.push('/search');
}
+
+ this._unfocus();
}
_getOptions () {
@@ -337,6 +361,20 @@ class Search extends PureComponent {
>
)}
+
+ {searchEnabled && (
+ <>
+
+
+
+ {this.defaultOptions.map(({ key, label, action }, i) => (
+
+ ))}
+
+ >
+ )}
);
diff --git a/app/javascript/flavours/glitch/features/favourites/index.jsx b/app/javascript/flavours/glitch/features/favourites/index.jsx
index 2b36945eee8291..49fd62b9664be2 100644
--- a/app/javascript/flavours/glitch/features/favourites/index.jsx
+++ b/app/javascript/flavours/glitch/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 'flavours/glitch/actions/interactions';
+import { debounce } from 'lodash';
+
+import { fetchFavourites, expandFavourites } from 'flavours/glitch/actions/interactions';
import ColumnHeader from 'flavours/glitch/components/column_header';
import { Icon } from 'flavours/glitch/components/icon';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
@@ -23,7 +25,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 {
@@ -32,6 +36,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,
};
@@ -42,12 +48,6 @@ 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));
- }
- }
-
handleHeaderClick = () => {
this.column.scrollTop();
};
@@ -60,8 +60,12 @@ class Favourites extends ImmutablePureComponent {
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 (
@@ -87,6 +91,9 @@ class Favourites extends ImmutablePureComponent {
/>
diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/critical_update_banner.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/critical_update_banner.tsx
new file mode 100644
index 00000000000000..d0dd2b6acda66f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/home_timeline/components/critical_update_banner.tsx
@@ -0,0 +1,26 @@
+import { FormattedMessage } from 'react-intl';
+
+export const CriticalUpdateBanner = () => (
+
+
+
+
+
+
+ {' '}
+
+
+
+
+
+
+);
diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.jsx b/app/javascript/flavours/glitch/features/home_timeline/index.jsx
index e17680d8bb31f1..80dae5e4d0859a 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.jsx
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.jsx
@@ -14,7 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/act
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
-import { me } from 'flavours/glitch/initial_state';
+import { me, criticalUpdatesPending } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { expandHomeTimeline } from '../../actions/timelines';
@@ -23,6 +23,7 @@ import ColumnHeader from '../../components/column_header';
import StatusListContainer from '../ui/containers/status_list_container';
import { ColumnSettings } from './components/column_settings';
+import { CriticalUpdateBanner } from './components/critical_update_banner';
import { ExplorePrompt } from './components/explore_prompt';
const messages = defineMessages({
@@ -158,8 +159,9 @@ class HomeTimeline extends PureComponent {
const { intl, hasUnread, columnId, multiColumn, tooSlow, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
+ const banners = [];
- let announcementsButton, banner;
+ let announcementsButton;
if (hasAnnouncements) {
announcementsButton = (
@@ -174,8 +176,12 @@ class HomeTimeline extends PureComponent {
);
}
+ if (criticalUpdatesPending) {
+ banners.push();
+ }
+
if (tooSlow) {
- banner = ;
+ banners.push();
}
return (
@@ -197,7 +203,7 @@ class HomeTimeline extends PureComponent {
{signedIn ? (
({
- 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 {
@@ -35,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,
};
@@ -45,12 +47,6 @@ class Reblogs extends ImmutablePureComponent {
}
}
- UNSAFE_componentWillReceiveProps(nextProps) {
- if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
- this.props.dispatch(fetchReblogs(nextProps.params.statusId));
- }
- }
-
handleHeaderClick = () => {
this.column.scrollTop();
};
@@ -63,8 +59,12 @@ class Reblogs extends ImmutablePureComponent {
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 (
@@ -91,6 +91,9 @@ class Reblogs extends ImmutablePureComponent {
diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx
index c0726404ee40c7..f6984d5adbf2fd 100644
--- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx
@@ -29,6 +29,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.' },
app_settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
});
@@ -56,9 +57,13 @@ class NavigationPanel extends Component {
{transientSingleColumn && (
)}
diff --git a/app/javascript/flavours/glitch/initial_state.js b/app/javascript/flavours/glitch/initial_state.js
index 46228c33066d11..8d277f8c18c347 100644
--- a/app/javascript/flavours/glitch/initial_state.js
+++ b/app/javascript/flavours/glitch/initial_state.js
@@ -100,6 +100,7 @@ export const hasMultiColumnPath = initialPath === '/'
* @typedef InitialState
* @property {Record
} accounts
* @property {InitialStateLanguage[]} languages
+ * @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {object} local_settings
* @property {number} max_toot_chars
@@ -160,6 +161,7 @@ export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items');
export const version = getMeta('version');
export const languages = initialState?.languages;
+export const criticalUpdatesPending = initialState?.critical_updates_pending;
export const statusPageUrl = getMeta('status_page_url');
export const sso_redirect = getMeta('sso_redirect');
diff --git a/app/javascript/flavours/glitch/reducers/user_lists.js b/app/javascript/flavours/glitch/reducers/user_lists.js
index dd240e99d44d78..d37451d0050034 100644
--- a/app/javascript/flavours/glitch/reducers/user_lists.js
+++ b/app/javascript/flavours/glitch/reducers/user_lists.js
@@ -44,8 +44,18 @@ import {
FEATURED_TAGS_FETCH_FAIL,
} from 'flavours/glitch/actions/featured_tags';
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,
} from 'flavours/glitch/actions/interactions';
import {
MUTES_FETCH_REQUEST,
@@ -133,9 +143,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 NOTIFICATIONS_UPDATE:
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS:
diff --git a/app/javascript/flavours/glitch/styles/accounts.scss b/app/javascript/flavours/glitch/styles/accounts.scss
index ad0dfe017757b9..b0fe21bcf12aa8 100644
--- a/app/javascript/flavours/glitch/styles/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/accounts.scss
@@ -192,6 +192,8 @@
}
.account-role,
+.information-badge,
+.simple_form .overridden,
.simple_form .recommended,
.simple_form .not_recommended,
.simple_form .glitch_only {
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 7adeaeee0197ef..2f4027b03fb6ea 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -143,6 +143,11 @@ $content-width: 840px;
}
}
+ .warning a {
+ color: $gold-star;
+ font-weight: 700;
+ }
+
.simple-navigation-active-leaf a {
color: $primary-text-color;
background-color: $ui-highlight-color;
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index d4860258ed8946..39b9bd291d963d 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -228,6 +228,22 @@ $ui-header-height: 55px;
top: -48px;
}
+.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;
@@ -961,7 +977,8 @@ $ui-header-height: 55px;
}
}
-.dismissable-banner {
+.dismissable-banner,
+.warning-banner {
position: relative;
margin: 10px;
margin-bottom: 5px;
@@ -1039,6 +1056,21 @@ $ui-header-height: 55px;
}
}
+.warning-banner {
+ border: 1px solid $warning-red;
+ background: rgba($warning-red, 0.15);
+
+ &__message {
+ h1 {
+ color: $warning-red;
+ }
+
+ a {
+ color: $primary-text-color;
+ }
+ }
+}
+
.hashtag-header {
border-bottom: 1px solid lighten($ui-base-color, 8%);
padding: 15px;
diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss
index 99ed697e37da4b..be35077dcd70a5 100644
--- a/app/javascript/flavours/glitch/styles/components/search.scss
+++ b/app/javascript/flavours/glitch/styles/components/search.scss
@@ -25,6 +25,12 @@
}
&__menu {
+ margin-bottom: 20px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
&__message {
color: $dark-text-color;
padding: 0 10px;
diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss
index 2f8f7e2dd23f6b..7efcf0c0972e76 100644
--- a/app/javascript/flavours/glitch/styles/components/single_column.scss
+++ b/app/javascript/flavours/glitch/styles/components/single_column.scss
@@ -120,6 +120,7 @@
.filter-form {
display: flex;
+ flex-wrap: wrap;
}
.autosuggest-textarea__textarea {
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index 2b7c02f1152593..b8fc4a653fd906 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -103,6 +103,7 @@ code {
}
}
+ .overridden,
.recommended,
.not_recommended,
.glitch_only {
@@ -1187,14 +1188,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/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss
index b583d3d8ead210..44ef00ba7378ed 100644
--- a/app/javascript/flavours/glitch/styles/tables.scss
+++ b/app/javascript/flavours/glitch/styles/tables.scss
@@ -12,6 +12,11 @@
border-top: 1px solid $ui-base-color;
text-align: start;
background: darken($ui-base-color, 4%);
+
+ &.critical {
+ font-weight: 700;
+ color: $gold-star;
+ }
}
& > thead > tr > th {
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 5f7b8e949fa755..e68afcbf9de17a 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -84,6 +84,7 @@ const messages = defineMessages({
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
open: { id: 'compose.published.open', defaultMessage: 'Open' },
published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
+ saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' },
});
export const ensureComposeIsVisible = (getState, routerHistory) => {
@@ -246,7 +247,7 @@ export function submitCompose(routerHistory) {
}
dispatch(showAlert({
- message: messages.published,
+ message: statusId === null ? messages.published : messages.saved,
action: messages.open,
dismissAfter: 10000,
onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),
diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js
index 092a67ea7583d2..7d0144438aa29c 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 } 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';
@@ -26,6 +31,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 PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS';
export const PIN_FAIL = 'PIN_FAIL';
@@ -273,8 +282,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));
});
@@ -288,17 +299,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,
};
}
@@ -308,8 +364,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));
});
@@ -323,17 +381,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 674c481b8173a9..d45a6e20eb6dc4 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.jsx b/app/javascript/mastodon/components/status.jsx
index 30692d1cd970cb..e1728910ee14ce 100644
--- a/app/javascript/mastodon/components/status.jsx
+++ b/app/javascript/mastodon/components/status.jsx
@@ -550,7 +550,7 @@ class Status extends ImmutablePureComponent {
return (
-
+
{prepend}
diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx
index 682f8d3c8cb5fb..848b812632ba2c 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,16 @@ 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: <>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 +80,7 @@ class Search extends PureComponent {
handleKeyDown = (e) => {
const { selectedOption } = this.state;
- const options = this._getOptions();
+ const options = searchEnabled ? this._getOptions().concat(this.defaultOptions) : this._getOptions();
switch(e.key) {
case 'Escape':
@@ -100,11 +110,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 +155,7 @@ class Search extends PureComponent {
router.history.push(`/tags/${query}`);
onClickSearchResult(query, 'hashtag');
+ this._unfocus();
};
handleAccountClick = () => {
@@ -157,6 +166,7 @@ class Search extends PureComponent {
router.history.push(`/@${query}`);
onClickSearchResult(query, 'account');
+ this._unfocus();
};
handleURLClick = () => {
@@ -164,6 +174,7 @@ class Search extends PureComponent {
const { value, onOpenURL } = this.props;
onOpenURL(value, router.history);
+ this._unfocus();
};
handleStatusSearch = () => {
@@ -182,6 +193,8 @@ class Search extends PureComponent {
} else if (search.get('type') === 'hashtag') {
router.history.push(`/tags/${search.get('q')}`);
}
+
+ this._unfocus();
};
handleForgetRecentSearchClick = search => {
@@ -194,6 +207,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 +228,8 @@ class Search extends PureComponent {
if (openInRoute) {
router.history.push('/search');
}
+
+ this._unfocus();
}
_getOptions () {
@@ -325,6 +352,20 @@ class Search extends PureComponent {
>
)}
+
+ {searchEnabled && (
+ <>
+
+
+
+ {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 bfde78708eb466..b8ba9487283617 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/home_timeline/components/critical_update_banner.tsx b/app/javascript/mastodon/features/home_timeline/components/critical_update_banner.tsx
new file mode 100644
index 00000000000000..d0dd2b6acda66f
--- /dev/null
+++ b/app/javascript/mastodon/features/home_timeline/components/critical_update_banner.tsx
@@ -0,0 +1,26 @@
+import { FormattedMessage } from 'react-intl';
+
+export const CriticalUpdateBanner = () => (
+
+
+
+
+
+
+ {' '}
+
+
+
+
+
+
+);
diff --git a/app/javascript/mastodon/features/home_timeline/index.jsx b/app/javascript/mastodon/features/home_timeline/index.jsx
index 1cd6edd7aa5f37..8ff0377946ae5d 100644
--- a/app/javascript/mastodon/features/home_timeline/index.jsx
+++ b/app/javascript/mastodon/features/home_timeline/index.jsx
@@ -14,7 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
-import { me } from 'mastodon/initial_state';
+import { me, criticalUpdatesPending } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { expandHomeTimeline } from '../../actions/timelines';
@@ -23,6 +23,7 @@ import ColumnHeader from '../../components/column_header';
import StatusListContainer from '../ui/containers/status_list_container';
import { ColumnSettings } from './components/column_settings';
+import { CriticalUpdateBanner } from './components/critical_update_banner';
import { ExplorePrompt } from './components/explore_prompt';
const messages = defineMessages({
@@ -156,8 +157,9 @@ class HomeTimeline extends PureComponent {
const { intl, hasUnread, columnId, multiColumn, tooSlow, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
+ const banners = [];
- let announcementsButton, banner;
+ let announcementsButton;
if (hasAnnouncements) {
announcementsButton = (
@@ -173,8 +175,12 @@ class HomeTimeline extends PureComponent {
);
}
+ if (criticalUpdatesPending) {
+ banners.push();
+ }
+
if (tooSlow) {
- banner = ;
+ banners.push();
}
return (
@@ -196,7 +202,7 @@ class HomeTimeline extends PureComponent {
{signedIn ? (
({
- 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/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
index d36abf8f17bba1..8006ca89a24a39 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
@@ -31,6 +31,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 {
@@ -57,12 +58,17 @@ class NavigationPanel extends Component {
{signedIn && (
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 13b80c0925bfdf..34fd5439db3f2d 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -87,6 +87,7 @@
* @typedef InitialState
* @property {Record} accounts
* @property {InitialStateLanguage[]} languages
+ * @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {number} max_toot_chars
*/
@@ -141,6 +142,7 @@ export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items');
export const version = getMeta('version');
export const languages = initialState?.languages;
+export const criticalUpdatesPending = initialState?.critical_updates_pending;
// @ts-expect-error
export const statusPageUrl = getMeta('status_page_url');
export const sso_redirect = getMeta('sso_redirect');
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index d4cd8c7945c786..13cddba72356cf 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -137,6 +137,7 @@
"compose.language.search": "Search languages...",
"compose.published.body": "Post published.",
"compose.published.open": "Open",
+ "compose.saved.body": "Post saved.",
"compose_form.direct_message_warning_learn_more": "Learn more",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any sensitive information over Mastodon.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is not public. Only public posts can be searched by hashtag.",
@@ -309,6 +310,9 @@
"home.explore_prompt.body": "Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. If that feels too quiet, you may want to:",
"home.explore_prompt.title": "This is your home base within Mastodon.",
"home.hide_announcements": "Hide announcements",
+ "home.pending_critical_update.body": "Please update your Mastodon server as soon as possible!",
+ "home.pending_critical_update.link": "See updates",
+ "home.pending_critical_update.title": "Critical security update available!",
"home.show_announcements": "Show announcements",
"interaction_modal.description.favourite": "With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.",
"interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
@@ -410,6 +414,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",
@@ -585,8 +590,12 @@
"search.quick_action.open_url": "Open URL in Mastodon",
"search.quick_action.status_search": "Posts matching {x}",
"search.search_or_paste": "Search or paste URL",
+ "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 2bef3bb4b37f1e..116ed66d038c58 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 e33d365c9cd07e..089899398ed872 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,
} from '../actions/interactions';
import {
MUTES_FETCH_REQUEST,
@@ -134,9 +144,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 NOTIFICATIONS_UPDATE:
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS:
diff --git a/app/javascript/mastodon/test_setup.js b/app/javascript/mastodon/test_setup.js
index 666127af390a13..7b0828bfa80fb3 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 9f959036a410c7..4bfbf7e25a6218 100644
--- a/app/javascript/packs/public.jsx
+++ b/app/javascript/packs/public.jsx
@@ -7,7 +7,6 @@ import { defineMessages } from 'react-intl';
import { delegate } from '@rails/ujs';
import axios from 'axios';
-import { createBrowserHistory } from 'history';
import { throttle } from 'lodash';
import { start } from '../mastodon/common';
@@ -31,23 +30,6 @@ const messages = defineMessages({
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, {
@@ -141,27 +123,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 }));
@@ -179,9 +155,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;
@@ -230,6 +203,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/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index 2a5285ee027fc1..80d6c13cef72fb 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -188,6 +188,7 @@
}
.information-badge,
+.simple_form .overridden,
.simple_form .recommended,
.simple_form .not_recommended {
display: inline-block;
@@ -204,6 +205,7 @@
}
.information-badge,
+.simple_form .overridden,
.simple_form .recommended,
.simple_form .not_recommended {
background-color: rgba($ui-secondary-color, 0.1);
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 6bfb23a46f10dd..89c2adaea33270 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -143,6 +143,11 @@ $content-width: 840px;
}
}
+ .warning a {
+ color: $gold-star;
+ font-weight: 700;
+ }
+
.simple-navigation-active-leaf a {
color: $primary-text-color;
background-color: $ui-highlight-color;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index a5d4eee5efda32..470bee2cf5c4cd 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -2381,6 +2381,7 @@ $ui-header-height: 55px;
.filter-form {
display: flex;
+ flex-wrap: wrap;
}
.autosuggest-textarea__textarea {
@@ -3270,6 +3271,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;
@@ -4991,6 +5008,12 @@ a.status-card {
}
&__menu {
+ margin-bottom: 20px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
&__message {
color: $dark-text-color;
padding: 0 10px;
@@ -8837,7 +8860,8 @@ noscript {
}
}
-.dismissable-banner {
+.dismissable-banner,
+.warning-banner {
position: relative;
margin: 10px;
margin-bottom: 5px;
@@ -8915,6 +8939,21 @@ noscript {
}
}
+.warning-banner {
+ border: 1px solid $warning-red;
+ background: rgba($warning-red, 0.15);
+
+ &__message {
+ h1 {
+ color: $warning-red;
+ }
+
+ a {
+ color: $primary-text-color;
+ }
+ }
+}
+
.image {
position: relative;
overflow: hidden;
@@ -9302,19 +9341,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 beb45ab6e9e7f4..0f8eecee011f8f 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -103,6 +103,7 @@ code {
}
}
+ .overridden,
.recommended,
.not_recommended {
position: absolute;
@@ -1185,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/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index 38cfc872719a0f..dd5b483ec48e21 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -12,6 +12,11 @@
border-top: 1px solid $ui-base-color;
text-align: start;
background: darken($ui-base-color, 4%);
+
+ &.critical {
+ font-weight: 700;
+ color: $gold-star;
+ }
}
& > thead > tr > th {
diff --git a/app/lib/admin/metrics/dimension/software_versions_dimension.rb b/app/lib/admin/metrics/dimension/software_versions_dimension.rb
index 9ab3776c91c701..72a98a88ab78c4 100644
--- a/app/lib/admin/metrics/dimension/software_versions_dimension.rb
+++ b/app/lib/admin/metrics/dimension/software_versions_dimension.rb
@@ -10,7 +10,7 @@ def key
protected
def perform_query
- [mastodon_version, ruby_version, postgresql_version, redis_version]
+ [mastodon_version, ruby_version, postgresql_version, redis_version, elasticsearch_version].compact
end
def mastodon_version
@@ -57,6 +57,22 @@ def redis_version
}
end
+ def elasticsearch_version
+ return unless Chewy.enabled?
+
+ client_info = Chewy.client.info
+ version = client_info.dig('version', 'number')
+
+ {
+ key: 'elasticsearch',
+ human_key: client_info.dig('version', 'distribution') == 'opensearch' ? 'OpenSearch' : 'Elasticsearch',
+ value: version,
+ human_value: version,
+ }
+ rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error
+ nil
+ end
+
def redis_info
@redis_info ||= if redis.is_a?(Redis::Namespace)
redis.redis.info
diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb
index 89dfcef9f1dcac..25c88341a4efe1 100644
--- a/app/lib/admin/system_check.rb
+++ b/app/lib/admin/system_check.rb
@@ -2,6 +2,7 @@
class Admin::SystemCheck
ACTIVE_CHECKS = [
+ Admin::SystemCheck::SoftwareVersionCheck,
Admin::SystemCheck::MediaPrivacyCheck,
Admin::SystemCheck::DatabaseSchemaCheck,
Admin::SystemCheck::SidekiqProcessCheck,
diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb
index 91070756cb6478..c0a1a21e864572 100644
--- a/app/lib/admin/system_check/elasticsearch_check.rb
+++ b/app/lib/admin/system_check/elasticsearch_check.rb
@@ -6,6 +6,7 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
AccountsIndex,
TagsIndex,
StatusesIndex,
+ PublicStatusesIndex,
].freeze
def skip?
@@ -85,7 +86,7 @@ def compatible_version?
def mismatched_indexes
@mismatched_indexes ||= INDEXES.filter_map do |klass|
- klass.index_name if Chewy.client.indices.get_mapping[klass.index_name]&.deep_symbolize_keys != klass.mappings_hash
+ klass.base_name if Chewy.client.indices.get_mapping[klass.index_name]&.deep_symbolize_keys != klass.mappings_hash
end
end
diff --git a/app/lib/admin/system_check/software_version_check.rb b/app/lib/admin/system_check/software_version_check.rb
new file mode 100644
index 00000000000000..e142feddf0d000
--- /dev/null
+++ b/app/lib/admin/system_check/software_version_check.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class Admin::SystemCheck::SoftwareVersionCheck < Admin::SystemCheck::BaseCheck
+ include RoutingHelper
+
+ def skip?
+ !current_user.can?(:view_devops) || !SoftwareUpdate.check_enabled?
+ end
+
+ def pass?
+ software_updates.empty?
+ end
+
+ def message
+ if software_updates.any?(&:urgent?)
+ Admin::SystemCheck::Message.new(:software_version_critical_check, nil, admin_software_updates_path, true)
+ else
+ Admin::SystemCheck::Message.new(:software_version_patch_check, nil, admin_software_updates_path)
+ end
+ end
+
+ private
+
+ def software_updates
+ @software_updates ||= SoftwareUpdate.pending_to_a.filter { |update| update.urgent? || update.patch_type? }
+ end
+end
diff --git a/app/lib/importer/accounts_index_importer.rb b/app/lib/importer/accounts_index_importer.rb
index fd869c39602fab..d8b919027514b2 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 cc1b7b44d7c243..a21557d303a480 100644
--- a/app/lib/importer/base_importer.rb
+++ b/app/lib/importer/base_importer.rb
@@ -68,6 +68,14 @@ def clean_up!
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 7318b51b5d6a3b..ebdceb72edc245 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
new file mode 100644
index 00000000000000..ebaac3794fe7e2
--- /dev/null
+++ b/app/lib/importer/public_statuses_index_importer.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class Importer::PublicStatusesIndexImporter < Importer::BaseImporter
+ def import!
+ 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
+ build_bulk_body(index.adapter.default_scope.where(id: status_ids))
+ end
+
+ indexed = bulk.size
+ deleted = 0
+
+ Chewy::Index::Import::BulkRequest.new(index).perform(bulk)
+
+ [indexed, deleted]
+ end
+ end
+
+ wait!
+ end
+
+ private
+
+ def index
+ PublicStatusesIndex
+ end
+
+ 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 b0721c2e0269fa..08ad3e3797a81d 100644
--- a/app/lib/importer/statuses_index_importer.rb
+++ b/app/lib/importer/statuses_index_importer.rb
@@ -13,32 +13,25 @@ def import!
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 77710ed7def878..067fd8cd2de371 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/plain_text_formatter.rb b/app/lib/plain_text_formatter.rb
index 8eac730bec4024..d1ff6808b2a995 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 @@ def to_s
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 @@ def to_s
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 15956d4cfd2075..1c57b9b024832c 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 aef05e9d9d883c..e81c0c081e3c49 100644
--- a/app/lib/search_query_transformer.rb
+++ b/app/lib/search_query_transformer.rb
@@ -1,58 +1,42 @@
# 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
- { multi_match: { type: 'most_fields', query: clause.term, fields: ['text', 'text.stemmed'] } }
- 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
- { term: { clause.filter => clause.term } }
- else
- raise "Unexpected clause type: #{clause}"
- end
- end
end
class Operator
class << self
def symbol(str)
case str
- when '+'
+ when '+', nil
:must
when '-'
:must_not
- when nil
- :should
else
raise "Unknown operator: #{str}"
end
@@ -61,61 +45,133 @@ def symbol(str)
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' } }
+ 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 :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
+ when 'has', 'is'
+ @filter = :properties
+ @type = :term
+ @term = term
+ when 'language'
+ @filter = :language
+ @type = :term
+ @term = language_code_from_term(term)
when 'from'
@filter = :account_id
+ @type = :term
+ @term = account_id_from_term(term)
+ when 'before'
+ @filter = :created_at
+ @type = :range
+ @term = { lt: term, time_zone: @options[:current_account]&.user_time_zone || 'UTC' }
+ when 'after'
+ @filter = :created_at
+ @type = :range
+ @term = { gt: term, time_zone: @options[:current_account]&.user_time_zone || 'UTC' }
+ when 'during'
+ @filter = :created_at
+ @type = :range
+ @term = { gte: term, lte: term, time_zone: @options[:current_account]&.user_time_zone || 'UTC' }
+ else
+ raise "Unknown prefix: #{prefix}"
+ end
+ end
- username, domain = term.gsub(/\A@/, '').split('@')
- domain = nil if TagManager.instance.local_domain?(domain)
- account = Account.find_remote!(username, domain)
-
- @term = account.id
+ def to_query
+ if @negated
+ { bool: { must_not: { @type => { @filter => @term } } } }
else
- raise Mastodon::SyntaxError
+ { @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)
+
+ # If the account is not found, we want to return empty results, so return
+ # an ID that does not exist
+ account&.id || -1
+ 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/lib/vacuum/statuses_vacuum.rb b/app/lib/vacuum/statuses_vacuum.rb
index 28c087b1c2e8eb..ad1de07380ceb9 100644
--- a/app/lib/vacuum/statuses_vacuum.rb
+++ b/app/lib/vacuum/statuses_vacuum.rb
@@ -20,7 +20,10 @@ def vacuum_statuses!
statuses.direct_visibility
.includes(mentions: :account)
.find_each(&:unlink_from_conversations!)
- remove_from_search_index(statuses.ids) if Chewy.enabled?
+ if Chewy.enabled?
+ remove_from_index(statuses.ids, 'chewy:queue:StatusesIndex')
+ remove_from_index(statuses.ids, 'chewy:queue:PublicStatusesIndex')
+ end
# Foreign keys take care of most associated records for us.
# Media attachments will be orphaned.
@@ -38,7 +41,7 @@ def retention_period_as_id
Mastodon::Snowflake.id_at(@retention_period.ago, with_random: false)
end
- def remove_from_search_index(status_ids)
- with_redis { |redis| redis.sadd('chewy:queue:StatusesIndex', status_ids) }
+ def remove_from_index(status_ids, index)
+ with_redis { |redis| redis.sadd(index, status_ids) }
end
end
diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb
index 5baf9b38a5c73f..990b92c3377d2e 100644
--- a/app/mailers/admin_mailer.rb
+++ b/app/mailers/admin_mailer.rb
@@ -45,6 +45,22 @@ def new_trends(links, tags, statuses)
end
end
+ def new_software_updates
+ locale_for_account(@me) do
+ mail subject: default_i18n_subject(instance: @instance)
+ end
+ end
+
+ def new_critical_software_updates
+ headers['Priority'] = 'urgent'
+ headers['X-Priority'] = '1'
+ headers['Importance'] = 'high'
+
+ locale_for_account(@me) do
+ mail subject: default_i18n_subject(instance: @instance)
+ end
+ end
+
private
def process_params
diff --git a/app/models/account.rb b/app/models/account.rb
index 28030a426657fe..99930c3229cc89 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -82,6 +82,7 @@ class Account < ApplicationRecord
include DomainMaterializable
include AccountMerging
include AccountSearch
+ include AccountStatusesSearch
MAX_DISPLAY_NAME_LENGTH = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i
MAX_NOTE_LENGTH = (ENV['MAX_BIO_CHARS'] || 500).to_i
diff --git a/app/models/concerns/account_statuses_search.rb b/app/models/concerns/account_statuses_search.rb
new file mode 100644
index 00000000000000..fa9238e6efdc51
--- /dev/null
+++ b/app/models/concerns/account_statuses_search.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module AccountStatusesSearch
+ extend ActiveSupport::Concern
+
+ included do
+ after_update_commit :enqueue_update_public_statuses_index, if: :saved_change_to_indexable?
+ after_destroy_commit :enqueue_remove_from_public_statuses_index, if: :indexable?
+ end
+
+ def enqueue_update_public_statuses_index
+ if indexable?
+ enqueue_add_to_public_statuses_index
+ else
+ enqueue_remove_from_public_statuses_index
+ end
+ end
+
+ def enqueue_add_to_public_statuses_index
+ return unless Chewy.enabled?
+
+ AddToPublicStatusesIndexWorker.perform_async(id)
+ end
+
+ def enqueue_remove_from_public_statuses_index
+ return unless Chewy.enabled?
+
+ RemoveFromPublicStatusesIndexWorker.perform_async(id)
+ end
+
+ def add_to_public_statuses_index!
+ return unless Chewy.enabled?
+
+ statuses.without_reblogs.where(visibility: :public).find_in_batches do |batch|
+ PublicStatusesIndex.import(batch)
+ end
+ end
+
+ def remove_from_public_statuses_index!
+ return unless Chewy.enabled?
+
+ PublicStatusesIndex.filter(term: { account_id: id }).delete_all
+ end
+end
diff --git a/app/models/concerns/status_search_concern.rb b/app/models/concerns/status_search_concern.rb
new file mode 100644
index 00000000000000..3ef45754ab32f9
--- /dev/null
+++ b/app/models/concerns/status_search_concern.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module StatusSearchConcern
+ extend ActiveSupport::Concern
+
+ included do
+ scope :indexable, -> { without_reblogs.where(visibility: :public).joins(:account).where(account: { indexable: true }) }
+ end
+
+ def searchable_by
+ @searchable_by ||= begin
+ ids = []
+
+ ids << account_id if local?
+
+ ids += local_mentioned.pluck(:id)
+ ids += local_favorited.pluck(:id)
+ ids += local_reblogged.pluck(:id)
+ ids += local_bookmarked.pluck(:id)
+ ids += preloadable_poll.local_voters.pluck(:id) if preloadable_poll.present?
+
+ ids.uniq
+ end
+ end
+
+ def searchable_text
+ [
+ spoiler_text,
+ FormattingHelper.extract_status_plain_text(self),
+ preloadable_poll&.options&.join("\n\n"),
+ ordered_media_attachments.map(&:description).join("\n\n"),
+ ].compact.join("\n\n")
+ end
+
+ def searchable_properties
+ [].tap do |properties|
+ properties << 'image' if ordered_media_attachments.any?(&:image?)
+ properties << 'video' if ordered_media_attachments.any?(&:video?)
+ properties << 'audio' if ordered_media_attachments.any?(&:audio?)
+ properties << 'media' if with_media?
+ properties << 'poll' if with_poll?
+ properties << 'link' if with_preview_card?
+ properties << 'embed' if preview_cards.any?(&:video?)
+ properties << 'sensitive' if sensitive?
+ properties << 'reply' if reply?
+ end
+ end
+end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 8f1dc4954d02d5..41014fb4189cd8 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -3,6 +3,8 @@
class Form::AdminSettings
include ActiveModel::Model
+ include AuthorizedFetchHelper
+
KEYS = %i(
site_contact_username
site_contact_email
@@ -42,6 +44,7 @@ class Form::AdminSettings
backups_retention_period
status_page_url
captcha_enabled
+ authorized_fetch
).freeze
INTEGER_KEYS = %i(
@@ -66,6 +69,7 @@ class Form::AdminSettings
noindex
require_invite_text
captcha_enabled
+ authorized_fetch
).freeze
UPLOAD_KEYS = %i(
@@ -77,6 +81,10 @@ class Form::AdminSettings
flavour_and_skin
).freeze
+ OVERRIDEN_SETTINGS = {
+ authorized_fetch: :authorized_fetch_mode?,
+ }.freeze
+
attr_accessor(*KEYS)
validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) }
@@ -96,6 +104,8 @@ class Form::AdminSettings
stored_value = if UPLOAD_KEYS.include?(key)
SiteUpload.where(var: key).first_or_initialize(var: key)
+ elsif OVERRIDEN_SETTINGS.include?(key)
+ public_send(OVERRIDEN_SETTINGS[key])
else
Setting.public_send(key)
end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 02b61bd5123e61..49063754ad0127 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -44,6 +44,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
@@ -98,17 +99,15 @@ 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
+ 'vf' => 'crop=floor(iw/2)*2:floor(ih/2)*2', # h264 requires width and height to be even. Crop instead of scale to avoid blurring
'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
@@ -135,7 +134,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 74a77978b94d58..efa625eb5bfac3 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/software_update.rb b/app/models/software_update.rb
new file mode 100644
index 00000000000000..cb3a6df2aeb96e
--- /dev/null
+++ b/app/models/software_update.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: software_updates
+#
+# id :bigint(8) not null, primary key
+# version :string not null
+# urgent :boolean default(FALSE), not null
+# type :integer default("patch"), not null
+# release_notes :string default(""), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class SoftwareUpdate < ApplicationRecord
+ self.inheritance_column = nil
+
+ enum type: { patch: 0, minor: 1, major: 2 }, _suffix: :type
+
+ def gem_version
+ Gem::Version.new(version)
+ end
+
+ class << self
+ def check_enabled?
+ ENV['UPDATE_CHECK_URL'] != ''
+ end
+
+ def pending_to_a
+ return [] unless check_enabled?
+
+ all.to_a.filter { |update| update.gem_version > Mastodon::Version.gem_version }
+ end
+
+ def urgent_pending?
+ pending_to_a.any?(&:urgent?)
+ end
+ end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index ae68f8c8544a87..13bcf1daa20018 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -39,6 +39,7 @@ class Status < ApplicationRecord
include StatusSnapshotConcern
include RateLimitable
include StatusSafeReblogInsert
+ include StatusSearchConcern
rate_limit by: :account, family: :statuses
@@ -49,6 +50,7 @@ class Status < ApplicationRecord
attr_accessor :override_timestamps
update_index('statuses', :proper)
+ update_index('public_statuses', :proper)
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4 }, _suffix: :visibility
@@ -72,6 +74,12 @@ class Status < ApplicationRecord
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
has_many :media_attachments, dependent: :nullify
+ # 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_and_belongs_to_many :tags
has_and_belongs_to_many :preview_cards
@@ -172,37 +180,6 @@ def cache_key
"v3:#{super}"
end
- def searchable_by(preloaded = nil)
- ids = []
-
- 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 += 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.reblogs[id] || []
- ids += preloaded.bookmarks[id] || []
- ids += preloaded.votes[id] || []
- end
-
- ids.uniq
- end
-
- def searchable_text
- [
- spoiler_text,
- FormattingHelper.extract_status_plain_text(self),
- preloadable_poll ? preloadable_poll.options.join("\n\n") : nil,
- ordered_media_attachments.map(&:description).join("\n\n"),
- ].compact.join("\n\n")
- end
-
def to_log_human_identifier
account.acct
end
@@ -277,6 +254,10 @@ def with_preview_card?
preview_cards.any?
end
+ def with_poll?
+ preloadable_poll.present?
+ end
+
def non_sensitive_with_media?
!sensitive? && with_media?
end
diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb
index 7a64159d845370..9956b88513bf74 100644
--- a/app/models/user_settings.rb
+++ b/app/models/user_settings.rb
@@ -52,6 +52,7 @@ class KeyError < Error; end
setting :link_trends, default: false
setting :status_trends, default: false
setting :appeal, default: true
+ setting :software_updates, default: 'critical', in: %w(none critical patch all)
end
namespace :interactions do
diff --git a/app/policies/software_update_policy.rb b/app/policies/software_update_policy.rb
new file mode 100644
index 00000000000000..dcb565814fcd41
--- /dev/null
+++ b/app/policies/software_update_policy.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class SoftwareUpdatePolicy < ApplicationPolicy
+ def index?
+ role.can?(:view_devops)
+ end
+end
diff --git a/app/presenters/initial_state_presenter.rb b/app/presenters/initial_state_presenter.rb
index b87cff51e10a72..222cc8566cf009 100644
--- a/app/presenters/initial_state_presenter.rb
+++ b/app/presenters/initial_state_presenter.rb
@@ -3,9 +3,13 @@
class InitialStatePresenter < ActiveModelSerializers::Model
attributes :settings, :push_subscription, :token,
:current_account, :admin, :owner, :text, :visibility,
- :disabled_account, :moved_to_account
+ :disabled_account, :moved_to_account, :critical_updates_pending
def role
current_account&.user_role
end
+
+ def critical_updates_pending
+ role&.can?(:view_devops) && SoftwareUpdate.urgent_pending?
+ end
end
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 4998d003998ef5..31f39954fb077d 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -8,13 +8,13 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
context_extensions :manually_approves_followers, :featured, :also_known_as,
:moved_to, :property_value, :discoverable, :olm, :suspended,
- :memorial
+ :memorial, :indexable
attributes :id, :type, :following, :followers,
:inbox, :outbox, :featured, :featured_tags,
:preferred_username, :name, :summary,
:url, :manually_approves_followers,
- :discoverable, :published, :memorial
+ :discoverable, :indexable, :published, :memorial
has_one :public_key, serializer: ActivityPub::PublicKeySerializer
@@ -99,6 +99,10 @@ def discoverable
object.suspended? ? false : (object.discoverable || false)
end
+ def indexable
+ object.suspended? ? false : (object.indexable || false)
+ end
+
def name
object.suspended? ? object.username : (object.display_name.presence || object.username)
end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 3903b444b006b1..178c25a49b8ce0 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -8,6 +8,8 @@ class InitialStateSerializer < ActiveModel::Serializer
:max_toot_chars, :poll_limits,
:languages
+ attribute :critical_updates_pending, if: -> { object&.role&.can?(:view_devops) && SoftwareUpdate.check_enabled? }
+
has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer
has_one :role, serializer: REST::RoleSerializer
diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb
index 3ca34411697eb7..b67cd2771a2372 100644
--- a/app/serializers/webfinger_serializer.rb
+++ b/app/serializers/webfinger_serializer.rb
@@ -18,18 +18,31 @@ def aliases
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/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index 348f94a090ef18..b74ae1a4129051 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -38,7 +38,10 @@ def call(statuses, **options)
# Since we skipped all callbacks, we also need to manually
# deindex the statuses
- Chewy.strategy.current.update(StatusesIndex, statuses_and_reblogs) if Chewy.enabled?
+ if Chewy.enabled?
+ Chewy.strategy.current.update(StatusesIndex, statuses_and_reblogs)
+ Chewy.strategy.current.update(PublicStatusesIndex, statuses_and_reblogs)
+ end
return if options[:skip_side_effects]
diff --git a/app/services/concerns/payloadable.rb b/app/services/concerns/payloadable.rb
index 1389a42ed652d2..bd9d9d74b516ac 100644
--- a/app/services/concerns/payloadable.rb
+++ b/app/services/concerns/payloadable.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module Payloadable
+ include AuthorizedFetchHelper
+
# @param [ActiveModelSerializers::Model] record
# @param [ActiveModelSerializers::Serializer] serializer
# @param [Hash] options
@@ -23,6 +25,6 @@ def serialize_payload(record, serializer, options = {})
end
def signing_enabled?
- ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.limited_federation_mode
+ !authorized_fetch_mode?
end
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 30937471bd5e56..9a40d7bdd5735a 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -1,8 +1,10 @@
# frozen_string_literal: true
class SearchService < BaseService
+ QUOTE_EQUIVALENT_CHARACTERS = /[“”„«»「」『』《》]/
+
def call(query, account, limit, options = {})
- @query = query&.strip
+ @query = query&.strip&.gsub(QUOTE_EQUIVALENT_CHARACTERS, '"')
@account = account
@options = options
@limit = limit.to_i
@@ -17,7 +19,7 @@ def call(query, account, limit, options = {})
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
@@ -39,25 +41,15 @@ def perform_accounts_search!
end
def perform_statuses_search!
- definition = parsed_query.apply(StatusesIndex.filter(term: { searchable_by: @account.id }))
-
- definition = definition.filter(term: { account_id: @options[:account_id] }) if @options[:account_id].present?
-
- if @options[:min_id].present? || @options[:max_id].present?
- range = {}
- range[:gt] = @options[:min_id].to_i if @options[:min_id].present?
- range[:lt] = @options[:max_id].to_i if @options[:max_id].present?
- definition = definition.filter(range: { id: range })
- end
-
- results = definition.limit(@limit).offset(@offset).objects.compact
- account_ids = results.map(&:account_id)
- account_domains = results.map(&:account_domain)
- preloaded_relations = @account.relations_map(account_ids, account_domains)
-
- results.reject { |status| StatusFilter.new(status, @account, preloaded_relations).filtered? }
- rescue Faraday::ConnectionFailed, Parslet::ParseFailed
- []
+ StatusesSearchService.new.call(
+ @query,
+ @account,
+ limit: @limit,
+ offset: @offset,
+ account_id: @options[:account_id],
+ min_id: @options[:min_id],
+ max_id: @options[:max_id]
+ )
end
def perform_hashtags_search!
@@ -89,18 +81,16 @@ def url_resource_symbol
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?
@@ -111,11 +101,7 @@ def hashtag_search?
@options[:type].blank? || @options[:type] == 'hashtags'
end
- def statuses_search?
+ def status_search?
@options[:type].blank? || @options[:type] == 'statuses'
end
-
- def parsed_query
- SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query))
- end
end
diff --git a/app/services/software_update_check_service.rb b/app/services/software_update_check_service.rb
new file mode 100644
index 00000000000000..49b92f104db0f7
--- /dev/null
+++ b/app/services/software_update_check_service.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+class SoftwareUpdateCheckService < BaseService
+ def call
+ clean_outdated_updates!
+ return unless SoftwareUpdate.check_enabled?
+
+ process_update_notices!(fetch_update_notices)
+ end
+
+ private
+
+ def clean_outdated_updates!
+ SoftwareUpdate.find_each do |software_update|
+ software_update.delete if Mastodon::Version.gem_version >= software_update.gem_version
+ rescue ArgumentError
+ software_update.delete
+ end
+ end
+
+ def fetch_update_notices
+ Request.new(:get, "#{api_url}?version=#{version}").add_headers('Accept' => 'application/json', 'User-Agent' => 'Mastodon update checker').perform do |res|
+ return Oj.load(res.body_with_limit, mode: :strict) if res.code == 200
+ end
+ rescue HTTP::Error, OpenSSL::SSL::SSLError, Oj::ParseError
+ nil
+ end
+
+ def api_url
+ ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check')
+ end
+
+ def version
+ @version ||= Mastodon::Version.to_s.split('+')[0]
+ end
+
+ def process_update_notices!(update_notices)
+ return if update_notices.blank? || update_notices['updatesAvailable'].blank?
+
+ # Clear notices that are not listed by the update server anymore
+ SoftwareUpdate.where.not(version: update_notices['updatesAvailable'].pluck('version')).delete_all
+
+ # Check if any of the notices is new, and issue notifications
+ known_versions = SoftwareUpdate.where(version: update_notices['updatesAvailable'].pluck('version')).pluck(:version)
+ new_update_notices = update_notices['updatesAvailable'].filter { |notice| known_versions.exclude?(notice['version']) }
+ return if new_update_notices.blank?
+
+ new_updates = new_update_notices.map do |notice|
+ SoftwareUpdate.create!(version: notice['version'], urgent: notice['urgent'], type: notice['type'], release_notes: notice['releaseNotes'])
+ end
+
+ notify_devops!(new_updates)
+ end
+
+ def should_notify_user?(user, urgent_version, patch_version)
+ case user.settings['notification_emails.software_updates']
+ when 'none'
+ false
+ when 'critical'
+ urgent_version
+ when 'patch'
+ urgent_version || patch_version
+ when 'all'
+ true
+ end
+ end
+
+ def notify_devops!(new_updates)
+ has_new_urgent_version = new_updates.any?(&:urgent?)
+ has_new_patch_version = new_updates.any?(&:patch_type?)
+
+ User.those_who_can(:view_devops).includes(:account).find_each do |user|
+ next unless should_notify_user?(user, has_new_urgent_version, has_new_patch_version)
+
+ if has_new_urgent_version
+ AdminMailer.with(recipient: user.account).new_critical_software_updates.deliver_later
+ else
+ AdminMailer.with(recipient: user.account).new_software_updates.deliver_later
+ end
+ end
+ end
+end
diff --git a/app/services/statuses_search_service.rb b/app/services/statuses_search_service.rb
new file mode 100644
index 00000000000000..2317a2a1acbf53
--- /dev/null
+++ b/app/services/statuses_search_service.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+class StatusesSearchService < BaseService
+ def call(query, account = nil, options = {})
+ @query = query&.strip
+ @account = account
+ @options = options
+ @limit = options[:limit].to_i
+ @offset = options[:offset].to_i
+
+ status_search_results
+ end
+
+ private
+
+ def status_search_results
+ definition = parsed_query.apply(
+ Chewy::Search::Request.new(StatusesIndex, PublicStatusesIndex).filter(
+ bool: {
+ should: [
+ publicly_searchable,
+ non_publicly_searchable,
+ ],
+
+ minimum_should_match: 1,
+ }
+ )
+ )
+
+ results = definition.collapse(field: :id).order(id: { order: :desc }).limit(@limit).offset(@offset).objects.compact
+ account_ids = results.map(&:account_id)
+ account_domains = results.map(&:account_domain)
+ preloaded_relations = @account.relations_map(account_ids, account_domains)
+
+ results.reject { |status| StatusFilter.new(status, @account, preloaded_relations).filtered? }
+ rescue Faraday::ConnectionFailed, Parslet::ParseFailed
+ []
+ end
+
+ def publicly_searchable
+ {
+ term: { _index: PublicStatusesIndex.index_name },
+ }
+ end
+
+ def non_publicly_searchable
+ {
+ bool: {
+ must: [
+ {
+ term: { _index: StatusesIndex.index_name },
+ },
+ {
+ term: { searchable_by: @account.id },
+ },
+ ],
+ },
+ }
+ end
+
+ def parsed_query
+ SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query), current_account: @account)
+ end
+end
diff --git a/app/views/admin/settings/discovery/show.html.haml b/app/views/admin/settings/discovery/show.html.haml
index 460bb5709150dc..c12ba34aeb3e19 100644
--- a/app/views/admin/settings/discovery/show.html.haml
+++ b/app/views/admin/settings/discovery/show.html.haml
@@ -42,6 +42,11 @@
.fields-group
= f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, recommended: :recommended
+ %h4= t('admin.settings.security.federation_authentication')
+
+ .fields-group
+ = f.input :authorized_fetch, as: :boolean, wrapper: :with_label, label: t('admin.settings.security.authorized_fetch'), warning_hint: authorized_fetch_overridden? ? t('admin.settings.security.authorized_fetch_overridden_hint') : nil, hint: t('admin.settings.security.authorized_fetch_hint'), disabled: authorized_fetch_overridden?, recommended: authorized_fetch_overridden? ? :overridden : nil
+
%h4= t('admin.settings.discovery.follow_recommendations')
.fields-group
diff --git a/app/views/admin/software_updates/index.html.haml b/app/views/admin/software_updates/index.html.haml
new file mode 100644
index 00000000000000..7a223ee07b3347
--- /dev/null
+++ b/app/views/admin/software_updates/index.html.haml
@@ -0,0 +1,29 @@
+- content_for :page_title do
+ = t('admin.software_updates.title')
+
+.simple_form
+ %p.lead
+ = t('admin.software_updates.description')
+ = link_to t('admin.software_updates.documentation_link'), 'https://docs.joinmastodon.org/admin/upgrading/#automated_checks', target: '_new'
+
+%hr.spacer
+
+- unless @software_updates.empty?
+ .table-wrapper
+ %table.table
+ %thead
+ %tr
+ %th= t('admin.software_updates.version')
+ %th= t('admin.software_updates.type')
+ %th
+ %th
+ %tbody
+ - @software_updates.each do |update|
+ %tr
+ %td= update.version
+ %td= t("admin.software_updates.types.#{update.type}")
+ - if update.urgent?
+ %td.critical= t("admin.software_updates.critical_update")
+ - else
+ %td
+ %td= table_link_to 'link', t('admin.software_updates.release_notes'), update.release_notes
diff --git a/app/views/admin_mailer/new_critical_software_updates.text.erb b/app/views/admin_mailer/new_critical_software_updates.text.erb
new file mode 100644
index 00000000000000..c901bc50f785ce
--- /dev/null
+++ b/app/views/admin_mailer/new_critical_software_updates.text.erb
@@ -0,0 +1,5 @@
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
+
+<%= raw t('admin_mailer.new_critical_software_updates.body') %>
+
+<%= raw t('application_mailer.view')%> <%= admin_software_updates_url %>
diff --git a/app/views/admin_mailer/new_software_updates.text.erb b/app/views/admin_mailer/new_software_updates.text.erb
new file mode 100644
index 00000000000000..2fc4d1a5f20107
--- /dev/null
+++ b/app/views/admin_mailer/new_software_updates.text.erb
@@ -0,0 +1,5 @@
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
+
+<%= raw t('admin_mailer.new_software_updates.body') %>
+
+<%= raw t('application_mailer.view')%> <%= admin_software_updates_url %>
diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml
index a366dbad5b0ad1..e0cbe36d1e1b1d 100644
--- a/app/views/settings/preferences/notifications/show.html.haml
+++ b/app/views/settings/preferences/notifications/show.html.haml
@@ -22,7 +22,7 @@
.fields-group
= ff.input :always_send_emails, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_always_send_emails'), hint: I18n.t('simple_form.hints.defaults.setting_always_send_emails')
- - if current_user.can?(:manage_reports, :manage_appeals, :manage_users, :manage_taxonomies)
+ - if current_user.can?(:manage_reports, :manage_appeals, :manage_users, :manage_taxonomies) || (SoftwareUpdate.check_enabled? && current_user.can?(:view_devops))
%h4= t 'notifications.administration_emails'
.fields-group
@@ -33,6 +33,10 @@
= ff.input :'notification_emails.link_trends', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.trending_link') if current_user.can?(:manage_taxonomies)
= ff.input :'notification_emails.status_trends', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.trending_status') if current_user.can?(:manage_taxonomies)
+ - if SoftwareUpdate.check_enabled? && current_user.can?(:view_devops)
+ .fields-group
+ = ff.input :'notification_emails.software_updates', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.software_updates.label'), collection: %w(none critical patch all), label_method: ->(setting) { I18n.t("simple_form.labels.notification_emails.software_updates.#{setting}") }, include_blank: false, hint: false
+
%h4= t 'notifications.other_settings'
.fields-group
diff --git a/app/views/settings/privacy/show.html.haml b/app/views/settings/privacy/show.html.haml
index 6b7ae14e94ae91..3fb14023b06f09 100644
--- a/app/views/settings/privacy/show.html.haml
+++ b/app/views/settings/privacy/show.html.haml
@@ -24,6 +24,9 @@
%p.lead= t('privacy.search_hint_html')
+ .fields-group
+ = f.input :indexable, as: :boolean, wrapper: :with_label
+
= f.simple_fields_for :settings, current_user.settings do |ff|
.fields-group
= ff.input :indexable, wrapper: :with_label
diff --git a/app/workers/add_to_public_statuses_index_worker.rb b/app/workers/add_to_public_statuses_index_worker.rb
new file mode 100644
index 00000000000000..80d921eab0421d
--- /dev/null
+++ b/app/workers/add_to_public_statuses_index_worker.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddToPublicStatusesIndexWorker
+ include Sidekiq::Worker
+ include DatabaseHelper
+
+ sidekiq_options queue: 'pull'
+
+ def perform(account_id)
+ with_primary do
+ @account = Account.find(account_id)
+ end
+
+ return unless @account.indexable?
+
+ with_read_replica do
+ @account.add_to_public_statuses_index!
+ end
+ rescue ActiveRecord::RecordNotFound
+ true
+ end
+end
diff --git a/app/workers/remove_from_public_statuses_index_worker.rb b/app/workers/remove_from_public_statuses_index_worker.rb
new file mode 100644
index 00000000000000..e108a5c2096780
--- /dev/null
+++ b/app/workers/remove_from_public_statuses_index_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class RemoveFromPublicStatusesIndexWorker
+ include Sidekiq::Worker
+
+ def perform(account_id)
+ account = Account.find(account_id)
+
+ return if account.indexable?
+
+ account.remove_from_public_statuses_index!
+ rescue ActiveRecord::RecordNotFound
+ true
+ end
+end
diff --git a/app/workers/scheduler/indexing_scheduler.rb b/app/workers/scheduler/indexing_scheduler.rb
index 2868a3b7157d66..ff1b744442e9c7 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
@@ -16,6 +17,7 @@ def perform
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)
+
redis.srem("chewy:queue:#{type.name}", ids)
end
end
@@ -23,6 +25,6 @@ def perform
end
def indexes
- [AccountsIndex, TagsIndex, StatusesIndex]
+ [AccountsIndex, TagsIndex, PublicStatusesIndex, StatusesIndex]
end
end
diff --git a/app/workers/scheduler/software_update_check_scheduler.rb b/app/workers/scheduler/software_update_check_scheduler.rb
new file mode 100644
index 00000000000000..c732bdedc01125
--- /dev/null
+++ b/app/workers/scheduler/software_update_check_scheduler.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class Scheduler::SoftwareUpdateCheckScheduler
+ include Sidekiq::Worker
+
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.hour.to_i
+
+ def perform
+ SoftwareUpdateCheckService.new.call
+ end
+end
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 31a39624588cce..9a6637bdb9596d 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -35,6 +35,8 @@
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/i18n-tasks.yml b/config/i18n-tasks.yml
index 6c11ccc5a4579f..e5a4eb9a825b02 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -57,7 +57,7 @@ ignore_unused:
- 'activerecord.errors.*'
- '{devise,pagination,doorkeeper}.*'
- '{date,datetime,time,number}.*'
- - 'simple_form.{yes,no,recommended,not_recommended,glitch_only}'
+ - 'simple_form.{yes,no,recommended,not_recommended,overridden,glitch_only}'
- 'simple_form.{placeholders,hints,labels}.*'
- 'simple_form.{error_notification,required}.:'
- 'errors.messages.*'
diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb
index 6a0960021f7fd5..e7b4f2d458a9c0 100644
--- a/config/initializers/simple_form.rb
+++ b/config/initializers/simple_form.rb
@@ -108,7 +108,8 @@ def glitch_only(_wrapper_options = nil)
end
end
- b.use :hint, wrap_with: { tag: :span, class: :hint }
+ b.use :warning_hint, wrap_with: { tag: :span, class: [:hint, 'warning-hint'] }
+ b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :error, wrap_with: { tag: :span, class: :error }
end
@@ -122,8 +123,8 @@ def glitch_only(_wrapper_options = nil)
config.wrappers :with_block_label, class: [:input, :with_block_label], hint_class: :field_with_hint, error_class: :field_with_errors do |b|
b.use :html5
b.use :label
- b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :warning_hint, wrap_with: { tag: :span, class: [:hint, 'warning-hint'] }
+ b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :input, wrap_with: { tag: :div, class: :label_input }
b.use :error, wrap_with: { tag: :span, class: :error }
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 71121bb2e2281f..71e5fb843ecded 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -309,6 +309,7 @@ en:
unpublish: Unpublish
unpublished_msg: Announcement successfully unpublished!
updated_msg: Announcement successfully updated!
+ critical_update_pending: Critical update pending
custom_emojis:
assign_category: Assign category
by_domain: Domain
@@ -770,10 +771,27 @@ en:
approved: Approval required for sign up
none: Nobody can sign up
open: Anyone can sign up
+ security:
+ authorized_fetch: Require authentication from federated servers
+ authorized_fetch_hint: Requiring authentication from federated servers enables stricter enforcement of both user-level and server-level blocks. However, this comes at the cost of a performance penalty, reduces the reach of your replies, and may introduce compatibility issues with some federated services. In addition, this will not prevent dedicated actors from fetching your public posts and accounts.
+ authorized_fetch_overridden_hint: You are currently unable to change this setting because it is overridden by an environment variable.
+ federation_authentication: Federation authentication enforcement
title: Server settings
site_uploads:
delete: Delete uploaded file
destroyed_msg: Site upload successfully deleted!
+ software_updates:
+ critical_update: Critical — please update quickly
+ description: It is recommended to keep your Mastodon installation up to date to benefit from the latest fixes and features. Moreover, it is sometimes critical to update Mastodon in a timely manner to avoid security issues. For these reasons, Mastodon checks for updates every 30 minutes, and will notify you according to your e-mail notification preferences.
+ documentation_link: Learn more
+ release_notes: Release notes
+ title: Available updates
+ type: Type
+ types:
+ major: Major release
+ minor: Minor release
+ patch: Patch release — bugfixes and easy to apply changes
+ version: Version
statuses:
account: Author
application: Application
@@ -838,6 +856,12 @@ en:
message_html: You haven't defined any server rules.
sidekiq_process_check:
message_html: No Sidekiq process running for the %{value} queue(s). Please review your Sidekiq configuration
+ software_version_critical_check:
+ action: See available updates
+ message_html: A critical Mastodon update is available, please update as quickly as possible.
+ software_version_patch_check:
+ action: See available updates
+ message_html: A bugfix Mastodon update is available.
upload_check_privacy_error:
action: Check here for more information
message_html: "Your web server is misconfigured. The privacy of your users is at risk."
@@ -951,6 +975,9 @@ en:
body: "%{target} is appealing a moderation decision by %{action_taken_by} from %{date}, which was %{type}. They wrote:"
next_steps: You can approve the appeal to undo the moderation decision, or ignore it.
subject: "%{username} is appealing a moderation decision on %{instance}"
+ new_critical_software_updates:
+ body: New critical versions of Mastodon have been released, you may want to update as soon as possible!
+ subject: Critical Mastodon updates are available for %{instance}!
new_pending_account:
body: The details of the new account are below. You can approve or reject this application.
subject: New account up for review on %{instance} (%{username})
@@ -958,6 +985,9 @@ en:
body: "%{reporter} has reported %{target}"
body_remote: Someone from %{domain} has reported %{target}
subject: New report for %{instance} (#%{id})
+ new_software_updates:
+ body: New Mastodon versions have been released, you may want to update!
+ subject: New Mastodon versions are available for %{instance}!
new_trends:
body: 'The following items need a review before they can be displayed publicly:'
new_trending_links:
@@ -1709,6 +1739,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/simple_form.en.yml b/config/locales/simple_form.en.yml
index 443b7617ff3585..0b718c5b6542d3 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -6,6 +6,7 @@ en:
discoverable: Your public posts and profile may be featured or recommended in various areas of Mastodon and your profile may be suggested to other users.
display_name: Your full name or your fun name.
fields: Your homepage, pronouns, age, anything you want.
+ indexable: Your public posts may appear in search results on Mastodon. People who have interacted with your posts may be able to search them regardless.
note: 'You can @mention other people or #hashtags.'
show_collections: People will be able to browse through your follows and followers. People that you follow will see that you follow them regardless.
unlocked: People will be able to follow you without requesting approval. Uncheck if you want to review follow requests and chose whether to accept or reject new followers.
@@ -143,6 +144,7 @@ en:
fields:
name: Label
value: Content
+ indexable: Include public posts in search results
show_collections: Show follows and followers on profile
unlocked: Automatically accept new followers
account_alias:
@@ -289,6 +291,12 @@ en:
pending_account: New account needs review
reblog: Someone boosted your post
report: New report is submitted
+ software_updates:
+ all: Notify on all updates
+ critical: Notify on critical updates only
+ label: A new Mastodon version is available
+ none: Never notify of updates (not recommended)
+ patch: Notify on bugfix updates
trending_tag: New trend requires review
rule:
text: Rule
@@ -315,6 +323,7 @@ en:
url: Endpoint URL
'no': 'No'
not_recommended: Not recommended
+ overridden: Overridden
recommended: Recommended
required:
mark: "*"
diff --git a/config/navigation.rb b/config/navigation.rb
index 8a91c59f3807a3..4ea88fb4f2bfe0 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -3,6 +3,9 @@
SimpleNavigation::Configuration.run do |navigation|
navigation.items do |n|
n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_path
+
+ n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.critical_update_pending')]), admin_software_updates_path, if: -> { ENV['UPDATE_CHECK_URL'] != '' && current_user.can?(:view_devops) && SoftwareUpdate.urgent_pending? }, html: { class: 'warning' }
+
n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? }, highlights_on: %r{/settings/profile|/settings/featured_tags|/settings/verification|/settings/privacy}
n.item :preferences, safe_join([fa_icon('cog fw'), t('settings.preferences')]), settings_preferences_path, if: -> { current_user.functional? } do |s|
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 2e43a98e424c9b..14695c36c5f43a 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -202,4 +202,6 @@
end
end
end
+
+ resources :software_updates, only: [:index]
end
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index 12c45c22a1be64..f1ba5651dd4e3e 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -58,3 +58,7 @@
interval: 1 minute
class: Scheduler::SuspendedUserCleanupScheduler
queue: scheduler
+ software_update_check_scheduler:
+ interval: 30 minutes
+ class: Scheduler::SoftwareUpdateCheckScheduler
+ queue: scheduler
diff --git a/config/webpacker.yml b/config/webpacker.yml
index f5c93a6840e7fb..921c1306c175ef 100644
--- a/config/webpacker.yml
+++ b/config/webpacker.yml
@@ -59,7 +59,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/migrate/20230822081029_create_software_updates.rb b/db/migrate/20230822081029_create_software_updates.rb
new file mode 100644
index 00000000000000..146d5d3037b2a0
--- /dev/null
+++ b/db/migrate/20230822081029_create_software_updates.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateSoftwareUpdates < ActiveRecord::Migration[7.0]
+ def change
+ create_table :software_updates do |t|
+ t.string :version, null: false
+ t.boolean :urgent, default: false, null: false
+ t.integer :type, default: 0, null: false
+ t.string :release_notes, default: '', null: false
+
+ t.timestamps
+ end
+
+ add_index :software_updates, :version, unique: true
+ end
+end
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 c35ad800282343..3e9ab134b7d045 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 @@ def down
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/db/schema.rb b/db/schema.rb
index 863cddcbc16c62..8e84110807b692 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_08_18_142253) do
+ActiveRecord::Schema[7.0].define(version: 2023_08_22_081029) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -903,6 +903,16 @@
t.index ["var"], name: "index_site_uploads_on_var", unique: true
end
+ create_table "software_updates", force: :cascade do |t|
+ t.string "version", null: false
+ t.boolean "urgent", default: false, null: false
+ t.integer "type", default: 0, null: false
+ t.string "release_notes", default: "", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["version"], name: "index_software_updates_on_version", unique: true
+ end
+
create_table "status_edits", force: :cascade do |t|
t.bigint "status_id", null: false
t.bigint "account_id"
diff --git a/dist/nginx.conf b/dist/nginx.conf
index 39fa58e50dc021..5bb99038649019 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/mastodon/cli/search.rb b/lib/mastodon/cli/search.rb
index 41862b5b6bbd58..481e01d8e734c4 100644
--- a/lib/mastodon/cli/search.rb
+++ b/lib/mastodon/cli/search.rb
@@ -10,6 +10,7 @@ class Search < Base
InstancesIndex,
AccountsIndex,
TagsIndex,
+ PublicStatusesIndex,
StatusesIndex,
].freeze
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 26ffaf0388c7a0..db609de5e9cdab 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -16,12 +16,16 @@ def patch
0
end
- def flags
- ENV['MASTODON_VERSION_FLAGS'].presence || '-beta2'
+ def default_prerelease
+ 'beta2'
end
- def suffix
- "+glitch#{ENV.fetch('MASTODON_VERSION_SUFFIX', '')}"
+ def prerelease
+ ENV['MASTODON_VERSION_PRERELEASE'].presence || default_prerelease
+ end
+
+ def build_metadata
+ ['glitch', ENV.fetch('MASTODON_VERSION_METADATA', nil)].compact.join('.')
end
def to_a
@@ -29,7 +33,14 @@ def to_a
end
def to_s
- [to_a.join('.'), flags, suffix].join
+ components = [to_a.join('.')]
+ components << "-#{prerelease}" if prerelease.present?
+ components << "+#{build_metadata}" if build_metadata.present?
+ components.join
+ end
+
+ def gem_version
+ @gem_version ||= Gem::Version.new(to_s.split('+')[0])
end
def repository
diff --git a/lib/paperclip/transcoder.rb b/lib/paperclip/transcoder.rb
index 0f2e30f7d5e45e..b88cf662c2f4e6 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 @@ def make
@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/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 010caaf8eaa98a..f68d1cf1f8fbeb 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -424,6 +424,10 @@ namespace :mastodon do
end
end
+ prompt.say "\n"
+
+ env['UPDATE_CHECK_URL'] = '' unless prompt.yes?('Do you want Mastodon to periodically check for important updates and notify you? (Recommended)', default: true)
+
prompt.say "\n"
prompt.say 'This configuration will be written to .env.production'
diff --git a/package.json b/package.json
index abd470906f166b..ff9ba32b8e0898 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,7 @@
"immutable": "^4.3.0",
"imports-loader": "^1.2.0",
"intl-messageformat": "^10.3.5",
+ "ioredis": "^5.3.2",
"js-yaml": "^4.1.0",
"jsdom": "^22.1.0",
"lodash": "^4.17.21",
@@ -121,7 +122,6 @@
"react-swipeable-views": "^0.14.0",
"react-textarea-autosize": "^8.4.1",
"react-toggle": "^4.1.3",
- "redis": "^4.6.5",
"redux": "^4.2.1",
"redux-immutable": "^4.0.0",
"redux-thunk": "^2.4.2",
@@ -155,7 +155,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/chewy/public_statuses_index_spec.rb b/spec/chewy/public_statuses_index_spec.rb
new file mode 100644
index 00000000000000..2f93d0ff025a59
--- /dev/null
+++ b/spec/chewy/public_statuses_index_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe PublicStatusesIndex do
+ describe 'Searching the index' do
+ before do
+ mock_elasticsearch_response(described_class, raw_response)
+ end
+
+ it 'returns results from a query' do
+ results = described_class.query(match: { name: 'status' })
+
+ expect(results).to eq []
+ end
+ end
+
+ def raw_response
+ {
+ took: 3,
+ hits: {
+ hits: [
+ {
+ _id: '0',
+ _score: 1.6375021,
+ },
+ ],
+ },
+ }
+ end
+end
diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
index 718911083362de..8896f02a77272e 100644
--- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
@@ -5,36 +5,70 @@
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
+
+ before do
+ PostStatusService.new.call(user.account, text: 'It is a #test')
+ end
- describe 'GET #show' do
+ context 'when the instance allows public preview' do
before do
- PostStatusService.new.call(user.account, text: 'It is a #test')
+ Setting.timeline_preview = true
+ end
+
+ 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
+
+ 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
- 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
+ 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 8dc0f329b65570..20770a72114b3e 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 @@
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/fabricators/software_update_fabricator.rb b/spec/fabricators/software_update_fabricator.rb
new file mode 100644
index 00000000000000..622fff66e8dd95
--- /dev/null
+++ b/spec/fabricators/software_update_fabricator.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+Fabricator(:software_update) do
+ version '99.99.99'
+ urgent false
+ type 'patch'
+end
diff --git a/spec/features/admin/software_updates_spec.rb b/spec/features/admin/software_updates_spec.rb
new file mode 100644
index 00000000000000..4a635d1a794f8f
--- /dev/null
+++ b/spec/features/admin/software_updates_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'finding software updates through the admin interface' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: true, release_notes: 'https://github.com/mastodon/mastodon/releases/v99')
+
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Owner')), scope: :user
+ end
+
+ it 'shows a link to the software updates page, which links to release notes' do
+ visit settings_profile_path
+ click_on I18n.t('admin.critical_update_pending')
+
+ expect(page).to have_title(I18n.t('admin.software_updates.title'))
+
+ expect(page).to have_content('99.99.99')
+
+ click_on I18n.t('admin.software_updates.release_notes')
+ expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true)
+ end
+end
diff --git a/spec/lib/admin/system_check/elasticsearch_check_spec.rb b/spec/lib/admin/system_check/elasticsearch_check_spec.rb
index f3918d403d1fdd..a885640ce0c603 100644
--- a/spec/lib/admin/system_check/elasticsearch_check_spec.rb
+++ b/spec/lib/admin/system_check/elasticsearch_check_spec.rb
@@ -17,6 +17,7 @@
allow(Chewy.client.indices).to receive_messages(get_mapping: {
AccountsIndex.index_name => AccountsIndex.mappings_hash.deep_stringify_keys,
StatusesIndex.index_name => StatusesIndex.mappings_hash.deep_stringify_keys,
+ PublicStatusesIndex.index_name => PublicStatusesIndex.mappings_hash.deep_stringify_keys,
InstancesIndex.index_name => InstancesIndex.mappings_hash.deep_stringify_keys,
TagsIndex.index_name => TagsIndex.mappings_hash.deep_stringify_keys,
}, get_settings: {
@@ -90,6 +91,7 @@
allow(Chewy.client.indices).to receive(:get_mapping).and_return({
AccountsIndex.index_name => AccountsIndex.mappings_hash.deep_stringify_keys,
StatusesIndex.index_name => StatusesIndex.mappings_hash.deep_stringify_keys,
+ PublicStatusesIndex.index_name => PublicStatusesIndex.mappings_hash.deep_stringify_keys,
InstancesIndex.index_name => InstancesIndex.mappings_hash.deep_stringify_keys,
TagsIndex.index_name => TagsIndex.mappings_hash.deep_stringify_keys,
})
diff --git a/spec/lib/admin/system_check/software_version_check_spec.rb b/spec/lib/admin/system_check/software_version_check_spec.rb
new file mode 100644
index 00000000000000..de4335fc519fc2
--- /dev/null
+++ b/spec/lib/admin/system_check/software_version_check_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::SoftwareVersionCheck do
+ include RoutingHelper
+
+ subject(:check) { described_class.new(user) }
+
+ let(:user) { Fabricate(:user) }
+
+ describe 'skip?' do
+ context 'when user cannot view devops' do
+ before { allow(user).to receive(:can?).with(:view_devops).and_return(false) }
+
+ it 'returns true' do
+ expect(check.skip?).to be true
+ end
+ end
+
+ context 'when user can view devops' do
+ before { allow(user).to receive(:can?).with(:view_devops).and_return(true) }
+
+ it 'returns false' do
+ expect(check.skip?).to be false
+ end
+
+ context 'when checks are disabled' do
+ around do |example|
+ ClimateControl.modify UPDATE_CHECK_URL: '' do
+ example.run
+ end
+ end
+
+ it 'returns true' do
+ expect(check.skip?).to be true
+ end
+ end
+ end
+ end
+
+ describe 'pass?' do
+ context 'when there is no known update' do
+ it 'returns true' do
+ expect(check.pass?).to be true
+ end
+ end
+
+ context 'when there is a non-urgent major release' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: false)
+ end
+
+ it 'returns true' do
+ expect(check.pass?).to be true
+ end
+ end
+
+ context 'when there is an urgent major release' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: true)
+ end
+
+ it 'returns false' do
+ expect(check.pass?).to be false
+ end
+ end
+
+ context 'when there is an urgent minor release' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'minor', urgent: true)
+ end
+
+ it 'returns false' do
+ expect(check.pass?).to be false
+ end
+ end
+
+ context 'when there is an urgent patch release' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: true)
+ end
+
+ it 'returns false' do
+ expect(check.pass?).to be false
+ end
+ end
+
+ context 'when there is a non-urgent patch release' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: false)
+ end
+
+ it 'returns false' do
+ expect(check.pass?).to be false
+ end
+ end
+ end
+
+ describe 'message' do
+ context 'when there is a non-urgent patch release pending' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: false)
+ end
+
+ it 'sends class name symbol to message instance' do
+ allow(Admin::SystemCheck::Message).to receive(:new)
+ .with(:software_version_patch_check, anything, anything)
+
+ check.message
+
+ expect(Admin::SystemCheck::Message).to have_received(:new)
+ .with(:software_version_patch_check, nil, admin_software_updates_path)
+ end
+ end
+
+ context 'when there is an urgent patch release pending' do
+ before do
+ Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: true)
+ end
+
+ it 'sends class name symbol to message instance' do
+ allow(Admin::SystemCheck::Message).to receive(:new)
+ .with(:software_version_critical_check, anything, anything, anything)
+
+ check.message
+
+ expect(Admin::SystemCheck::Message).to have_received(:new)
+ .with(:software_version_critical_check, nil, admin_software_updates_path, true)
+ end
+ end
+ end
+end
diff --git a/spec/lib/importer/public_statuses_index_importer_spec.rb b/spec/lib/importer/public_statuses_index_importer_spec.rb
new file mode 100644
index 00000000000000..bc7c038a97c5a8
--- /dev/null
+++ b/spec/lib/importer/public_statuses_index_importer_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Importer::PublicStatusesIndexImporter do
+ describe 'import!' do
+ let(:pool) { Concurrent::FixedThreadPool.new(5) }
+ let(:importer) { described_class.new(batch_size: 123, executor: pool) }
+
+ before { Fabricate(:status, account: Fabricate(:account, indexable: true)) }
+
+ it 'indexes relevant statuses' do
+ expect { importer.import! }.to update_index(PublicStatusesIndex)
+ end
+ 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 00000000000000..66b0e8f9e2367b
--- /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 1095334695485d..17f06d2833d37d 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_a(SearchQueryTransformer::TermClause)
- expect(transformer.must_clauses.first).to be_nil
- 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/mailers/admin_mailer_spec.rb b/spec/mailers/admin_mailer_spec.rb
index 9123804a480458..423dce88ab09ee 100644
--- a/spec/mailers/admin_mailer_spec.rb
+++ b/spec/mailers/admin_mailer_spec.rb
@@ -85,4 +85,46 @@
expect(mail.body.encoded).to match 'The following items need a review before they can be displayed publicly'
end
end
+
+ describe '.new_software_updates' do
+ let(:recipient) { Fabricate(:account, username: 'Bob') }
+ let(:mail) { described_class.with(recipient: recipient).new_software_updates }
+
+ before do
+ recipient.user.update(locale: :en)
+ end
+
+ it 'renders the headers' do
+ expect(mail.subject).to eq('New Mastodon versions are available for cb6e6126.ngrok.io!')
+ expect(mail.to).to eq [recipient.user_email]
+ expect(mail.from).to eq ['notifications@localhost']
+ end
+
+ it 'renders the body' do
+ expect(mail.body.encoded).to match 'New Mastodon versions have been released, you may want to update!'
+ end
+ end
+
+ describe '.new_critical_software_updates' do
+ let(:recipient) { Fabricate(:account, username: 'Bob') }
+ let(:mail) { described_class.with(recipient: recipient).new_critical_software_updates }
+
+ before do
+ recipient.user.update(locale: :en)
+ end
+
+ it 'renders the headers', :aggregate_failures do
+ expect(mail.subject).to eq('Critical Mastodon updates are available for cb6e6126.ngrok.io!')
+ expect(mail.to).to eq [recipient.user_email]
+ expect(mail.from).to eq ['notifications@localhost']
+
+ expect(mail['Importance'].value).to eq 'high'
+ expect(mail['Priority'].value).to eq 'urgent'
+ expect(mail['X-Priority'].value).to eq '1'
+ end
+
+ it 'renders the body' do
+ expect(mail.body.encoded).to match 'New critical versions of Mastodon have been released, you may want to update as soon as possible!'
+ end
+ end
end
diff --git a/spec/models/concerns/account_statuses_search_spec.rb b/spec/models/concerns/account_statuses_search_spec.rb
new file mode 100644
index 00000000000000..46362936f4a330
--- /dev/null
+++ b/spec/models/concerns/account_statuses_search_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe AccountStatusesSearch do
+ let(:account) { Fabricate(:account, indexable: indexable) }
+
+ before do
+ allow(Chewy).to receive(:enabled?).and_return(true)
+ end
+
+ describe '#enqueue_update_public_statuses_index' do
+ before do
+ allow(account).to receive(:enqueue_add_to_public_statuses_index)
+ allow(account).to receive(:enqueue_remove_from_public_statuses_index)
+ end
+
+ context 'when account is indexable' do
+ let(:indexable) { true }
+
+ it 'enqueues add_to_public_statuses_index and not to remove_from_public_statuses_index' do
+ account.enqueue_update_public_statuses_index
+ expect(account).to have_received(:enqueue_add_to_public_statuses_index)
+ expect(account).to_not have_received(:enqueue_remove_from_public_statuses_index)
+ end
+ end
+
+ context 'when account is not indexable' do
+ let(:indexable) { false }
+
+ it 'enqueues remove_from_public_statuses_index and not to add_to_public_statuses_index' do
+ account.enqueue_update_public_statuses_index
+ expect(account).to have_received(:enqueue_remove_from_public_statuses_index)
+ expect(account).to_not have_received(:enqueue_add_to_public_statuses_index)
+ end
+ end
+ end
+
+ describe '#enqueue_add_to_public_statuses_index' do
+ let(:indexable) { true }
+ let(:worker) { AddToPublicStatusesIndexWorker }
+
+ before do
+ allow(worker).to receive(:perform_async)
+ end
+
+ it 'enqueues AddToPublicStatusesIndexWorker' do
+ account.enqueue_add_to_public_statuses_index
+ expect(worker).to have_received(:perform_async).with(account.id)
+ end
+ end
+
+ describe '#enqueue_remove_from_public_statuses_index' do
+ let(:indexable) { false }
+ let(:worker) { RemoveFromPublicStatusesIndexWorker }
+
+ before do
+ allow(worker).to receive(:perform_async)
+ end
+
+ it 'enqueues RemoveFromPublicStatusesIndexWorker' do
+ account.enqueue_remove_from_public_statuses_index
+ expect(worker).to have_received(:perform_async).with(account.id)
+ end
+ end
+end
diff --git a/spec/models/software_update_spec.rb b/spec/models/software_update_spec.rb
new file mode 100644
index 00000000000000..0a494b0c4ce626
--- /dev/null
+++ b/spec/models/software_update_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe SoftwareUpdate do
+ describe '.pending_to_a' do
+ before do
+ allow(Mastodon::Version).to receive(:gem_version).and_return(Gem::Version.new(mastodon_version))
+
+ Fabricate(:software_update, version: '3.4.42', type: 'patch', urgent: true)
+ Fabricate(:software_update, version: '3.5.0', type: 'minor', urgent: false)
+ Fabricate(:software_update, version: '4.2.0', type: 'major', urgent: false)
+ end
+
+ context 'when the Mastodon version is an outdated release' do
+ let(:mastodon_version) { '3.4.0' }
+
+ it 'returns the expected versions' do
+ expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('3.4.42', '3.5.0', '4.2.0')
+ end
+ end
+
+ context 'when the Mastodon version is more recent than anything last returned by the server' do
+ let(:mastodon_version) { '5.0.0' }
+
+ it 'returns the expected versions' do
+ expect(described_class.pending_to_a.pluck(:version)).to eq []
+ end
+ end
+
+ context 'when the Mastodon version is an outdated nightly' do
+ let(:mastodon_version) { '4.3.0-nightly.2023-09-10' }
+
+ before do
+ Fabricate(:software_update, version: '4.3.0-nightly.2023-09-12', type: 'major', urgent: true)
+ end
+
+ it 'returns the expected versions' do
+ expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-nightly.2023-09-12')
+ end
+ end
+
+ context 'when the Mastodon version is a very outdated nightly' do
+ let(:mastodon_version) { '4.2.0-nightly.2023-07-10' }
+
+ it 'returns the expected versions' do
+ expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.2.0')
+ end
+ end
+
+ context 'when the Mastodon version is an outdated dev version' do
+ let(:mastodon_version) { '4.3.0-0.dev.0' }
+
+ before do
+ Fabricate(:software_update, version: '4.3.0-0.dev.2', type: 'major', urgent: true)
+ end
+
+ it 'returns the expected versions' do
+ expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-0.dev.2')
+ end
+ end
+
+ context 'when the Mastodon version is an outdated beta version' do
+ let(:mastodon_version) { '4.3.0-beta1' }
+
+ before do
+ Fabricate(:software_update, version: '4.3.0-beta2', type: 'major', urgent: true)
+ end
+
+ it 'returns the expected versions' do
+ expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-beta2')
+ end
+ end
+
+ context 'when the Mastodon version is an outdated beta version and there is a rc' do
+ let(:mastodon_version) { '4.3.0-beta1' }
+
+ before do
+ Fabricate(:software_update, version: '4.3.0-rc1', type: 'major', urgent: true)
+ end
+
+ it 'returns the expected versions' do
+ expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-rc1')
+ end
+ end
+ end
+end
diff --git a/spec/policies/software_update_policy_spec.rb b/spec/policies/software_update_policy_spec.rb
new file mode 100644
index 00000000000000..e19ba616128db2
--- /dev/null
+++ b/spec/policies/software_update_policy_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'pundit/rspec'
+
+RSpec.describe SoftwareUpdatePolicy do
+ subject { described_class }
+
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')).account }
+ let(:john) { Fabricate(:account) }
+
+ permissions :index? do
+ context 'when owner' do
+ it 'permits' do
+ expect(subject).to permit(admin, SoftwareUpdate)
+ end
+ end
+
+ context 'when not owner' do
+ it 'denies' do
+ expect(subject).to_not permit(john, SoftwareUpdate)
+ end
+ end
+ end
+end
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 123bd4f5603121..cb69af5f548a26 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -83,15 +83,6 @@
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/spec/services/software_update_check_service_spec.rb b/spec/services/software_update_check_service_spec.rb
new file mode 100644
index 00000000000000..c8821348ac237f
--- /dev/null
+++ b/spec/services/software_update_check_service_spec.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe SoftwareUpdateCheckService, type: :service do
+ subject { described_class.new }
+
+ shared_examples 'when the feature is enabled' do
+ let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.to_s.split('+')[0]}" }
+
+ let(:devops_role) { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) }
+ let(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
+ let(:old_devops_user) { Fabricate(:user) }
+ let(:none_user) { Fabricate(:user, role: devops_role) }
+ let(:patch_user) { Fabricate(:user, role: devops_role) }
+ let(:critical_user) { Fabricate(:user, role: devops_role) }
+
+ around do |example|
+ queue_adapter = ActiveJob::Base.queue_adapter
+ ActiveJob::Base.queue_adapter = :test
+
+ example.run
+
+ ActiveJob::Base.queue_adapter = queue_adapter
+ end
+
+ before do
+ Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false)
+ Fabricate(:software_update, version: '42.13.12', type: 'major', urgent: false)
+
+ owner_user.settings.update('notification_emails.software_updates': 'all')
+ owner_user.save!
+
+ old_devops_user.settings.update('notification_emails.software_updates': 'all')
+ old_devops_user.save!
+
+ none_user.settings.update('notification_emails.software_updates': 'none')
+ none_user.save!
+
+ patch_user.settings.update('notification_emails.software_updates': 'patch')
+ patch_user.save!
+
+ critical_user.settings.update('notification_emails.software_updates': 'critical')
+ critical_user.save!
+ end
+
+ context 'when the update server errors out' do
+ before do
+ stub_request(:get, full_update_check_url).to_return(status: 404)
+ end
+
+ it 'deletes outdated update records but keeps valid update records' do
+ expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['42.13.12'])
+ end
+ end
+
+ context 'when the server returns new versions' do
+ let(:server_json) do
+ {
+ updatesAvailable: [
+ {
+ version: '4.2.1',
+ urgent: false,
+ type: 'patch',
+ releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.2.1',
+ },
+ {
+ version: '4.3.0',
+ urgent: false,
+ type: 'minor',
+ releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.3.0',
+ },
+ {
+ version: '5.0.0',
+ urgent: false,
+ type: 'minor',
+ releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0',
+ },
+ ],
+ }
+ end
+
+ before do
+ stub_request(:get, full_update_check_url).to_return(body: Oj.dump(server_json))
+ end
+
+ it 'updates the list of known updates' do
+ expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['4.2.1', '4.3.0', '5.0.0'])
+ end
+
+ context 'when no update is urgent' do
+ it 'sends e-mail notifications according to settings', :aggregate_failures do
+ expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_software_updates)
+ .with(hash_including(params: { recipient: owner_user.account })).once
+ .and(have_enqueued_mail(AdminMailer, :new_software_updates).with(hash_including(params: { recipient: patch_user.account })).once)
+ .and(have_enqueued_mail.at_most(2))
+ end
+ end
+
+ context 'when an update is urgent' do
+ let(:server_json) do
+ {
+ updatesAvailable: [
+ {
+ version: '5.0.0',
+ urgent: true,
+ type: 'minor',
+ releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0',
+ },
+ ],
+ }
+ end
+
+ it 'sends e-mail notifications according to settings', :aggregate_failures do
+ expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_critical_software_updates)
+ .with(hash_including(params: { recipient: owner_user.account })).once
+ .and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: patch_user.account })).once)
+ .and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: critical_user.account })).once)
+ .and(have_enqueued_mail.at_most(3))
+ end
+ end
+ end
+ end
+
+ context 'when update checking is disabled' do
+ around do |example|
+ ClimateControl.modify UPDATE_CHECK_URL: '' do
+ example.run
+ end
+ end
+
+ before do
+ Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false)
+ end
+
+ it 'deletes outdated update records' do
+ expect { subject.call }.to change(SoftwareUpdate, :count).from(1).to(0)
+ end
+ end
+
+ context 'when using the default update checking API' do
+ let(:update_check_url) { 'https://api.joinmastodon.org/update-check' }
+
+ it_behaves_like 'when the feature is enabled'
+ end
+
+ context 'when using a custom update check URL' do
+ let(:update_check_url) { 'https://api.example.com/update_check' }
+
+ around do |example|
+ ClimateControl.modify UPDATE_CHECK_URL: 'https://api.example.com/update_check' do
+ example.run
+ end
+ end
+
+ it_behaves_like 'when the feature is enabled'
+ end
+end
diff --git a/spec/workers/add_to_public_statuses_index_worker_spec.rb b/spec/workers/add_to_public_statuses_index_worker_spec.rb
new file mode 100644
index 00000000000000..fa150722419bd4
--- /dev/null
+++ b/spec/workers/add_to_public_statuses_index_worker_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe AddToPublicStatusesIndexWorker do
+ describe '#perform' do
+ let(:account) { Fabricate(:account, indexable: indexable) }
+ let(:account_id) { account.id }
+
+ before do
+ allow(Account).to receive(:find).with(account_id).and_return(account) unless account.nil?
+ allow(account).to receive(:add_to_public_statuses_index!) unless account.nil?
+ end
+
+ context 'when account is indexable' do
+ let(:indexable) { true }
+
+ it 'adds the account to the public statuses index' do
+ subject.perform(account_id)
+ expect(account).to have_received(:add_to_public_statuses_index!)
+ end
+ end
+
+ context 'when account is not indexable' do
+ let(:indexable) { false }
+
+ it 'does not add the account to public statuses index' do
+ subject.perform(account_id)
+ expect(account).to_not have_received(:add_to_public_statuses_index!)
+ end
+ end
+
+ context 'when account does not exist' do
+ let(:account) { nil }
+ let(:account_id) { 999 }
+
+ it 'does not raise an error' do
+ expect { subject.perform(account_id) }.to_not raise_error
+ end
+ end
+ end
+end
diff --git a/spec/workers/remove_from_public_statuses_index_worker_spec.rb b/spec/workers/remove_from_public_statuses_index_worker_spec.rb
new file mode 100644
index 00000000000000..43ff211eaa772a
--- /dev/null
+++ b/spec/workers/remove_from_public_statuses_index_worker_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe RemoveFromPublicStatusesIndexWorker do
+ describe '#perform' do
+ let(:account) { Fabricate(:account, indexable: indexable) }
+ let(:account_id) { account.id }
+
+ before do
+ allow(Account).to receive(:find).with(account_id).and_return(account) unless account.nil?
+ allow(account).to receive(:remove_from_public_statuses_index!) unless account.nil?
+ end
+
+ context 'when account is not indexable' do
+ let(:indexable) { false }
+
+ it 'removes the account from public statuses index' do
+ subject.perform(account_id)
+ expect(account).to have_received(:remove_from_public_statuses_index!)
+ end
+ end
+
+ context 'when account is indexable' do
+ let(:indexable) { true }
+
+ it 'does not remove the account from public statuses index' do
+ subject.perform(account_id)
+ expect(account).to_not have_received(:remove_from_public_statuses_index!)
+ end
+ end
+
+ context 'when account does not exist' do
+ let(:account) { nil }
+ let(:account_id) { 999 }
+
+ it 'does not raise an error' do
+ expect { subject.perform(account_id) }.to_not raise_error
+ end
+ end
+ end
+end
diff --git a/spec/workers/scheduler/software_update_check_scheduler_spec.rb b/spec/workers/scheduler/software_update_check_scheduler_spec.rb
new file mode 100644
index 00000000000000..f596c0a1eca5a7
--- /dev/null
+++ b/spec/workers/scheduler/software_update_check_scheduler_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Scheduler::SoftwareUpdateCheckScheduler do
+ subject { described_class.new }
+
+ describe 'perform' do
+ let(:service_double) { instance_double(SoftwareUpdateCheckService, call: nil) }
+
+ before do
+ allow(SoftwareUpdateCheckService).to receive(:new).and_return(service_double)
+ end
+
+ it 'calls SoftwareUpdateCheckService' do
+ subject.perform
+ expect(service_double).to have_received(:call)
+ end
+ end
+end
diff --git a/streaming/index.js b/streaming/index.js
index ffb40ebc4aa2b0..2f784f24894420 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -6,12 +6,12 @@ const url = require('url');
const dotenv = require('dotenv');
const express = require('express');
+const Redis = require('ioredis');
const { JSDOM } = require('jsdom');
const log = require('npmlog');
const pg = require('pg');
const dbUrlToConfig = require('pg-connection-string').parse;
const metrics = require('prom-client');
-const redis = require('redis');
const uuid = require('uuid');
const WebSocket = require('ws');
@@ -24,30 +24,12 @@ dotenv.config({
log.level = process.env.LOG_LEVEL || 'verbose';
/**
- * @param {Object.} defaultConfig
- * @param {string} redisUrl
+ * @param {Object.} config
*/
-const redisUrlToClient = async (defaultConfig, redisUrl) => {
- const config = defaultConfig;
-
- let client;
-
- if (!redisUrl) {
- client = redis.createClient(config);
- } else if (redisUrl.startsWith('unix://')) {
- client = redis.createClient(Object.assign(config, {
- socket: {
- path: redisUrl.slice(7),
- },
- }));
- } else {
- client = redis.createClient(Object.assign(config, {
- url: redisUrl,
- }));
- }
-
+const createRedisClient = async (config) => {
+ const { redisParams, redisUrl } = config;
+ const client = new Redis(redisUrl, redisParams);
client.on('error', (err) => log.error('Redis Client Error!', err));
- await client.connect();
return client;
};
@@ -147,23 +129,22 @@ const pgConfigFromEnv = (env) => {
* @returns {Object.} configuration for the Redis connection
*/
const redisConfigFromEnv = (env) => {
- const redisNamespace = env.REDIS_NAMESPACE || null;
+ // ioredis *can* transparently add prefixes for us, but it doesn't *in some cases*,
+ // which means we can't use it. But this is something that should be looked into.
+ const redisPrefix = env.REDIS_NAMESPACE ? `${env.REDIS_NAMESPACE}:` : '';
const redisParams = {
- socket: {
- host: env.REDIS_HOST || '127.0.0.1',
- port: env.REDIS_PORT || 6379,
- },
- database: env.REDIS_DB || 0,
+ host: env.REDIS_HOST || '127.0.0.1',
+ port: env.REDIS_PORT || 6379,
+ db: env.REDIS_DB || 0,
password: env.REDIS_PASSWORD || undefined,
};
- if (redisNamespace) {
- redisParams.namespace = redisNamespace;
+ // redisParams.path takes precedence over host and port.
+ if (env.REDIS_URL && env.REDIS_URL.startsWith('unix://')) {
+ redisParams.path = env.REDIS_URL.slice(7);
}
- const redisPrefix = redisNamespace ? `${redisNamespace}:` : '';
-
return {
redisParams,
redisPrefix,
@@ -179,15 +160,15 @@ const startServer = async () => {
const pgPool = new pg.Pool(pgConfigFromEnv(process.env));
const server = http.createServer(app);
- const { redisParams, redisUrl, redisPrefix } = redisConfigFromEnv(process.env);
-
/**
* @type {Object.): void>>}
*/
const subs = {};
- const redisSubscribeClient = await redisUrlToClient(redisParams, redisUrl);
- const redisClient = await redisUrlToClient(redisParams, redisUrl);
+ const redisConfig = redisConfigFromEnv(process.env);
+ const redisSubscribeClient = await createRedisClient(redisConfig);
+ const redisClient = await createRedisClient(redisConfig);
+ const { redisPrefix } = redisConfig;
// Collect metrics from Node.js
metrics.collectDefaultMetrics();
@@ -277,13 +258,13 @@ const startServer = async () => {
};
/**
- * @param {string} message
* @param {string} channel
+ * @param {string} message
*/
- const onRedisMessage = (message, channel) => {
+ const onRedisMessage = (channel, message) => {
const callbacks = subs[channel];
- log.silly(`New message on channel ${channel}`);
+ log.silly(`New message on channel ${redisPrefix}${channel}`);
if (!callbacks) {
return;
@@ -294,6 +275,7 @@ const startServer = async () => {
callbacks.forEach(callback => callback(json));
};
+ redisSubscribeClient.on("message", onRedisMessage);
/**
* @callback SubscriptionListener
@@ -312,8 +294,14 @@ const startServer = async () => {
if (subs[channel].length === 0) {
log.verbose(`Subscribe ${channel}`);
- redisSubscribeClient.subscribe(channel, onRedisMessage);
- redisSubscriptions.inc();
+ redisSubscribeClient.subscribe(channel, (err, count) => {
+ if (err) {
+ log.error(`Error subscribing to ${channel}`);
+ }
+ else {
+ redisSubscriptions.set(count);
+ }
+ });
}
subs[channel].push(callback);
@@ -334,8 +322,14 @@ const startServer = async () => {
if (subs[channel].length === 0) {
log.verbose(`Unsubscribe ${channel}`);
- redisSubscribeClient.unsubscribe(channel);
- redisSubscriptions.dec();
+ redisSubscribeClient.unsubscribe(channel, (err, count) => {
+ if (err) {
+ log.error(`Error unsubscribing to ${channel}`);
+ }
+ else {
+ redisSubscriptions.set(count);
+ }
+ });
delete subs[channel];
}
};
diff --git a/yarn.lock b/yarn.lock
index 783ddbef178d34..37286328837b97 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,30 +44,38 @@
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==
"@babel/core@^7.10.4", "@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.22.1":
- version "7.22.10"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.10.tgz#aad442c7bcd1582252cb4576747ace35bc122f35"
- integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.11.tgz#8033acaa2aa24c3f814edaaa057f3ce0ba559c24"
+ integrity sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.22.10"
"@babel/generator" "^7.22.10"
"@babel/helper-compilation-targets" "^7.22.10"
"@babel/helper-module-transforms" "^7.22.9"
- "@babel/helpers" "^7.22.10"
- "@babel/parser" "^7.22.10"
+ "@babel/helpers" "^7.22.11"
+ "@babel/parser" "^7.22.11"
"@babel/template" "^7.22.5"
- "@babel/traverse" "^7.22.10"
- "@babel/types" "^7.22.10"
+ "@babel/traverse" "^7.22.11"
+ "@babel/types" "^7.22.11"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
- json5 "^2.2.2"
+ json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.22.10", "@babel/generator@^7.22.5", "@babel/generator@^7.7.2":
@@ -113,10 +121,10 @@
lru-cache "^5.1.1"
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==
+"@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==
dependencies:
"@babel/helper-annotate-as-pure" "^7.22.5"
"@babel/helper-environment-visitor" "^7.22.5"
@@ -268,16 +276,25 @@
"@babel/template" "^7.22.5"
"@babel/types" "^7.22.10"
-"@babel/helpers@^7.22.10":
- version "7.22.10"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.10.tgz#ae6005c539dfbcb5cd71fb51bfc8a52ba63bc37a"
- integrity sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==
+"@babel/helpers@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.11.tgz#b02f5d5f2d7abc21ab59eeed80de410ba70b056a"
+ integrity sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==
dependencies:
"@babel/template" "^7.22.5"
- "@babel/traverse" "^7.22.10"
- "@babel/types" "^7.22.10"
+ "@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==
@@ -286,11 +303,21 @@
chalk "^2.4.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.10", "@babel/parser@^7.22.5":
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55"
integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==
+"@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"
@@ -467,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"
@@ -508,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"
@@ -562,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"
@@ -578,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"
@@ -602,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"
@@ -617,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"
@@ -640,22 +667,22 @@
"@babel/helper-module-transforms" "^7.22.5"
"@babel/helper-plugin-utils" "^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==
+"@babel/plugin-transform-modules-commonjs@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz#d7991d3abad199c03b68ee66a64f216c47ffdfae"
+ integrity sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g==
dependencies:
- "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-module-transforms" "^7.22.9"
"@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"
@@ -682,29 +709,29 @@
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":
- version "7.22.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381"
- integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==
+"@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==
dependencies:
"@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"
@@ -717,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"
@@ -749,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"
@@ -877,13 +904,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
-"@babel/plugin-transform-typescript@^7.22.5":
- version "7.22.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz#5c0f7adfc1b5f38c4dbc8f79b1f0f8074134bd7d"
- integrity sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA==
+"@babel/plugin-transform-typescript@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.11.tgz#9f27fb5e51585729374bb767ab6a6d9005a23329"
+ integrity sha512-0E4/L+7gfvHub7wsbTv03oRtD69X31LByy44fGmFzbZScpupFByMcgCJ0VbBTkzyjSJKuRoGN8tcijOWKTmqOA==
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-typescript" "^7.22.5"
@@ -919,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"
@@ -949,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"
@@ -997,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"
@@ -1026,15 +1053,15 @@
"@babel/plugin-transform-react-pure-annotations" "^7.22.5"
"@babel/preset-typescript@^7.21.5":
- version "7.22.5"
- resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8"
- integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.11.tgz#f218cd0345524ac888aa3dc32f029de5b064b575"
+ integrity sha512-tWY5wyCZYBGY7IlalfKI1rLiGlIfnwsRHZqlky0HVv8qviwQ1Uo/05M6+s+TcTCVa6Bmoo2uJW5TMFX6Wa4qVg==
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
"@babel/helper-validator-option" "^7.22.5"
"@babel/plugin-syntax-jsx" "^7.22.5"
- "@babel/plugin-transform-modules-commonjs" "^7.22.5"
- "@babel/plugin-transform-typescript" "^7.22.5"
+ "@babel/plugin-transform-modules-commonjs" "^7.22.11"
+ "@babel/plugin-transform-typescript" "^7.22.11"
"@babel/regjsgen@^0.8.0":
version "0.8.0"
@@ -1049,9 +1076,9 @@
regenerator-runtime "^0.12.0"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
- version "7.22.10"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682"
- integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.11.tgz#7a9ba3bbe406ad6f9e8dd4da2ece453eb23a77a4"
+ integrity sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==
dependencies:
regenerator-runtime "^0.14.0"
@@ -1080,10 +1107,10 @@
debug "^4.1.0"
globals "^11.1.0"
-"@babel/traverse@^7.22.10":
- version "7.22.10"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.10.tgz#20252acb240e746d27c2e82b4484f199cf8141aa"
- integrity sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==
+"@babel/traverse@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.11.tgz#71ebb3af7a05ff97280b83f05f8865ac94b2027c"
+ integrity sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==
dependencies:
"@babel/code-frame" "^7.22.10"
"@babel/generator" "^7.22.10"
@@ -1091,12 +1118,12 @@
"@babel/helper-function-name" "^7.22.5"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
- "@babel/parser" "^7.22.10"
- "@babel/types" "^7.22.10"
+ "@babel/parser" "^7.22.11"
+ "@babel/types" "^7.22.11"
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.5", "@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==
@@ -1114,6 +1141,15 @@
"@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.4.4":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.11.tgz#0e65a6a1d4d9cbaa892b2213f6159485fe632ea2"
+ integrity sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.5"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -1249,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"
@@ -1268,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"
@@ -1416,6 +1452,11 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+"@ioredis/commands@^1.1.1":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11"
+ integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==
+
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -1500,12 +1541,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"
@@ -1567,10 +1608,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"
@@ -1624,12 +1665,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" "*"
@@ -1750,40 +1791,6 @@
resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.0.7.tgz#54af8d66160a8a7bf7d8f184703d2bf4b3fab914"
integrity sha512-J2v5Ca7HgejO7diGKiDylaVDQKmbQ5FJih6Oo3hXuBKEuXlcaccJu64lj8MNVLaPVyZx0g4gaOQZQz95QEb/hg==
-"@redis/bloom@1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.2.0.tgz#d3fd6d3c0af3ef92f26767b56414a370c7b63b71"
- integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==
-
-"@redis/client@1.5.9":
- version "1.5.9"
- resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.5.9.tgz#c4ee81bbfedb4f1d9c7c5e9859661b9388fb4021"
- integrity sha512-SffgN+P1zdWJWSXBvJeynvEnmnZrYmtKSRW00xl8pOPFOMJjxRR9u0frSxJpPR6Y4V+k54blJjGW7FgxbTI7bQ==
- dependencies:
- cluster-key-slot "1.1.2"
- generic-pool "3.9.0"
- yallist "4.0.0"
-
-"@redis/graph@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@redis/graph/-/graph-1.1.0.tgz#cc2b82e5141a29ada2cce7d267a6b74baa6dd519"
- integrity sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==
-
-"@redis/json@1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@redis/json/-/json-1.0.4.tgz#f372b5f93324e6ffb7f16aadcbcb4e5c3d39bda1"
- integrity sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==
-
-"@redis/search@1.1.3":
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.1.3.tgz#b5a6837522ce9028267fe6f50762a8bcfd2e998b"
- integrity sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==
-
-"@redis/time-series@1.0.5":
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.5.tgz#a6d70ef7a0e71e083ea09b967df0a0ed742bc6ad"
- integrity sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==
-
"@reduxjs/toolkit@^1.9.5":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4"
@@ -1984,14 +1991,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"
@@ -2187,7 +2193,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==
@@ -2250,9 +2256,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"
@@ -2428,9 +2434,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" "*"
@@ -2497,13 +2503,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"
@@ -2570,15 +2569,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"
@@ -2587,31 +2586,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==
- 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"
+ 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.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"
@@ -2620,10 +2619,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"
@@ -2638,30 +2637,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":
@@ -2672,12 +2671,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":
@@ -2883,16 +2882,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"
@@ -3319,9 +3318,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"
@@ -3906,7 +3905,12 @@ caniuse-lite@^1.0.30001502:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz#418aefeed9d024cd3129bfae0ccc782d4cb8f12b"
integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==
-caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520:
+caniuse-lite@^1.0.30001517:
+ 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"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz#62e2b7a1c7b35269594cf296a80bdf8cb9565006"
integrity sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==
@@ -4083,7 +4087,7 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
-cluster-key-slot@1.1.2:
+cluster-key-slot@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
@@ -4305,11 +4309,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"
@@ -4829,6 +4833,11 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
+denque@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
+ integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
+
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@@ -4884,10 +4893,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"
@@ -5055,9 +5064,9 @@ electron-to-chromium@^1.4.428:
integrity sha512-/g3UyNDmDd6ebeWapmAoiyy+Sy2HyJ+/X8KyvNeHfKRFfHaA2W8oF5fxD5F3tjBDcjpwo0iek6YNgxNXDBoEtA==
electron-to-chromium@^1.4.477:
- version "1.4.490"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz#d99286f6e915667fa18ea4554def1aa60eb4d5f1"
- integrity sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==
+ 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"
@@ -5490,14 +5499,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"
@@ -5701,7 +5710,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==
@@ -5962,14 +5982,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==
@@ -6109,11 +6130,6 @@ gauge@^5.0.0:
strip-ansi "^6.0.1"
wide-align "^1.1.5"
-generic-pool@3.9.0:
- version "3.9.0"
- resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4"
- integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==
-
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -6199,9 +6215,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"
@@ -6658,9 +6674,9 @@ immutable@^3.8.2:
integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==
immutable@^4.0.0, immutable@^4.0.0-rc.1, immutable@^4.3.0:
- version "4.3.3"
- resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.3.tgz#8934ff6826d996a7642c8dc4b46e694dd19561e3"
- integrity sha512-808ZFYMsIRAjLAu5xkKo0TsbY9LBy9H5MazTKIEHerNkg0ymgilGfBPMR/3G7d/ihGmuK2Hw8S1izY2d3kd3wA==
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f"
+ integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==
import-fresh@^3.2.1:
version "3.3.0"
@@ -6793,6 +6809,21 @@ invariant@^2.2.2, invariant@^2.2.4:
dependencies:
loose-envify "^1.0.0"
+ioredis@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7"
+ integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==
+ dependencies:
+ "@ioredis/commands" "^1.1.1"
+ cluster-key-slot "^1.1.0"
+ debug "^4.3.4"
+ denque "^2.1.0"
+ lodash.defaults "^4.2.0"
+ lodash.isarguments "^3.1.0"
+ redis-errors "^1.2.0"
+ redis-parser "^3.0.0"
+ standard-as-callback "^2.1.0"
+
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -7304,9 +7335,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:
@@ -7402,15 +7433,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"
@@ -7456,10 +7487,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"
@@ -7488,28 +7519,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"
@@ -7636,12 +7667,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"
@@ -7815,6 +7846,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"
@@ -7859,7 +7895,7 @@ json5@^1.0.1, json5@^1.0.2:
dependencies:
minimist "^1.2.0"
-json5@^2.1.2, json5@^2.2.0, json5@^2.2.2:
+json5@^2.1.2, json5@^2.2.0, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
@@ -7906,6 +7942,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"
@@ -8056,6 +8099,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"
@@ -8066,6 +8119,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"
@@ -8091,6 +8149,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"
@@ -8106,6 +8169,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"
@@ -8148,9 +8216,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"
@@ -8482,9 +8550,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"
@@ -9581,9 +9649,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"
@@ -9669,12 +9737,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"
@@ -10216,17 +10284,17 @@ redent@^4.0.0:
indent-string "^5.0.0"
strip-indent "^4.0.0"
-redis@^4.6.5:
- version "4.6.8"
- resolved "https://registry.yarnpkg.com/redis/-/redis-4.6.8.tgz#54c5992e8a5ba512506fe9f53142cadc405547e7"
- integrity sha512-S7qNkPUYrsofQ0ztWlTHSaK0Qqfl1y+WMIxrzeAGNG+9iUZB4HGeBgkHxE6uJJ6iXrkvLd1RVJ2nvu6H1sAzfQ==
+redis-errors@^1.0.0, redis-errors@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
+ integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==
+
+redis-parser@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
+ integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==
dependencies:
- "@redis/bloom" "1.2.0"
- "@redis/client" "1.5.9"
- "@redis/graph" "1.1.0"
- "@redis/json" "1.0.4"
- "@redis/search" "1.1.3"
- "@redis/time-series" "1.0.5"
+ redis-errors "^1.0.0"
redux-immutable@^4.0.0:
version "4.0.0"
@@ -10861,14 +10929,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"
@@ -11144,6 +11212,11 @@ stacktrace-js@^2.0.2:
stack-generator "^2.0.5"
stacktrace-gps "^3.0.4"
+standard-as-callback@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
+ integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -11301,7 +11374,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==
@@ -11788,10 +11860,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"
@@ -12344,19 +12416,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:
@@ -12893,16 +12972,16 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-yallist@4.0.0, yallist@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
- integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"