diff --git a/.browserslistrc b/.browserslistrc index 6367e4d358..0135379d6e 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -1,10 +1,6 @@ -[production] defaults > 0.2% firefox >= 78 ios >= 15.6 not dead not OperaMini all - -[development] -supports es6-module diff --git a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml index 0552e985f7..45bd689f1e 100644 --- a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml @@ -59,7 +59,7 @@ body: Any additional technical details you may have, like logs or error traces value: | If this is happening on your own Mastodon server, please fill out those: - - Ruby version: (from `ruby --version`, eg. v3.3.5) + - Ruby version: (from `ruby --version`, eg. v3.4.1) - Node.js version: (from `node --version`, eg. v20.18.0) validations: required: false diff --git a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml b/.github/ISSUE_TEMPLATE/3.troubleshooting.yml index 7c6914bd7b..1f70046ed4 100644 --- a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml +++ b/.github/ISSUE_TEMPLATE/3.troubleshooting.yml @@ -60,7 +60,7 @@ body: value: | Please at least include those informations: - Operating system: (eg. Ubuntu 22.04) - - Ruby version: (from `ruby --version`, eg. v3.3.5) + - Ruby version: (from `ruby --version`, eg. v3.4.1) - Node.js version: (from `node --version`, eg. v20.18.0) validations: required: false diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 3d3499922e..8a10676283 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -14,11 +14,6 @@ // If we do not want a package to be grouped with others, we need to set its groupName // to `null` after any other rule set it to something. dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).', - constraints: { - // Mastodon should work on Ruby 3.4, but its test dependencies are currently uninstallable on Ruby 3.4. - // TODO: remove this once https://github.com/briandunn/flatware/issues/103 is fixed - ruby: '3.3', - }, postUpdateOptions: ['yarnDedupeHighest'], packageRules: [ { diff --git a/.github/workflows/crowdin-download-stable.yml b/.github/workflows/crowdin-download-stable.yml index 3d888f179e..191eb5bc2c 100644 --- a/.github/workflows/crowdin-download-stable.yml +++ b/.github/workflows/crowdin-download-stable.yml @@ -51,7 +51,7 @@ jobs: # Create or update the pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v7.0.5 + uses: peter-evans/create-pull-request@v7.0.6 with: commit-message: 'New Crowdin translations' title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)' diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index 781fd5080f..1fdd1e08b4 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -53,7 +53,7 @@ jobs: # Create or update the pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v7.0.5 + uses: peter-evans/create-pull-request@v7 with: commit-message: 'New Crowdin translations' title: 'New Crowdin Translations (automated)' diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 900faad07f..4deb08d328 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -125,6 +125,7 @@ jobs: matrix: ruby-version: - '3.2' + - '3.3' - '.ruby-version' steps: - uses: actions/checkout@v4 @@ -226,6 +227,7 @@ jobs: matrix: ruby-version: - '3.2' + - '3.3' - '.ruby-version' steps: - uses: actions/checkout@v4 @@ -304,6 +306,7 @@ jobs: matrix: ruby-version: - '3.2' + - '3.3' - '.ruby-version' steps: @@ -420,6 +423,7 @@ jobs: matrix: ruby-version: - '3.2' + - '3.3' - '.ruby-version' search-image: - docker.elastic.co/elasticsearch/elasticsearch:7.17.13 diff --git a/.nvmrc b/.nvmrc index 35d2d08ea1..fb0a135541 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.12 +22.13 diff --git a/.rubocop/style.yml b/.rubocop/style.yml index 7dd4299c3e..df1da2ba36 100644 --- a/.rubocop/style.yml +++ b/.rubocop/style.yml @@ -29,9 +29,6 @@ Style/IfUnlessModifier: Style/KeywordArgumentsMerging: Enabled: false -Style/MultipleComparison: - Enabled: false - Style/NumericLiterals: AllowedPatterns: - \d{4}_\d{2}_\d{2}_\d{6} diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8890879b74..6ec3141fe6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.69.1. +# using RuboCop version 1.69.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -8,7 +8,7 @@ Lint/NonLocalExitFromIterator: Exclude: - - 'app/helpers/jsonld_helper.rb' + - 'app/helpers/json_ld_helper.rb' # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: @@ -82,7 +82,7 @@ Style/MutableConstant: # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - - 'app/helpers/jsonld_helper.rb' + - 'app/helpers/json_ld_helper.rb' - 'app/lib/admin/system_check/message.rb' - 'app/lib/request.rb' - 'app/lib/webfinger.rb' diff --git a/.ruby-version b/.ruby-version index 9c25013dbb..47b322c971 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.6 +3.4.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66aa01ffe4..2b49bbeb98 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ You can contribute in the following ways: If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). Please review the org-level [contribution guidelines] for high-level acceptance -criteria guidance. +criteria guidance and the [DEVELOPMENT] guide for environment-specific details. [contribution guidelines]: https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md @@ -94,3 +94,5 @@ It is not always possible to phrase every change in such a manner, but it is des The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation). + +[DEVELOPMENT]: docs/DEVELOPMENT.md diff --git a/Dockerfile b/Dockerfile index d80a4e1555..97c7a91499 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,9 +10,9 @@ ARG TARGETPLATFORM=${TARGETPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM} -# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"] +# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"] # renovate: datasource=docker depName=docker.io/ruby -ARG RUBY_VERSION="3.3.6" +ARG RUBY_VERSION="3.4.1" # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # renovate: datasource=node-version depName=node ARG NODE_MAJOR_VERSION="22" @@ -20,7 +20,7 @@ ARG NODE_MAJOR_VERSION="22" ARG DEBIAN_VERSION="bookworm" # Node image to use for base image based on combined variables (ex: 20-bookworm-slim) FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node -# Ruby image to use for base image based on combined variables (ex: 3.3.x-slim-bookworm) +# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm) FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA diff --git a/Gemfile b/Gemfile index ad174cfdc4..8df00d8c0a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ # frozen_string_literal: true source 'https://rubygems.org' -ruby '>= 3.2.0', '< 3.5' +ruby '>= 3.2.0', '< 3.5.0' gem 'propshaft' gem 'puma', '~> 6.3' @@ -108,7 +108,7 @@ group :opentelemetry do gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.21.0', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false - gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false + gem 'opentelemetry-instrumentation-faraday', '~> 0.25.0', require: false gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false diff --git a/Gemfile.lock b/Gemfile.lock index f49de6bf3b..cb5e2bbbae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,7 +94,7 @@ GEM ast (2.4.2) attr_required (1.0.2) aws-eventstream (1.3.0) - aws-partitions (1.1029.0) + aws-partitions (1.1032.0) aws-sdk-core (3.214.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -103,7 +103,7 @@ GEM aws-sdk-kms (1.96.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.176.1) + aws-sdk-s3 (1.177.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -160,7 +160,7 @@ GEM cocoon (1.2.15) color_diff (0.1) concurrent-ruby (1.3.4) - connection_pool (2.4.1) + connection_pool (2.5.0) cose (1.3.1) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) @@ -233,16 +233,16 @@ GEM faraday-net_http (3.4.0) net-http (>= 0.5.0) fast_blank (1.0.1) - fastimage (2.3.1) + fastimage (2.4.0) ffi (1.17.1) ffi-compiler (1.3.2) ffi (>= 1.15.5) rake - flatware (2.3.3) + flatware (2.3.4) drb thor (< 2.0) - flatware-rspec (2.3.3) - flatware (= 2.3.3) + flatware-rspec (2.3.4) + flatware (= 2.3.4) rspec (>= 3.6) fog-core (2.5.0) builder @@ -490,7 +490,7 @@ GEM opentelemetry-instrumentation-active_job (0.7.8) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_model_serializers (0.21.0) + opentelemetry-instrumentation-active_model_serializers (0.21.1) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (>= 0.7.0) opentelemetry-instrumentation-base (~> 0.22.1) @@ -510,7 +510,7 @@ GEM opentelemetry-instrumentation-excon (0.22.5) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-faraday (0.24.8) + opentelemetry-instrumentation-faraday (0.25.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-http (0.23.5) @@ -522,7 +522,7 @@ GEM opentelemetry-instrumentation-net_http (0.22.8) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-pg (0.29.1) + opentelemetry-instrumentation-pg (0.29.2) opentelemetry-api (~> 1.0) opentelemetry-helpers-sql-obfuscation opentelemetry-instrumentation-base (~> 0.22.1) @@ -709,7 +709,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.13.2) - rubocop (1.69.2) + rubocop (1.70.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -723,7 +723,7 @@ GEM parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) - rubocop-performance (1.23.0) + rubocop-performance (1.23.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) rubocop-rails (2.28.0) @@ -744,7 +744,7 @@ GEM ruby-vips (2.2.2) ffi (~> 1.12) logger - rubyzip (2.3.2) + rubyzip (2.4.1) rufus-scheduler (3.9.2) fugit (~> 1.1, >= 1.11.1) safety_net_attestation (0.4.0) @@ -809,7 +809,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) terrapin (1.0.1) climate_control - test-prof (1.4.3) + test-prof (1.4.4) thor (1.3.2) tilt (2.5.0) timeout (0.4.3) @@ -963,7 +963,7 @@ DEPENDENCIES opentelemetry-instrumentation-active_model_serializers (~> 0.21.0) opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2) opentelemetry-instrumentation-excon (~> 0.22.0) - opentelemetry-instrumentation-faraday (~> 0.24.1) + opentelemetry-instrumentation-faraday (~> 0.25.0) opentelemetry-instrumentation-http (~> 0.23.2) opentelemetry-instrumentation-http_client (~> 0.22.3) opentelemetry-instrumentation-net_http (~> 0.22.4) @@ -1034,7 +1034,7 @@ DEPENDENCIES xorcist (~> 1.1) RUBY VERSION - ruby 3.3.6p108 + ruby 3.4.1p0 BUNDLED WITH 2.6.2 diff --git a/README.md b/README.md index b0ef1b0d52..f1c6cd4322 100644 --- a/README.md +++ b/README.md @@ -14,27 +14,27 @@ Mastodon Glitch Edition is a fork of [Mastodon](https://github.com/mastodon/mast --- -

- - - Mastodon -

+> [!NOTE] +> Want to learn more about Mastodon? +> Click below to find out more in a video. -[![GitHub release](https://img.shields.io/github/release/mastodon/mastodon.svg)][releases] -[![Ruby Testing](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin] +

+ + Mastodon hero image + +

-[releases]: https://github.com/mastodon/mastodon/releases -[crowdin]: https://crowdin.com/project/mastodon +

+ + Release + + Ruby Testing + + Crowdin +

Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!) -Click below to **learn more** in a video: - -[![Screenshot](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/ezgif-2-60f1b00403.gif)][youtube_demo] - -[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE - ## Navigation - [Project homepage 🐘](https://joinmastodon.org) @@ -53,25 +53,15 @@ Click below to **learn more** in a video: -### No vendor lock-in: Fully interoperable with any conforming platform +**No vendor lock-in: Fully interoperable with any conforming platform** - It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/) -It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/) +**Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well! -### Real-time, chronological timeline updates +**Media attachments like images and short videos** - upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously! -Updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well! +**Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/) -### Media attachments like images and short videos - -Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously! - -### Safety and moderation tools - -Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/) - -### OAuth2 and a straightforward REST API - -Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices! +**OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices! ## Deployment @@ -90,85 +80,40 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. -## Development - -### Vagrant - -A **Vagrant** configuration is included for development purposes. To use it, complete the following steps: - -- Install Vagrant and Virtualbox -- Install the `vagrant-hostsupdater` plugin: `vagrant plugin install vagrant-hostsupdater` -- Run `vagrant up` -- Run `vagrant ssh -c "cd /vagrant && bin/dev"` -- Open `http://mastodon.local` in your browser - -### macOS - -To set up **macOS** for native development, complete the following steps: - -- Install [Homebrew] and run `brew install postgresql@14 redis imagemagick -libidn nvm` to install the required project dependencies -- Use a Ruby version manager to activate the ruby in `.ruby-version` and run - `nvm use` to activate the node version from `.nvmrc` -- Run the `bin/setup` script, which will install the required ruby gems and node - packages and prepare the database for local development -- Finally, run the `bin/dev` script which will launch services via `overmind` - (if installed) or `foreman` - -### Docker - -For production hosting and deployment with **Docker**, use the `Dockerfile` and -`docker-compose.yml` in the project root directory. - -For local development, install and launch [Docker], and run: - -```shell -docker compose -f .devcontainer/compose.yaml up -d -docker compose -f .devcontainer/compose.yaml exec app bin/setup -docker compose -f .devcontainer/compose.yaml exec app bin/dev -``` - -### Dev Containers - -Within IDEs that support the [Development Containers] specification, start the -"Mastodon on local machine" container from the editor. The necessary `docker -compose` commands to build and setup the container should run automatically. For -**Visual Studio Code** this requires installing the [Dev Container extension]. - -### GitHub Codespaces - -[GitHub Codespaces] provides a web-based version of VS Code and a cloud hosted -development environment configured with the software needed for this project. - -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)][codespace] - -- Click the button to create a new codespace, and confirm the options -- Wait for the environment to build (takes a few minutes) -- When the editor is ready, run `bin/dev` in the terminal -- Wait for an _Open in Browser_ prompt. This will open Mastodon -- On the _Ports_ tab "stream" setting change _Port visibility_ → _Public_ - ## Contributing Mastodon is **free, open-source software** licensed under **AGPLv3**. -You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository or submit translations using Crowdin. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). +You can open issues for bugs you've found or features you think are missing. You +can also submit pull requests to this repository or translations via Crowdin. To +get started, look at the [CONTRIBUTING] and [DEVELOPMENT] guides. For changes +accepted into Mastodon, you can request to be paid through our [OpenCollective]. -**IRC channel**: #mastodon on irc.libera.chat +**IRC channel**: #mastodon on [`irc.libera.chat`](https://libera.chat) ## License -Copyright (C) 2016-2024 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) +Copyright (c) 2016-2024 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md)) -This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE): -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +``` +Copyright (c) 2016-2024 Eugen Rochko & other Mastodon contributors -You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. -[codespace]: https://codespaces.new/mastodon/mastodon?quickstart=1&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json -[Dev Container extension]: https://containers.dev/supporting#dev-containers -[Development Containers]: https://containers.dev/supporting -[Docker]: https://docs.docker.com -[GitHub Codespaces]: https://docs.github.com/en/codespaces -[Homebrew]: https://brew.sh +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +details. + +You should have received a copy of the GNU Affero General Public License along +with this program. If not, see https://www.gnu.org/licenses/ +``` + +[CONTRIBUTING]: CONTRIBUTING.md +[DEVELOPMENT]: docs/DEVELOPMENT.md +[OpenCollective]: https://opencollective.com/mastodon diff --git a/app/controllers/api/v2/notifications_controller.rb b/app/controllers/api/v2/notifications_controller.rb index c070c0e5e7..cc38b95114 100644 --- a/app/controllers/api/v2/notifications_controller.rb +++ b/app/controllers/api/v2/notifications_controller.rb @@ -80,10 +80,31 @@ class Api::V2::NotificationsController < Api::BaseController return [] if @notifications.empty? MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do - NotificationGroup.from_notifications(@notifications, pagination_range: (@notifications.last.id)..(@notifications.first.id), grouped_types: params[:grouped_types]) + pagination_range = (@notifications.last.id)..@notifications.first.id + + # If the page is incomplete, we know we are on the last page + if incomplete_page? + if paginating_up? + pagination_range = @notifications.last.id...(params[:max_id]&.to_i) + else + range_start = params[:since_id]&.to_i + range_start += 1 unless range_start.nil? + pagination_range = range_start..(@notifications.first.id) + end + end + + NotificationGroup.from_notifications(@notifications, pagination_range: pagination_range, grouped_types: params[:grouped_types]) end end + def incomplete_page? + @notifications.size < limit_param(DEFAULT_NOTIFICATIONS_LIMIT) + end + + def paginating_up? + params[:min_id].present? + end + def browserable_account_notifications current_account.notifications.without_suspended.browserable( types: Array(browserable_params[:types]), diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb index eb6417698a..5b98914114 100644 --- a/app/controllers/custom_css_controller.rb +++ b/app/controllers/custom_css_controller.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController - before_action :set_user_roles - def show - expires_in 3.minutes, public: true + expires_in 1.month, public: true render content_type: 'text/css' end @@ -14,8 +12,4 @@ class CustomCssController < ActionController::Base # rubocop:disable Rails/Appli Setting.custom_css end helper_method :custom_css_styles - - def set_user_roles - @user_roles = UserRole.providing_styles - end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index feb5ce434b..32beed43ab 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -239,6 +239,14 @@ module ApplicationHelper I18n.t 'user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(people), count: people end + def app_store_url_ios + 'https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974' + end + + def app_store_url_android + 'https://play.google.com/store/apps/details?id=org.joinmastodon.android' + end + # glitch-soc addition to handle the multiple flavors def preload_locale_pack supported_locales = Themes.instance.flavour(current_flavour)['locales'] diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/json_ld_helper.rb similarity index 100% rename from app/helpers/jsonld_helper.rb rename to app/helpers/json_ld_helper.rb diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index c5d226f70e..88cc7d0f1f 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -27,8 +27,31 @@ module ThemeHelper end end + def custom_stylesheet + if active_custom_stylesheet.present? + stylesheet_link_tag( + custom_css_path(active_custom_stylesheet), + host: root_url, + media: :all, + skip_pipeline: true + ) + end + end + private + def active_custom_stylesheet + if cached_custom_css_digest.present? + [:custom, cached_custom_css_digest.to_s.first(8)] + .compact_blank + .join('-') + end + end + + def cached_custom_css_digest + Rails.cache.read(:setting_digest_custom_css) + end + def theme_color_for(theme) theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark] end diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index 9e8ff9caa1..0560e76628 100644 --- a/app/javascript/entrypoints/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -119,7 +119,11 @@ function loaded() { formattedContent = dateFormat.format(datetime); } - content.title = formattedContent; + const timeGiven = content.dateTime.includes('T'); + content.title = timeGiven + ? dateTimeFormat.format(datetime) + : dateFormat.format(datetime); + content.textContent = formattedContent; }); diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js index 8ab75cdc44..e8fec13453 100644 --- a/app/javascript/mastodon/actions/store.js +++ b/app/javascript/mastodon/actions/store.js @@ -1,4 +1,4 @@ -import { Iterable, fromJS } from 'immutable'; +import { fromJS, isIndexed } from 'immutable'; import { hydrateCompose } from './compose'; import { importFetchedAccounts } from './importer'; @@ -9,8 +9,7 @@ export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; const convertState = rawState => fromJS(rawState, (k, v) => - Iterable.isIndexed(v) ? v.toList() : v.toMap()); - + isIndexed(v) ? v.toList() : v.toMap()); export function hydrateStore(rawState) { return dispatch => { diff --git a/app/javascript/mastodon/components/relative_timestamp.tsx b/app/javascript/mastodon/components/relative_timestamp.tsx index ac385e88c6..6253525091 100644 --- a/app/javascript/mastodon/components/relative_timestamp.tsx +++ b/app/javascript/mastodon/components/relative_timestamp.tsx @@ -1,6 +1,6 @@ import { Component } from 'react'; -import type { IntlShape } from 'react-intl'; +import type { MessageDescriptor, PrimitiveType, IntlShape } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl'; const messages = defineMessages({ @@ -102,7 +102,13 @@ const getUnitDelay = (units: string) => { }; export const timeAgoString = ( - intl: Pick, + intl: { + formatDate: IntlShape['formatDate']; + formatMessage: ( + { id, defaultMessage }: MessageDescriptor, + values?: Record, + ) => string; + }, date: Date, now: number, year: number, diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx index 713ad9f069..ad66d2e5fa 100644 --- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx +++ b/app/javascript/mastodon/features/getting_started/components/announcements.jsx @@ -335,15 +335,29 @@ class Announcement extends ImmutablePureComponent { const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at')); const now = new Date(); const hasTimeRange = startsAt && endsAt; - const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); - const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); const skipTime = announcement.get('all_day'); + let timestamp = null; + if (hasTimeRange) { + const skipYear = startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); + const skipEndDate = startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); + timestamp = ( + <> + - + + ); + } else { + const publishedAt = new Date(announcement.get('published_at')); + timestamp = ( + + ); + } + return (
- {hasTimeRange && · - } + · {timestamp} diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index ffe15f538f..d7585b4ac2 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -457,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "Dangos/cuddio testun tu ĂŽl i CW", "keyboard_shortcuts.toggle_sensitivity": "Dangos/cuddio cyfryngau", "keyboard_shortcuts.toot": "Dechrau post newydd", + "keyboard_shortcuts.translate": "i gyfieithu postiad", "keyboard_shortcuts.unfocus": "Dad-ffocysu ardal cyfansoddi testun/chwilio", "keyboard_shortcuts.up": "Symud yn uwch yn y rhestr", "lightbox.close": "Cau", @@ -835,6 +836,7 @@ "status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.", "status.redraft": "Dileu ac ailddrafftio", "status.remove_bookmark": "Tynnu nod tudalen", + "status.remove_favourite": "Tynnu o'r ffefrynnau", "status.replied_in_thread": "Atebodd mewn edefyn", "status.replied_to": "Wedi ateb {name}", "status.reply": "Ateb", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 92cfb62f51..ad81e5f35a 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -482,6 +482,7 @@ "lists.exclusive": "Skjul medlemmer i Hjem", "lists.exclusive_hint": "Er nogen er pĂ„ denne liste, skjul personen i hjemme-feeds for at undgĂ„ at se vedkommendes indlĂŠg to gange.", "lists.find_users_to_add": "Find brugere at tilfĂžje", + "lists.list_members": "Liste over medlemmer", "lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmer}}", "lists.list_name": "Listetitel", "lists.new_list_name": "Ny listetitel", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index c4d5f7296a..91acfb0e15 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -362,6 +362,7 @@ "footer.privacy_policy": "PrivatlĂ­vspolitikkur", "footer.source_code": "VĂ­s keldukotuna", "footer.status": "StÞða", + "footer.terms_of_service": "TĂŠnastutreytir", "generic.saved": "Goymt", "getting_started.heading": "At byrja", "hashtag.admin_moderation": "Lat umsjĂłnarmarkamĂłt upp fyri #{name}", @@ -858,6 +859,7 @@ "subscribed_languages.target": "Broyt haldaramĂĄl fyri {target}", "tabs_bar.home": "Heim", "tabs_bar.notifications": "FrĂĄboĂ°anir", + "terms_of_service.title": "TĂŠnastutreytir", "time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir", "time_remaining.hours": "{number, plural, one {# tĂ­mi} other {# tĂ­mar}} eftir", "time_remaining.minutes": "{number, plural, one {# minuttur} other {# minuttir}} eftir", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index d1ae388675..847f7871fa 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -453,10 +453,11 @@ "keyboard_shortcuts.requests": "KövetĂ©si kĂ©rĂ©sek listĂĄjĂĄnak megnyitĂĄsa", "keyboard_shortcuts.search": "FĂłkuszĂĄlĂĄs a keresƑsĂĄvra", "keyboard_shortcuts.spoilers": "Tartalmi figyelmeztetĂ©s mezƑ megjelenĂ­tĂ©se/elrejtĂ©se", - "keyboard_shortcuts.start": "\"ElsƑ lĂ©pĂ©sek\" oszlop megnyitĂĄsa", + "keyboard_shortcuts.start": "„ElsƑ lĂ©pĂ©sek” oszlop megnyitĂĄsa", "keyboard_shortcuts.toggle_hidden": "Tartalmi figyelmeztetĂ©ssel ellĂĄtott szöveg megjelenĂ­tĂ©se/elrejtĂ©se", "keyboard_shortcuts.toggle_sensitivity": "MĂ©dia megjelenĂ­tĂ©se/elrejtĂ©se", "keyboard_shortcuts.toot": "Új bejegyzĂ©s Ă­rĂĄsa", + "keyboard_shortcuts.translate": "BejegyzĂ©s lefordĂ­tĂĄsa", "keyboard_shortcuts.unfocus": "SzerkesztĂ©s/keresĂ©s fĂłkuszbĂłl valĂł kivĂ©tele", "keyboard_shortcuts.up": "MozgĂĄs felfelĂ© a listĂĄban", "lightbox.close": "BezĂĄrĂĄs", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index 0b17e60710..e2f821022e 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -457,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "Monstrar/celar texto detra advertimento de contento", "keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar multimedia", "keyboard_shortcuts.toot": "Initiar un nove message", + "keyboard_shortcuts.translate": "a traducer un message", "keyboard_shortcuts.unfocus": "Disfocalisar le area de composition de texto/de recerca", "keyboard_shortcuts.up": "Displaciar in alto in le lista", "lightbox.close": "Clauder", @@ -836,6 +837,7 @@ "status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.", "status.redraft": "Deler e reconciper", "status.remove_bookmark": "Remover marcapagina", + "status.remove_favourite": "Remover del favoritos", "status.replied_in_thread": "Respondite in le discussion", "status.replied_to": "Respondite a {name}", "status.reply": "Responder", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 9593744e4b..abaf40931c 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -414,6 +414,7 @@ "interaction_modal.title.reblog": "{name} 님의 êČŒì‹œëŹŒì„ 부슀튞", "interaction_modal.title.reply": "{name} 님의 êČŒì‹œëŹŒì— 닔Ꞁ", "interaction_modal.title.vote": "{name} 님의 íˆŹí‘œì— ì°žì—Ź", + "interaction_modal.username_prompt": "예시: {example}", "intervals.full.days": "{number} 음", "intervals.full.hours": "{number} 시간", "intervals.full.minutes": "{number} 분", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 1bc3c68382..9244f3509c 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -52,7 +52,7 @@ "account.mute_notifications_short": "Izslēgt paziƆojumu skaƆu", "account.mute_short": "Apklusināt", "account.muted": "Apklusināts", - "account.mutual": "Savstarpējs", + "account.mutual": "Abpusēji", "account.no_bio": "Apraksts nav sniegts.", "account.open_original_page": "Atvērt oriÄŁinālo lapu", "account.posts": "Ieraksti", @@ -85,6 +85,7 @@ "alert.rate_limited.title": "BieĆŸums ierobeĆŸots", "alert.unexpected.message": "Radās negaidÄ«ta kÄŒĆ«da.", "alert.unexpected.title": "Ups!", + "alt_text_badge.title": "Alt teksts", "announcement.announcement": "PaziƆojums", "annual_report.summary.archetype.oracle": "Orākuls", "annual_report.summary.archetype.replier": "Sabiedriskais tauriƆơ", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 6091f1679f..1a7f92be18 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -837,6 +837,7 @@ "status.reblogs.empty": "Ingen har framheva dette tutet enno. Om nokon gjer, sĂ„ dukkar det opp her.", "status.redraft": "Slett & skriv pĂ„ nytt", "status.remove_bookmark": "Fjern bokmerke", + "status.remove_favourite": "Fjern frĂ„ favorittar", "status.replied_in_thread": "Svara i trĂ„den", "status.replied_to": "Svarte {name}", "status.reply": "Svar", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index fe56267ea1..f9cb9743ac 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -406,6 +406,9 @@ "ignore_notifications_modal.not_followers_title": "Ignoruj powiadomienia od uĆŒytkownikĂłw ktĂłrzy cię nie obserwują?", "ignore_notifications_modal.not_following_title": "Ignoruj powiadomienia od uĆŒytkownikĂłw ktĂłrych nie obserwujesz?", "ignore_notifications_modal.private_mentions_title": "Ignoruj powiadomienia o nieproszonych wzmiankach prywatnych?", + "interaction_modal.action.favourite": "Aby kontynuować, musisz dodać do ulubionych na swoim koncie.", + "interaction_modal.action.follow": "Aby kontynuować, musisz obserwować ze swojego konta.", + "interaction_modal.no_account_yet": "Nie masz jeszcze konta?", "interaction_modal.on_another_server": "Na innym serwerze", "interaction_modal.on_this_server": "Na tym serwerze", "interaction_modal.title.favourite": "Polub wpis uĆŒytkownika {name}", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1505d333ba..142ae33c58 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -243,12 +243,12 @@ "dismissable_banner.explore_statuses": "Estas publicaçÔes atravĂ©s do fediverse estĂŁo ganhando atenção hoje. PublicaçÔes mais recentes com mais boosts e favoritos sĂŁo classificados mais altamente.", "dismissable_banner.explore_tags": "Estas hashtags estĂŁo ganhando atenção hoje no fediverse. Hashtags usadas por muitas pessoas diferentes sĂŁo classificadas mais altamente.", "dismissable_banner.public_timeline": "Estas sĂŁo as publicaçÔes mais recentes das pessoas no fediverse que as pessoas do {domain} seguem.", - "domain_block_modal.block": "Servidor de blocos.", - "domain_block_modal.block_account_instead": "Bloco @(nome)", + "domain_block_modal.block": "Bloquear servidor", + "domain_block_modal.block_account_instead": "Bloquear @{name}", "domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicaçÔes antigas.", "domain_block_modal.they_cant_follow": "NinguĂ©m deste servidor pode lhe seguir.", "domain_block_modal.they_wont_know": "Eles nĂŁo saberĂŁo que foram bloqueados.", - "domain_block_modal.title": "Dominio do bloco", + "domain_block_modal.title": "Bloquear domĂ­nio?", "domain_block_modal.you_will_lose_num_followers": "VocĂȘ perderĂĄ {followersCount, plural, one {{followersCountDisplay} seguidor} other {{followersCountDisplay} seguidores}} e {followingCount, plural, one {{followingCountDisplay} pessoa que vocĂȘ segue} other {{followingCountDisplay} pessoas que vocĂȘ segue}}.", "domain_block_modal.you_will_lose_relationships": "VocĂȘ irĂĄ perder todos os seguidores e pessoas que vocĂȘ segue neste servidor.", "domain_block_modal.you_wont_see_posts": "VocĂȘ nĂŁo verĂĄ postagens ou notificaçÔes de usuĂĄrios neste servidor.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index ade10fc478..f7329ad97e 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -85,7 +85,7 @@ "alert.rate_limited.title": "Limite de tentativas", "alert.unexpected.message": "Ocorreu um erro inesperado.", "alert.unexpected.title": "Bolas!", - "alt_text_badge.title": "Texto alternativo", + "alt_text_badge.title": "Texto descritivo", "announcement.announcement": "Mensagem de manutenção", "annual_report.summary.archetype.booster": "O caçador de frescura", "annual_report.summary.archetype.lurker": "O espreitador", @@ -244,7 +244,7 @@ "dismissable_banner.explore_tags": "Estas etiquetas estĂŁo a ganhar força no fediverso atualmente. As etiquetas que sĂŁo utilizadas por mais pessoas diferentes sĂŁo classificadas numa posição mais elevada.", "dismissable_banner.public_timeline": "Estas sĂŁo as publicaçÔes pĂșblicas mais recentes de pessoas no fediverso que as pessoas em {domain} seguem.", "domain_block_modal.block": "Bloquear servidor", - "domain_block_modal.block_account_instead": "Bloquear antes @{name}", + "domain_block_modal.block_account_instead": "Em vez disso, bloquear @{name}", "domain_block_modal.they_can_interact_with_old_posts": "As pessoas deste servidor podem interagir com as tuas publicaçÔes antigas.", "domain_block_modal.they_cant_follow": "NinguĂ©m deste servidor pode seguir-te.", "domain_block_modal.they_wont_know": "Eles nĂŁo saberĂŁo que foram bloqueados.", @@ -260,7 +260,7 @@ "domain_pill.their_username": "O identificador Ășnico dele no seu servidor. É possĂ­vel encontrar utilizadores com o mesmo nome de utilizador em servidores diferentes.", "domain_pill.username": "Nome de utilizador", "domain_pill.whats_in_a_handle": "Em que consiste um identificador?", - "domain_pill.who_they_are": "Uma vez que os identificadores dizem quem Ă© alguĂ©m e onde estĂĄ, pode interagir com as pessoas atravĂ©s da rede social de .", + "domain_pill.who_they_are": "Uma vez que os identificadores dizem quem Ă© alguĂ©m e onde estĂĄ, podes interagir com as pessoas atravĂ©s da rede social de .", "domain_pill.who_you_are": "Uma vez que o teu identificador indica quem Ă©s e onde estĂĄs, as pessoas podem interagir contigo atravĂ©s da rede social de .", "domain_pill.your_handle": "O teu identificador:", "domain_pill.your_server": "A tua casa digital, onde se encontram todas as tuas publicaçÔes. NĂŁo gostas deste? Muda de servidor a qualquer momento e leva tambĂ©m os teus seguidores.", @@ -457,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "mostrar / esconder texto atrĂĄs do aviso de conteĂșdo", "keyboard_shortcuts.toggle_sensitivity": "mostrar / ocultar multimĂ©dia", "keyboard_shortcuts.toot": "criar uma nova publicação", + "keyboard_shortcuts.translate": "traduzir uma publicação", "keyboard_shortcuts.unfocus": "remover o foco da ĂĄrea de texto / pesquisa", "keyboard_shortcuts.up": "mover para cima na lista", "lightbox.close": "Fechar", @@ -641,10 +642,10 @@ "notifications.policy.filter_hint": "Enviar para a caixa de notificaçÔes filtradas", "notifications.policy.filter_limited_accounts_hint": "Limitado pelos moderadores do servidor", "notifications.policy.filter_limited_accounts_title": "Contas moderadas", - "notifications.policy.filter_new_accounts.hint": "Criada {days, plural, one {no Ășltimo dia} other {nos Ășltimos # dias}}", + "notifications.policy.filter_new_accounts.hint": "Criadas {days, plural, one {no Ășltimo dia} other {nos Ășltimos # dias}}", "notifications.policy.filter_new_accounts_title": "Novas contas", "notifications.policy.filter_not_followers_hint": "Incluindo pessoas que te seguem hĂĄ menos de {days, plural, one {um dia} other {# dias}}", - "notifications.policy.filter_not_followers_title": "Pessoas nĂŁo te seguem", + "notifications.policy.filter_not_followers_title": "Pessoas que nĂŁo te seguem", "notifications.policy.filter_not_following_hint": "AtĂ© que os aproves manualmente", "notifications.policy.filter_not_following_title": "Pessoas que nĂŁo segues", "notifications.policy.filter_private_mentions_hint": "Filtrado, a nĂŁo ser que seja em resposta Ă  tua prĂłpria menção ou se seguires o remetente", @@ -836,6 +837,7 @@ "status.reblogs.empty": "Ainda ninguĂ©m impulsionou esta publicação. Quando alguĂ©m o fizer, aparecerĂĄ aqui.", "status.redraft": "Eliminar e reescrever", "status.remove_bookmark": "Retirar dos marcadores", + "status.remove_favourite": "Remover dos favoritos", "status.replied_in_thread": "Responder na conversa", "status.replied_to": "Respondeu a {name}", "status.reply": "Responder", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index d53825295f..f451caf6f9 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -141,7 +141,7 @@ "column.bookmarks": "BokmĂ€rken", "column.community": "Lokal tidslinje", "column.create_list": "Skapa lista", - "column.direct": "Privata nĂ€mningar", + "column.direct": "Privata omnĂ€mnande", "column.directory": "BlĂ€ddra bland profiler", "column.domain_blocks": "Blockerade domĂ€ner", "column.edit_list": "Redigera lista", @@ -239,6 +239,10 @@ "disabled_account_banner.text": "Ditt konto {disabledAccount} Ă€r för nĂ€rvarande inaktiverat.", "dismissable_banner.community_timeline": "Dessa Ă€r de senaste offentliga inlĂ€ggen frĂ„n personer vars konton tillhandahĂ„lls av {domain}.", "dismissable_banner.dismiss": "AvfĂ€rda", + "dismissable_banner.explore_links": "Dessa nyhetshistorier delas mest pĂ„ fediversum idag. Nyare nyhetshistorier som publiceras av fler olika personer rankas högre.", + "dismissable_banner.explore_statuses": "Dessa inlĂ€gg frĂ„n fediversum vinner dragkraft idag. Nyare inlĂ€gg som mĂ„nga boostar och favoritmarkerar rankas högre.", + "dismissable_banner.explore_tags": "De hĂ€r hashtaggarna vinner dragkraft i fediversum idag. Hashtaggar som anvĂ€nds av fler olika personer rankas högre.", + "dismissable_banner.public_timeline": "De hĂ€r Ă€r de aktuella publika inlĂ€gg frĂ„n personer i fediversum som personer i {domain} följer.", "domain_block_modal.block": "Blockera server", "domain_block_modal.block_account_instead": "Blockera @{name} istĂ€llet", "domain_block_modal.they_can_interact_with_old_posts": "Personer frĂ„n denna server kan interagera med dina gamla inlĂ€gg.", @@ -285,7 +289,7 @@ "empty_column.blocks": "Du har Ă€nnu ej blockerat nĂ„gra anvĂ€ndare.", "empty_column.bookmarked_statuses": "Du har inte bokmĂ€rkt nĂ„gra inlĂ€gg Ă€n. NĂ€r du bokmĂ€rker ett inlĂ€gg kommer det synas hĂ€r.", "empty_column.community": "Den lokala tidslinjen Ă€r tom. Skriv nĂ„got offentligt för att sĂ€tta bollen i rullning!", - "empty_column.direct": "Du har inga privata nĂ€mningar. NĂ€r du skickar eller tar emot ett direktmeddelande kommer det att visas hĂ€r.", + "empty_column.direct": "Du har inga privata omnĂ€mninande. NĂ€r du skickar eller tar emot ett direktmeddelande kommer det att visas hĂ€r.", "empty_column.domain_blocks": "Det finns Ă€nnu inga dolda domĂ€ner.", "empty_column.explore_statuses": "Ingenting Ă€r trendigt just nu. Kom tillbaka senare!", "empty_column.favourited_statuses": "Du har inga favoritmarkerade inlĂ€gg Ă€nnu. NĂ€r du favoritmĂ€rker ett sĂ„ kommer det att dyka upp hĂ€r.", @@ -402,7 +406,14 @@ "ignore_notifications_modal.new_accounts_title": "Vill du ignorera aviseringar frĂ„n nya konton?", "ignore_notifications_modal.not_followers_title": "Vill du ignorera aviseringar frĂ„n personer som inte följer dig?", "ignore_notifications_modal.not_following_title": "Vill du blockera aviseringar frĂ„n personer som du inte följer dig?", - "ignore_notifications_modal.private_mentions_title": "Vill du ignorera aviseringar frĂ„n oönskade privata omnĂ€mningar?", + "ignore_notifications_modal.private_mentions_title": "Vill du ignorera aviseringar frĂ„n oombedda privata omnĂ€mnanden?", + "interaction_modal.action.favourite": "För att fortsĂ€tta, mĂ„ste du favoritmarkera frĂ„n ditt konto.", + "interaction_modal.action.follow": "För att fortsĂ€tta, mĂ„ste du följa frĂ„n ditt konto.", + "interaction_modal.action.reblog": "För att fortsĂ€tta, mĂ„ste du boosta frĂ„n ditt konto.", + "interaction_modal.action.reply": "För att fortsĂ€tta, mĂ„ste du svara frĂ„n ditt konto.", + "interaction_modal.action.vote": "För att fortsĂ€tta, mĂ„ste du rösta frĂ„n ditt konto.", + "interaction_modal.go": "Vidare", + "interaction_modal.no_account_yet": "Har du inget konto Ă€n?", "interaction_modal.on_another_server": "PĂ„ en annan server", "interaction_modal.on_this_server": "PĂ„ denna server", "interaction_modal.title.favourite": "Favoritmarkera {name}s inlĂ€gg", @@ -410,6 +421,7 @@ "interaction_modal.title.reblog": "Boosta {name}s inlĂ€gg", "interaction_modal.title.reply": "Svara pĂ„ {name}s inlĂ€gg", "interaction_modal.title.vote": "Rösta i {name}s enkĂ€t", + "interaction_modal.username_prompt": "T.ex. {example}", "intervals.full.days": "{number, plural, one {# dag} other {# dagar}}", "intervals.full.hours": "{number, plural, one {# timme} other {# timmar}}", "intervals.full.minutes": "{number, plural, one {# minut} other {# minuter}}", @@ -419,7 +431,7 @@ "keyboard_shortcuts.column": "Fokusera kolumn", "keyboard_shortcuts.compose": "Fokusera skrivfĂ€ltet", "keyboard_shortcuts.description": "Beskrivning", - "keyboard_shortcuts.direct": "för att öppna privata nĂ€mningskolumnen", + "keyboard_shortcuts.direct": "för att öppna privata omnĂ€mnandekolumnen", "keyboard_shortcuts.down": "Flytta ner i listan", "keyboard_shortcuts.enter": "Öppna inlĂ€gg", "keyboard_shortcuts.favourite": "Favoritmarkera inlĂ€gg", @@ -445,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "Visa/gömma text bakom CW", "keyboard_shortcuts.toggle_sensitivity": "Visa/gömma media", "keyboard_shortcuts.toot": "Starta nytt inlĂ€gg", + "keyboard_shortcuts.translate": "för att översĂ€tta ett inlĂ€gg", "keyboard_shortcuts.unfocus": "Avfokusera skrivfĂ€lt/sökfĂ€lt", "keyboard_shortcuts.up": "Flytta uppĂ„t i listan", "lightbox.close": "StĂ€ng", @@ -466,6 +479,7 @@ "lists.delete": "Radera lista", "lists.done": "Klar", "lists.edit": "Redigera lista", + "lists.exclusive": "Dölj medlemmar i Hem flödet", "lists.exclusive_hint": "Om nĂ„gon Ă€r med pĂ„ den hĂ€r listan, göm dem i ditt Hemtidlinje för att undvika att se deras inlĂ€gg tvĂ„ gĂ„nger.", "lists.find_users_to_add": "Hitta anvĂ€ndare att lĂ€gga till", "lists.list_members": "Lista medlemmar", @@ -474,12 +488,14 @@ "lists.new_list_name": "Nytt listnamn", "lists.no_lists_yet": "Ännu inga listor.", "lists.no_members_yet": "Inga medlemmar Ă€nnu.", + "lists.no_results_found": "Inga resultat hittades.", "lists.remove_member": "Ta bort", "lists.replies_policy.followed": "Alla anvĂ€ndare som följs", "lists.replies_policy.list": "Medlemmar i listan", "lists.replies_policy.none": "Ingen", "lists.save": "Spara", "lists.search": "Sök", + "lists.show_replies_to": "Inkludera svar frĂ„n listmedlemmar till", "load_pending": "{count, plural, one {# nytt objekt} other {# nya objekt}}", "loading_indicator.label": "Laddar
", "media_gallery.hide": "Dölj", @@ -500,7 +516,7 @@ "navigation_bar.bookmarks": "BokmĂ€rken", "navigation_bar.community_timeline": "Lokal tidslinje", "navigation_bar.compose": "Författa nytt inlĂ€gg", - "navigation_bar.direct": "Privata nĂ€mningar", + "navigation_bar.direct": "Privata omnĂ€mnande", "navigation_bar.discover": "UpptĂ€ck", "navigation_bar.domain_blocks": "Dolda domĂ€ner", "navigation_bar.explore": "Utforska", @@ -532,12 +548,14 @@ "notification.annual_report.view": "Visa #Wrapstodon", "notification.favourite": "{name} favoritmarkerade ditt inlĂ€gg", "notification.favourite.name_and_others_with_link": "{name} och {count, plural, one {# annan} other {# andra}} har favoritmarkerat ditt inlĂ€gg", + "notification.favourite_pm": "{name} favoritmarkerade ditt privata omnĂ€mnande", + "notification.favourite_pm.name_and_others_with_link": "{name} och {count, plural, one {# annan} other {# andra}} favoritmarkerade ditt privata omnĂ€mnande", "notification.follow": "{name} följer dig", "notification.follow.name_and_others": "{name} och {count, plural, one {# annan} other {# andra}} följer dig", "notification.follow_request": "{name} har begĂ€rt att följa dig", "notification.follow_request.name_and_others": "{name} och {count, plural, one {# en annan} other {# andra}} har bett att följa dig", "notification.label.mention": "NĂ€mn", - "notification.label.private_mention": "Privat nĂ€mning", + "notification.label.private_mention": "Privat omnĂ€mnande", "notification.label.private_reply": "Privata svar", "notification.label.reply": "Svar", "notification.mention": "NĂ€mn", @@ -640,6 +658,7 @@ "onboarding.follows.done": "FĂ€rdig", "onboarding.follows.empty": "TyvĂ€rr kan inga resultat visas just nu. Du kan prova att anvĂ€nda sökfunktionen eller utforska sidan för att hitta personer att följa, eller försök igen senare.", "onboarding.follows.search": "Sök", + "onboarding.follows.title": "Följ mĂ€nniskor för att komma igĂ„ng", "onboarding.profile.discoverable": "Gör min profil upptĂ€ckbar", "onboarding.profile.discoverable_hint": "NĂ€r du vĂ€ljer att vara upptĂ€ckbar pĂ„ Mastodon kan dina inlĂ€gg visas i sök- och trendresultat, och din profil kan föreslĂ„s för personer med liknande intressen som du.", "onboarding.profile.display_name": "Visningsnamn", @@ -677,6 +696,8 @@ "privacy_policy.title": "Integritetspolicy", "recommended": "Rekommenderas", "refresh": "LĂ€s om", + "regeneration_indicator.please_stand_by": "VĂ€nligen vĂ€nta.", + "regeneration_indicator.preparing_your_home_feed": "Förbereder ditt hemflöde
", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sedan", "relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan", @@ -760,15 +781,18 @@ "search_results.accounts": "Profiler", "search_results.all": "Alla", "search_results.hashtags": "Hashtaggar", + "search_results.no_results": "Inga resultat.", + "search_results.no_search_yet": "Prova att söka efter inlĂ€gg, profiler eller hashtags.", "search_results.see_all": "Visa alla", "search_results.statuses": "InlĂ€gg", + "search_results.title": "Sök efter \"{q}\"", "server_banner.about_active_users": "Personer som anvĂ€nt denna server de senaste 30 dagarna (mĂ„natligt aktiva anvĂ€ndare)", "server_banner.active_users": "aktiva anvĂ€ndare", "server_banner.administered_by": "Administrerad av:", "server_banner.is_one_of_many": "{domain} Ă€r en av de mĂ„nga oberoende Mastodon-servrar som du kan anvĂ€nda för att delta i Fediversen.", "server_banner.server_stats": "Serverstatistik:", "sign_in_banner.create_account": "Skapa konto", - "sign_in_banner.follow_anyone": "Följ vem som helst över Fediverse och se allt i kronologisk ordning. Inga algoritmer, inga annonser och inga klickbeten i sikte.", + "sign_in_banner.follow_anyone": "Följ vem som helst över Fediversum och se allt i kronologisk ordning. Inga algoritmer, annonser eller klickbeten i sikte.", "sign_in_banner.mastodon_is": "Mastodon Ă€r det bĂ€sta sĂ€ttet att hĂ€nga med i vad som hĂ€nder.", "sign_in_banner.sign_in": "Logga in", "sign_in_banner.sso_redirect": "Logga in eller registrera dig", @@ -783,8 +807,8 @@ "status.copy": "Kopiera inlĂ€ggslĂ€nk", "status.delete": "Radera", "status.detailed_status": "Detaljerad samtalsvy", - "status.direct": "NĂ€mn @{name} privat", - "status.direct_indicator": "Privat nĂ€mning", + "status.direct": "OmnĂ€mn @{name} privat", + "status.direct_indicator": "Privat omnĂ€mnande", "status.edit": "Redigera", "status.edited": "Senast Ă€ndrad {date}", "status.edited_x_times": "Redigerad {count, plural, one {{count} gĂ„ng} other {{count} gĂ„nger}}", @@ -813,6 +837,7 @@ "status.reblogs.empty": "Ingen har boostat detta inlĂ€gg Ă€n. NĂ€r nĂ„gon gör det kommer de synas hĂ€r.", "status.redraft": "Radera & gör om", "status.remove_bookmark": "Ta bort bokmĂ€rke", + "status.remove_favourite": "Ta bort frĂ„n Favoriter", "status.replied_in_thread": "Svarade i trĂ„den", "status.replied_to": "Svarade pĂ„ {name}", "status.reply": "Svara", @@ -834,6 +859,7 @@ "subscribed_languages.target": "Ändra sprĂ„kprenumerationer för {target}", "tabs_bar.home": "Hem", "tabs_bar.notifications": "Aviseringar", + "terms_of_service.title": "AnvĂ€ndarvillkor", "time_remaining.days": "{number, plural, one {# dag} other {# dagar}} kvar", "time_remaining.hours": "{number, plural, one {# timme} other {# timmar}} kvar", "time_remaining.minutes": "{number, plural, one {# minut} other {# minuter}} kvar", diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index d79ab2aa21..82c53de5f7 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -659,6 +659,10 @@ code { } } } + + .status-card { + contain: unset; + } } .block-icon { diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 23b44be372..a489928407 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -13,7 +13,7 @@ class ActivityPub::TagManager }.freeze def public_collection?(uri) - uri == COLLECTIONS[:public] || uri == 'as:Public' || uri == 'Public' + uri == COLLECTIONS[:public] || %w(as:Public Public).include?(uri) end def url_for(target) diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb index d7aaeba5bd..d8090d15bc 100644 --- a/app/lib/application_extension.rb +++ b/app/lib/application_extension.rb @@ -3,14 +3,18 @@ module ApplicationExtension extend ActiveSupport::Concern + APP_NAME_LIMIT = 60 + APP_REDIRECT_URI_LIMIT = 2_000 + APP_WEBSITE_LIMIT = 2_000 + included do include Redisable has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application - validates :name, length: { maximum: 60 } - validates :website, url: true, length: { maximum: 2_000 }, if: :website? - validates :redirect_uri, length: { maximum: 2_000 } + validates :name, length: { maximum: APP_NAME_LIMIT } + validates :redirect_uri, length: { maximum: APP_REDIRECT_URI_LIMIT } + validates :website, url: true, length: { maximum: APP_WEBSITE_LIMIT }, if: :website? # The relationship used between Applications and AccessTokens is using # dependent: delete_all, which means the ActiveRecord callback in diff --git a/app/lib/request.rb b/app/lib/request.rb index 3d2a0c0e31..f984f0e63e 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -111,16 +111,10 @@ class Request end begin - # If we are using a persistent connection, we have to - # read every response to be able to move forward at all. - # However, simply calling #to_s or #flush may not be safe, - # as the response body, if malicious, could be too big - # for our memory. So we use the #body_with_limit method - response.body_with_limit if http_client.persistent? - yield response if block_given? ensure - http_client.close unless http_client.persistent? + response.truncated_body if http_client.persistent? && !response.connection.finished_request? + http_client.close unless http_client.persistent? && response.connection.finished_request? end end diff --git a/app/models/account.rb b/app/models/account.rb index 9857746b0e..d332edc149 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -107,23 +107,23 @@ class Account < ApplicationRecord validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? } # Remote user validations, also applies to internal actors - validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (!local? || actor_type == 'Application') && will_save_change_to_username? } + validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? } # Remote user validations validates :uri, presence: true, unless: :local?, on: :create # Local user validations - validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } - validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } + validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && !actor_type_application? } + validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && !actor_type_application? } validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? } validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? } validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? } validates_with EmptyProfileFieldNamesValidator, if: -> { local? && will_save_change_to_fields? } - with_options on: :create do - validates :uri, absence: true, if: :local? - validates :inbox_url, absence: true, if: :local? - validates :shared_inbox_url, absence: true, if: :local? - validates :followers_url, absence: true, if: :local? + with_options on: :create, if: :local? do + validates :followers_url, absence: true + validates :inbox_url, absence: true + validates :shared_inbox_url, absence: true + validates :uri, absence: true end normalizes :username, with: ->(username) { username.squish } @@ -186,6 +186,10 @@ class Account < ApplicationRecord domain.nil? end + def remote? + domain.present? + end + def moved? moved_to_account_id.present? end @@ -204,6 +208,10 @@ class Account < ApplicationRecord self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person' end + def actor_type_application? + actor_type == 'Application' + end + def group? actor_type == 'Group' end diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb index 7aa474887b..9058f73fb8 100644 --- a/app/models/account_warning.rb +++ b/app/models/account_warning.rb @@ -27,6 +27,7 @@ class AccountWarning < ApplicationRecord suspend: 4_000, }, suffix: :action + APPEAL_WINDOW = 20.days RECENT_PERIOD = 3.months.freeze normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true @@ -49,6 +50,10 @@ class AccountWarning < ApplicationRecord overruled_at.present? end + def appeal_eligible? + created_at >= APPEAL_WINDOW.ago + end + def to_log_human_identifier target_account.acct end diff --git a/app/models/appeal.rb b/app/models/appeal.rb index fafa75e69d..6a75fec661 100644 --- a/app/models/appeal.rb +++ b/app/models/appeal.rb @@ -16,8 +16,6 @@ # updated_at :datetime not null # class Appeal < ApplicationRecord - MAX_STRIKE_AGE = 20.days - TEXT_LENGTH_LIMIT = 2_000 belongs_to :account @@ -68,6 +66,6 @@ class Appeal < ApplicationRecord private def validate_time_frame - errors.add(:base, I18n.t('strikes.errors.too_late')) if strike.created_at < MAX_STRIKE_AGE.ago + errors.add(:base, I18n.t('strikes.errors.too_late')) unless strike.appeal_eligible? end end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 32a649af40..5e84293111 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -84,6 +84,10 @@ class Form::AdminSettings flavour_and_skin ).freeze + DIGEST_KEYS = %i( + custom_css + ).freeze + OVERRIDEN_SETTINGS = { authorized_fetch: :authorized_fetch_mode?, }.freeze @@ -137,6 +141,8 @@ class Form::AdminSettings KEYS.each do |key| next if PSEUDO_KEYS.include?(key) || !instance_variable_defined?(:"@#{key}") + cache_digest_value(key) if DIGEST_KEYS.include?(key) + if UPLOAD_KEYS.include?(key) public_send(key).save else @@ -156,6 +162,18 @@ class Form::AdminSettings private + def cache_digest_value(key) + Rails.cache.delete(:"setting_digest_#{key}") + + key_value = instance_variable_get(:"@#{key}") + if key_value.present? + Rails.cache.write( + :"setting_digest_#{key}", + Digest::SHA256.hexdigest(key_value) + ) + end + end + def typecast_value(key, value) if BOOLEAN_KEYS.include?(key) value == '1' diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 9331b9406f..bf790bf7cd 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -64,21 +64,31 @@ class NotificationGroup < ActiveModelSerializers::Model binds = [ account_id, SAMPLE_ACCOUNTS_SIZE, - pagination_range.begin, - pagination_range.end, ActiveRecord::Relation::QueryAttribute.new('group_keys', group_keys, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::String.new)), + pagination_range.begin || 0, ] + binds << pagination_range.end unless pagination_range.end.nil? + + upper_bound_cond = begin + if pagination_range.end.nil? + '' + elsif pagination_range.exclude_end? + 'AND id < $5' + else + 'AND id <= $5' + end + end ActiveRecord::Base.connection.select_all(<<~SQL.squish, 'grouped_notifications', binds).cast_values.to_h { |k, *values| [k, values] } SELECT groups.group_key, - (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1), - array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT $2), - (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4) AS notifications_count, - (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $3 ORDER BY id ASC LIMIT 1) AS min_id, - (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1) + (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1), + array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT $2), + (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond}) AS notifications_count, + (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $4 ORDER BY id ASC LIMIT 1) AS min_id, + (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1) FROM - unnest($5::text[]) AS groups(group_key); + unnest($3::text[]) AS groups(group_key); SQL else binds = [ diff --git a/app/models/poll.rb b/app/models/poll.rb index b4e0edcd0f..93ef0cc589 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -61,11 +61,7 @@ class Poll < ApplicationRecord votes.where(account: account).pluck(:choice) end - delegate :local?, to: :account - - def remote? - !local? - end + delegate :local?, :remote?, to: :account def emojis @emojis ||= CustomEmoji.from_text(options.join(' '), account.domain) diff --git a/app/models/trends/links.rb b/app/models/trends/links.rb index 0f3ead43f8..57ad486631 100644 --- a/app/models/trends/links.rb +++ b/app/models/trends/links.rb @@ -16,7 +16,7 @@ class Trends::Links < Trends::Base class Query < Trends::Query def to_arel scope = PreviewCard.joins(:trend).reorder(score: :desc) - scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present? + scope = scope.merge(language_order_clause) if preferred_languages.present? scope = scope.merge(PreviewCardTrend.allowed) if @allowed scope = scope.offset(@offset) if @offset.present? scope = scope.limit(@limit) if @limit.present? @@ -26,7 +26,7 @@ class Trends::Links < Trends::Base private def language_order_clause - Arel::Nodes::Case.new.when(PreviewCardTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) + language_order_for(PreviewCardTrend) end end diff --git a/app/models/trends/query.rb b/app/models/trends/query.rb index 590e81f4fd..abed64042e 100644 --- a/app/models/trends/query.rb +++ b/app/models/trends/query.rb @@ -94,6 +94,13 @@ class Trends::Query to_arel.to_a end + def language_order_for(trend_class) + trend_class + .reorder(nil) + .in_order_of(:language, [preferred_languages], filter: false) + .order(score: :desc) + end + def preferred_languages if @account&.chosen_languages.present? @account.chosen_languages diff --git a/app/models/trends/statuses.rb b/app/models/trends/statuses.rb index 66dab4519f..29f31f1969 100644 --- a/app/models/trends/statuses.rb +++ b/app/models/trends/statuses.rb @@ -15,7 +15,7 @@ class Trends::Statuses < Trends::Base class Query < Trends::Query def to_arel scope = Status.joins(:trend).reorder(score: :desc) - scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present? + scope = scope.merge(language_order_clause) if preferred_languages.present? scope = scope.merge(StatusTrend.allowed) if @allowed scope = scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present? scope = scope.offset(@offset) if @offset.present? @@ -26,7 +26,7 @@ class Trends::Statuses < Trends::Base private def language_order_clause - Arel::Nodes::Case.new.when(StatusTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) + language_order_for(StatusTrend) end end diff --git a/app/models/trends/tags.rb b/app/models/trends/tags.rb index 18f2a9a949..84e8dde11a 100644 --- a/app/models/trends/tags.rb +++ b/app/models/trends/tags.rb @@ -15,7 +15,8 @@ class Trends::Tags < Trends::Base class Query < Trends::Query def to_arel - scope = Tag.joins(:trend).reorder(language_order_clause.desc, score: :desc) + scope = Tag.joins(:trend).reorder(score: :desc) + scope = scope.merge(language_order_clause) if preferred_languages.present? scope = scope.merge(TagTrend.allowed) if @allowed scope = scope.offset(@offset) if @offset.present? scope = scope.limit(@limit) if @limit.present? @@ -25,7 +26,7 @@ class Trends::Tags < Trends::Base private def language_order_clause - Arel::Nodes::Case.new.when(TagTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) + language_order_for(TagTrend) end end diff --git a/app/models/user_role.rb b/app/models/user_role.rb index f9c4c14c4b..d567bf5eca 100644 --- a/app/models/user_role.rb +++ b/app/models/user_role.rb @@ -42,6 +42,7 @@ class UserRole < ApplicationRecord NOBODY_POSITION = -1 POSITION_LIMIT = (2**31) - 1 + CSS_COLORS = /\A#?(?:[A-F0-9]{3}){1,2}\z/i # CSS-style hex colors module Flags NONE = 0 @@ -90,7 +91,7 @@ class UserRole < ApplicationRecord attr_writer :current_account validates :name, presence: true, unless: :everyone? - validates :color, format: { with: /\A#?(?:[A-F0-9]{3}){1,2}\z/i }, unless: -> { color.blank? } + validates :color, format: { with: CSS_COLORS }, if: :color? validates :position, numericality: { in: (-POSITION_LIMIT..POSITION_LIMIT) } validate :validate_permissions_elevation @@ -101,9 +102,6 @@ class UserRole < ApplicationRecord before_validation :set_position scope :assignable, -> { where.not(id: EVERYONE_ROLE_ID).order(position: :asc) } - scope :highlighted, -> { where(highlighted: true) } - scope :with_color, -> { where.not(color: [nil, '']) } - scope :providing_styles, -> { highlighted.with_color } has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify diff --git a/app/policies/account_warning_policy.rb b/app/policies/account_warning_policy.rb index 4f8df7420e..976cae6c9c 100644 --- a/app/policies/account_warning_policy.rb +++ b/app/policies/account_warning_policy.rb @@ -6,7 +6,7 @@ class AccountWarningPolicy < ApplicationPolicy end def appeal? - target? && record.created_at >= Appeal::MAX_STRIKE_AGE.ago + target? && record.appeal_eligible? end private diff --git a/app/policies/admin/status_policy.rb b/app/policies/admin/status_policy.rb index e9379c25ec..c4ba5c2606 100644 --- a/app/policies/admin/status_policy.rb +++ b/app/policies/admin/status_policy.rb @@ -12,7 +12,7 @@ class Admin::StatusPolicy < ApplicationPolicy end def show? - role.can?(:manage_reports, :manage_users) && (record.public_visibility? || record.unlisted_visibility? || record.reported? || viewable_through_normal_policy?) + role.can?(:manage_reports, :manage_users) && eligible_to_show? end def destroy? @@ -29,6 +29,10 @@ class Admin::StatusPolicy < ApplicationPolicy private + def eligible_to_show? + record.distributable? || record.reported? || viewable_through_normal_policy? + end + def viewable_through_normal_policy? StatusPolicy.new(current_account, record, @preloaded_relations).show? end diff --git a/app/policies/user_role_policy.rb b/app/policies/user_role_policy.rb index 6144a0ec4a..44b5589581 100644 --- a/app/policies/user_role_policy.rb +++ b/app/policies/user_role_policy.rb @@ -10,10 +10,16 @@ class UserRolePolicy < ApplicationPolicy end def update? - role.can?(:manage_roles) && (role.overrides?(record) || role.id == record.id) + role.can?(:manage_roles) && (role.overrides?(record) || self_editing?) end def destroy? - !record.everyone? && role.can?(:manage_roles) && role.overrides?(record) && role.id != record.id + !record.everyone? && role.can?(:manage_roles) && role.overrides?(record) && !self_editing? + end + + private + + def self_editing? + role.id == record.id end end diff --git a/app/serializers/rest/scheduled_status_serializer.rb b/app/serializers/rest/scheduled_status_serializer.rb index 8aa0d89386..7c54f39c0d 100644 --- a/app/serializers/rest/scheduled_status_serializer.rb +++ b/app/serializers/rest/scheduled_status_serializer.rb @@ -8,8 +8,4 @@ class REST::ScheduledStatusSerializer < ActiveModel::Serializer def id object.id.to_s end - - def params - object.params.without('application_id') - end end diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml index dd60f7eabd..9c8e267d62 100644 --- a/app/views/admin/report_notes/_report_note.html.haml +++ b/app/views/admin/report_notes/_report_note.html.haml @@ -4,7 +4,7 @@ .report-notes__item__header %span.username = link_to report_note.account.username, admin_account_path(report_note.account_id) - %time.relative-formatted{ datetime: report_note.created_at.iso8601 } + %time.relative-formatted{ datetime: report_note.created_at.iso8601, title: report_note.created_at } = l report_note.created_at.to_date .report-notes__item__content diff --git a/app/views/admin/reports/_comment.html.haml b/app/views/admin/reports/_comment.html.haml index 8c07210af9..2b3af15c49 100644 --- a/app/views/admin/reports/_comment.html.haml +++ b/app/views/admin/reports/_comment.html.haml @@ -18,7 +18,7 @@ = link_to report.account.username, admin_account_path(report.account_id) - else = link_to report.account.domain, admin_instance_path(report.account.domain) - %time.relative-formatted{ datetime: report.created_at.iso8601 } + %time.relative-formatted{ datetime: report.created_at.iso8601, title: report.created_at } = l report.created_at.to_date .report-notes__item__content = simple_format(h(report.comment)) diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml index 636127354b..085bdbd156 100644 --- a/app/views/admin/roles/_role.html.haml +++ b/app/views/admin/roles/_role.html.haml @@ -1,7 +1,7 @@ .announcements-list__item - if can?(:update, role) = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do - %span.user-role{ class: "user-role-#{role.id}" } + %span.user-role = material_symbol 'group' - if role.everyone? @@ -10,7 +10,7 @@ = role.name - else %span.announcements-list__item__title - %span.user-role{ class: "user-role-#{role.id}" } + %span.user-role = material_symbol 'group' - if role.everyone? diff --git a/app/views/application/mailer/_checklist.html.haml b/app/views/application/mailer/_checklist.html.haml index 91c7c98f26..4b460fa26d 100644 --- a/app/views/application/mailer/_checklist.html.haml +++ b/app/views/application/mailer/_checklist.html.haml @@ -29,8 +29,8 @@ %div - if defined?(show_apps_buttons) && show_apps_buttons .email-welcome-apps-btns - = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-app-store.png'), alt: t('user_mailer.welcome.apps_ios_action'), width: 120, height: 40), 'https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974' - = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-google-play.png'), alt: t('user_mailer.welcome.apps_android_action'), width: 120, height: 40), 'https://play.google.com/store/apps/details?id=org.joinmastodon.android' + = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-app-store.png'), alt: t('user_mailer.welcome.apps_ios_action'), width: 120, height: 40), app_store_url_ios + = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-google-play.png'), alt: t('user_mailer.welcome.apps_android_action'), width: 120, height: 40), app_store_url_android - elsif defined?(button_text) && defined?(button_url) && defined?(checked) && !checked = render 'application/mailer/button', text: button_text, url: button_url, has_arrow: false /[if mso] diff --git a/app/views/custom_css/show.css.erb b/app/views/custom_css/show.css.erb index 78da809ed6..d4b24b2106 100644 --- a/app/views/custom_css/show.css.erb +++ b/app/views/custom_css/show.css.erb @@ -2,9 +2,3 @@ <%= raw custom_css_styles %> <%- end %> -<%- @user_roles.each do |role| %> -.user-role-<%= role.id %> { - --user-role-accent: <%= role.color %>; -} - -<%- end %> diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml index 150dc06759..322d820a2a 100644 --- a/app/views/disputes/strikes/show.html.haml +++ b/app/views/disputes/strikes/show.html.haml @@ -66,7 +66,7 @@ .report-notes__item__header %span.username = link_to @appeal.account.username, can?(:show, @appeal.account) ? admin_account_path(@appeal.account_id) : short_account_url(@appeal.account) - %time.relative-formatted{ datetime: @appeal.created_at.iso8601 } + %time.relative-formatted{ datetime: @appeal.created_at.iso8601, title: @appeal.created_at } = l @appeal.created_at.to_date .report-notes__item__content diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index a76dc75f63..fe73fdb5f7 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -35,7 +35,7 @@ = csrf_meta_tags unless skip_csrf_meta_tags? %meta{ name: 'style-nonce', content: request.content_security_policy_nonce } - = stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all' + = custom_stylesheet = yield :header_tags diff --git a/app/views/user_mailer/welcome.text.erb b/app/views/user_mailer/welcome.text.erb index 144d44b842..383f436f8e 100644 --- a/app/views/user_mailer/welcome.text.erb +++ b/app/views/user_mailer/welcome.text.erb @@ -30,8 +30,8 @@ 5. <%= t('user_mailer.welcome.apps_title') %> <%= t('user_mailer.welcome.apps_step') %> - * iOS: https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974 - * Android: https://play.google.com/store/apps/details?id=org.joinmastodon.android + * iOS: <%= app_store_url_ios %> + * Android: <%= app_store_url_android %> --- diff --git a/config/initializers/settings_digests.rb b/config/initializers/settings_digests.rb new file mode 100644 index 0000000000..2a5d925c70 --- /dev/null +++ b/config/initializers/settings_digests.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +Rails.application.config.to_prepare do + custom_css = begin + Setting.custom_css + rescue ActiveRecord::AdapterError # Running without a database, not migrated, no connection, etc + nil + end + + if custom_css.present? + Rails + .cache + .write( + :setting_digest_custom_css, + Digest::SHA256.hexdigest(custom_css) + ) + end +end diff --git a/config/locales/activerecord.cy.yml b/config/locales/activerecord.cy.yml index 3620c1c2f6..9bc9b3b2bc 100644 --- a/config/locales/activerecord.cy.yml +++ b/config/locales/activerecord.cy.yml @@ -24,6 +24,8 @@ cy: models: account: attributes: + fields: + fields_with_values_missing_labels: yn cynnwys gwerthoedd gyda labeli coll username: invalid: rhaid iddo gynnwys dim ond llythrennau, rhifau a thanlinellau reserved: wedi ei neilltuo diff --git a/config/locales/activerecord.gl.yml b/config/locales/activerecord.gl.yml index 0c4941f048..8fdff40282 100644 --- a/config/locales/activerecord.gl.yml +++ b/config/locales/activerecord.gl.yml @@ -24,6 +24,8 @@ gl: models: account: attributes: + fields: + fields_with_values_missing_labels: contĂ©n valores aos que lle faltan etiquetas username: invalid: sĂł letras, nĂșmeros e trazo baixo reserved: estĂĄ reservado diff --git a/config/locales/activerecord.hu.yml b/config/locales/activerecord.hu.yml index 27ca9018d4..b26d1afbe3 100644 --- a/config/locales/activerecord.hu.yml +++ b/config/locales/activerecord.hu.yml @@ -24,6 +24,8 @@ hu: models: account: attributes: + fields: + fields_with_values_missing_labels: hiĂĄnyzĂł cĂ­mkĂ©kkel rendelkezƑ Ă©rtĂ©keket tartalmaz username: invalid: csak betƱket, szĂĄmokat vagy alĂĄvonĂĄst tartalmazhat reserved: foglalt diff --git a/config/locales/activerecord.ia.yml b/config/locales/activerecord.ia.yml index fcb3f68278..35b28a19aa 100644 --- a/config/locales/activerecord.ia.yml +++ b/config/locales/activerecord.ia.yml @@ -24,6 +24,8 @@ ia: models: account: attributes: + fields: + fields_with_values_missing_labels: contine valores con etiquettas perdite username: invalid: debe continer solmente litteras, numeros e lineettas basse reserved: es reservate diff --git a/config/locales/activerecord.lv.yml b/config/locales/activerecord.lv.yml index b7e2db65e8..3fb928c89c 100644 --- a/config/locales/activerecord.lv.yml +++ b/config/locales/activerecord.lv.yml @@ -24,6 +24,8 @@ lv: models: account: attributes: + fields: + fields_with_values_missing_labels: satur vērtÄ«bas ar trĆ«kstoƥām iezÄ«mēm username: invalid: drÄ«kst saturēt tikai burtus, ciparus un pasvÄ«tras reserved: ir rezervēts @@ -39,6 +41,11 @@ lv: attributes: data: malformed: ir nepareizi veidots + list_account: + attributes: + account_id: + taken: jau ir sarakstā + must_be_following: jābĆ«t kontam, kuram seko status: attributes: reblog: diff --git a/config/locales/activerecord.pt-PT.yml b/config/locales/activerecord.pt-PT.yml index 86581331db..8b17ade2eb 100644 --- a/config/locales/activerecord.pt-PT.yml +++ b/config/locales/activerecord.pt-PT.yml @@ -24,6 +24,8 @@ pt-PT: models: account: attributes: + fields: + fields_with_values_missing_labels: contĂ©m valores com etiquetas em falta username: invalid: deve conter apenas letras, nĂșmeros e traços inferiores reserved: estĂĄ reservado diff --git a/config/locales/activerecord.sv.yml b/config/locales/activerecord.sv.yml index f05161992c..bf1ef22f3d 100644 --- a/config/locales/activerecord.sv.yml +++ b/config/locales/activerecord.sv.yml @@ -24,6 +24,8 @@ sv: models: account: attributes: + fields: + fields_with_values_missing_labels: innehĂ„ller vĂ€rden med saknade etiketter username: invalid: endast bokstĂ€ver, siffror och understrykning reserved: Ă€r reserverat diff --git a/config/locales/activerecord.tr.yml b/config/locales/activerecord.tr.yml index 095aa72e67..4795e629fb 100644 --- a/config/locales/activerecord.tr.yml +++ b/config/locales/activerecord.tr.yml @@ -25,7 +25,7 @@ tr: account: attributes: fields: - fields_with_values_missing_labels: değerleri eksik etiketler içeriyor + fields_with_values_missing_labels: etiketleri eksik değerler içeriyor username: invalid: sadece harfler, sayılar ve alt çizgiler reserved: kullanılamaz diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml index 774a2648f9..ba9a440a11 100644 --- a/config/locales/doorkeeper.sk.yml +++ b/config/locales/doorkeeper.sk.yml @@ -134,6 +134,7 @@ sk: media: MediĂĄlne prĂ­lohy mutes: StĂ­ĆĄenia notifications: Upozornenia + profile: VĂĄĆĄ Mastodon profil push: Upozornenia push reports: HlĂĄsenia search: VyhÄŸadĂĄvanie diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 00ffed90bb..441217ec0e 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -214,6 +214,7 @@ fo: enable_user: Ger brĂșkara virknan memorialize_account: Minnst til Konto promote_user: VĂ­s fram BrĂșkara + publish_terms_of_service: Útgev tĂŠnastutreytir reject_appeal: AvvĂ­s mĂłtmali reject_user: AvvĂ­s BrĂșkara remove_avatar_user: Sletta Avatar @@ -278,6 +279,7 @@ fo: enable_user_html: "%{name} gjĂžrdi innritan virkna fyri brĂșkaran %{target}" memorialize_account_html: "%{name} broytti kontuna hjĂĄ %{target} til eina minnissĂ­Ă°u" promote_user_html: "%{name} flutti brĂșkaran %{target} fram" + publish_terms_of_service_html: "%{name} Ăștgav dagfĂžringar til tĂŠnastutreytirnar" reject_appeal_html: "%{name} avvĂ­sti umsjĂłnarĂĄheitan frĂĄ %{target}" reject_user_html: "%{name} avvĂ­sti skrĂĄseting hjĂĄ %{target}" remove_avatar_user_html: "%{name} strikaĂ°i eftirgjĂžrda skapningin hjĂĄ %{target}" @@ -925,6 +927,32 @@ fo: search: Leita title: FrĂĄmerki updated_msg: FrĂĄmerkjastillingar dagfĂžrdar + terms_of_service: + back: Aftur til tĂŠnastutreytir + changelog: Hvat er broytt + create: BrĂșka tĂ­nar egnu + current: NĂșverandi + draft: Kladda + generate: BrĂșka leist + generates: + action: FramleiĂ° + chance_to_review_html: "Framleiddu tĂŠnastutreytirnar verĂ°a ikki Ăștgivnar av sĂŠr sjĂĄlvum. TĂș fĂŠr mĂžguleika at eftirhyggja Ășrslitini. Vinarliga Ăștfyll neyĂ°ugu smĂĄlutirnar fyri at halda fram." + explanation_html: Leisturin viĂ° tĂŠnastutreytum er einans til kunningar og skal ikki fatast sum lĂžgfrÞðislig rĂĄĂ°geving yvirhĂžvur. Vinarliga spyr tĂ­n egna lĂžgfrÞðisliga rĂĄĂ°geva um tĂ­na stÞðu og Ă­tĂžkiligu lĂžgfrÞðisligu spurningarnar hjĂĄ tĂŠr. + title: Uppseting av tĂŠnastutreytum + history: SĂžga + live: BeinleiĂ°is + no_history: Enn eru ongar skrĂĄsettar broytingar Ă­ tĂŠnastutreytunum. + no_terms_of_service_html: Í lĂžtuni hevur tĂș ongar tĂŠnastutreytir uppsettar. Hugsanin viĂ° tĂŠnastutreytum er at veita greidleika og at verja teg Ă­mĂłti mĂžguligum ĂĄbyrgdum Ă­ Ăłsemjum viĂ° tĂ­nar brĂșkarar. + notified_on_html: FrĂĄboĂ°an latin brĂșkarum %{date} + notify_users: Gev brĂșkarum frĂĄboĂ°an + preview: + explanation_html: 'TeldubrĂŠviĂ° verĂ°ur sent til %{display_count} brĂșkarar, sum hava stovna kontu ĂĄĂ°renn %{date}. Fylgjandi tekstur kemur viĂ° Ă­ teldubrĂŠviĂ°:' + send_preview: Send undanvĂ­sing til %{email} + title: UndanvĂ­s frĂĄboĂ°an um tĂŠnastutreytir + publish: Útgev + published_on_html: ÚtgiviĂ° %{date} + save_draft: Goym kladdu + title: TĂŠnastutreytir title: Umsiting trends: allow: Loyv @@ -1156,6 +1184,7 @@ fo: set_new_password: Áset nĂœtt loyniorĂ° setup: email_below_hint_html: Kekka mappuna viĂ° ruskposti ella biĂ° um ein annan. TĂș kanst rĂŠtta teldupostadressuna, um hon er skeiv. + email_settings_hint_html: TrĂœst ĂĄ leinkiĂ°, sum vit sendu til %{email} fyri at byrja at brĂșka Mastodon. Vit bĂ­Ă°a beint her. link_not_received: Fekk tĂș einki leinki? new_confirmation_instructions_sent: TĂș fer at mĂłttaka eitt nĂœtt teldubrĂŠv viĂ° vĂĄttanarleinkinum um nakrar fĂĄar minuttir! title: Kekka innbakkan hjĂĄ tĂŠr @@ -1164,6 +1193,7 @@ fo: title: Rita inn ĂĄ %{domain} sign_up: manual_review: Tilmeldingar til %{domain} fara Ă­gjĂžgnum eina manuella eftirkanning av okkara kjakleiĂ°arum. Fyri at hjĂĄlpa okkum at skunda undir skrĂĄsetingina, skriva eitt sindur um teg sjĂĄlva/n og hvĂ­ tĂș vil hava eina kontu ĂĄ %{domain}. + preamble: ViĂ° eini kontu ĂĄ hesum Mastodon ambĂŠtaranum ber til hjĂĄ tĂŠr at fylgja ein og hvĂžnn annan persĂłn ĂĄ fediversinum, ĂłansĂŠĂ° hvar teirra konta er hĂœst. title: Latum okkum fĂĄa teg settan upp ĂĄ %{domain}. status: account_status: KontustÞða @@ -1175,6 +1205,7 @@ fo: view_strikes: VĂ­s eldri atsĂłknir mĂłti tĂ­ni kontu too_fast: OyĂ°ublaĂ°iĂ° innsent ov skjĂłtt, royn aftur. use_security_key: BrĂșka trygdarlykil + user_agreement_html: Eg havi lisiĂ° og taki undir viĂ° tĂŠnastutreytunum og privatlĂ­vspolitikkinum author_attribution: example_title: TekstadĂžmi hint_html: Skrivar tĂș tĂ­Ă°indi ella greinar til bloggin uttanfyri Mastodon? Her kanst tĂș stĂœra, hvussu tĂș verĂ°ur tilsipaĂ°/ur, tĂĄ iĂ° tĂ­tt tilfar verĂ°ur deilt ĂĄ Mastodon. @@ -1836,6 +1867,8 @@ fo: too_late: TaĂ° er ov seint at kĂŠra hesa atsĂłkn tags: does_not_match_previous_name: samsvarar ikki viĂ° undanfarna navniĂ° + terms_of_service: + title: TĂŠnastutreytir themes: contrast: Mastodon (hĂžgur kontrastur) default: Mastodon (myrkt) @@ -1896,6 +1929,15 @@ fo: further_actions_html: Var hetta ikki tĂș, so mĂŠla vit til, at tĂș %{action} beinan vegin og at tĂș ger vĂĄttan Ă­ tveimum stigum virkna fyri at konta tĂ­n kann vera trygg. subject: Atgongd er fingin til kontu tĂ­na frĂĄ eini nĂœggjari IP adressu title: Ein nĂœggj innritan + terms_of_service_changed: + agreement: ViĂ° framhaldandi at brĂșka %{domain} góðtekur tĂș hesar treytir. Tekur tĂș ikki undir viĂ° dagfĂžrdu treytunum, so kanst tĂș til einhvĂžrja tĂ­Ă° uppsiga avtaluna viĂ° %{domain} viĂ° at strika kontu tĂ­na. + changelog: 'Í stuttum merkir henda dagfĂžringin:' + description: 'TĂș mĂłttekur hetta teldubrĂŠviĂ°, tĂ­ at vit gera nakrar broytingar Ă­ okkara tĂŠnastutreytum ĂĄ %{domain}. Vit eggja tĂŠr til at eftirhyggja dagfĂžrdu treytirnar her:' + description_html: TĂș mĂłttekur hetta teldubrĂŠviĂ°, tĂ­ at vit gera nakrar broytingar Ă­ okkara tĂŠnastutreytum ĂĄ %{domain}. Vit eggja tĂŠr til at eftirhyggja dagfĂžrdu og samlaĂ°u treytirnar her. + sign_off: "%{domain} toymiĂ°" + subject: DagfĂžringar til okkara tĂŠnastutreytir + subtitle: TĂŠnastutreytirnar hjĂĄ %{domain} eru viĂ° at verĂ°a broyttar + title: TĂœdningarmikil dagfĂžring warning: appeal: Innsend eina kĂŠru appeal_description: TrĂœrt tĂș, at hetta er ein feilur, so kanst tĂș senda eina kĂŠru til starvsfĂłlkini ĂĄ %{instance}. diff --git a/config/locales/gl.yml b/config/locales/gl.yml index ac6581c8c9..63a2b9b340 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1859,9 +1859,9 @@ gl: '63113904': 2 anos '7889238': 3 meses min_age_label: LĂ­mite temporal - min_favs: Manter as publicaciĂłns favorecidas polo menos + min_favs: Manter publicaciĂłns favorecidas polo menos min_favs_hint: Non elimina ningunha das tĂșas publicaciĂłns que recibiron alomenos esta cantidade de favorecementos. Deixa en branco para eliminar publicaciĂłns independentemente do nĂșmero de favorecementos - min_reblogs: Manter publicaciĂłns promovidas mĂĄis de + min_reblogs: Manter publicaciĂłns promovidas polo menos min_reblogs_hint: Non elimina ningunha das tĂșas publicaciĂłns se foron promovidas mĂĄis deste nĂșmero de veces. Deixa en branco para eliminar publicaciĂłns independentemente do seu nĂșmero de promociĂłns stream_entries: sensitive_content: Contido sensible diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 0f757c6370..2d256b475e 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -217,6 +217,7 @@ lv: enable_user: Ieslēgt Lietotāju memorialize_account: Saglabāt Kontu PiemiƆai promote_user: Izceltt Lietotāju + publish_terms_of_service: Publicēt pakalpojuma izmantoĆĄanas noteikumus reject_appeal: NoraidÄ«t Apelāciju reject_user: NoraidÄ«t lietotāju remove_avatar_user: NoƆemt profila attēlu @@ -273,6 +274,7 @@ lv: enable_user_html: "%{name} iespējoja pieteikĆĄanos lietotājam %{target}" memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiƆas lapu" promote_user_html: "%{name} paaugstināja lietotāju %{target}" + publish_terms_of_service_html: "%{name} padarÄ«ja pieejamus pakalpojuma izmantoĆĄanas noteikumu atjauninājumus" reject_appeal_html: "%{name} noraidÄ«ja satura pārraudzÄ«bas lēmuma iebildumu no %{target}" reject_user_html: "%{name} noraidÄ«ja reÄŁistrēơanos no %{target}" remove_avatar_user_html: "%{name} noƆēma %{target} profila attēlu" @@ -641,7 +643,7 @@ lv: create_and_resolve: Atrisināt ar piezÄ«mi create_and_unresolve: Atvērt atkārtoti ar piezÄ«mi delete: Dzēst - placeholder: Apraksti veiktās darbÄ«bas vai citus saistÄ«tus atjauninājumus... + placeholder: Jāapraksta veiktās darbÄ«bas vai jebkuri citi saistÄ«tie atjauninājumi... title: PiezÄ«mes notes_description_html: Skati un atstāj piezÄ«mes citiem moderatoriem un sev nākotnei processed_msg: 'Pārskats #%{id} veiksmÄ«gi apstrādāts' @@ -746,13 +748,13 @@ lv: rules: add_new: Pievienot noteikumu delete: Dzēst - description_html: Lai gan lielākā daÄŒa apgalvo, ka ir izlasÄ«juĆĄi pakalpojumu sniegĆĄanas noteikumus un piekrÄ«t tiem, parasti cilvēki to izlasa tikai pēc problēmas raĆĄanās. Padariet vienkārƥāku sava servera noteikumu uztverĆĄanu, veidojot tos vienkārƥā sarakstā pa punktiem. Centieties, lai atseviĆĄÄ·i noteikumi bĆ«tu Ä«si un vienkārĆĄi, taču arÄ« nesadaliet tos daudzos atseviĆĄÄ·os vienumos. + description_html: Kaut arÄ« lielākā daÄŒa apgalvo, ka ir lasÄ«juĆĄi un piekrÄ«t pakalpojuma izmantoĆĄanas noteikumiem, parasti cilvēki tos neizlasa, lÄ«dz rodas sareĆŸÄŁÄ«jumi. Padari vienkārƥāku sava servera noteikumu pārskatÄ«ĆĄanu, sniedzot tos vienkārƥā uzsvēruma punktu sarakstā! Jāmēģina atseviĆĄÄ·us noteikumus veidot Ä«sus un vienkārĆĄus, bet jāmēģina arÄ« tos nesadalÄ«t daudzos atseviĆĄÄ·os vienumos. edit: Labot nosacÄ«jumu - empty: Servera noteikumi vēl nav definēti. + empty: Vēl nav pievienots neviens servera noteikums. title: Servera noteikumi settings: about: - manage_rules: PārvaldÄ«t servera nosacÄ«jumus + manage_rules: PārvaldÄ«t servera noteikumus preamble: Sniedz padziÄŒinātu informāciju par to, kā serveris tiek darbināts, moderēts un finansēts. rules_hint: Noteikumiem, kas taviem lietotājiem ir jāievēro, ir Ä«paĆĄa sadaÄŒa. title: Par @@ -821,6 +823,7 @@ lv: back_to_account: AtpakaÄŒ uz konta lapu back_to_report: AtpakaÄŒ uz paziƆojumu lapu batch: + add_to_report: 'Pievienot atskaitei #%{id}' remove_from_report: NoƆemt no ziƆojuma report: ZiƆojums contents: Saturs @@ -832,13 +835,17 @@ lv: media: title: Multivide metadata: Metadati + no_history: Ć is ieraksts nav bijis labots no_status_selected: Neviena ziƆa netika mainÄ«ta, jo neviena netika atlasÄ«ta open: Atvērt ziƆu original_status: OriÄŁinālā ziƆa reblogs: Reblogi + replied_to_html: Atbildēja %{acct_link} status_changed: ZiƆa mainÄ«ta status_title: Publicēja @%{name} + title: Konta ieraksti - @%{name} trending: Aktuāli + view_publicly: SkatÄ«t publiski visibility: RedzamÄ«ba with_media: Ar multividi strikes: @@ -876,8 +883,8 @@ lv: message_html: 'NesaderÄ«ga Elasticsearch versija: %{value}' version_comparison: Darbojas Elasticsearch %{running_version}, tomēr ir nepiecieĆĄama %{required_version} rules_check: - action: PārvaldÄ«t servera nosacÄ«jumus - message_html: Tu neesi definējis nevienu servera nosacÄ«jumu. + action: PārvaldÄ«t servera noteikumus + message_html: Nav pievienots neviens servera noteikums. sidekiq_process_check: message_html: Rindā(s) %{value} nedarbojas neviens Sidekiq process. LĆ«dzu, pārskati savu Sidekiq konfigurāciju software_version_check: @@ -907,16 +914,42 @@ lv: name: Nosaukums newest: Jaunākie oldest: Vecākie + open: ApskatÄ«t publiski reset: AtiestatÄ«t review: PārskatÄ«t stāvokli search: Meklēt title: Tēmturi updated_msg: Tēmtura iestatÄ«jumi ir veiksmÄ«gi atjaunināti terms_of_service: + back: AtpakaÄŒ uz pakalpojuma izmantoĆĄanas noteikumiem changelog: Kas ir mainÄ«jies + create: Izmantot savus + current: PaĆĄreizējie + draft: Melnraksts + generate: Izmantot sagatavi + generates: + action: Izveidot + chance_to_review_html: "Izveidotie pakalpojuma izmantoĆĄanas noteikumi netiks automātiski publicēti. BĆ«s iespēja izskatÄ«t iznākumu. LĆ«gums norādÄ«t nepiecieĆĄamo informāciju, lai turpinātu." + explanation_html: Pakalpojuma izmantoĆĄanas noteikumu sagatave tiek piedāvāta tikai izzināƥanas nolĆ«kam, un to nevajadzētu izmantot kā juridisku padomu jebkurā jautājumā. LĆ«gums sazināties ar savu juridisko padomdevēju par saviem apstākÄŒiem un noteiktiem juridiskiem jautājumiem. + title: Pakalpojuma izmantoƥānas noteikumu uzstādÄ«ĆĄana history: Vēsture + live: DarbÄ«bā + no_history: Nav ierakstu par pakalpojuma izmantoĆĄanas noteikumu izmaiƆām. + no_terms_of_service_html: PaĆĄlaik nav uzstādÄ«ti pakalpojuma izmantoĆĄanas noteikumi. Tie ir paredzēti, lai sniegtu skaidrÄ«bu un aizsargātu no iespējamas atbildÄ«bas strÄ«dos ar lietotājiem. + notified_on_html: Lietotājiem paziƆots %{date} + notify_users: PaziƆot lietotājiem + preview: + explanation_html: 'E-pasta ziƆojums tiks nosĆ«tÄ«ts %{display_count} lietotājiem, kuri ir reÄŁistrējuĆĄies pirms %{date}. Ć is teksts tiks iekÄŒauts e-pasta ziƆojumā:' + send_preview: NosĆ«tÄ«t priekĆĄskatÄ«jumu uz %{email} + send_to_all: + one: NosĆ«tÄ«t %{display_count} e-pasta ziƆojumu + other: NosĆ«tÄ«t %{display_count} e-pasta ziƆojumus + zero: NosĆ«tÄ«t %{display_count} e-pasta ziƆojumu + title: PriekĆĄkatÄ«t pakalpojuma izmantoĆĄanas noteikumu paziƆojumu publish: Publicēt - published_on_html: Publicēts %{date} + published_on_html: Publicēti %{date} + save_draft: Saglabāt melnrakstu + title: Pakalpojuma izmantoĆĄanas noteikumi title: PārvaldÄ«ba trends: allow: AtÄŒaut @@ -1157,6 +1190,7 @@ lv: view_strikes: Skati iepriekơējos brÄ«dinājumus par savu kontu too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz. use_security_key: Lietot droĆĄÄ«bas atslēgu + user_agreement_html: Es esmu izlasÄ«jis un piekrÄ«tu pakalpojuma izmantoĆĄanas noteikumiem un privātuma nosacÄ«jumiem author_attribution: example_title: Parauga teksts more_from_html: Vairāk no %{name} @@ -1777,6 +1811,8 @@ lv: too_late: BrÄ«dinājuma apstrÄ«dēơanas laiks ir nokavēts tags: does_not_match_previous_name: nesakrÄ«t ar iepriekơējo nosaukumu + terms_of_service: + title: Pakalpojuma izmantoĆĄanas noteikumi themes: contrast: Mastodon (Augsts kontrasts) default: Mastodon (TumĆĄs) @@ -1826,6 +1862,15 @@ lv: further_actions_html: Ja tas nebiji tu, iesakām nekavējoties %{action} un iespējot divu faktoru autentifikāciju, lai tavs konts bĆ«tu droĆĄÄ«bā. subject: Tavam kontam ir piekÄŒĆ«ts no jaunas IP adreses title: Jauna pieteikĆĄanās + terms_of_service_changed: + agreement: Ar %{domain} izmantoĆĄanas tuprināƥanu tiek piekrists ĆĄiem noteikumiem. Ja ir iebildumi pret atjauninātajiem noteikumiem, savu piekriĆĄanu var atcelt jebkurā laikā ar sava konta izdzēơanu. + changelog: 'Ć eit Ä«sumā ir aprakstÄ«ts, ko ĆĄis atjauninājums nozÄ«mē:' + description: 'Ć is e-pasta ziƆojums tika saƆemts, jo mēs veicam daĆŸas izmaiƆas savos pakalpojuma izmantoĆĄanas noteikumos %{domain}. Mēs aicinām pārskatÄ«t pilnus atjauninātos noteikumus ĆĄeit:' + description_html: Ć is e-pasta ziƆojums tika saƆemts, jo mēs veicam daĆŸas izmaiƆas savos pakalpojuma izmantoĆĄanas noteikumos %{domain}. Mēs aicinām pārskatÄ«t pilnus atjauninātos noteikumus ĆĄeit. + sign_off: "%{domain} komanda" + subject: MĆ«su pakalpojuma izmantoĆĄanas noteikumu atjauninājumi + subtitle: Mainās %{domain} pakalpojuma izmantoĆĄanas noteikumi + title: SvarÄ«gs atjauninājums warning: appeal: Iesniegt apelāciju appeal_description: Ja uzskatāt, ka tā ir kÄŒĆ«da, varat iesniegt apelāciju %{instance} darbiniekiem. diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 41fc9fbecc..bba71f165d 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -10,7 +10,7 @@ pt-PT: followers: one: Seguidor other: Seguidores - following: A seguir + following: Seguindo instance_actor_flash: Esta conta Ă© um ator virtual utilizado para representar o servidor em si e nĂŁo um utilizador individual. É utilizada para efeitos de federação e nĂŁo deve ser suspensa. last_active: Ășltima atividade link_verified_on: A posse desta hiperligação foi verificada em %{date} diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index 0c9ec51722..99bb1ccaa0 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -130,6 +130,17 @@ fo: show_application: ÓansĂŠĂ°, so er altĂ­Ă° mĂžguligt hjĂĄ tĂŠr at sĂ­ggja, hvĂžr app postaĂ°i tĂ­n post. tag: name: TĂș kanst einans broyta millum stĂłrar og smĂĄar stavir, til dĂžmis fyri at gera taĂ° meira lesiligt + terms_of_service: + changelog: Kunnu vera uppbygdar viĂ° Markdown syntaksi. + text: Kunnu vera uppbygdar viĂ° Markdown syntaksi. + terms_of_service_generator: + admin_email: LĂžgfrÞðisligar frĂĄboĂ°anir fata um rĂŠttarligar mĂłtfrĂĄboĂ°anir, ĂșrskurĂ°ir, ĂĄheitanir um at taka niĂ°ur og lĂžgfrÞðisligar ĂĄheitanir. + arbitration_address: Kann vera tann sami sum fysiski bĂșstaĂ°urin omanfyri ella "N/A", um teldupostur verĂ°ur brĂșktur + arbitration_website: Kann vera ein vevformularur ella "N/A", um teldupostur verĂ°ur brĂșktur + dmca_address: FyristÞðufĂłlk og -felĂžg Ă­ USA brĂșka bĂșstaĂ°in, iĂ° er skrĂĄsettur Ă­ DMCA Designated Agent Directory. Ein postsmoguskrĂĄseting er tĂžk, um biĂ°iĂ° verĂ°ur um hana beinleiĂ°is. BrĂșka DMCA Designated Agent Post Office Box Waiver Request fyri at senda teldubrĂŠv til Copyright Office og greiĂ° frĂĄ, at tĂș er ein kjakleiĂ°ari, sum virkar heimanifrĂĄ, og at tĂș er bangin fyri hevnd ella afturlĂžning fyri tĂ­, tĂș ger, og at tĂș hevur tĂžrv ĂĄ at brĂșka eina postsmogu fyri at fjala heimabĂșstaĂ°in fyri almenninginum. + dmca_email: Kann vera sami teldupoststaĂ°ur, sum er brĂșktur til "TeldupoststaĂ°ur fyri lĂžgfrÞðisligar frĂĄboĂ°anir" omanfyri + domain: Makaleys eyĂ°merking av nettĂŠnastuni, sum tĂș veitir. + jurisdiction: Lista landiĂ°, har sum tann, iĂ° rindar rokningarnar, livir. Er taĂ° eitt felag ella ein onnur eind, lista landiĂ°, har taĂ° er skrĂĄsett, umframt bĂœin, ĂžkiĂ°, umveldiĂ° ella statin, alt eftir hvat er hĂłskandi. user: chosen_languages: TĂĄ hetta er valt, verĂ°a einans postar Ă­ valdum mĂĄlum vĂ­stir ĂĄ almennum tĂ­Ă°arlinjum role: Leikluturin stĂœrir hvĂžrji rĂŠttindi, brĂșkarin hevur. @@ -319,6 +330,17 @@ fo: name: TvĂ­krossur trendable: Loyv hesum frĂĄmerki at sĂ­ggjast undir rĂĄkum usable: Loyv postum at brĂșka hetta frĂĄmerki lokalt + terms_of_service: + changelog: Hvat er broytt? + text: TĂŠnastutreytir + terms_of_service_generator: + admin_email: TeldupoststaĂ°ur fyri lĂžgfrÞðisligar frĂĄboĂ°anir + arbitration_address: Fysisk adressa fyri gerĂ°arrĂŠttarfrĂĄboĂ°anir + arbitration_website: HeimasĂ­Ă°a har gerĂ°arrĂŠttarfrĂĄboĂ°anir kunnu innlatast + dmca_address: Fysiskur bĂșstaĂ°ur fyri DMCA/copyright frĂĄboĂ°anir + dmca_email: TeldubĂșstaĂ°ur fyri DMCA/copyright frĂĄboĂ°anir + domain: NavnaĂžki + jurisdiction: LĂžgdĂžmi user: role: Leiklutur time_zone: TĂ­Ă°arsona diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 1b0469f7a3..62dd23f930 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -174,7 +174,7 @@ fr: admin_account_action: include_statuses: Inclure les messages signalĂ©s dans le courriel send_email_notification: Notifier l’utilisateur par courriel - text: Attention personnalisĂ©e + text: Avertissement personnalisĂ© type: Action types: disable: DĂ©sactiver diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 2f4a05dca4..7c7b11c1b3 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -53,7 +53,7 @@ lv: locale: Lietotāja saskarnes, e-pasta ziƆojumu un push paziƆojumu valoda password: Izmanto vismaz 8 rakstzÄ«mes phrase: Tiks saskaƆots neatkarÄ«gi no ziƆas teksta reÄŁistra vai satura brÄ«dinājuma - scopes: Kuriem API lietojumprogrammai bĆ«s atÄŒauta piekÄŒuve. Ja izvēlies augstākā lÄ«meƆa tvērumu, tev nav jāatlasa atseviĆĄÄ·i vienumi. + scopes: Kuriem API lietotnei bĆ«s ÄŒauts piekÄŒĆ«t. Ja atlasa augstākā lÄ«meƆa tvērumu, nav nepiecieĆĄamas atlasÄ«t atseviĆĄÄ·us. setting_aggregate_reblogs: NerādÄ«t jaunus izcēlumus ziƆām, kas nesen tika palielinātas (ietekmē tikai nesen saƆemtos palielinājumus) setting_always_send_emails: Parasti e-pasta paziƆojumi netiek sĆ«tÄ«ti, kad aktÄ«vi izmantojat Mastodon setting_default_sensitive: SensitÄ«va multivide pēc noklusējuma ir paslēpti, un tos var atklāt, noklikĆĄÄ·inot @@ -119,8 +119,8 @@ lv: sign_up_requires_approval: Jaunām reÄŁistrācijām bĆ«s nepiecieĆĄams tavs apstiprinājums severity: Izvēlies, kas notiks ar pieprasÄ«jumiem no ĆĄÄ«s IP adreses rule: - hint: Izvēles. Sniedz vairāk informācijas par nosacÄ«jumu - text: Apraksti nosacÄ«jumus vai prasÄ«bas ĆĄÄ« servera lietotājiem. Centies, lai tas bĆ«tu Ä«ss un vienkārĆĄs + hint: Izvēles. Sniedz vairāk informācijas par noteikumu + text: Jāapraksta nosacÄ«jums vai prasÄ«ba ĆĄÄ« servera lietotājiem. Jāmēģina to veidot Ä«su un vienkārĆĄu sessions: otp: 'Ievadi divfaktoru kodu, ko ÄŁenerējusi tava tālruƆa lietotne, vai izmanto kādu no atkopĆĄanas kodiem:' webauthn: Ja tā ir USB atslēga, noteikti ievieto to un, ja nepiecieĆĄams, pieskaries tai. @@ -317,6 +317,11 @@ lv: name: Tēmturis trendable: AtÄŒaut ĆĄim tēmturim parādÄ«ties zem tendencēm usable: Ä»aut ierakstos vietēji izmantot ĆĄo tēmturi + terms_of_service: + changelog: Kas ir mainÄ«jies? + text: Pakalpojuma izmantoĆĄanas nosacÄ«jumi + terms_of_service_generator: + domain: Domēna vārds user: role: Loma time_zone: Laika josla diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml index efc64d6dc6..f5a7e3a27e 100644 --- a/config/locales/simple_form.pt-PT.yml +++ b/config/locales/simple_form.pt-PT.yml @@ -161,7 +161,7 @@ pt-PT: fields: name: RĂłtulo value: ConteĂșdo - indexable: Incluir mensagens pĂșblicas nos resultados da pesquisa + indexable: Incluir mensagens pĂșblicas nos resultados de pesquisas show_collections: Mostrar quem sigo e os meus seguidores no perfil unlocked: Aceitar automaticamente novos seguidores account_alias: @@ -205,7 +205,7 @@ pt-PT: email: Endereço de correio electrĂłnico expires_in: Expira em fields: Metadados de perfil - header: Cabeçalho + header: Imagem de cabeçalho honeypot: "%{label} (nĂŁo preencher)" inbox_url: URL da caixa de entrada do repetidor irreversible: Expandir em vez de esconder diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index 0815170fcc..2d2c1c0060 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -130,6 +130,14 @@ sv: show_application: Du kommer alltid att kunna se vilken app som publicerat ditt inlĂ€gg oavsett. tag: name: Du kan bara Ă€ndra skriftlĂ€get av bokstĂ€verna, till exempel, för att göra det mer lĂ€sbart + terms_of_service: + changelog: Kan struktureras med Markdown syntax. + text: Kan struktureras med Markdown syntax. + terms_of_service_generator: + arbitration_address: Kan vara samma som fysisk adress ovan, eller “N/A” om du anvĂ€nder e-post + arbitration_website: Kan vara ett webbformulĂ€r, eller ”N/A” om du anvĂ€nder e-post + dmca_email: Kan vara samma e-postadress som anvĂ€nds för “E-postadress för juridiska meddelanden” ovan + jurisdiction: Lista det land dĂ€r vem som Ă€n betalar rĂ€kningarna bor. Om det Ă€r ett företag eller annan enhet, lista landet dĂ€r det Ă€r inkorporerat, och staden, regionen, territoriet eller staten pĂ„ lĂ€mpligt sĂ€tt. user: chosen_languages: Vid aktivering visas bara inlĂ€gg pĂ„ dina valda sprĂ„k i offentliga tidslinjer role: Rollen styr vilka behörigheter anvĂ€ndaren har. @@ -151,7 +159,7 @@ sv: name: Etikett value: InnehĂ„ll indexable: Inkludera offentliga inlĂ€gg i sökresultaten - show_collections: Göm följare och följeslagare pĂ„ profilen + show_collections: Visa följare och följeslagare pĂ„ profilen unlocked: GodkĂ€nn nya följare automatiskt account_alias: acct: Namnet pĂ„ det gamla kontot @@ -224,6 +232,7 @@ sv: setting_hide_network: Göm ditt nĂ€tverk setting_reduce_motion: Minska rörelser i animationer setting_system_font_ui: AnvĂ€nd systemets standardfont + setting_system_scrollbars_ui: AnvĂ€nd systemets standardrullningsfĂ€lt setting_theme: Sidans tema setting_trends: Visa dagens trender setting_unfollow_modal: Visa bekrĂ€ftelse innan du slutar följa nĂ„gon @@ -318,6 +327,14 @@ sv: name: Hashtagg trendable: TillĂ„t denna hashtagg att visas under trender usable: TillĂ„t inlĂ€gg att anvĂ€nda denna fyrkantstagg + terms_of_service: + changelog: Vad har Ă€ndrats? + text: AnvĂ€ndarvillkor + terms_of_service_generator: + admin_email: E-postadress för juridiska meddelanden + dmca_address: Fysisk adress för meddelanden om DMCA/upphovsrĂ€tt + dmca_email: Fysisk adress för meddelanden om DMCA/upphovsrĂ€tt + domain: DomĂ€n user: role: Roll time_zone: Tidszon diff --git a/config/mastodon.yml b/config/mastodon.yml index 2c09c59e0a..feaf8e20d8 100644 --- a/config/mastodon.yml +++ b/config/mastodon.yml @@ -2,3 +2,6 @@ shared: self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %> software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check') %> + version: + metadata: <%= ['glitch', ENV.fetch('MASTODON_VERSION_METADATA', nil)].compact_blank.join('.') %> + prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %> diff --git a/config/routes.rb b/config/routes.rb index 3909dd1b77..5adec04c7d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -55,7 +55,8 @@ Rails.application.routes.draw do get 'manifest', to: 'manifests#show', defaults: { format: 'json' } get 'intent', to: 'intents#show' - get 'custom.css', to: 'custom_css#show', as: :custom_css + get 'custom.css', to: 'custom_css#show' + resources :custom_css, only: :show, path: :css get 'remote_interaction_helper', to: 'remote_interaction_helper#index' diff --git a/docker-compose.yml b/docker-compose.yml index 6048129318..63f17bf495 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,7 +100,8 @@ services: - redis sidekiq: - build: . + # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes + # build: . image: ghcr.io/mastodon/mastodon:v4.3.2 restart: always env_file: .env.production diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000000..39f57dd402 --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,75 @@ +# Development + +## Overview + +Before starting local development, read the [CONTRIBUTING] guide to understand +what changes are desirable and what general processes to use. + +## Environments + +### Vagrant + +A **Vagrant** configuration is included for development purposes. To use it, +complete the following steps: + +- Install Vagrant and Virtualbox +- Install the `vagrant-hostsupdater` plugin: + `vagrant plugin install vagrant-hostsupdater` +- Run `vagrant up` +- Run `vagrant ssh -c "cd /vagrant && bin/dev"` +- Open `http://mastodon.local` in your browser + +### macOS + +To set up **macOS** for native development, complete the following steps: + +- Install [Homebrew] and run: + `brew install postgresql@14 redis imagemagick libidn nvm` + to install the required project dependencies +- Use a Ruby version manager to activate the ruby in `.ruby-version` and run + `nvm use` to activate the node version from `.nvmrc` +- Run the `bin/setup` script, which will install the required ruby gems and node + packages and prepare the database for local development +- Finally, run the `bin/dev` script which will launch services via `overmind` + (if installed) or `foreman` + +### Docker + +For production hosting and deployment with **Docker**, use the `Dockerfile` and +`docker-compose.yml` in the project root directory. + +For local development, install and launch [Docker], and run: + +```shell +docker compose -f .devcontainer/compose.yaml up -d +docker compose -f .devcontainer/compose.yaml exec app bin/setup +docker compose -f .devcontainer/compose.yaml exec app bin/dev +``` + +### Dev Containers + +Within IDEs that support the [Development Containers] specification, start the +"Mastodon on local machine" container from the editor. The necessary `docker +compose` commands to build and setup the container should run automatically. For +**Visual Studio Code** this requires installing the [Dev Container extension]. + +### GitHub Codespaces + +[GitHub Codespaces] provides a web-based version of VS Code and a cloud hosted +development environment configured with the software needed for this project. + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)][codespace] + +- Click the button to create a new codespace, and confirm the options +- Wait for the environment to build (takes a few minutes) +- When the editor is ready, run `bin/dev` in the terminal +- Wait for an _Open in Browser_ prompt. This will open Mastodon +- On the _Ports_ tab "stream" setting change _Port visibility_ → _Public_ + +[codespace]: https://codespaces.new/mastodon/mastodon?quickstart=1&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json +[CONTRIBUTING]: CONTRIBUTING.md +[Dev Container extension]: https://containers.dev/supporting#dev-containers +[Development Containers]: https://containers.dev/supporting +[Docker]: https://docs.docker.com +[GitHub Codespaces]: https://docs.github.com/en/codespaces +[Homebrew]: https://brew.sh diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb index 532fbc328a..32ee35c7c7 100644 --- a/lib/mastodon/cli/maintenance.rb +++ b/lib/mastodon/cli/maintenance.rb @@ -192,6 +192,7 @@ module Mastodon::CLI verify_schema_version! verify_sidekiq_not_active! verify_backup_warning! + disable_timeout! end def process_deduplications @@ -251,6 +252,13 @@ module Mastodon::CLI fail_with_message 'Maintenance process stopped.' unless yes?('Continue? (Yes/No)') end + def disable_timeout! + # Remove server-configured timeout if present + database_connection.execute(<<~SQL.squish) + SET statement_timeout = 0 + SQL + end + def deduplicate_accounts! remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower') diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index a16b3d550f..1827714122 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,11 +21,11 @@ module Mastodon end def prerelease - ENV['MASTODON_VERSION_PRERELEASE'].presence || default_prerelease + version_configuration[:prerelease].presence || default_prerelease end def build_metadata - ['glitch', ENV.fetch('MASTODON_VERSION_METADATA', nil)].compact_blank.join('.') + version_configuration[:metadata] end def to_a @@ -77,5 +77,9 @@ module Mastodon def user_agent @user_agent ||= "Mastodon/#{Version} (#{HTTP::Request::USER_AGENT}; +http#{Rails.configuration.x.use_https ? 's' : ''}://#{Rails.configuration.x.web_domain}/)" end + + def version_configuration + Rails.configuration.x.mastodon.version + end end end diff --git a/package.json b/package.json index 7da0cc9908..51ecc8a317 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "react-hotkeys": "^1.1.4", "react-immutable-proptypes": "^2.2.0", "react-immutable-pure-component": "^2.2.2", - "react-intl": "^6.4.2", + "react-intl": "^7.0.0", "react-motion": "^0.5.2", "react-notification": "^6.8.5", "react-overlays": "^5.2.1", @@ -201,6 +201,8 @@ "webpack-dev-server": "^3.11.3" }, "resolutions": { + "@types/react": "^18.2.7", + "@types/react-dom": "^18.2.4", "kind-of": "^6.0.3", "webpack/terser-webpack-plugin": "^4.2.3" }, diff --git a/spec/controllers/admin/follow_recommendations_controller_spec.rb b/spec/controllers/admin/follow_recommendations_controller_spec.rb deleted file mode 100644 index 82446cd467..0000000000 --- a/spec/controllers/admin/follow_recommendations_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::FollowRecommendationsController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #show' do - it 'returns http success' do - get :show - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/controllers/admin/terms_of_service/histories_controller_spec.rb b/spec/controllers/admin/terms_of_service/histories_controller_spec.rb deleted file mode 100644 index 8c2c3a3de3..0000000000 --- a/spec/controllers/admin/terms_of_service/histories_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::TermsOfService::HistoriesController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #show' do - it 'returns http success' do - get :show - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 52d92a2b6c..1678005871 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' RSpec.describe ApplicationController do + render_views + controller do def success head 200 @@ -23,9 +25,22 @@ RSpec.describe ApplicationController do shared_examples 'respond_with_error' do |code| it "returns http #{code} for http and renders template" do - expect(subject).to render_template("errors/#{code}", layout: 'error') + subject - expect(response).to have_http_status(code) + expect(response) + .to have_http_status(code) + expect(response.parsed_body) + .to have_css('body[class=error]') + expect(response.parsed_body.css('h1').to_s) + .to include(error_content(code)) + end + + def error_content(code) + if code == 422 + I18n.t('errors.422.content') + else + I18n.t("errors.#{code}") + end end end diff --git a/spec/controllers/settings/migrations_controller_spec.rb b/spec/controllers/settings/migrations_controller_spec.rb deleted file mode 100644 index dca4c925fd..0000000000 --- a/spec/controllers/settings/migrations_controller_spec.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Settings::MigrationsController do - render_views - - describe 'GET #show' do - context 'when user is not sign in' do - subject { get :show } - - it { is_expected.to redirect_to new_user_session_path } - end - - context 'when user is sign in' do - subject { get :show } - - let(:user) { Fabricate(:account, moved_to_account: moved_to_account).user } - - before { sign_in user, scope: :user } - - context 'when user does not have moved to account' do - let(:moved_to_account) { nil } - - it 'renders show page' do - expect(subject).to have_http_status 200 - expect(subject).to render_template :show - end - end - - context 'when user has a moved to account' do - let(:moved_to_account) { Fabricate(:account) } - - it 'renders show page' do - expect(subject).to have_http_status 200 - expect(subject).to render_template :show - end - end - end - end - - describe 'POST #create' do - context 'when user is not sign in' do - subject { post :create } - - it { is_expected.to redirect_to new_user_session_path } - end - - context 'when user is signed in' do - subject { post :create, params: { account_migration: { acct: acct, current_password: '12345678' } } } - - let(:user) { Fabricate(:user, password: '12345678') } - - before { sign_in user, scope: :user } - - context 'when migration account is changed' do - let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) } - - it 'updates moved to account' do - expect(subject).to redirect_to settings_migration_path - expect(user.account.reload.moved_to_account_id).to eq acct.id - end - end - - context 'when acct is the current account' do - let(:acct) { user.account } - - it 'does not update the moved account', :aggregate_failures do - subject - - expect(user.account.reload.moved_to_account_id).to be_nil - expect(response).to render_template :show - end - end - - context 'when target account does not reference the account being moved from' do - let(:acct) { Fabricate(:account, also_known_as: []) } - - it 'does not update the moved account', :aggregate_failures do - subject - - expect(user.account.reload.moved_to_account_id).to be_nil - expect(response).to render_template :show - end - end - - context 'when a recent migration already exists' do - let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) } - - before do - moved_to = Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) - user.account.migrations.create!(acct: moved_to.acct) - end - - it 'does not update the moved account', :aggregate_failures do - subject - - expect(user.account.reload.moved_to_account_id).to be_nil - expect(response).to render_template :show - end - end - end - end -end diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb index 83a68f4739..f9dfd4d48e 100644 --- a/spec/helpers/theme_helper_spec.rb +++ b/spec/helpers/theme_helper_spec.rb @@ -79,6 +79,26 @@ RSpec.describe ThemeHelper do end end + describe '#custom_stylesheet' do + context 'when custom css setting value digest is present' do + before { Rails.cache.write(:setting_digest_custom_css, '1a2s3d4f1a2s3d4f') } + + it 'returns value from settings' do + expect(custom_stylesheet) + .to match('/css/custom-1a2s3d4f.css') + end + end + + context 'when custom css setting value digest is not present' do + before { Rails.cache.delete(:setting_digest_custom_css) } + + it 'returns default value' do + expect(custom_stylesheet) + .to be_blank + end + end + end + private def html_links diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index a98e6896ca..ad4cb9997d 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -162,12 +162,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when object publication date is below ISO8601 range' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - published: '-0977-11-03T08:31:22Z', - } + build_object( + published: '-0977-11-03T08:31:22Z' + ) end it 'creates status with a valid creation date', :aggregate_failures do @@ -184,12 +181,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when object publication date is above ISO8601 range' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - published: '10000-11-03T08:31:22Z', - } + build_object( + published: '10000-11-03T08:31:22Z' + ) end it 'creates status with a valid creation date', :aggregate_failures do @@ -206,13 +200,10 @@ RSpec.describe ActivityPub::Activity::Create do context 'when object has been edited' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( published: '2022-01-22T15:00:00Z', - updated: '2022-01-22T16:00:00Z', - } + updated: '2022-01-22T16:00:00Z' + ) end it 'creates status with appropriate creation and edition dates', :aggregate_failures do @@ -232,13 +223,10 @@ RSpec.describe ActivityPub::Activity::Create do context 'when object has update date equal to creation date' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( published: '2022-01-22T15:00:00Z', - updated: '2022-01-22T15:00:00Z', - } + updated: '2022-01-22T15:00:00Z' + ) end it 'creates status and does not mark it as edited' do @@ -254,11 +242,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'with an unknown object type' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Banana', - content: 'Lorem ipsum', - } + build_object( + type: 'Banana' + ) end it 'does not create a status' do @@ -267,13 +253,7 @@ RSpec.describe ActivityPub::Activity::Create do end context 'with a standalone' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - } - end + let(:object_json) { build_object } it 'creates status' do expect { subject.perform }.to change(sender.statuses, :count).by(1) @@ -296,12 +276,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when public with explicit public address' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'https://www.w3.org/ns/activitystreams#Public', - } + build_object( + to: 'https://www.w3.org/ns/activitystreams#Public' + ) end it 'creates status' do @@ -316,12 +293,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when public with as:Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'as:Public', - } + build_object( + to: 'as:Public' + ) end it 'creates status' do @@ -336,12 +310,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when public with Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'Public', - } + build_object( + to: 'Public' + ) end it 'creates status' do @@ -356,12 +327,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when unlisted with explicit public address' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: 'https://www.w3.org/ns/activitystreams#Public', - } + build_object( + cc: 'https://www.w3.org/ns/activitystreams#Public' + ) end it 'creates status' do @@ -376,12 +344,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when unlisted with as:Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: 'as:Public', - } + build_object( + cc: 'as:Public' + ) end it 'creates status' do @@ -396,12 +361,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when unlisted with Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: 'Public', - } + build_object( + cc: 'Public' + ) end it 'creates status' do @@ -416,12 +378,9 @@ RSpec.describe ActivityPub::Activity::Create do context 'when private' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'http://example.com/followers', - } + build_object( + to: 'http://example.com/followers' + ) end it 'creates status' do @@ -436,16 +395,13 @@ RSpec.describe ActivityPub::Activity::Create do context 'when private with inlined Collection in audience' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( to: { type: 'OrderedCollection', id: 'http://example.com/followers', first: 'http://example.com/followers?page=true', - }, - } + } + ) end it 'creates status' do @@ -462,12 +418,9 @@ RSpec.describe ActivityPub::Activity::Create do let(:recipient) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: ActivityPub::TagManager.instance.uri_for(recipient), - } + build_object( + to: ActivityPub::TagManager.instance.uri_for(recipient) + ) end it 'creates status with a silent mention' do @@ -512,16 +465,13 @@ RSpec.describe ActivityPub::Activity::Create do let(:recipient) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( to: ActivityPub::TagManager.instance.uri_for(recipient), tag: { type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(recipient), - }, - } + } + ) end it 'creates status with direct visibility' do @@ -561,12 +511,9 @@ RSpec.describe ActivityPub::Activity::Create do let(:original_status) { Fabricate(:status) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status), - } + build_object( + inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status) + ) end it 'creates status' do @@ -586,17 +533,14 @@ RSpec.describe ActivityPub::Activity::Create do let(:recipient) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(recipient), }, - ], - } + ] + ) end it 'creates status' do @@ -611,16 +555,13 @@ RSpec.describe ActivityPub::Activity::Create do context 'with mentions missing href' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Mention', }, - ], - } + ] + ) end it 'creates status' do @@ -633,10 +574,7 @@ RSpec.describe ActivityPub::Activity::Create do context 'with media attachments' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', @@ -648,8 +586,8 @@ RSpec.describe ActivityPub::Activity::Create do mediaType: 'image/png', url: 'http://example.com/emoji.png', }, - ], - } + ] + ) end it 'creates status with correctly-ordered media attachments' do @@ -665,19 +603,16 @@ RSpec.describe ActivityPub::Activity::Create do context 'with media attachments with long description' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', mediaType: 'image/png', url: 'http://example.com/attachment.png', - name: '*' * 1500, + name: '*' * MediaAttachment::MAX_DESCRIPTION_LENGTH, }, - ], - } + ] + ) end it 'creates status' do @@ -686,25 +621,22 @@ RSpec.describe ActivityPub::Activity::Create do status = sender.statuses.first expect(status).to_not be_nil - expect(status.media_attachments.map(&:description)).to include('*' * 1500) + expect(status.media_attachments.map(&:description)).to include('*' * MediaAttachment::MAX_DESCRIPTION_LENGTH) end end context 'with media attachments with long description as summary' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', mediaType: 'image/png', url: 'http://example.com/attachment.png', - summary: '*' * 1500, + summary: '*' * MediaAttachment::MAX_DESCRIPTION_LENGTH, }, - ], - } + ] + ) end it 'creates status' do @@ -713,16 +645,13 @@ RSpec.describe ActivityPub::Activity::Create do status = sender.statuses.first expect(status).to_not be_nil - expect(status.media_attachments.map(&:description)).to include('*' * 1500) + expect(status.media_attachments.map(&:description)).to include('*' * MediaAttachment::MAX_DESCRIPTION_LENGTH) end end context 'with media attachments with focal points' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', @@ -730,8 +659,8 @@ RSpec.describe ActivityPub::Activity::Create do url: 'http://example.com/attachment.png', focalPoint: [0.5, -0.7], }, - ], - } + ] + ) end it 'creates status' do @@ -746,17 +675,14 @@ RSpec.describe ActivityPub::Activity::Create do context 'with media attachments missing url' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', mediaType: 'image/png', }, - ], - } + ] + ) end it 'creates status' do @@ -769,18 +695,15 @@ RSpec.describe ActivityPub::Activity::Create do context 'with hashtags' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Hashtag', href: 'http://example.com/blah', name: '#test', }, - ], - } + ] + ) end it 'creates status' do @@ -795,10 +718,7 @@ RSpec.describe ActivityPub::Activity::Create do context 'with featured hashtags' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( to: 'https://www.w3.org/ns/activitystreams#Public', tag: [ { @@ -806,8 +726,8 @@ RSpec.describe ActivityPub::Activity::Create do href: 'http://example.com/blah', name: '#test', }, - ], - } + ] + ) end before do @@ -829,17 +749,14 @@ RSpec.describe ActivityPub::Activity::Create do context 'with hashtags missing name' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Hashtag', href: 'http://example.com/blah', }, - ], - } + ] + ) end it 'creates status' do @@ -852,18 +769,15 @@ RSpec.describe ActivityPub::Activity::Create do context 'with hashtags invalid name' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Hashtag', href: 'http://example.com/blah', name: 'foo, #eh !', }, - ], - } + ] + ) end it 'creates status' do @@ -876,9 +790,7 @@ RSpec.describe ActivityPub::Activity::Create do context 'with emojis' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinking:', tag: [ { @@ -888,8 +800,8 @@ RSpec.describe ActivityPub::Activity::Create do }, name: 'tinking', }, - ], - } + ] + ) end it 'creates status' do @@ -904,9 +816,7 @@ RSpec.describe ActivityPub::Activity::Create do context 'with emojis served with invalid content-type' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinkong:', tag: [ { @@ -916,8 +826,8 @@ RSpec.describe ActivityPub::Activity::Create do }, name: 'tinkong', }, - ], - } + ] + ) end it 'creates status' do @@ -932,9 +842,7 @@ RSpec.describe ActivityPub::Activity::Create do context 'with emojis missing name' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinking:', tag: [ { @@ -943,8 +851,8 @@ RSpec.describe ActivityPub::Activity::Create do url: 'http://example.com/emoji.png', }, }, - ], - } + ] + ) end it 'creates status' do @@ -957,17 +865,15 @@ RSpec.describe ActivityPub::Activity::Create do context 'with emojis missing icon' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinking:', tag: [ { type: 'Emoji', name: 'tinking', }, - ], - } + ] + ) end it 'creates status' do @@ -980,8 +886,7 @@ RSpec.describe ActivityPub::Activity::Create do context 'with poll' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + build_object( type: 'Question', content: 'Which color was the submarine?', oneOf: [ @@ -999,8 +904,8 @@ RSpec.describe ActivityPub::Activity::Create do totalItems: 3, }, }, - ], - } + ] + ) end it 'creates status with a poll' do @@ -1023,12 +928,10 @@ RSpec.describe ActivityPub::Activity::Create do let!(:local_status) { Fabricate(:status, poll: poll) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( name: 'Yellow', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), - } + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + ).except(:content) end it 'adds a vote to the poll with correct uri' do @@ -1050,12 +953,10 @@ RSpec.describe ActivityPub::Activity::Create do let!(:local_status) { Fabricate(:status, poll: poll) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( name: 'Yellow', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), - } + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + ).except(:content) end it 'does not add a vote to the poll' do @@ -1067,10 +968,7 @@ RSpec.describe ActivityPub::Activity::Create do context 'with counts' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( likes: { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/likes'].join, type: 'Collection', @@ -1080,8 +978,8 @@ RSpec.describe ActivityPub::Activity::Create do id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/shares'].join, type: 'Collection', totalItems: 100, - }, - } + } + ) end it 'uses the counts from the created object' do @@ -1110,12 +1008,9 @@ RSpec.describe ActivityPub::Activity::Create do end let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'https://www.w3.org/ns/activitystreams#Public', - } + build_object( + to: 'https://www.w3.org/ns/activitystreams#Public' + ) end before do @@ -1145,13 +1040,7 @@ RSpec.describe ActivityPub::Activity::Create do subject.perform end - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - } - end + let(:object_json) { build_object } it 'creates status' do status = sender.statuses.first @@ -1166,12 +1055,9 @@ RSpec.describe ActivityPub::Activity::Create do let!(:local_status) { Fabricate(:status) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), - } + build_object( + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + ) end before do @@ -1190,13 +1076,11 @@ RSpec.describe ActivityPub::Activity::Create do subject { described_class.new(json, sender, delivery: true) } let!(:local_account) { Fabricate(:account) } + let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: ActivityPub::TagManager.instance.uri_for(local_account), - } + build_object( + to: ActivityPub::TagManager.instance.uri_for(local_account) + ) end before do @@ -1216,12 +1100,9 @@ RSpec.describe ActivityPub::Activity::Create do let!(:local_account) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: ActivityPub::TagManager.instance.uri_for(local_account), - } + build_object( + cc: ActivityPub::TagManager.instance.uri_for(local_account) + ) end before do @@ -1243,17 +1124,19 @@ RSpec.describe ActivityPub::Activity::Create do subject.perform end - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - } - end + let(:object_json) { build_object } it 'does not create anything' do expect(sender.statuses.count).to eq 0 end end + + def build_object(options = {}) + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + }.merge(options) + end end end diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb index 6a15677f43..3e8eb9c360 100644 --- a/spec/lib/mastodon/cli/maintenance_spec.rb +++ b/spec/lib/mastodon/cli/maintenance_spec.rb @@ -89,10 +89,8 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower - _remote_account = Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain) - _remote_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false) - _local_account = Fabricate(:account, username: duplicate_account_username, domain: nil) - _local_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: nil).save(validate: false) + duplicate_record(:account, username: duplicate_account_username, domain: duplicate_account_domain) + duplicate_record(:account, username: duplicate_account_username, domain: nil) end def choose_local_account_to_keep @@ -127,8 +125,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :users, :email - Fabricate(:user, email: duplicate_email) - Fabricate.build(:user, email: duplicate_email).save(validate: false) + duplicate_record(:user, email: duplicate_email) end end @@ -156,8 +153,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :users, :confirmation_token - Fabricate(:user, confirmation_token: duplicate_confirmation_token) - Fabricate.build(:user, confirmation_token: duplicate_confirmation_token).save(validate: false) + duplicate_record(:user, confirmation_token: duplicate_confirmation_token) end end @@ -185,8 +181,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :users, :reset_password_token - Fabricate(:user, reset_password_token: duplicate_reset_password_token) - Fabricate.build(:user, reset_password_token: duplicate_reset_password_token).save(validate: false) + duplicate_record(:user, reset_password_token: duplicate_reset_password_token) end end @@ -214,8 +209,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :account_domain_blocks, [:account_id, :domain] - Fabricate(:account_domain_block, account: account, domain: duplicate_domain) - Fabricate.build(:account_domain_block, account: account, domain: duplicate_domain).save(validate: false) + duplicate_record(:account_domain_block, account: account, domain: duplicate_domain) end end @@ -244,8 +238,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :announcement_reactions, [:account_id, :announcement_id, :name] - Fabricate(:announcement_reaction, account: account, announcement: announcement, name: name) - Fabricate.build(:announcement_reaction, account: account, announcement: announcement, name: name).save(validate: false) + duplicate_record(:announcement_reaction, account: account, announcement: announcement, name: name) end end @@ -272,8 +265,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :conversations, :uri - Fabricate(:conversation, uri: uri) - Fabricate.build(:conversation, uri: uri).save(validate: false) + duplicate_record(:conversation, uri: uri) end end @@ -301,8 +293,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :custom_emojis, [:shortcode, :domain] - Fabricate(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain) - Fabricate.build(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain).save(validate: false) + duplicate_record(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain) end end @@ -329,8 +320,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :custom_emoji_categories, :name - Fabricate(:custom_emoji_category, name: duplicate_name) - Fabricate.build(:custom_emoji_category, name: duplicate_name).save(validate: false) + duplicate_record(:custom_emoji_category, name: duplicate_name) end end @@ -357,8 +347,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :domain_allows, :domain - Fabricate(:domain_allow, domain: domain) - Fabricate.build(:domain_allow, domain: domain).save(validate: false) + duplicate_record(:domain_allow, domain: domain) end end @@ -385,8 +374,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :domain_blocks, :domain - Fabricate(:domain_block, domain: domain) - Fabricate.build(:domain_block, domain: domain).save(validate: false) + duplicate_record(:domain_block, domain: domain) end end @@ -413,8 +401,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :email_domain_blocks, :domain - Fabricate(:email_domain_block, domain: domain) - Fabricate.build(:email_domain_block, domain: domain).save(validate: false) + duplicate_record(:email_domain_block, domain: domain) end end @@ -441,8 +428,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :media_attachments, :shortcode - Fabricate(:media_attachment, shortcode: shortcode) - Fabricate.build(:media_attachment, shortcode: shortcode).save(validate: false) + duplicate_record(:media_attachment, shortcode: shortcode) end end @@ -469,8 +455,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :preview_cards, :url - Fabricate(:preview_card, url: url) - Fabricate.build(:preview_card, url: url).save(validate: false) + duplicate_record(:preview_card, url: url) end end @@ -530,8 +515,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :tags, name: 'index_tags_on_name_lower_btree' - Fabricate(:tag, name: name) - Fabricate.build(:tag, name: name).save(validate: false) + duplicate_record(:tag, name: name) end end @@ -558,8 +542,7 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :webauthn_credentials, :external_id - Fabricate(:webauthn_credential, external_id: external_id) - Fabricate.build(:webauthn_credential, external_id: external_id).save(validate: false) + duplicate_record(:webauthn_credential, external_id: external_id) end end @@ -586,11 +569,15 @@ RSpec.describe Mastodon::CLI::Maintenance do def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :webhooks, :url - Fabricate(:webhook, url: url) - Fabricate.build(:webhook, url: url).save(validate: false) + duplicate_record(:webhook, url: url) end end + def duplicate_record(fabricator, options = {}) + Fabricate(fabricator, options) + Fabricate.build(fabricator, options).save(validate: false) + end + def agree_to_backup_warning allow(cli.shell) .to receive(:yes?) diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index c600a48ee2..f17cf637b9 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -4,7 +4,9 @@ require 'rails_helper' require 'securerandom' RSpec.describe Request do - subject { described_class.new(:get, 'http://example.com') } + subject { described_class.new(:get, 'http://example.com', **options) } + + let(:options) { {} } describe '#headers' do it 'returns user agent' do @@ -39,8 +41,8 @@ RSpec.describe Request do end describe '#perform' do - context 'with valid host' do - before { stub_request(:get, 'http://example.com') } + context 'with valid host and non-persistent connection' do + before { stub_request(:get, 'http://example.com').to_return(body: 'lorem ipsum') } it 'executes a HTTP request' do expect { |block| subject.perform(&block) }.to yield_control @@ -71,9 +73,9 @@ RSpec.describe Request do expect(subject.send(:http_client)).to have_received(:close) end - it 'returns response which implements body_with_limit' do + it 'yields response' do subject.perform do |response| - expect(response).to respond_to :body_with_limit + expect(response.body_with_limit).to eq 'lorem ipsum' end end end @@ -95,6 +97,43 @@ RSpec.describe Request do expect { subject.perform }.to raise_error Mastodon::ValidationError end end + + context 'with persistent connection' do + before { stub_request(:get, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes)) } + + let(:http_client) { described_class.http_client.persistent('http://example.com') } + let(:options) { { http_client: http_client } } + + it 'leaves connection open after completely consumed response' do + allow(http_client).to receive(:close) + + subject.perform { |response| response.truncated_body(3.megabytes) } + + expect(http_client).to_not have_received(:close) + end + + it 'leaves connection open after nearly consumed response' do + allow(http_client).to receive(:close) + + subject.perform { |response| response.truncated_body(1.8.megabytes) } + + expect(http_client).to_not have_received(:close) + end + + it 'closes connection after unconsumed response' do + allow(http_client).to receive(:close) + + subject.perform + + expect(http_client).to have_received(:close) + end + + it 'yields response' do + subject.perform do |response| + expect(response.body_with_limit(2.megabytes).size).to eq 2.megabytes + end + end + end end describe "response's body_with_limit method" do diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 967809b8e1..5b995b4af6 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -3,6 +3,7 @@ require 'rails_helper' RSpec.describe Account do + include_examples 'Account::Search' include_examples 'Reviewable' context 'with an account record' do @@ -48,14 +49,48 @@ RSpec.describe Account do end describe '#local?' do - it 'returns true when the account is local' do + it 'returns true when domain is null' do account = Fabricate(:account, domain: nil) - expect(account.local?).to be true + expect(account).to be_local end - it 'returns false when the account is on a different domain' do + it 'returns false when domain is present' do account = Fabricate(:account, domain: 'foreign.tld') - expect(account.local?).to be false + expect(account).to_not be_local + end + end + + describe '#remote?' do + context 'when the domain is null' do + subject { Fabricate.build :account, domain: nil } + + it { is_expected.to_not be_remote } + end + + context 'when the domain is blank' do + subject { Fabricate.build :account, domain: '' } + + it { is_expected.to_not be_remote } + end + + context 'when the domain is present' do + subject { Fabricate.build :account, domain: 'host.example' } + + it { is_expected.to be_remote } + end + end + + describe '#actor_type_application?' do + context 'when the actor is not of type application' do + subject { Fabricate.build :account, actor_type: 'Person' } + + it { is_expected.to_not be_actor_type_application } + end + + context 'when the actor is of type application' do + subject { Fabricate.build :account, actor_type: 'Application' } + + it { is_expected.to be_actor_type_application } end end @@ -324,271 +359,6 @@ RSpec.describe Account do end end - describe '.search_for' do - before do - _missing = Fabricate( - :account, - display_name: 'Missing', - username: 'missing', - domain: 'missing.com' - ) - end - - it 'does not return suspended users' do - Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com', - suspended: true - ) - - results = described_class.search_for('username') - expect(results).to eq [] - end - - it 'does not return unapproved users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(approved: false) - - results = described_class.search_for('username') - expect(results).to eq [] - end - - it 'does not return unconfirmed users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(confirmed_at: nil) - - results = described_class.search_for('username') - expect(results).to eq [] - end - - it 'accepts ?, \, : and space as delimiter' do - match = Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('A?l\i:c e') - expect(results).to eq [match] - end - - it 'finds accounts with matching display_name' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('display') - expect(results).to eq [match] - end - - it 'finds accounts with matching username' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('username') - expect(results).to eq [match] - end - - it 'finds accounts with matching domain' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('example') - expect(results).to eq [match] - end - - it 'limits via constant by default' do - stub_const('Account::Search::DEFAULT_LIMIT', 1) - 2.times.each { Fabricate(:account, display_name: 'Display Name') } - results = described_class.search_for('display') - expect(results.size).to eq 1 - end - - it 'accepts arbitrary limits' do - 2.times.each { Fabricate(:account, display_name: 'Display Name') } - results = described_class.search_for('display', limit: 1) - expect(results.size).to eq 1 - end - - it 'ranks multiple matches higher' do - matches = [ - { username: 'username', display_name: 'username' }, - { display_name: 'Display Name', username: 'username', domain: 'example.com' }, - ].map(&method(:Fabricate).curry(2).call(:account)) - - results = described_class.search_for('username') - expect(results).to eq matches - end - end - - describe '.advanced_search_for' do - let(:account) { Fabricate(:account) } - - context 'when limiting search to followed accounts' do - it 'accepts ?, \, : and space as delimiter' do - match = Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - account.follow!(match) - - results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) - expect(results).to eq [match] - end - - it 'does not return non-followed accounts' do - Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - - results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) - expect(results).to eq [] - end - - it 'does not return suspended users' do - Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com', - suspended: true - ) - - results = described_class.advanced_search_for('username', account, limit: 10, following: true) - expect(results).to eq [] - end - - it 'does not return unapproved users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(approved: false) - - results = described_class.advanced_search_for('username', account, limit: 10, following: true) - expect(results).to eq [] - end - - it 'does not return unconfirmed users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(confirmed_at: nil) - - results = described_class.advanced_search_for('username', account, limit: 10, following: true) - expect(results).to eq [] - end - end - - it 'does not return suspended users' do - Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com', - suspended: true - ) - - results = described_class.advanced_search_for('username', account) - expect(results).to eq [] - end - - it 'does not return unapproved users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(approved: false) - - results = described_class.advanced_search_for('username', account) - expect(results).to eq [] - end - - it 'does not return unconfirmed users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(confirmed_at: nil) - - results = described_class.advanced_search_for('username', account) - expect(results).to eq [] - end - - it 'accepts ?, \, : and space as delimiter' do - match = Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - - results = described_class.advanced_search_for('A?l\i:c e', account) - expect(results).to eq [match] - end - - it 'limits result count by default value' do - stub_const('Account::Search::DEFAULT_LIMIT', 1) - 2.times { Fabricate(:account, display_name: 'Display Name') } - results = described_class.advanced_search_for('display', account) - expect(results.size).to eq 1 - end - - it 'accepts arbitrary limits' do - 2.times { Fabricate(:account, display_name: 'Display Name') } - results = described_class.advanced_search_for('display', account, limit: 1) - expect(results.size).to eq 1 - end - - it 'ranks followed accounts higher' do - match = Fabricate(:account, username: 'Matching') - followed_match = Fabricate(:account, username: 'Matcher') - Fabricate(:follow, account: account, target_account: followed_match) - - results = described_class.advanced_search_for('match', account) - expect(results).to eq [followed_match, match] - expect(results.first.rank).to be > results.last.rank - end - end - describe '#statuses_count' do subject { Fabricate(:account) } @@ -824,8 +594,12 @@ RSpec.describe Account do it { is_expected.to_not allow_values(account_note_over_limit).for(:note) } it { is_expected.to allow_value(fields_empty_name_value).for(:fields) } - it { is_expected.to_not allow_value(fields_over_limit).for(:fields) } - it { is_expected.to_not allow_value(fields_empty_name).for(:fields) } + it { is_expected.to_not allow_values(fields_over_limit, fields_empty_name).for(:fields) } + + it { is_expected.to validate_absence_of(:followers_url).on(:create) } + it { is_expected.to validate_absence_of(:inbox_url).on(:create) } + it { is_expected.to validate_absence_of(:shared_inbox_url).on(:create) } + it { is_expected.to validate_absence_of(:uri).on(:create) } end context 'when account is remote' do diff --git a/spec/models/account_warning_spec.rb b/spec/models/account_warning_spec.rb index 37866ce3da..9fe2b331eb 100644 --- a/spec/models/account_warning_spec.rb +++ b/spec/models/account_warning_spec.rb @@ -8,4 +8,18 @@ RSpec.describe AccountWarning do it { is_expected.to normalize(:text).from(nil).to('') } end end + + describe '#appeal_eligible?' do + context 'when created too long ago' do + subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW * 2).ago } + + it { is_expected.to_not be_appeal_eligible } + end + + context 'when created recently' do + subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW - 2.days).ago } + + it { is_expected.to be_appeal_eligible } + end + end end diff --git a/spec/models/appeal_spec.rb b/spec/models/appeal_spec.rb index e974ff9254..d624e14949 100644 --- a/spec/models/appeal_spec.rb +++ b/spec/models/appeal_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Appeal do it { is_expected.to validate_length_of(:text).is_at_most(described_class::TEXT_LENGTH_LIMIT) } context 'with a strike created too long ago' do - let(:strike) { Fabricate.build :account_warning, created_at: 100.days.ago } + let(:strike) { Fabricate.build :account_warning, created_at: (AccountWarning::APPEAL_WINDOW * 2).ago } it { is_expected.to_not allow_values(strike).for(:strike).against(:base).on(:create) } end diff --git a/spec/models/doorkeeper/application_spec.rb b/spec/models/doorkeeper/application_spec.rb new file mode 100644 index 0000000000..e026d90caa --- /dev/null +++ b/spec/models/doorkeeper/application_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Doorkeeper::Application do + describe 'Associations' do + it { is_expected.to have_many(:created_users).class_name('User').inverse_of(:created_by_application).with_foreign_key(:created_by_application_id) } + end + + describe 'Validations' do + it { is_expected.to validate_length_of(:name).is_at_most(described_class::APP_NAME_LIMIT) } + it { is_expected.to validate_length_of(:redirect_uri).is_at_most(described_class::APP_REDIRECT_URI_LIMIT) } + it { is_expected.to validate_length_of(:website).is_at_most(described_class::APP_WEBSITE_LIMIT) } + end +end diff --git a/spec/models/form/admin_settings_spec.rb b/spec/models/form/admin_settings_spec.rb index 73106f2b69..899d56703a 100644 --- a/spec/models/form/admin_settings_spec.rb +++ b/spec/models/form/admin_settings_spec.rb @@ -17,4 +17,40 @@ RSpec.describe Form::AdminSettings do end end end + + describe '#save' do + describe 'updating digest values' do + context 'when updating custom css to real value' do + subject { described_class.new(custom_css: css) } + + let(:css) { 'body { color: red; }' } + let(:digested) { Digest::SHA256.hexdigest(css) } + + it 'changes relevant digest value' do + expect { subject.save } + .to(change { Rails.cache.read(:setting_digest_custom_css) }.to(digested)) + end + end + + context 'when updating custom css to empty value' do + subject { described_class.new(custom_css: '') } + + before { Rails.cache.write(:setting_digest_custom_css, 'previous-value') } + + it 'changes relevant digest value' do + expect { subject.save } + .to(change { Rails.cache.read(:setting_digest_custom_css) }.to(be_blank)) + end + end + + context 'when updating other fields' do + subject { described_class.new(site_contact_email: 'test@example.host') } + + it 'does not update digests' do + expect { subject.save } + .to(not_change { Rails.cache.read(:setting_digest_custom_css) }) + end + end + end + end end diff --git a/spec/models/trends/links_spec.rb b/spec/models/trends/links_spec.rb new file mode 100644 index 0000000000..b0d41d4613 --- /dev/null +++ b/spec/models/trends/links_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Trends::Links do + describe 'Trends::Links::Query' do + subject { described_class.new.query } + + describe '#records' do + context 'with scored cards' do + let!(:higher_score) { Fabricate :preview_card_trend, score: 10, language: 'en' } + let!(:lower_score) { Fabricate :preview_card_trend, score: 1, language: 'es' } + + it 'returns higher score first' do + expect(subject.records) + .to eq([higher_score.preview_card, lower_score.preview_card]) + end + + context 'with preferred locale' do + before { subject.in_locale!('es') } + + it 'returns in language order' do + expect(subject.records) + .to eq([lower_score.preview_card, higher_score.preview_card]) + end + end + end + end + end +end diff --git a/spec/models/trends/statuses_spec.rb b/spec/models/trends/statuses_spec.rb index 7c30b5b997..abb1535d04 100644 --- a/spec/models/trends/statuses_spec.rb +++ b/spec/models/trends/statuses_spec.rb @@ -45,6 +45,31 @@ RSpec.describe Trends::Statuses do end end + describe 'Trends::Statuses::Query methods' do + subject { described_class.new.query } + + describe '#records' do + context 'with scored cards' do + let!(:higher_score) { Fabricate :status_trend, score: 10, language: 'en' } + let!(:lower_score) { Fabricate :status_trend, score: 1, language: 'es' } + + it 'returns higher score first' do + expect(subject.records) + .to eq([higher_score.status, lower_score.status]) + end + + context 'with preferred locale' do + before { subject.in_locale!('es') } + + it 'returns in language order' do + expect(subject.records) + .to eq([lower_score.status, higher_score.status]) + end + end + end + end + end + describe '#add' do let(:status) { Fabricate(:status) } diff --git a/spec/models/trends/tags_spec.rb b/spec/models/trends/tags_spec.rb index 936b441d92..8f36b4a50d 100644 --- a/spec/models/trends/tags_spec.rb +++ b/spec/models/trends/tags_spec.rb @@ -29,6 +29,31 @@ RSpec.describe Trends::Tags do end end + describe 'Trends::Tags::Query' do + subject { described_class.new.query } + + describe '#records' do + context 'with scored cards' do + let!(:higher_score) { Fabricate :tag_trend, score: 10, language: 'en' } + let!(:lower_score) { Fabricate :tag_trend, score: 1, language: 'es' } + + it 'returns higher score first' do + expect(subject.records) + .to eq([higher_score.tag, lower_score.tag]) + end + + context 'with preferred locale' do + before { subject.in_locale!('es') } + + it 'returns in language order' do + expect(subject.records) + .to eq([lower_score.tag, higher_score.tag]) + end + end + end + end + end + describe '#refresh' do let!(:today) { at_time } let!(:yesterday) { today - 1.day } diff --git a/spec/policies/account_warning_policy_spec.rb b/spec/policies/account_warning_policy_spec.rb index 75142e2071..2603794886 100644 --- a/spec/policies/account_warning_policy_spec.rb +++ b/spec/policies/account_warning_policy_spec.rb @@ -31,11 +31,11 @@ RSpec.describe AccountWarningPolicy do context 'when account is target' do context 'when record is appealable' do - it { is_expected.to permit(account, AccountWarning.new(target_account_id: account.id, created_at: Appeal::MAX_STRIKE_AGE.ago + 1.hour)) } + it { is_expected.to permit(account, AccountWarning.new(target_account_id: account.id, created_at: AccountWarning::APPEAL_WINDOW.ago + 1.hour)) } end context 'when record is not appealable' do - it { is_expected.to_not permit(account, AccountWarning.new(target_account_id: account.id, created_at: Appeal::MAX_STRIKE_AGE.ago - 1.hour)) } + it { is_expected.to_not permit(account, AccountWarning.new(target_account_id: account.id, created_at: AccountWarning::APPEAL_WINDOW.ago - 1.hour)) } end end end diff --git a/spec/policies/backup_policy_spec.rb b/spec/policies/backup_policy_spec.rb index 031021d91d..28995d195b 100644 --- a/spec/policies/backup_policy_spec.rb +++ b/spec/policies/backup_policy_spec.rb @@ -23,22 +23,30 @@ RSpec.describe BackupPolicy do context 'when backups are too old' do it 'permits' do - travel(-8.days) do + travel(-before_time) do Fabricate(:backup, user: john.user) end expect(subject).to permit(john, Backup) end + + def before_time + described_class::MIN_AGE + 2.days + end end context 'when backups are newer' do it 'denies' do - travel(-3.days) do + travel(-within_time) do Fabricate(:backup, user: john.user) end expect(subject).to_not permit(john, Backup) end + + def within_time + described_class::MIN_AGE - 2.days + end end end end diff --git a/spec/requests/accounts_spec.rb b/spec/requests/accounts_spec.rb index afd9ac80e2..72913ebf22 100644 --- a/spec/requests/accounts_spec.rb +++ b/spec/requests/accounts_spec.rb @@ -53,8 +53,9 @@ RSpec.describe 'Accounts show response' do it 'returns a standard HTML response', :aggregate_failures do expect(response) .to have_http_status(200) - .and render_template(:show) .and have_http_link_header(ActivityPub::TagManager.instance.uri_for(account)).for(rel: 'alternate') + expect(response.parsed_body.at('title').content) + .to include(account.username) end end diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb index 966d1f598e..0badc17e1b 100644 --- a/spec/requests/api/v1/accounts/credentials_spec.rb +++ b/spec/requests/api/v1/accounts/credentials_spec.rb @@ -85,7 +85,7 @@ RSpec.describe 'credentials API' do end describe 'with invalid data' do - let(:params) { { note: "This is too long. #{'a' * Account::NOTE_LENGTH_LIMIT}" } } + let(:params) { { note: 'a' * 2 * Account::NOTE_LENGTH_LIMIT } } it 'returns http unprocessable entity' do subject diff --git a/spec/requests/api/v1/accounts/notes_spec.rb b/spec/requests/api/v1/accounts/notes_spec.rb index 1677ec07e3..e616df1e6f 100644 --- a/spec/requests/api/v1/accounts/notes_spec.rb +++ b/spec/requests/api/v1/accounts/notes_spec.rb @@ -29,7 +29,7 @@ RSpec.describe 'Accounts Notes API' do end context 'when account note exceeds allowed length', :aggregate_failures do - let(:comment) { 'a' * 2_001 } + let(:comment) { 'a' * AccountNote::COMMENT_SIZE_LIMIT * 2 } it 'does not create account note' do subject diff --git a/spec/requests/api/v1/apps_spec.rb b/spec/requests/api/v1/apps_spec.rb index 4e9147ba32..3120ab9c64 100644 --- a/spec/requests/api/v1/apps_spec.rb +++ b/spec/requests/api/v1/apps_spec.rb @@ -122,7 +122,7 @@ RSpec.describe 'Apps' do end context 'with a too-long name' do - let(:client_name) { 'hoge' * 20 } + let(:client_name) { 'a' * Doorkeeper::Application::APP_NAME_LIMIT * 2 } it 'returns http unprocessable entity' do subject @@ -134,7 +134,7 @@ RSpec.describe 'Apps' do end context 'with a too-long website' do - let(:website) { "https://foo.bar/#{'hoge' * 2_000}" } + let(:website) { "https://foo.bar/#{'a' * Doorkeeper::Application::APP_WEBSITE_LIMIT * 2}" } it 'returns http unprocessable entity' do subject @@ -146,7 +146,7 @@ RSpec.describe 'Apps' do end context 'with a too-long redirect_uri' do - let(:redirect_uris) { "https://app.example/#{'hoge' * 2_000}" } + let(:redirect_uris) { "https://app.example/#{'a' * Doorkeeper::Application::APP_REDIRECT_URI_LIMIT * 2}" } it 'returns http unprocessable entity' do subject diff --git a/spec/requests/api/v2/media_spec.rb b/spec/requests/api/v2/media_spec.rb index 807e427d3f..18ebb9cdda 100644 --- a/spec/requests/api/v2/media_spec.rb +++ b/spec/requests/api/v2/media_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Media API', :attachment_processing do let(:params) do { file: fixture_file_upload('attachment-jpg.123456_abcd', 'image/jpeg'), - description: 'aa' * MediaAttachment::MAX_DESCRIPTION_LENGTH, + description: 'a' * MediaAttachment::MAX_DESCRIPTION_LENGTH * 2, } end diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb index ffa0a71c77..aa4a861557 100644 --- a/spec/requests/api/v2/notifications_spec.rb +++ b/spec/requests/api/v2/notifications_spec.rb @@ -143,6 +143,55 @@ RSpec.describe 'Notifications' do end end + context 'when there are numerous notifications for the same final group' do + before do + user.account.notifications.destroy_all + 5.times.each { FavouriteService.new.call(Fabricate(:account), user.account.statuses.first) } + end + + context 'with no options' do + it 'returns a notification group covering all notifications' do + subject + + notification_ids = user.account.notifications.reload.pluck(:id) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:notification_groups]).to contain_exactly( + a_hash_including( + type: 'favourite', + sample_account_ids: have_attributes(size: 5), + page_min_id: notification_ids.first.to_s, + page_max_id: notification_ids.last.to_s + ) + ) + end + end + + context 'with min_id param' do + let(:params) { { min_id: user.account.notifications.reload.first.id - 1 } } + + it 'returns a notification group covering all notifications' do + subject + + notification_ids = user.account.notifications.reload.pluck(:id) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:notification_groups]).to contain_exactly( + a_hash_including( + type: 'favourite', + sample_account_ids: have_attributes(size: 5), + page_min_id: notification_ids.first.to_s, + page_max_id: notification_ids.last.to_s + ) + ) + end + end + end + context 'with no options' do it 'returns expected notification types', :aggregate_failures do subject diff --git a/spec/controllers/auth/challenges_controller_spec.rb b/spec/requests/auth/challenges_spec.rb similarity index 64% rename from spec/controllers/auth/challenges_controller_spec.rb rename to spec/requests/auth/challenges_spec.rb index 3c9d2a5964..628bfe499b 100644 --- a/spec/controllers/auth/challenges_controller_spec.rb +++ b/spec/requests/auth/challenges_spec.rb @@ -2,9 +2,7 @@ require 'rails_helper' -RSpec.describe Auth::ChallengesController do - render_views - +RSpec.describe 'Auth Challenges' do let(:password) { 'foobar12345' } let(:user) { Fabricate(:user, password: password) } @@ -14,9 +12,9 @@ RSpec.describe Auth::ChallengesController do let(:return_to) { edit_user_registration_path } context 'with correct password' do - before { post :create, params: { form_challenge: { return_to: return_to, current_password: password } } } - it 'redirects back and sets challenge passed at in session' do + post '/auth/challenge', params: { form_challenge: { return_to: return_to, current_password: password } } + expect(response) .to redirect_to(return_to) expect(session[:challenge_passed_at]) @@ -25,13 +23,12 @@ RSpec.describe Auth::ChallengesController do end context 'with incorrect password' do - before { post :create, params: { form_challenge: { return_to: return_to, current_password: 'hhfggjjd562' } } } - it 'renders challenge, displays error, does not set session' do - expect(response) - .to render_template('auth/challenges/new') + post '/auth/challenge', params: { form_challenge: { return_to: return_to, current_password: 'hhfggjjd562' } } + expect(response.body) - .to include 'Invalid password' + .to include(I18n.t('challenge.prompt')) + .and include('Invalid password') expect(session[:challenge_passed_at]) .to be_nil end diff --git a/spec/requests/cache_spec.rb b/spec/requests/cache_spec.rb index 2a52e4dea9..8ca2817263 100644 --- a/spec/requests/cache_spec.rb +++ b/spec/requests/cache_spec.rb @@ -10,6 +10,7 @@ module TestEndpoints /.well-known/nodeinfo /nodeinfo/2.0 /manifest + /css/custom-1a2s3d4f.css /custom.css /actor /api/v1/instance/extended_description diff --git a/spec/requests/custom_css_spec.rb b/spec/requests/custom_css_spec.rb index d97da00187..66ff5c4b13 100644 --- a/spec/requests/custom_css_spec.rb +++ b/spec/requests/custom_css_spec.rb @@ -5,10 +5,10 @@ require 'rails_helper' RSpec.describe 'Custom CSS' do include RoutingHelper - describe 'GET /custom.css' do + describe 'GET /css/:id.css' do context 'without any CSS or User Roles' do it 'returns empty stylesheet' do - get '/custom.css' + get '/css/custom-123.css' expect(response) .to have_http_status(200) @@ -27,7 +27,7 @@ RSpec.describe 'Custom CSS' do end it 'returns stylesheet from settings' do - get '/custom.css' + get '/css/custom-456.css' expect(response) .to have_http_status(200) @@ -45,34 +45,5 @@ RSpec.describe 'Custom CSS' do CSS end end - - context 'with highlighted colored UserRole records' do - before do - _highlighted_colored = Fabricate :user_role, highlighted: true, color: '#336699', id: '123_123_123' - _highlighted_no_color = Fabricate :user_role, highlighted: true, color: '' - _no_highlight_with_color = Fabricate :user_role, highlighted: false, color: '' - end - - it 'returns stylesheet from settings' do - get '/custom.css' - - expect(response) - .to have_http_status(200) - .and have_cacheable_headers - .and have_attributes( - content_type: match('text/css') - ) - expect(response.body.strip) - .to eq(expected_css) - end - - def expected_css - <<~CSS.strip - .user-role-123123123 { - --user-role-accent: #336699; - } - CSS - end - end end end diff --git a/spec/requests/remote_interaction_helper_spec.rb b/spec/requests/remote_interaction_helper_spec.rb index 942f70b9a4..b89060b5b2 100644 --- a/spec/requests/remote_interaction_helper_spec.rb +++ b/spec/requests/remote_interaction_helper_spec.rb @@ -9,7 +9,6 @@ RSpec.describe 'Remote Interaction Helper' do expect(response) .to have_http_status(200) - .and render_template(:index, layout: 'helper_frame') .and have_attributes( headers: include( 'X-Frame-Options' => 'SAMEORIGIN', @@ -17,6 +16,8 @@ RSpec.describe 'Remote Interaction Helper' do 'Content-Security-Policy' => expected_csp_headers ) ) + expect(response.body) + .to match(/remote_interaction_helper/) end end diff --git a/spec/requests/settings/migrations_spec.rb b/spec/requests/settings/migrations_spec.rb new file mode 100644 index 0000000000..4103d6b320 --- /dev/null +++ b/spec/requests/settings/migrations_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Settings Migrations' do + describe 'GET #show' do + context 'when user is not signed in' do + subject { get '/settings/migration' } + + it { is_expected.to redirect_to new_user_session_path } + end + end + + describe 'POST #create' do + context 'when user is not signed in' do + subject { post '/settings/migration' } + + it { is_expected.to redirect_to new_user_session_path } + end + end +end diff --git a/spec/routing/custom_css_routing_spec.rb b/spec/routing/custom_css_routing_spec.rb new file mode 100644 index 0000000000..26139b4744 --- /dev/null +++ b/spec/routing/custom_css_routing_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Custom CSS routes' do + describe 'the legacy route' do + it 'routes to correct place' do + expect(get('/custom.css')) + .to route_to('custom_css#show') + end + end + + describe 'the custom digest route' do + it 'routes to correct place' do + expect(get('/css/custom-1a2s3d4f.css')) + .to route_to('custom_css#show', id: 'custom-1a2s3d4f', format: 'css') + end + end +end diff --git a/spec/serializers/rest/scheduled_status_serializer_spec.rb b/spec/serializers/rest/scheduled_status_serializer_spec.rb index 08ad8a6f8a..2cf0098654 100644 --- a/spec/serializers/rest/scheduled_status_serializer_spec.rb +++ b/spec/serializers/rest/scheduled_status_serializer_spec.rb @@ -17,7 +17,7 @@ RSpec.describe REST::ScheduledStatusSerializer do expect(subject.deep_symbolize_keys) .to include( scheduled_at: be_a(String).and(match_api_datetime_format), - params: not_include(:application_id) + params: include(:application_id) ) end end diff --git a/spec/support/examples/models/concerns/account/search.rb b/spec/support/examples/models/concerns/account/search.rb new file mode 100644 index 0000000000..9d9b499732 --- /dev/null +++ b/spec/support/examples/models/concerns/account/search.rb @@ -0,0 +1,268 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Account::Search' do + describe '.search_for' do + before do + _missing = Fabricate( + :account, + display_name: 'Missing', + username: 'missing', + domain: 'missing.com' + ) + end + + it 'does not return suspended users' do + Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com', + suspended: true + ) + + results = described_class.search_for('username') + expect(results).to eq [] + end + + it 'does not return unapproved users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(approved: false) + + results = described_class.search_for('username') + expect(results).to eq [] + end + + it 'does not return unconfirmed users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(confirmed_at: nil) + + results = described_class.search_for('username') + expect(results).to eq [] + end + + it 'accepts ?, \, : and space as delimiter' do + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('A?l\i:c e') + expect(results).to eq [match] + end + + it 'finds accounts with matching display_name' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('display') + expect(results).to eq [match] + end + + it 'finds accounts with matching username' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('username') + expect(results).to eq [match] + end + + it 'finds accounts with matching domain' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('example') + expect(results).to eq [match] + end + + it 'limits via constant by default' do + stub_const('Account::Search::DEFAULT_LIMIT', 1) + 2.times.each { Fabricate(:account, display_name: 'Display Name') } + results = described_class.search_for('display') + expect(results.size).to eq 1 + end + + it 'accepts arbitrary limits' do + 2.times.each { Fabricate(:account, display_name: 'Display Name') } + results = described_class.search_for('display', limit: 1) + expect(results.size).to eq 1 + end + + it 'ranks multiple matches higher' do + matches = [ + { username: 'username', display_name: 'username' }, + { display_name: 'Display Name', username: 'username', domain: 'example.com' }, + ].map(&method(:Fabricate).curry(2).call(:account)) + + results = described_class.search_for('username') + expect(results).to eq matches + end + end + + describe '.advanced_search_for' do + let(:account) { Fabricate(:account) } + + context 'when limiting search to followed accounts' do + it 'accepts ?, \, : and space as delimiter' do + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + account.follow!(match) + + results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) + expect(results).to eq [match] + end + + it 'does not return non-followed accounts' do + Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) + expect(results).to eq [] + end + + it 'does not return suspended users' do + Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com', + suspended: true + ) + + results = described_class.advanced_search_for('username', account, limit: 10, following: true) + expect(results).to eq [] + end + + it 'does not return unapproved users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(approved: false) + + results = described_class.advanced_search_for('username', account, limit: 10, following: true) + expect(results).to eq [] + end + + it 'does not return unconfirmed users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(confirmed_at: nil) + + results = described_class.advanced_search_for('username', account, limit: 10, following: true) + expect(results).to eq [] + end + end + + it 'does not return suspended users' do + Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com', + suspended: true + ) + + results = described_class.advanced_search_for('username', account) + expect(results).to eq [] + end + + it 'does not return unapproved users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(approved: false) + + results = described_class.advanced_search_for('username', account) + expect(results).to eq [] + end + + it 'does not return unconfirmed users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(confirmed_at: nil) + + results = described_class.advanced_search_for('username', account) + expect(results).to eq [] + end + + it 'accepts ?, \, : and space as delimiter' do + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = described_class.advanced_search_for('A?l\i:c e', account) + expect(results).to eq [match] + end + + it 'limits result count by default value' do + stub_const('Account::Search::DEFAULT_LIMIT', 1) + 2.times { Fabricate(:account, display_name: 'Display Name') } + results = described_class.advanced_search_for('display', account) + expect(results.size).to eq 1 + end + + it 'accepts arbitrary limits' do + 2.times { Fabricate(:account, display_name: 'Display Name') } + results = described_class.advanced_search_for('display', account, limit: 1) + expect(results.size).to eq 1 + end + + it 'ranks followed accounts higher' do + match = Fabricate(:account, username: 'Matching') + followed_match = Fabricate(:account, username: 'Matcher') + Fabricate(:follow, account: account, target_account: followed_match) + + results = described_class.advanced_search_for('match', account) + expect(results).to eq [followed_match, match] + expect(results.first.rank).to be > results.last.rank + end + end +end diff --git a/spec/system/admin/follow_recommendations_spec.rb b/spec/system/admin/follow_recommendations_spec.rb new file mode 100644 index 0000000000..141a0f8152 --- /dev/null +++ b/spec/system/admin/follow_recommendations_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Follow Recommendations' do + let(:user) { Fabricate(:admin_user) } + + before { sign_in(user) } + + describe 'Viewing follow recommendations details' do + it 'shows a list of accounts' do + visit admin_follow_recommendations_path + + expect(page) + .to have_content(I18n.t('admin.follow_recommendations.title')) + end + end +end diff --git a/spec/system/admin/terms_of_service/histories_spec.rb b/spec/system/admin/terms_of_service/histories_spec.rb new file mode 100644 index 0000000000..aa59550d09 --- /dev/null +++ b/spec/system/admin/terms_of_service/histories_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Terms of Service Histories' do + let(:current_user) { Fabricate(:admin_user) } + + before { sign_in(current_user) } + + describe 'Viewing TOS histories' do + before { Fabricate :terms_of_service, changelog: 'The changelog notes from v1 are here' } + + it 'shows previous terms versions' do + visit admin_terms_of_service_history_path + + expect(page) + .to have_content(I18n.t('admin.terms_of_service.history')) + .and have_content(/changelog notes from v1/) + end + end +end diff --git a/spec/system/settings/migrations_spec.rb b/spec/system/settings/migrations_spec.rb new file mode 100644 index 0000000000..fecde36f35 --- /dev/null +++ b/spec/system/settings/migrations_spec.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Settings Migrations' do + describe 'Viewing settings migrations' do + let(:user) { Fabricate(:account, moved_to_account: moved_to_account).user } + + before { sign_in(user) } + + context 'when user does not have moved to account' do + let(:moved_to_account) { nil } + + it 'renders show page' do + visit settings_migration_path + + expect(page) + .to have_content(I18n.t('settings.migrate')) + end + end + + context 'when user has a moved to account' do + let(:moved_to_account) { Fabricate(:account) } + + it 'renders show page and account details' do + visit settings_migration_path + + expect(page) + .to have_content(I18n.t('settings.migrate')) + .and have_content(moved_to_account.pretty_acct) + end + end + end + + describe 'Creating migrations' do + let(:user) { Fabricate(:user, password: '12345678') } + + before { sign_in(user) } + + context 'when migration account is changed' do + let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) } + + it 'updates moved to account' do + visit settings_migration_path + + expect { fill_in_and_submit } + .to(change { user.account.reload.moved_to_account_id }.to(acct.id)) + expect(page) + .to have_content(I18n.t('settings.migrate')) + end + end + + context 'when acct is the current account' do + let(:acct) { user.account } + + it 'does not update the moved account', :aggregate_failures do + visit settings_migration_path + + expect { fill_in_and_submit } + .to_not(change { user.account.reload.moved_to_account_id }.from(nil)) + expect(page) + .to have_content(I18n.t('settings.migrate')) + end + end + + context 'when target account does not reference the account being moved from' do + let(:acct) { Fabricate(:account, also_known_as: []) } + + it 'does not update the moved account', :aggregate_failures do + visit settings_migration_path + + expect { fill_in_and_submit } + .to_not(change { user.account.reload.moved_to_account_id }.from(nil)) + expect(page) + .to have_content(I18n.t('settings.migrate')) + end + end + + context 'when a recent migration already exists' do + let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) } + let(:moved_to) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) } + + before { user.account.migrations.create!(acct: moved_to.acct) } + + it 'can not update the moved account', :aggregate_failures do + visit settings_migration_path + + expect(find_by_id('account_migration_acct')) + .to be_disabled + end + end + + def fill_in_and_submit + fill_in 'account_migration_acct', with: acct.username + fill_in 'account_migration_current_password', with: '12345678' + click_on I18n.t('migrations.proceed_with_move') + end + end +end diff --git a/streaming/package.json b/streaming/package.json index 2419ffd273..b98608edd5 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -21,7 +21,7 @@ "dotenv": "^16.0.3", "express": "^4.18.2", "ioredis": "^5.3.2", - "jsdom": "^25.0.0", + "jsdom": "^26.0.0", "pg": "^8.5.0", "pg-connection-string": "^2.6.0", "pino": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index 5aa4814237..8335f8b992 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,6 +42,19 @@ __metadata: languageName: node linkType: hard +"@asamuzakjp/css-color@npm:^2.8.2": + version: 2.8.2 + resolution: "@asamuzakjp/css-color@npm:2.8.2" + dependencies: + "@csstools/css-calc": "npm:^2.1.1" + "@csstools/css-color-parser": "npm:^3.0.7" + "@csstools/css-parser-algorithms": "npm:^3.0.4" + "@csstools/css-tokenizer": "npm:^3.0.3" + lru-cache: "npm:^11.0.2" + checksum: 10c0/352b91ca7741876e459cd3cb350a969e842da1e532577157d38365a6da89b7d6e6944249489366ee61b8a225ede1b521e7ab305b70ad4c688b01404061eecca8 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0": version: 7.26.0 resolution: "@babel/code-frame@npm:7.26.0" @@ -2267,14 +2280,15 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.2.3": - version: 2.2.3 - resolution: "@formatjs/ecma402-abstract@npm:2.2.3" +"@formatjs/ecma402-abstract@npm:2.3.2": + version: 2.3.2 + resolution: "@formatjs/ecma402-abstract@npm:2.3.2" dependencies: - "@formatjs/fast-memoize": "npm:2.2.3" - "@formatjs/intl-localematcher": "npm:0.5.7" + "@formatjs/fast-memoize": "npm:2.2.6" + "@formatjs/intl-localematcher": "npm:0.5.10" + decimal.js: "npm:10" tslib: "npm:2" - checksum: 10c0/611d12bf320fc5c5b85cb2b57e3dcebe8490a51c6a0459c857c7a3560656cd2bdba5b117e9dd7cf174f5aa120c11eaad7a65a6783637b816caa59a1bc5c727f6 + checksum: 10c0/364e9e7de974fed976e0e8142a0f888ee0af4a11a61899115e5761ed933e7c1f16379b7b54a01524fd3c5d58bf08b71308237ea969cd54889eaf7bb2d30ec776 languageName: node linkType: hard @@ -2287,12 +2301,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/fast-memoize@npm:2.2.3": - version: 2.2.3 - resolution: "@formatjs/fast-memoize@npm:2.2.3" +"@formatjs/fast-memoize@npm:2.2.6": + version: 2.2.6 + resolution: "@formatjs/fast-memoize@npm:2.2.6" dependencies: tslib: "npm:2" - checksum: 10c0/f1004c3b280de7e362bd37c5f48ff34c2ba1d6271d4a7b695fed561d1201a3379397824d8bffbf15fecee344d1e70398393bbb04297f242692310a305f12e75b + checksum: 10c0/dccdc21105af673e58ec7b04eb17cd6fde1fb1a7e7a446273ca43f7ab97c26d5c0fcc2b9e80d5b54bf9b80354f9e1e681273c0ed26633ec72f0adc2d116dfd7f languageName: node linkType: hard @@ -2307,14 +2321,24 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.9.3": - version: 2.9.3 - resolution: "@formatjs/icu-messageformat-parser@npm:2.9.3" +"@formatjs/icu-messageformat-parser@npm:2.9.8": + version: 2.9.8 + resolution: "@formatjs/icu-messageformat-parser@npm:2.9.8" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" - "@formatjs/icu-skeleton-parser": "npm:1.8.7" + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/icu-skeleton-parser": "npm:1.8.12" tslib: "npm:2" - checksum: 10c0/519b59f7b4cf90681315c5382f7fcd105eb1974486f0d62d9227b6d0498895114ccc818792c208baae1ef79571d93b0edb9914c676e5ab76924dddb7fd6c28a0 + checksum: 10c0/df97c7f24fbeb8ef49ae1371f9498ad90f231f88211bf1effb7b2e8ac3531bec67c5d9147ddcb1add0ba697e8d089729add44a9a9c5015e0e8d61e7a43f062d9 + languageName: node + linkType: hard + +"@formatjs/icu-skeleton-parser@npm:1.8.12": + version: 1.8.12 + resolution: "@formatjs/icu-skeleton-parser@npm:1.8.12" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.3.2" + tslib: "npm:2" + checksum: 10c0/03e743aa09acb2137e37d03b98578fcbbc949d056b8c151763778e885d04d621e69c82f7656547f0532351d2a987bffac0a8c4c3d81186f47a28047ba64385e2 languageName: node linkType: hard @@ -2328,35 +2352,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-skeleton-parser@npm:1.8.7": - version: 1.8.7 - resolution: "@formatjs/icu-skeleton-parser@npm:1.8.7" +"@formatjs/intl-localematcher@npm:0.5.10": + version: 0.5.10 + resolution: "@formatjs/intl-localematcher@npm:0.5.10" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" tslib: "npm:2" - checksum: 10c0/e29eb4151580f2d324e6591509dc4543e2326266fc209a08580c94d502acab14acc3560d98b3aaf9ffbd5ff8e2683601ff08c65b32886f22da015c31ca35c5d0 - languageName: node - linkType: hard - -"@formatjs/intl-displaynames@npm:6.8.3": - version: 6.8.3 - resolution: "@formatjs/intl-displaynames@npm:6.8.3" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" - "@formatjs/intl-localematcher": "npm:0.5.7" - tslib: "npm:2" - checksum: 10c0/54d3ecaabc45dc8494be4cd9633bbf5eb0bc40c5f62ed5e5073aa5b214c4a9a9620d61d1c8a6f0074618474fd9c2ccc42904605078d2f6341c242bf43627bb3a - languageName: node - linkType: hard - -"@formatjs/intl-listformat@npm:7.7.3": - version: 7.7.3 - resolution: "@formatjs/intl-listformat@npm:7.7.3" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" - "@formatjs/intl-localematcher": "npm:0.5.7" - tslib: "npm:2" - checksum: 10c0/2683513e86cc7885528f23237079e3ab9e3a8bc7111aa10d4c685dcbe368ef07039573115df240112fb20f2ec0b70c8ea189ea3b79cbfed7e3dc46024a4667ff + checksum: 10c0/362ec83aca9382165be575f1cefa477478339e6fead8ca8866185ce6e58427ea1487a811b12c73d1bcfa99fd4db0c24543b35c823451839f585576bfccb8c9cc languageName: node linkType: hard @@ -2369,43 +2370,33 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.5.7": - version: 0.5.7 - resolution: "@formatjs/intl-localematcher@npm:0.5.7" - dependencies: - tslib: "npm:2" - checksum: 10c0/1ae374ca146a0d7457794926eed808c99971628e594f704a42ae2540b1f38928b26cbf942a7bbcc2796cc9fe8d9d7a603ac422bd9b89b714d2f91b506da40792 - languageName: node - linkType: hard - "@formatjs/intl-pluralrules@npm:^5.2.2": - version: 5.3.3 - resolution: "@formatjs/intl-pluralrules@npm:5.3.3" + version: 5.4.2 + resolution: "@formatjs/intl-pluralrules@npm:5.4.2" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" - "@formatjs/intl-localematcher": "npm:0.5.7" + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/intl-localematcher": "npm:0.5.10" + decimal.js: "npm:10" tslib: "npm:2" - checksum: 10c0/003d33af6f5d902517f230b7038e39b8336da3a84500fe5f4278fa5cac0c9a1b746de484f1c2bb2315026fd771491eafdc0aa59a809bb25189b63093efb8400d + checksum: 10c0/0ecb9da19084d7a15e636362c206c7ee14297ad365e3e63ea53c82c8442d02cd4dcf8a0da65af9b0f835b32f0e8c3d43407d6ee0a0d7974c3044c92574b49686 languageName: node linkType: hard -"@formatjs/intl@npm:2.10.13": - version: 2.10.13 - resolution: "@formatjs/intl@npm:2.10.13" +"@formatjs/intl@npm:3.1.0": + version: 3.1.0 + resolution: "@formatjs/intl@npm:3.1.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" - "@formatjs/fast-memoize": "npm:2.2.3" - "@formatjs/icu-messageformat-parser": "npm:2.9.3" - "@formatjs/intl-displaynames": "npm:6.8.3" - "@formatjs/intl-listformat": "npm:7.7.3" - intl-messageformat: "npm:10.7.5" + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/fast-memoize": "npm:2.2.6" + "@formatjs/icu-messageformat-parser": "npm:2.9.8" + intl-messageformat: "npm:10.7.11" tslib: "npm:2" peerDependencies: - typescript: ^4.7 || 5 + typescript: 5 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/14edcc45addc181c7a45845bc34cec5c73bb5544dcf8ed455eca916bbd3a5023c7204a8e8e289060545b85006933c6f297c33c4f04a8a4cb3890aa7baae5bfbf + checksum: 10c0/a073768fffc51696eb7bd25fe1f0afdda1a0e38db3e2dd9b2fc3138ea799f00ef522f3d3083626ad3acbf913593254cfd728a6c6b08ef4f167dd132626a7e9fc languageName: node linkType: hard @@ -2429,11 +2420,11 @@ __metadata: languageName: node linkType: hard -"@formatjs/ts-transformer@npm:3.13.22": - version: 3.13.22 - resolution: "@formatjs/ts-transformer@npm:3.13.22" +"@formatjs/ts-transformer@npm:3.13.27": + version: 3.13.27 + resolution: "@formatjs/ts-transformer@npm:3.13.27" dependencies: - "@formatjs/icu-messageformat-parser": "npm:2.9.3" + "@formatjs/icu-messageformat-parser": "npm:2.9.8" "@types/json-stable-stringify": "npm:1" "@types/node": "npm:14 || 16 || 17 || 18 || 20 || 22" chalk: "npm:4" @@ -2445,7 +2436,7 @@ __metadata: peerDependenciesMeta: ts-jest: optional: true - checksum: 10c0/42503292248bcae728181fdf68e79eac4169c18064953beb9245097d2c58e5434ae7a7978e6ce2829dfd6eb4b4155f78fecef70ac6820626a338f231c66f60cb + checksum: 10c0/ab26ce081835ce2a1384172a6e24db42d48bc9f8b9b941c646fba2f01ce8370123480623d1fbc1e75f6ec90a232f8ff67b5243c5e9ad18311dc03d7061a4892b languageName: node linkType: hard @@ -2942,7 +2933,7 @@ __metadata: react-hotkeys: "npm:^1.1.4" react-immutable-proptypes: "npm:^2.2.0" react-immutable-pure-component: "npm:^2.2.2" - react-intl: "npm:^6.4.2" + react-intl: "npm:^7.0.0" react-motion: "npm:^0.5.2" react-notification: "npm:^6.8.5" react-overlays: "npm:^5.2.1" @@ -3012,7 +3003,7 @@ __metadata: eslint-define-config: "npm:^2.0.0" express: "npm:^4.18.2" ioredis: "npm:^5.3.2" - jsdom: "npm:^25.0.0" + jsdom: "npm:^26.0.0" pg: "npm:^8.5.0" pg-connection-string: "npm:^2.6.0" pino: "npm:^9.0.0" @@ -3294,22 +3285,22 @@ __metadata: linkType: hard "@reduxjs/toolkit@npm:^2.0.1": - version: 2.3.0 - resolution: "@reduxjs/toolkit@npm:2.3.0" + version: 2.5.0 + resolution: "@reduxjs/toolkit@npm:2.5.0" dependencies: immer: "npm:^10.0.3" redux: "npm:^5.0.1" redux-thunk: "npm:^3.1.0" reselect: "npm:^5.1.0" peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 10c0/414e90b706331385a2122fc79e33f90c59a9caf9a59419f1bfd7f5e594bc8e4987902fd1bccbc53eb96d22c65ec2981ff5581f3d2df3ecd381a630f391edfc3e + checksum: 10c0/81748a5a6d2f52a14769b6ed25aea1e77cda81b1db6599c7c3a1d1605696c65c4469f55146c2b2a7a2f8ebafa5ecd4996aa8deecb37aebb5307217ec2fe384ac languageName: node linkType: hard @@ -3833,12 +3824,12 @@ __metadata: linkType: hard "@types/hoist-non-react-statics@npm:3, @types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.5 - resolution: "@types/hoist-non-react-statics@npm:3.3.5" + version: 3.3.6 + resolution: "@types/hoist-non-react-statics@npm:3.3.6" dependencies: "@types/react": "npm:*" hoist-non-react-statics: "npm:^3.3.0" - checksum: 10c0/2a3b64bf3d9817d7830afa60ee314493c475fb09570a64e7737084cd482d2177ebdddf888ce837350bac51741278b077683facc9541f052d4bbe8487b4e3e618 + checksum: 10c0/149a4c217d81f21f8a1e152160a59d5b99b6a9aa6d354385d5f5bc02760cbf1e170a8442ba92eb653befff44b0c5bc2234bb77ce33e0d11a65f779e8bab5c321 languageName: node linkType: hard @@ -4018,9 +4009,9 @@ __metadata: linkType: hard "@types/prop-types@npm:*, @types/prop-types@npm:^15.7.5": - version: 15.7.13 - resolution: "@types/prop-types@npm:15.7.13" - checksum: 10c0/1b20fc67281902c6743379960247bc161f3f0406ffc0df8e7058745a85ea1538612109db0406290512947f9632fe9e10e7337bf0ce6338a91d6c948df16a7c61 + version: 15.7.14 + resolution: "@types/prop-types@npm:15.7.14" + checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1 languageName: node linkType: hard @@ -4060,11 +4051,11 @@ __metadata: linkType: hard "@types/react-dom@npm:^18.2.4": - version: 18.3.1 - resolution: "@types/react-dom@npm:18.3.1" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/8b416551c60bb6bd8ec10e198c957910cfb271bc3922463040b0d57cf4739cdcd24b13224f8d68f10318926e1ec3cd69af0af79f0291b599a992f8c80d47f1eb + version: 18.3.5 + resolution: "@types/react-dom@npm:18.3.5" + peerDependencies: + "@types/react": ^18.0.0 + checksum: 10c0/b163d35a6b32a79f5782574a7aeb12a31a647e248792bf437e6d596e2676961c394c5e3c6e91d1ce44ae90441dbaf93158efb4f051c0d61e2612f1cb04ce4faa languageName: node linkType: hard @@ -4127,20 +4118,20 @@ __metadata: linkType: hard "@types/react-swipeable-views@npm:^0.13.1": - version: 0.13.5 - resolution: "@types/react-swipeable-views@npm:0.13.5" + version: 0.13.6 + resolution: "@types/react-swipeable-views@npm:0.13.6" dependencies: "@types/react": "npm:*" - checksum: 10c0/d1dcc78d862f37d30a43d79d915fdb388e05dce0b2ac07462ca4f1b00e0eef37cb41d75997f5685dec79bcce1ffee0dfbc744f20d5266dd3090658def5b4e193 + checksum: 10c0/a26879146748417234bb7f44c5a71e6bab2b76c0b34c72f0493c18403487a5d77021510e8665bd8bd22786904fbbd90d6db55c8dd2bf983c32421139de851c94 languageName: node linkType: hard "@types/react-test-renderer@npm:^18.0.0": - version: 18.3.0 - resolution: "@types/react-test-renderer@npm:18.3.0" + version: 18.3.1 + resolution: "@types/react-test-renderer@npm:18.3.1" dependencies: - "@types/react": "npm:*" - checksum: 10c0/3c9748be52e8e659e7adf91dea6939486463264e6f633bf21c4cb116de18af7bef0595568a1e588160420b2f65289473075dda1cb417c2875df8cf7a09f5d913 + "@types/react": "npm:^18" + checksum: 10c0/9fc8467ff1a3f14be6cc3498a75fc788d2c92c0fffa7bf21269ed5d9d82db9195bf2178ddc42ea16a0836995c1b77601c6be8abb27bd1864668c418c6d0e5a3b languageName: node linkType: hard @@ -4162,13 +4153,13 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:16 || 17 || 18, @types/react@npm:>=16.9.11, @types/react@npm:^18.2.7": - version: 18.3.12 - resolution: "@types/react@npm:18.3.12" +"@types/react@npm:^18.2.7": + version: 18.3.18 + resolution: "@types/react@npm:18.3.18" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10c0/8bae8d9a41619804561574792e29112b413044eb0d53746dde2b9720c1f9a59f71c895bbd7987cd8ce9500b00786e53bc032dced38cddf42910458e145675290 + checksum: 10c0/8fb2b00672072135d0858dc9db07873ea107cc238b6228aaa2a9afd1ef7a64a7074078250db38afbeb19064be8ea6af5eac32d404efdd5f45e093cc4829d87f8 languageName: node linkType: hard @@ -4743,12 +4734,10 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0": - version: 7.1.0 - resolution: "agent-base@npm:7.1.0" - dependencies: - debug: "npm:^4.3.4" - checksum: 10c0/fc974ab57ffdd8421a2bc339644d312a9cca320c20c3393c9d8b1fd91731b9bbabdb985df5fc860f5b79d81c3e350daa3fcb31c5c07c0bb385aafc817df004ce +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 languageName: node linkType: hard @@ -5351,21 +5340,21 @@ __metadata: linkType: hard "babel-plugin-formatjs@npm:^10.5.1": - version: 10.5.24 - resolution: "babel-plugin-formatjs@npm:10.5.24" + version: 10.5.30 + resolution: "babel-plugin-formatjs@npm:10.5.30" dependencies: "@babel/core": "npm:^7.25.0" "@babel/helper-plugin-utils": "npm:^7.25.0" "@babel/plugin-syntax-jsx": "npm:^7.25.0" "@babel/traverse": "npm:^7.25.0" "@babel/types": "npm:^7.25.0" - "@formatjs/icu-messageformat-parser": "npm:2.9.3" - "@formatjs/ts-transformer": "npm:3.13.22" + "@formatjs/icu-messageformat-parser": "npm:2.9.8" + "@formatjs/ts-transformer": "npm:3.13.27" "@types/babel__core": "npm:^7.20.5" "@types/babel__helper-plugin-utils": "npm:^7.10.3" "@types/babel__traverse": "npm:^7.20.6" tslib: "npm:2" - checksum: 10c0/a99c92e62edb30e0b6a5bf1115811eb18336b851109f8c096b5a9aa2be3eb72299eea3a8cb706e03705eb79edfa5a62d69bb52d80a375c1d5a1c963e3d00c40e + checksum: 10c0/cf9f17b71da9e95a402e0722cfbf4a549c984a8bb536f6b46dc187fa4b69dd2e248e6a825ea95d5ddfa2a9b09b3eb399cefa2dce1dcf52fd6e661adb99a9d3fa languageName: node linkType: hard @@ -6544,9 +6533,9 @@ __metadata: linkType: hard "core-js@npm:^3.30.2": - version: 3.39.0 - resolution: "core-js@npm:3.39.0" - checksum: 10c0/f7602069b6afb2e3298eec612a5c1e0c3e6a458930fbfc7a4c5f9ac03426507f49ce395eecdd2d9bae9024f820e44582b67ffe16f2272395af26964f174eeb6b + version: 3.40.0 + resolution: "core-js@npm:3.40.0" + checksum: 10c0/db7946ada881e845d8b157061945b1187618fa45cf162f392a151e8a497962aed2da688c982eaa1d444c864be97a70f8be4d73385294b515d224dd164d19f1d4 languageName: node linkType: hard @@ -7003,12 +6992,13 @@ __metadata: languageName: node linkType: hard -"cssstyle@npm:^4.1.0": - version: 4.1.0 - resolution: "cssstyle@npm:4.1.0" +"cssstyle@npm:^4.2.1": + version: 4.2.1 + resolution: "cssstyle@npm:4.2.1" dependencies: - rrweb-cssom: "npm:^0.7.1" - checksum: 10c0/05c6597e5d3e0ec6b15221f2c0ce9a0443a46cc50a6089a3ba9ee1ac27f83ff86a445a8f95435137dadd859f091fc61b6d342abaf396d3c910471b5b33cfcbfa + "@asamuzakjp/css-color": "npm:^2.8.2" + rrweb-cssom: "npm:^0.8.0" + checksum: 10c0/02ba8c47c0caaab57acadacb3eb6c0f5f009000f55d61f6563670e07d389b26edefeed497e6c1847fcd2e6bbe0b6974c2d4291f97fa0c6ec6add13a7fa926d84 languageName: node linkType: hard @@ -7131,7 +7121,7 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.4.2, decimal.js@npm:^10.4.3": +"decimal.js@npm:10, decimal.js@npm:^10.4.2, decimal.js@npm:^10.4.3": version: 10.4.3 resolution: "decimal.js@npm:10.4.3" checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee @@ -7719,7 +7709,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0, entities@npm:^4.4.0": +"entities@npm:^4.2.0, entities@npm:^4.5.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 @@ -8890,14 +8880,14 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" +"form-data@npm:^4.0.0, form-data@npm:^4.0.1": + version: 4.0.1 + resolution: "form-data@npm:4.0.1" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" mime-types: "npm:^2.1.12" - checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 languageName: node linkType: hard @@ -9677,13 +9667,13 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5": - version: 7.0.5 - resolution: "https-proxy-agent@npm:7.0.5" +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" dependencies: - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.2" debug: "npm:4" - checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac languageName: node linkType: hard @@ -9942,15 +9932,15 @@ __metadata: languageName: node linkType: hard -"intl-messageformat@npm:10.7.5, intl-messageformat@npm:^10.3.5": - version: 10.7.5 - resolution: "intl-messageformat@npm:10.7.5" +"intl-messageformat@npm:10.7.11, intl-messageformat@npm:^10.3.5": + version: 10.7.11 + resolution: "intl-messageformat@npm:10.7.11" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" - "@formatjs/fast-memoize": "npm:2.2.3" - "@formatjs/icu-messageformat-parser": "npm:2.9.3" + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/fast-memoize": "npm:2.2.6" + "@formatjs/icu-messageformat-parser": "npm:2.9.8" tslib: "npm:2" - checksum: 10c0/1aa173a8c16ace50520af3de7d3f0ce9bafda90d47b6d674eb88cc47c12c3460d4d53c97ca412fae1141b00abb8ff2ba313250997a040837f01a25379165c949 + checksum: 10c0/7ccd972277cc6798038af876c830203084db6552becfa99c3706541fd67838552013f57f8ed0ed3aed03d4fba436591a83a25f913365d66ad04ee9332eee7b73 languageName: node linkType: hard @@ -11276,21 +11266,21 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:^25.0.0": - version: 25.0.1 - resolution: "jsdom@npm:25.0.1" +"jsdom@npm:^26.0.0": + version: 26.0.0 + resolution: "jsdom@npm:26.0.0" dependencies: - cssstyle: "npm:^4.1.0" + cssstyle: "npm:^4.2.1" data-urls: "npm:^5.0.0" decimal.js: "npm:^10.4.3" - form-data: "npm:^4.0.0" + form-data: "npm:^4.0.1" html-encoding-sniffer: "npm:^4.0.0" http-proxy-agent: "npm:^7.0.2" - https-proxy-agent: "npm:^7.0.5" + https-proxy-agent: "npm:^7.0.6" is-potential-custom-element-name: "npm:^1.0.1" - nwsapi: "npm:^2.2.12" - parse5: "npm:^7.1.2" - rrweb-cssom: "npm:^0.7.1" + nwsapi: "npm:^2.2.16" + parse5: "npm:^7.2.1" + rrweb-cssom: "npm:^0.8.0" saxes: "npm:^6.0.0" symbol-tree: "npm:^3.2.4" tough-cookie: "npm:^5.0.0" @@ -11298,15 +11288,15 @@ __metadata: webidl-conversions: "npm:^7.0.0" whatwg-encoding: "npm:^3.1.1" whatwg-mimetype: "npm:^4.0.0" - whatwg-url: "npm:^14.0.0" + whatwg-url: "npm:^14.1.0" ws: "npm:^8.18.0" xml-name-validator: "npm:^5.0.0" peerDependencies: - canvas: ^2.11.2 + canvas: ^3.0.0 peerDependenciesMeta: canvas: optional: true - checksum: 10c0/6bda32a6dfe4e37a30568bf51136bdb3ba9c0b72aadd6356280404275a34c9e097c8c25b5eb3c742e602623741e172da977ff456684befd77c9042ed9bf8c2b4 + checksum: 10c0/e48725ba4027edcfc9bca5799eaec72c6561ecffe3675a8ff87fe9c3541ca4ff9f82b4eff5b3d9c527302da0d859b2f60e9364347a5d42b77f5c76c436c569dc languageName: node linkType: hard @@ -11773,6 +11763,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.2": + version: 11.0.2 + resolution: "lru-cache@npm:11.0.2" + checksum: 10c0/c993b8e06ead0b24b969c1dbb5b301716aed66e320e9014a80012f5febe280b438f28ff50046b2c55ff404e889351ccb332ff91f8dd175a21f5eae80e3fb155f + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -12600,10 +12597,10 @@ __metadata: languageName: node linkType: hard -"nwsapi@npm:^2.2.12, nwsapi@npm:^2.2.2": - version: 2.2.12 - resolution: "nwsapi@npm:2.2.12" - checksum: 10c0/95e9623d63df111405503df8c5d800e26f71675d319e2c9c70cddfa31e5ace1d3f8b6d98d354544fc156a1506d920ec291e303fab761e4f99296868e199a466e +"nwsapi@npm:^2.2.16, nwsapi@npm:^2.2.2": + version: 2.2.16 + resolution: "nwsapi@npm:2.2.16" + checksum: 10c0/0aa0637f4d51043d0183d994e08336bae996b03b42984381bf09ebdf3ff4909c018eda6b2a8aba0a08f3ea8303db8a0dad0608b38dc0bff15fd87017286ae21a languageName: node linkType: hard @@ -13000,12 +12997,12 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^7.0.0, parse5@npm:^7.1.1, parse5@npm:^7.1.2": - version: 7.1.2 - resolution: "parse5@npm:7.1.2" +"parse5@npm:^7.0.0, parse5@npm:^7.1.1, parse5@npm:^7.2.1": + version: 7.2.1 + resolution: "parse5@npm:7.2.1" dependencies: - entities: "npm:^4.4.0" - checksum: 10c0/297d7af8224f4b5cb7f6617ecdae98eeaed7f8cbd78956c42785e230505d5a4f07cef352af10d3006fa5c1544b76b57784d3a22d861ae071bbc460c649482bf4 + entities: "npm:^4.5.0" + checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80 languageName: node linkType: hard @@ -14685,27 +14682,25 @@ __metadata: languageName: node linkType: hard -"react-intl@npm:^6.4.2": - version: 6.8.6 - resolution: "react-intl@npm:6.8.6" +"react-intl@npm:^7.0.0": + version: 7.1.0 + resolution: "react-intl@npm:7.1.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.3" - "@formatjs/icu-messageformat-parser": "npm:2.9.3" - "@formatjs/intl": "npm:2.10.13" - "@formatjs/intl-displaynames": "npm:6.8.3" - "@formatjs/intl-listformat": "npm:7.7.3" + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/icu-messageformat-parser": "npm:2.9.8" + "@formatjs/intl": "npm:3.1.0" "@types/hoist-non-react-statics": "npm:3" "@types/react": "npm:16 || 17 || 18" hoist-non-react-statics: "npm:3" - intl-messageformat: "npm:10.7.5" + intl-messageformat: "npm:10.7.11" tslib: "npm:2" peerDependencies: react: ^16.6.0 || 17 || 18 - typescript: ^4.7 || 5 + typescript: 5 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/ec88cc2b1d0edf089f04c8061ffda730840fb3317177d0dc1a6df208245ec278f52b4ca8546ff9511b0b28ff2d89863c5837a274d33731d4c8f75a3b3baafbe2 + checksum: 10c0/9d69e316a5f5c6d31fa77f136b595079db2f75f63398cf8253d407878246dd5bcf0cc2eb4d7d4aa0646530ee58b16ce9a8c3876a5c2f0dc38fdda7e4f8c07615 languageName: node linkType: hard @@ -14971,15 +14966,15 @@ __metadata: linkType: hard "react-textarea-autosize@npm:^8.4.1": - version: 8.5.6 - resolution: "react-textarea-autosize@npm:8.5.6" + version: 8.5.7 + resolution: "react-textarea-autosize@npm:8.5.7" dependencies: "@babel/runtime": "npm:^7.20.13" use-composed-ref: "npm:^1.3.0" use-latest: "npm:^1.2.1" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/652d290d316c55a253507ecf65ca27f2162801dace10c715f2241203e81d82e9de6d282095b758b26c6bc9e1af9ca552cab5c3a361b230e5fcf25bec31e1bd25 + checksum: 10c0/ff004797ea28faca442460c42b30042d4c34a140f324eeeddee74508688dbc0f98966d21282c945630655006ad28a87edbcb59e6da7f9e762f4f3042c72f9f24 languageName: node linkType: hard @@ -15550,10 +15545,10 @@ __metadata: languageName: node linkType: hard -"rrweb-cssom@npm:^0.7.1": - version: 0.7.1 - resolution: "rrweb-cssom@npm:0.7.1" - checksum: 10c0/127b8ca6c8aac45e2755abbae6138d4a813b1bedc2caabf79466ae83ab3cfc84b5bfab513b7033f0aa4561c7753edf787d0dd01163ceacdee2e8eb1b6bf7237e +"rrweb-cssom@npm:^0.8.0": + version: 0.8.0 + resolution: "rrweb-cssom@npm:0.8.0" + checksum: 10c0/56f2bfd56733adb92c0b56e274c43f864b8dd48784d6fe946ef5ff8d438234015e59ad837fc2ad54714b6421384141c1add4eb569e72054e350d1f8a50b8ac7b languageName: node linkType: hard @@ -17872,11 +17867,11 @@ __metadata: linkType: hard "uuid@npm:^11.0.0": - version: 11.0.4 - resolution: "uuid@npm:11.0.4" + version: 11.0.5 + resolution: "uuid@npm:11.0.5" bin: uuid: dist/esm/bin/uuid - checksum: 10c0/3c13591c4dedaa3741f925e284df5974e3d6e0b1cb0f6f75f98c36f9c01d2a414350364fd067613ef600a21c6973dab0506530d4f499ff878f32a06f84569ead + checksum: 10c0/6f59f0c605e02c14515401084ca124b9cb462b4dcac866916a49862bcf831874508a308588c23a7718269226ad11a92da29b39d761ad2b86e736623e3a33b6e7 languageName: node linkType: hard @@ -18294,13 +18289,13 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^14.0.0": - version: 14.0.0 - resolution: "whatwg-url@npm:14.0.0" +"whatwg-url@npm:^14.0.0, whatwg-url@npm:^14.1.0": + version: 14.1.0 + resolution: "whatwg-url@npm:14.1.0" dependencies: tr46: "npm:^5.0.0" webidl-conversions: "npm:^7.0.0" - checksum: 10c0/ac32e9ba9d08744605519bbe9e1371174d36229689ecc099157b6ba102d4251a95e81d81f3d80271eb8da182eccfa65653f07f0ab43ea66a6934e643fd091ba9 + checksum: 10c0/f00104f1c67ce086ba8ffedab529cbbd9aefd8c0a6555320026de7aeff31f91c38680f95818b140a7c9cc657cde3781e567835dda552ddb1e2b8faaba0ac3cb6 languageName: node linkType: hard