diff --git a/.bundler-audit.yml b/.bundler-audit.yml new file mode 100644 index 0000000000..0671df390f --- /dev/null +++ b/.bundler-audit.yml @@ -0,0 +1,6 @@ +--- +ignore: + # devise-two-factor advisory about brute-forcing TOTP + # We have rate-limits on authentication endpoints in place (including second + # factor verification) since Mastodon v3.2.0 + - CVE-2024-0227 diff --git a/.devcontainer/codespaces/devcontainer.json b/.devcontainer/codespaces/devcontainer.json index ca9156fdaa..b32e4026d2 100644 --- a/.devcontainer/codespaces/devcontainer.json +++ b/.devcontainer/codespaces/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "features": { - "ghcr.io/devcontainers/features/sshd:1": {} + "ghcr.io/devcontainers/features/sshd:1": {}, }, "runServices": ["app", "db", "redis"], @@ -15,16 +15,16 @@ "portsAttributes": { "3000": { "label": "web", - "onAutoForward": "notify" + "onAutoForward": "notify", }, "4000": { "label": "stream", - "onAutoForward": "silent" - } + "onAutoForward": "silent", + }, }, "otherPortsAttributes": { - "onAutoForward": "silent" + "onAutoForward": "silent", }, "remoteEnv": { @@ -33,7 +33,7 @@ "STREAMING_API_BASE_URL": "https://${localEnv:CODESPACE_NAME}-4000.app.github.dev", "DISABLE_FORGERY_REQUEST_PROTECTION": "true", "ES_ENABLED": "", - "LIBRE_TRANSLATE_ENDPOINT": "" + "LIBRE_TRANSLATE_ENDPOINT": "", }, "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", @@ -43,7 +43,7 @@ "customizations": { "vscode": { "settings": {}, - "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] - } - } + "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"], + }, + }, } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fa8d6542c1..ed71235b3b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "features": { - "ghcr.io/devcontainers/features/sshd:1": {} + "ghcr.io/devcontainers/features/sshd:1": {}, }, "forwardPorts": [3000, 4000], @@ -14,17 +14,17 @@ "3000": { "label": "web", "onAutoForward": "notify", - "requireLocalPort": true + "requireLocalPort": true, }, "4000": { "label": "stream", "onAutoForward": "silent", - "requireLocalPort": true - } + "requireLocalPort": true, + }, }, "otherPortsAttributes": { - "onAutoForward": "silent" + "onAutoForward": "silent", }, "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", @@ -34,7 +34,7 @@ "customizations": { "vscode": { "settings": {}, - "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] - } - } + "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"], + }, + }, } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 21ee078d60..88979723c3 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -70,7 +70,7 @@ services: hard: -1 libretranslate: - image: libretranslate/libretranslate:v1.5.3 + image: libretranslate/libretranslate:v1.5.4 restart: unless-stopped volumes: - lt-data:/home/libretranslate/.local diff --git a/.github/workflows/build-security.yml b/.github/workflows/build-security.yml new file mode 100644 index 0000000000..9919fbf138 --- /dev/null +++ b/.github/workflows/build-security.yml @@ -0,0 +1,61 @@ +name: Build security nightly container image +on: + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + compute-suffix: + runs-on: ubuntu-latest + steps: + - id: version_vars + env: + TZ: Etc/UTC + run: | + echo mastodon_version_prerelease=nightly.$(date --date='next day' +'%Y-%m-%d')-security>> $GITHUB_OUTPUT + outputs: + prerelease: ${{ steps.version_vars.outputs.mastodon_version_prerelease }} + + build-image: + needs: compute-suffix + uses: ./.github/workflows/build-container-image.yml + with: + file_to_build: Dockerfile + platforms: linux/amd64,linux/arm64 + use_native_arm64_builder: true + cache: false + push_to_images: | + ghcr.io/${{ github.repository_owner }}/mastodon + version_prerelease: ${{ needs.compute-suffix.outputs.prerelease }} + labels: | + org.opencontainers.image.description=Nightly build image used for testing purposes + flavor: | + latest=true + tags: | + type=raw,value=edge + type=raw,value=nightly + type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }} + secrets: inherit + + build-image-streaming: + needs: compute-suffix + uses: ./.github/workflows/build-container-image.yml + with: + file_to_build: streaming/Dockerfile + platforms: linux/amd64,linux/arm64 + use_native_arm64_builder: false + cache: false + push_to_images: | + ghcr.io/${{ github.repository_owner }}/mastodon + version_prerelease: ${{ needs.compute-suffix.outputs.prerelease }} + labels: | + org.opencontainers.image.description=Nightly build image used for testing purposes + flavor: | + latest=true + tags: | + type=raw,value=edge + type=raw,value=nightly + type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }} + secrets: inherit diff --git a/.github/workflows/test-migrations-one-step.yml b/.github/workflows/test-migrations-one-step.yml index 5dca8e376d..1ff5cc06b9 100644 --- a/.github/workflows/test-migrations-one-step.yml +++ b/.github/workflows/test-migrations-one-step.yml @@ -78,23 +78,8 @@ jobs: - name: Create database run: './bin/rails db:create' - - name: Run migrations up to v2.0.0 - run: './bin/rails db:migrate VERSION=20171010025614' - - - name: Populate database with test data - run: './bin/rails tests:migrations:populate_v2' - - - name: Run migrations up to v2.4.0 - run: './bin/rails db:migrate VERSION=20180514140000' - - - name: Populate database with test data - run: './bin/rails tests:migrations:populate_v2_4' - - - name: Run migrations up to v2.4.3 - run: './bin/rails db:migrate VERSION=20180707154237' - - - name: Populate database with test data - run: './bin/rails tests:migrations:populate_v2_4_3' + - name: Run historical migrations with data population + run: './bin/rails tests:migrations:prepare_database' - name: Run all remaining migrations run: './bin/rails db:migrate' diff --git a/.github/workflows/test-migrations-two-step.yml b/.github/workflows/test-migrations-two-step.yml index 59485d285d..6698847315 100644 --- a/.github/workflows/test-migrations-two-step.yml +++ b/.github/workflows/test-migrations-two-step.yml @@ -45,6 +45,7 @@ jobs: --health-retries 5 ports: - 5432:5432 + redis: image: redis:7-alpine options: >- @@ -77,28 +78,11 @@ jobs: - name: Create database run: './bin/rails db:create' - - name: Run migrations up to v2.0.0 - run: './bin/rails db:migrate VERSION=20171010025614' - - - name: Populate database with test data - run: './bin/rails tests:migrations:populate_v2' - - - name: Run pre-deployment migrations up to v2.4.0 - run: './bin/rails db:migrate VERSION=20180514140000' + - name: Run historical migrations with data population + run: './bin/rails tests:migrations:prepare_database' env: SKIP_POST_DEPLOYMENT_MIGRATIONS: true - - name: Populate database with test data - run: './bin/rails tests:migrations:populate_v2_4' - - - name: Run migrations up to v2.4.3 - run: './bin/rails db:migrate VERSION=20180707154237' - env: - SKIP_POST_DEPLOYMENT_MIGRATIONS: true - - - name: Populate database with test data - run: './bin/rails tests:migrations:populate_v2_4_3' - - name: Run all remaining pre-deployment migrations run: './bin/rails db:migrate' env: diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index ae25648a0b..346703ced4 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -52,7 +52,7 @@ jobs: run: | tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: matrix.mode == 'test' with: path: |- @@ -117,7 +117,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: './' name: ${{ github.sha }} @@ -193,7 +193,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: './public' name: ${{ github.sha }} @@ -213,14 +213,14 @@ jobs: - run: bundle exec rake spec:system - name: Archive logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: e2e-logs-${{ matrix.ruby-version }} path: log/ - name: Archive test screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: e2e-screenshots @@ -297,7 +297,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: './public' name: ${{ github.sha }} @@ -317,14 +317,14 @@ jobs: - run: bin/rspec --tag search - name: Archive logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: test-search-logs-${{ matrix.ruby-version }} path: log/ - name: Archive test screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: test-search-screenshots diff --git a/.rubocop.yml b/.rubocop.yml index bedd8f7850..64021b4cec 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -103,9 +103,26 @@ Rails/Exit: - 'config/boot.rb' - 'lib/mastodon/cli/*.rb' -Rails/SkipsModelValidations: +# Reason: Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions +# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railslexicallyscopedactionfilter +Rails/LexicallyScopedActionFilter: Exclude: - - 'db/*migrate/**/*' + - 'app/controllers/auth/*' + +# Reason: These tasks are doing local work which do not need full env loaded +# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsrakeenvironment +Rails/RakeEnvironment: + Exclude: + - 'lib/tasks/auto_annotate_models.rake' + - 'lib/tasks/emojis.rake' + - 'lib/tasks/mastodon.rake' + - 'lib/tasks/repo.rake' + - 'lib/tasks/statistics.rake' + +# Reason: There are appropriate times to use these features +# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsskipsmodelvalidations +Rails/SkipsModelValidations: + Enabled: false # Reason: We want to preserve the ability to migrate from arbitrary old versions, # and cannot guarantee that every installation has run every migration as they upgrade. diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 98943d9c92..47c49cb9a4 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-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.59.0. +# using RuboCop version 1.60.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 @@ -13,13 +13,6 @@ Bundler/OrderedGems: Exclude: - 'Gemfile' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. -# URISchemes: http, https -Layout/LineLength: - Exclude: - - 'app/models/account.rb' - Lint/NonLocalExitFromIterator: Exclude: - 'app/helpers/jsonld_helper.rb' @@ -64,64 +57,10 @@ Rails/HasAndBelongsToMany: - 'app/models/status.rb' - 'app/models/tag.rb' -# Configuration parameters: Include. -# Include: app/controllers/**/*.rb, app/mailers/**/*.rb -Rails/LexicallyScopedActionFilter: - Exclude: - - 'app/controllers/auth/passwords_controller.rb' - - 'app/controllers/auth/registrations_controller.rb' - Rails/OutputSafety: Exclude: - 'config/initializers/simple_form.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Include. -# Include: **/Rakefile, **/*.rake -Rails/RakeEnvironment: - Exclude: - - 'lib/tasks/auto_annotate_models.rake' - - 'lib/tasks/db.rake' - - 'lib/tasks/emojis.rake' - - 'lib/tasks/mastodon.rake' - - 'lib/tasks/repo.rake' - - 'lib/tasks/statistics.rake' - -# Configuration parameters: ForbiddenMethods, AllowedMethods. -# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all -Rails/SkipsModelValidations: - Exclude: - - 'app/controllers/admin/invites_controller.rb' - - 'app/controllers/concerns/session_tracking_concern.rb' - - 'app/models/concerns/account/merging.rb' - - 'app/models/concerns/expireable.rb' - - 'app/models/status.rb' - - 'app/models/trends/links.rb' - - 'app/models/trends/preview_card_batch.rb' - - 'app/models/trends/preview_card_provider_batch.rb' - - 'app/models/trends/status_batch.rb' - - 'app/models/trends/statuses.rb' - - 'app/models/trends/tag_batch.rb' - - 'app/models/trends/tags.rb' - - 'app/models/user.rb' - - 'app/services/activitypub/process_status_update_service.rb' - - 'app/services/approve_appeal_service.rb' - - 'app/services/block_domain_service.rb' - - 'app/services/delete_account_service.rb' - - 'app/services/process_mentions_service.rb' - - 'app/services/unallow_domain_service.rb' - - 'app/services/unblock_domain_service.rb' - - 'app/services/update_status_service.rb' - - 'app/workers/activitypub/post_upgrade_worker.rb' - - 'app/workers/move_worker.rb' - - 'app/workers/scheduler/ip_cleanup_scheduler.rb' - - 'app/workers/scheduler/scheduled_statuses_scheduler.rb' - - 'lib/mastodon/cli/accounts.rb' - - 'lib/mastodon/cli/maintenance.rb' - - 'spec/lib/activitypub/activity/follow_spec.rb' - - 'spec/services/follow_service_spec.rb' - - 'spec/services/update_account_service_spec.rb' - # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/UniqueValidationWithoutIndex: @@ -131,38 +70,6 @@ Rails/UniqueValidationWithoutIndex: - 'app/models/identity.rb' - 'app/models/webauthn_credential.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: exists, where -Rails/WhereExists: - Exclude: - - 'app/controllers/activitypub/inboxes_controller.rb' - - 'app/controllers/admin/email_domain_blocks_controller.rb' - - 'app/lib/activitypub/activity/create.rb' - - 'app/lib/delivery_failure_tracker.rb' - - 'app/lib/feed_manager.rb' - - 'app/lib/status_cache_hydrator.rb' - - 'app/lib/suspicious_sign_in_detector.rb' - - 'app/models/concerns/account/interactions.rb' - - 'app/models/featured_tag.rb' - - 'app/models/poll.rb' - - 'app/models/session_activation.rb' - - 'app/models/status.rb' - - 'app/models/user.rb' - - 'app/policies/status_policy.rb' - - 'app/serializers/rest/announcement_serializer.rb' - - 'app/serializers/rest/tag_serializer.rb' - - 'app/services/activitypub/fetch_remote_status_service.rb' - - 'app/services/vote_service.rb' - - 'app/validators/reaction_validator.rb' - - 'app/validators/vote_validator.rb' - - 'app/workers/move_worker.rb' - - 'lib/tasks/tests.rake' - - 'spec/models/account_spec.rb' - - 'spec/services/activitypub/process_collection_service_spec.rb' - - 'spec/services/purge_domain_service_spec.rb' - - 'spec/services/unallow_domain_service_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowedMethods, AllowedPatterns. # AllowedMethods: ==, equal?, eql? @@ -201,7 +108,6 @@ Style/FetchEnvVar: # AllowedMethods: redirect Style/FormatStringToken: Exclude: - - 'app/models/privacy_policy.rb' - 'config/initializers/devise.rb' - 'lib/paperclip/color_extractor.rb' @@ -215,10 +121,6 @@ Style/GlobalStdStream: # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Exclude: - - 'app/controllers/admin/confirmations_controller.rb' - - 'app/controllers/auth/confirmations_controller.rb' - - 'app/controllers/auth/passwords_controller.rb' - - 'app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb' - 'app/lib/activitypub/activity/block.rb' - 'app/lib/request.rb' - 'app/lib/request_pool.rb' @@ -373,13 +275,6 @@ Style/StringLiterals: - 'config/initializers/webauthn.rb' - 'config/routes.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, AllowSafeAssignment. -# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex -Style/TernaryParentheses: - Exclude: - - 'config/environments/development.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, no_comma diff --git a/.ruby-version b/.ruby-version index be94e6f53d..b347b11eac 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.2 +3.2.3 diff --git a/Dockerfile b/Dockerfile index 96f8b5cd27..119c266b89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,15 +7,15 @@ ARG TARGETPLATFORM=${TARGETPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM} -# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.2"] -ARG RUBY_VERSION="3.2.2" +# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.3"] +ARG RUBY_VERSION="3.2.3" # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] ARG NODE_MAJOR_VERSION="20" # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] 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.2.2-slim-bookworm) +# Ruby image to use for base image based on combined variables (ex: 3.2.3-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/FEDERATION.md b/FEDERATION.md index e3721d7241..2819fa935a 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -1,19 +1,35 @@ -## ActivityPub federation in Mastodon +# Federation + +## Supported federation protocols and standards + +- [ActivityPub](https://www.w3.org/TR/activitypub/) (Server-to-Server) +- [WebFinger](https://webfinger.net/) +- [Http Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) +- [NodeInfo](https://nodeinfo.diaspora.software/) + +## Supported FEPs + +- [FEP-67ff: FEDERATION.md](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) +- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md) +- [FEP-8fcf: Followers collection synchronization across servers](https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md) +- [FEP-5feb: Search indexing consent for actors](https://codeberg.org/fediverse/fep/src/branch/main/fep/5feb/fep-5feb.md) + +## ActivityPub in Mastodon Mastodon largely follows the ActivityPub server-to-server specification but it makes uses of some non-standard extensions, some of which are required for interacting with Mastodon at all. -Supported vocabulary: https://docs.joinmastodon.org/spec/activitypub/ +- [Supported ActivityPub vocabulary](https://docs.joinmastodon.org/spec/activitypub/) ### Required extensions -#### Webfinger +#### WebFinger In Mastodon, users are identified by a `username` and `domain` pair (e.g., `Gargron@mastodon.social`). This is used both for discovery and for unambiguously mentioning users across the fediverse. Furthermore, this is part of Mastodon's database design from its very beginnings. As a result, Mastodon requires that each ActivityPub actor uniquely maps back to an `acct:` URI that can be resolved via WebFinger. -More information and examples are available at: https://docs.joinmastodon.org/spec/webfinger/ +- [WebFinger information and examples](https://docs.joinmastodon.org/spec/webfinger/) #### HTTP Signatures @@ -21,11 +37,13 @@ In order to authenticate activities, Mastodon relies on HTTP Signatures, signing Mastodon requires all `POST` requests to be signed, and MAY require `GET` requests to be signed, depending on the configuration of the Mastodon server. -More information on HTTP Signatures, as well as examples, can be found here: https://docs.joinmastodon.org/spec/security/#http +- [HTTP Signatures information and examples](https://docs.joinmastodon.org/spec/security/#http) ### Optional extensions -- Linked-Data Signatures: https://docs.joinmastodon.org/spec/security/#ld -- Bearcaps: https://docs.joinmastodon.org/spec/bearcaps/ -- Followers collection synchronization: https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md -- Search indexing consent for actors: https://codeberg.org/fediverse/fep/src/branch/main/fep/5feb/fep-5feb.md +- [Linked-Data Signatures](https://docs.joinmastodon.org/spec/security/#ld) +- [Bearcaps](https://docs.joinmastodon.org/spec/bearcaps/) + +### Additional documentation + +- [Mastodon documentation](https://docs.joinmastodon.org/) diff --git a/Gemfile b/Gemfile index 4176ddb8de..cd0ef255b1 100644 --- a/Gemfile +++ b/Gemfile @@ -39,8 +39,7 @@ end gem 'net-ldap', '~> 0.18' -# TODO: Point back at released omniauth-cas gem when new version is released -gem 'omniauth-cas', github: 'dlindahl/omniauth-cas', ref: '9d9d3a91b316c55d49ab6e621977f2067010c5bf' +gem 'omniauth-cas', '~> 3.0.0.beta.1' gem 'omniauth-saml', '~> 2.0' gem 'omniauth_openid_connect', '~> 0.6.1' gem 'omniauth', '~> 2.0' @@ -124,7 +123,7 @@ group :test do gem 'database_cleaner-active_record' # Used to mock environment variables - gem 'climate_control', '~> 0.2' + gem 'climate_control' # Generating fake data for specs gem 'faker', '~> 3.2' diff --git a/Gemfile.lock b/Gemfile.lock index ccb9f4b115..57b2580722 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,16 +7,6 @@ GIT hkdf (~> 0.2) jwt (~> 2.0) -GIT - remote: https://github.com/dlindahl/omniauth-cas.git - revision: 9d9d3a91b316c55d49ab6e621977f2067010c5bf - ref: 9d9d3a91b316c55d49ab6e621977f2067010c5bf - specs: - omniauth-cas (3.0.0) - addressable (~> 2.8) - nokogiri (~> 1.12) - omniauth (~> 2.1) - GIT remote: https://github.com/jhawthorn/nsa.git revision: e020fcc3a54d993ab45b7194d89ab720296c111b @@ -31,35 +21,35 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.1.2) - actionpack (= 7.1.2) - activesupport (= 7.1.2) + actioncable (7.1.3) + actionpack (= 7.1.3) + activesupport (= 7.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.2) - actionpack (= 7.1.2) - activejob (= 7.1.2) - activerecord (= 7.1.2) - activestorage (= 7.1.2) - activesupport (= 7.1.2) + actionmailbox (7.1.3) + actionpack (= 7.1.3) + activejob (= 7.1.3) + activerecord (= 7.1.3) + activestorage (= 7.1.3) + activesupport (= 7.1.3) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.2) - actionpack (= 7.1.2) - actionview (= 7.1.2) - activejob (= 7.1.2) - activesupport (= 7.1.2) + actionmailer (7.1.3) + actionpack (= 7.1.3) + actionview (= 7.1.3) + activejob (= 7.1.3) + activesupport (= 7.1.3) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.2) - actionview (= 7.1.2) - activesupport (= 7.1.2) + actionpack (7.1.3) + actionview (= 7.1.3) + activesupport (= 7.1.3) nokogiri (>= 1.8.5) racc rack (>= 2.2.4) @@ -67,15 +57,15 @@ GEM rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.2) - actionpack (= 7.1.2) - activerecord (= 7.1.2) - activestorage (= 7.1.2) - activesupport (= 7.1.2) + actiontext (7.1.3) + actionpack (= 7.1.3) + activerecord (= 7.1.3) + activestorage (= 7.1.3) + activesupport (= 7.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.2) - activesupport (= 7.1.2) + actionview (7.1.3) + activesupport (= 7.1.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) @@ -85,22 +75,22 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (7.1.2) - activesupport (= 7.1.2) + activejob (7.1.3) + activesupport (= 7.1.3) globalid (>= 0.3.6) - activemodel (7.1.2) - activesupport (= 7.1.2) - activerecord (7.1.2) - activemodel (= 7.1.2) - activesupport (= 7.1.2) + activemodel (7.1.3) + activesupport (= 7.1.3) + activerecord (7.1.3) + activemodel (= 7.1.3) + activesupport (= 7.1.3) timeout (>= 0.4.0) - activestorage (7.1.2) - actionpack (= 7.1.2) - activejob (= 7.1.2) - activerecord (= 7.1.2) - activesupport (= 7.1.2) + activestorage (7.1.3) + actionpack (= 7.1.3) + activejob (= 7.1.3) + activerecord (= 7.1.3) + activesupport (= 7.1.3) marcel (~> 1.0) - activesupport (7.1.2) + activesupport (7.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -160,12 +150,12 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bigdecimal (3.1.5) + bigdecimal (3.1.6) bindata (2.4.15) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) blurhash (0.1.7) - bootsnap (1.17.0) + bootsnap (1.17.1) msgpack (~> 1.2) brakeman (6.1.1) racc @@ -190,15 +180,15 @@ GEM activesupport cbor (0.5.9.6) charlock_holmes (0.7.7) - chewy (7.4.0) + chewy (7.5.0) activesupport (>= 5.2) elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch-dsl chunky_png (1.4.0) - climate_control (0.2.0) + climate_control (1.2.0) cocoon (1.2.15) color_diff (0.1) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) cose (1.3.0) cbor (~> 0.5.9) @@ -267,7 +257,7 @@ GEM tzinfo excon (0.109.0) fabrication (2.31.0) - faker (3.2.2) + faker (3.2.3) i18n (>= 1.8.11, < 2) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -329,7 +319,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.53.0) + haml_lint (0.55.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -370,7 +360,7 @@ GEM rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) idn-ruby (0.1.5) - io-console (0.7.1) + io-console (0.7.2) irb (1.11.1) rdoc reline (>= 0.4.2) @@ -408,12 +398,12 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - kt-paperclip (7.2.1) + kt-paperclip (7.2.2) activemodel (>= 4.2.0) activesupport (>= 4.2.0) marcel (~> 1.0.1) mime-types - terrapin (~> 0.6.0) + terrapin (>= 0.6.0, < 2.0) language_server-protocol (3.17.0.3) launchy (2.5.2) addressable (~> 2.8) @@ -455,7 +445,7 @@ GEM mime-types-data (3.2023.1205) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.21.2) msgpack (1.7.2) multi_json (1.15.0) multipart-post (2.3.0) @@ -464,7 +454,7 @@ GEM uri net-http-persistent (4.0.2) connection_pool (~> 2.2) - net-imap (0.4.4) + net-imap (0.4.9.1) date net-protocol net-ldap (0.19.0) @@ -472,7 +462,7 @@ GEM net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol nio4r (2.5.9) nokogiri (1.16.0) @@ -484,6 +474,10 @@ GEM hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection + omniauth-cas (3.0.0.beta.1) + addressable (~> 2.8) + nokogiri (~> 1.12) + omniauth (~> 2.1) omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) @@ -510,7 +504,7 @@ GEM orm_adapter (0.5.0) ox (2.14.17) parallel (1.24.0) - parser (3.2.2.4) + parser (3.3.0.5) ast (~> 2.4.1) racc parslet (2.0.0) @@ -558,27 +552,27 @@ GEM rack rack-proxy (0.7.6) rack - rack-session (1.0.1) + rack-session (1.0.2) rack (< 3) rack-test (2.1.0) rack (>= 1.3) rackup (1.0.0) rack (< 3) webrick - rails (7.1.2) - actioncable (= 7.1.2) - actionmailbox (= 7.1.2) - actionmailer (= 7.1.2) - actionpack (= 7.1.2) - actiontext (= 7.1.2) - actionview (= 7.1.2) - activejob (= 7.1.2) - activemodel (= 7.1.2) - activerecord (= 7.1.2) - activestorage (= 7.1.2) - activesupport (= 7.1.2) + rails (7.1.3) + actioncable (= 7.1.3) + actionmailbox (= 7.1.3) + actionmailer (= 7.1.3) + actionpack (= 7.1.3) + actiontext (= 7.1.3) + actionview (= 7.1.3) + activejob (= 7.1.3) + activemodel (= 7.1.3) + activerecord (= 7.1.3) + activestorage (= 7.1.3) + activesupport (= 7.1.3) bundler (>= 1.15.0) - railties (= 7.1.2) + railties (= 7.1.3) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -593,9 +587,9 @@ GEM rails-i18n (7.0.8) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.1.2) - actionpack (= 7.1.2) - activesupport (= 7.1.2) + railties (7.1.3) + actionpack (= 7.1.3) + activesupport (= 7.1.3) irb rackup (>= 1.0.0) rake (>= 12.2) @@ -606,8 +600,8 @@ GEM rdf (3.3.1) bcp47_spec (~> 0.2) link_header (~> 0.0, >= 0.0.8) - rdf-normalize (0.6.1) - rdf (~> 3.2) + rdf-normalize (0.7.0) + rdf (~> 3.3) rdoc (6.6.2) psych (>= 4.0.0) redcarpet (3.6.0) @@ -616,7 +610,7 @@ GEM redis (>= 4) redlock (1.3.2) redis (>= 3.0.0, < 6.0) - regexp_parser (2.8.3) + regexp_parser (2.9.0) reline (0.4.2) io-console (~> 0.5) request_store (1.5.1) @@ -642,7 +636,7 @@ GEM rspec-mocks (3.12.6) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-rails (6.1.0) + rspec-rails (6.1.1) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -656,11 +650,11 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.12.1) - rubocop (1.59.0) + rubocop (1.60.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.4) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) @@ -702,7 +696,8 @@ GEM scenic (1.7.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - selenium-webdriver (4.16.0) + selenium-webdriver (4.17.0) + base64 (~> 0.2) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -736,7 +731,7 @@ GEM simplecov-lcov (0.8.0) simplecov_json_formatter (0.1.4) smart_properties (1.17.0) - stackprof (0.2.25) + stackprof (0.2.26) statsd-ruby (1.5.0) stoplight (3.0.2) redlock (~> 1.0) @@ -751,8 +746,8 @@ GEM temple (0.10.3) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - terrapin (0.6.0) - climate_control (>= 0.0.3, < 1.0) + terrapin (1.0.1) + climate_control test-prof (1.3.1) thor (1.3.0) tilt (2.3.0) @@ -841,7 +836,7 @@ DEPENDENCIES capybara (~> 3.39) charlock_holmes (~> 0.7.7) chewy (~> 7.3) - climate_control (~> 0.2) + climate_control cocoon (~> 1.2) color_diff (~> 0.1) concurrent-ruby @@ -894,7 +889,7 @@ DEPENDENCIES nsa! oj (~> 3.14) omniauth (~> 2.0) - omniauth-cas! + omniauth-cas (~> 3.0.0.beta.1) omniauth-rails_csrf_protection (~> 1.0) omniauth-saml (~> 2.0) omniauth_openid_connect (~> 0.6.1) @@ -961,4 +956,4 @@ RUBY VERSION ruby 3.2.2p53 BUNDLED WITH - 2.4.20 + 2.5.4 diff --git a/app/controllers/activitypub/followers_synchronizations_controller.rb b/app/controllers/activitypub/followers_synchronizations_controller.rb index 976caa3445..d2942104e5 100644 --- a/app/controllers/activitypub/followers_synchronizations_controller.rb +++ b/app/controllers/activitypub/followers_synchronizations_controller.rb @@ -24,7 +24,7 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro end def set_items - @items = @account.followers.where(Account.arel_table[:uri].matches("#{Account.sanitize_sql_like(uri_prefix)}/%", false, true)).or(@account.followers.where(uri: uri_prefix)).pluck(:uri) + @items = @account.followers.matches_uri_prefix(uri_prefix).pluck(:uri) end def collection_presenter diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index 5ee85474e7..ba85e0a722 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -24,7 +24,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController def unknown_affected_account? json = Oj.load(body, mode: :strict) - json.is_a?(Hash) && %w(Delete Update).include?(json['type']) && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists? + json.is_a?(Hash) && %w(Delete Update).include?(json['type']) && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.exists?(uri: json['actor']) rescue Oj::ParseError false end diff --git a/app/controllers/admin/action_logs_controller.rb b/app/controllers/admin/action_logs_controller.rb index 37a00ad225..8b8e83fde7 100644 --- a/app/controllers/admin/action_logs_controller.rb +++ b/app/controllers/admin/action_logs_controller.rb @@ -6,7 +6,7 @@ module Admin def index authorize :audit_log, :index? - @auditable_accounts = Account.where(id: Admin::ActionLog.select('distinct account_id')).select(:id, :username) + @auditable_accounts = Account.auditable.select(:id, :username) end private diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb index 6f4e426797..702550eecc 100644 --- a/app/controllers/admin/confirmations_controller.rb +++ b/app/controllers/admin/confirmations_controller.rb @@ -3,11 +3,11 @@ module Admin class ConfirmationsController < BaseController before_action :set_user - before_action :check_confirmation, only: [:resend] + before_action :redirect_confirmed_user, only: [:resend], if: :user_confirmed? def create authorize @user, :confirm? - @user.confirm! + @user.mark_email_as_confirmed! log_action :confirm, @user redirect_to admin_accounts_path end @@ -25,11 +25,13 @@ module Admin private - def check_confirmation - if @user.confirmed? - flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed') - redirect_to admin_accounts_path - end + def redirect_confirmed_user + flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed') + redirect_to admin_accounts_path + end + + def user_confirmed? + @user.confirmed? end end end diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb index ff754bc0b4..faa0a061a6 100644 --- a/app/controllers/admin/email_domain_blocks_controller.rb +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -38,7 +38,7 @@ module Admin log_action :create, @email_domain_block (@email_domain_block.other_domains || []).uniq.each do |domain| - next if EmailDomainBlock.where(domain: domain).exists? + next if EmailDomainBlock.exists?(domain: domain) other_email_domain_block = EmailDomainBlock.create!(domain: domain, allow_with_approval: @email_domain_block.allow_with_approval, parent: @email_domain_block) log_action :create, other_email_domain_block diff --git a/app/controllers/admin/export_domain_blocks_controller.rb b/app/controllers/admin/export_domain_blocks_controller.rb index ffc4478172..9caafd9684 100644 --- a/app/controllers/admin/export_domain_blocks_controller.rb +++ b/app/controllers/admin/export_domain_blocks_controller.rb @@ -49,7 +49,7 @@ module Admin next end - @warning_domains = Instance.where(domain: @domain_blocks.map(&:domain)).where('EXISTS (SELECT 1 FROM follows JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id WHERE accounts.domain = instances.domain)').pluck(:domain) + @warning_domains = instances_from_imported_blocks.pluck(:domain) rescue ActionController::ParameterMissing flash.now[:alert] = I18n.t('admin.export_domain_blocks.no_file') set_dummy_import! @@ -58,6 +58,10 @@ module Admin private + def instances_from_imported_blocks + Instance.with_domain_follows(@domain_blocks.map(&:domain)) + end + def export_filename 'domain_blocks.csv' end diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 21b1095f18..f60181f1eb 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController return [] if hide_results? scope = default_accounts - scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id + scope = scope.not_excluded_by_account(current_account) unless current_account.nil? || current_account.id == @account.id scope.merge(paginated_follows).to_a end @@ -30,7 +30,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController end def default_accounts - Account.includes(:active_relationships, :account_stat).references(:active_relationships) + Account.includes(:active_relationships, :account_stat, :user).references(:active_relationships) end def paginated_follows diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 1db521f79c..3ab8c1efd6 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController return [] if hide_results? scope = default_accounts - scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id + scope = scope.not_excluded_by_account(current_account) unless current_account.nil? || current_account.id == @account.id scope.merge(paginated_follows).to_a end @@ -30,7 +30,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController end def default_accounts - Account.includes(:passive_relationships, :account_stat).references(:passive_relationships) + Account.includes(:passive_relationships, :account_stat, :user).references(:passive_relationships) end def paginated_follows diff --git a/app/controllers/api/v1/annual_reports_controller.rb b/app/controllers/api/v1/annual_reports_controller.rb new file mode 100644 index 0000000000..9bc8e68ac2 --- /dev/null +++ b/app/controllers/api/v1/annual_reports_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Api::V1::AnnualReportsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index + before_action :require_user! + before_action :set_annual_report, except: :index + + def index + with_read_replica do + @presenter = AnnualReportsPresenter.new(GeneratedAnnualReport.where(account_id: current_account.id).pending) + @relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id) + end + + render json: @presenter, + serializer: REST::AnnualReportsSerializer, + relationships: @relationships + end + + def read + @annual_report.view! + render_empty + end + + private + + def set_annual_report + @annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: params[:id]) + end +end diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index 06a8bfa891..0934622f88 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -17,7 +17,7 @@ class Api::V1::BlocksController < Api::BaseController end def paginated_blocks - @paginated_blocks ||= Block.eager_load(target_account: :account_stat) + @paginated_blocks ||= Block.eager_load(target_account: [:account_stat, :user]) .joins(:target_account) .merge(Account.without_suspended) .where(account: current_account) diff --git a/app/controllers/api/v1/directories_controller.rb b/app/controllers/api/v1/directories_controller.rb index e79b20ce42..6c540404ea 100644 --- a/app/controllers/api/v1/directories_controller.rb +++ b/app/controllers/api/v1/directories_controller.rb @@ -27,7 +27,7 @@ class Api::V1::DirectoriesController < Api::BaseController scope.merge!(local_account_scope) if local_accounts? scope.merge!(account_exclusion_scope) if current_account scope.merge!(account_domain_block_scope) if current_account && !local_accounts? - end + end.includes(:account_stat, user: :role) end def local_accounts? diff --git a/app/controllers/api/v1/endorsements_controller.rb b/app/controllers/api/v1/endorsements_controller.rb index 46e3fcd647..2216a9860d 100644 --- a/app/controllers/api/v1/endorsements_controller.rb +++ b/app/controllers/api/v1/endorsements_controller.rb @@ -25,7 +25,7 @@ class Api::V1::EndorsementsController < Api::BaseController end def endorsed_accounts - current_account.endorsed_accounts.includes(:account_stat).without_suspended + current_account.endorsed_accounts.includes(:account_stat, :user).without_suspended end def insert_pagination_headers diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index ee717ebbcc..87f6df5f94 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -37,7 +37,7 @@ class Api::V1::FollowRequestsController < Api::BaseController end def default_accounts - Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests) + Account.without_suspended.includes(:follow_requests, :account_stat, :user).references(:follow_requests) end def paginated_follow_requests diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb index 8e12cb7b65..0604ad60fc 100644 --- a/app/controllers/api/v1/lists/accounts_controller.rb +++ b/app/controllers/api/v1/lists/accounts_controller.rb @@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController def load_accounts if unlimited? - @list.accounts.without_suspended.includes(:account_stat).all + @list.accounts.without_suspended.includes(:account_stat, :user).all else - @list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) + @list.accounts.without_suspended.includes(:account_stat, :user).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) end end diff --git a/app/controllers/api/v1/markers_controller.rb b/app/controllers/api/v1/markers_controller.rb index f8dfba8a94..8eaf7767df 100644 --- a/app/controllers/api/v1/markers_controller.rb +++ b/app/controllers/api/v1/markers_controller.rb @@ -19,7 +19,7 @@ class Api::V1::MarkersController < Api::BaseController @markers = {} resource_params.each_pair do |timeline, timeline_params| - @markers[timeline] = current_user.markers.find_or_initialize_by(timeline: timeline) + @markers[timeline] = current_user.markers.find_or_create_by(timeline: timeline) @markers[timeline].update!(timeline_params) end end diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index 555485823c..2fb685ac39 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -17,7 +17,7 @@ class Api::V1::MutesController < Api::BaseController end def paginated_mutes - @paginated_mutes ||= Mute.eager_load(:target_account) + @paginated_mutes ||= Mute.eager_load(target_account: [:account_stat, :user]) .joins(:target_account) .merge(Account.without_suspended) .where(account: current_account) diff --git a/app/controllers/api/v1/peers/search_controller.rb b/app/controllers/api/v1/peers/search_controller.rb index 0c503d9bc5..1780554c5d 100644 --- a/app/controllers/api/v1/peers/search_controller.rb +++ b/app/controllers/api/v1/peers/search_controller.rb @@ -27,7 +27,7 @@ class Api::V1::Peers::SearchController < Api::BaseController @domains = InstancesIndex.query(function_score: { query: { prefix: { - domain: TagManager.instance.normalize_domain(params[:q].strip), + domain: normalized_domain, }, }, @@ -37,11 +37,18 @@ class Api::V1::Peers::SearchController < Api::BaseController }, }).limit(10).pluck(:domain) else - domain = params[:q].strip - domain = TagManager.instance.normalize_domain(domain) - @domains = Instance.searchable.where(Instance.arel_table[:domain].matches("#{Instance.sanitize_sql_like(domain)}%", false, true)).limit(10).pluck(:domain) + domain = normalized_domain + @domains = Instance.searchable.domain_starts_with(domain).limit(10).pluck(:domain) end rescue Addressable::URI::InvalidURIError @domains = [] end + + def normalized_domain + TagManager.instance.normalize_domain(query_value) + end + + def query_value + params[:q].strip + end end diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index 3cca246ce8..069ad37cb2 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -14,14 +14,14 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas def load_accounts scope = default_accounts - scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope = scope.not_excluded_by_account(current_account) unless current_account.nil? scope.merge(paginated_favourites).to_a end def default_accounts Account .without_suspended - .includes(:favourites, :account_stat) + .includes(:favourites, :account_stat, :user) .references(:favourites) .where(favourites: { status_id: @status.id }) end diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index dd3e60846b..b8a997518d 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -14,12 +14,12 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base def load_accounts scope = default_accounts - scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope = scope.not_excluded_by_account(current_account) unless current_account.nil? scope.merge(paginated_statuses).to_a end def default_accounts - Account.without_suspended.includes(:statuses, :account_stat).references(:statuses) + Account.without_suspended.includes(:statuses, :account_stat, :user).references(:statuses) end def paginated_statuses diff --git a/app/controllers/api/v2/filters_controller.rb b/app/controllers/api/v2/filters_controller.rb index 2fcdeeae45..09d4813f34 100644 --- a/app/controllers/api/v2/filters_controller.rb +++ b/app/controllers/api/v2/filters_controller.rb @@ -35,7 +35,7 @@ class Api::V2::FiltersController < Api::BaseController private def set_filters - @filters = current_account.custom_filters.includes(:keywords) + @filters = current_account.custom_filters.includes(:keywords, :statuses) end def set_filter diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index 5d476d0440..8b8a27d260 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -8,7 +8,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController before_action :set_body_classes before_action :set_pack before_action :set_confirmation_user!, only: [:show, :confirm_captcha] - before_action :require_unconfirmed! + before_action :redirect_confirmed_user, if: :signed_in_confirmed_user? before_action :extend_csp_for_captcha!, only: [:show, :confirm_captcha] before_action :require_captcha_if_needed!, only: [:show] @@ -70,10 +70,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController use_pack 'auth' end - def require_unconfirmed! - if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank? - redirect_to(current_user.approved? ? root_path : edit_user_registration_path) - end + def redirect_confirmed_user + redirect_to(current_user.approved? ? root_path : edit_user_registration_path) + end + + def signed_in_confirmed_user? + user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank? end def set_body_classes diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb index 0c572d3b19..f0d47bf774 100644 --- a/app/controllers/auth/passwords_controller.rb +++ b/app/controllers/auth/passwords_controller.rb @@ -2,7 +2,7 @@ class Auth::PasswordsController < Devise::PasswordsController skip_before_action :check_self_destruct! - before_action :check_validity_of_reset_password_token, only: :edit + before_action :redirect_invalid_reset_token, only: :edit, unless: :reset_password_token_is_valid? before_action :set_pack before_action :set_body_classes @@ -20,11 +20,9 @@ class Auth::PasswordsController < Devise::PasswordsController private - def check_validity_of_reset_password_token - unless reset_password_token_is_valid? - flash[:error] = I18n.t('auth.invalid_reset_password_token') - redirect_to new_password_path(resource_name) - end + def redirect_invalid_reset_token + flash[:error] = I18n.t('auth.invalid_reset_password_token') + redirect_to new_password_path(resource_name) end def set_body_classes diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 1c773511b4..41c8562363 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class Auth::SessionsController < Devise::SessionsController + include Redisable + + MAX_2FA_ATTEMPTS_PER_HOUR = 10 + layout 'auth' skip_before_action :check_self_destruct! @@ -135,9 +139,23 @@ class Auth::SessionsController < Devise::SessionsController session.delete(:attempt_user_updated_at) end + def clear_2fa_attempt_from_user(user) + redis.del(second_factor_attempts_key(user)) + end + + def check_second_factor_rate_limits(user) + attempts, = redis.multi do |multi| + multi.incr(second_factor_attempts_key(user)) + multi.expire(second_factor_attempts_key(user), 1.hour) + end + + attempts >= MAX_2FA_ATTEMPTS_PER_HOUR + end + def on_authentication_success(user, security_measure) @on_authentication_success_called = true + clear_2fa_attempt_from_user(user) clear_attempt_from_session user.update_sign_in!(new_sign_in: true) @@ -168,5 +186,14 @@ class Auth::SessionsController < Devise::SessionsController ip: request.remote_ip, user_agent: request.user_agent ) + + # Only send a notification email every hour at most + return if redis.set("2fa_failure_notification:#{user.id}", '1', ex: 1.hour, get: true).present? + + UserMailer.failed_2fa(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later! + end + + def second_factor_attempts_key(user) + "2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}" end end diff --git a/app/controllers/concerns/auth/two_factor_authentication_concern.rb b/app/controllers/concerns/auth/two_factor_authentication_concern.rb index ebd6a93441..edcdd2990f 100644 --- a/app/controllers/concerns/auth/two_factor_authentication_concern.rb +++ b/app/controllers/concerns/auth/two_factor_authentication_concern.rb @@ -66,6 +66,11 @@ module Auth::TwoFactorAuthenticationConcern end def authenticate_with_two_factor_via_otp(user) + if check_second_factor_rate_limits(user) + flash.now[:alert] = I18n.t('users.rate_limited') + return prompt_for_two_factor(user) + end + if valid_otp_attempt?(user) on_authentication_success(user, :otp) else diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb index 5160bf043f..a6af62af97 100644 --- a/app/controllers/concerns/web_app_controller_concern.rb +++ b/app/controllers/concerns/web_app_controller_concern.rb @@ -22,11 +22,20 @@ module WebAppControllerConcern def redirect_unauthenticated_to_permalinks! return if user_signed_in? # NOTE: Different from upstream because we allow moved users to log in - redirect_path = PermalinkRedirector.new(request.path).redirect_path - return if redirect_path.blank? + permalink_redirector = PermalinkRedirector.new(request.path) + return if permalink_redirector.redirect_path.blank? expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in? - redirect_to(redirect_path) + + respond_to do |format| + format.html do + redirect_to(permalink_redirector.redirect_confirmation_path, allow_other_host: false) + end + + format.json do + redirect_to(permalink_redirector.redirect_uri, allow_other_host: true) + end + end end def set_pack diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb index e7a02ea89c..62f8e0d772 100644 --- a/app/controllers/custom_css_controller.rb +++ b/app/controllers/custom_css_controller.rb @@ -1,8 +1,21 @@ # 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 render content_type: 'text/css' end + + private + + def custom_css_styles + Setting.custom_css + end + helper_method :custom_css_styles + + def set_user_roles + @user_roles = UserRole.where(highlighted: true).where.not(color: [nil, '']) + end end diff --git a/app/controllers/redirect/accounts_controller.rb b/app/controllers/redirect/accounts_controller.rb new file mode 100644 index 0000000000..713ccf2ca1 --- /dev/null +++ b/app/controllers/redirect/accounts_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Redirect::AccountsController < Redirect::BaseController + private + + def set_resource + @resource = Account.find(params[:id]) + not_found if @resource.local? + end +end diff --git a/app/controllers/redirect/base_controller.rb b/app/controllers/redirect/base_controller.rb new file mode 100644 index 0000000000..ce55cfc53a --- /dev/null +++ b/app/controllers/redirect/base_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Redirect::BaseController < ApplicationController + vary_by 'Accept-Language' + + before_action :set_pack + before_action :set_resource + before_action :set_app_body_class + + def show + @redirect_path = ActivityPub::TagManager.instance.url_for(@resource) + + render 'redirects/show', layout: 'application' + end + + private + + def set_app_body_class + @body_classes = 'app-body' + end + + def set_resource + raise NotImplementedError + end + + def set_pack + use_pack 'public' + end +end diff --git a/app/controllers/redirect/statuses_controller.rb b/app/controllers/redirect/statuses_controller.rb new file mode 100644 index 0000000000..37a938c651 --- /dev/null +++ b/app/controllers/redirect/statuses_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Redirect::StatusesController < Redirect::BaseController + private + + def set_resource + @resource = Status.find(params[:id]) + not_found if @resource.local? || !@resource.distributable? + end +end diff --git a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb index 0128509dea..aed5af6e79 100644 --- a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb +++ b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb @@ -6,8 +6,8 @@ module Settings skip_before_action :check_self_destruct! skip_before_action :require_functional! - before_action :require_otp_enabled - before_action :require_webauthn_enabled, only: [:index, :destroy] + before_action :redirect_invalid_otp, unless: -> { current_user.otp_enabled? } + before_action :redirect_invalid_webauthn, only: [:index, :destroy], unless: -> { current_user.webauthn_enabled? } def index; end def new; end @@ -89,18 +89,14 @@ module Settings use_pack 'auth' end - def require_otp_enabled - unless current_user.otp_enabled? - flash[:error] = t('webauthn_credentials.otp_required') - redirect_to settings_two_factor_authentication_methods_path - end + def redirect_invalid_otp + flash[:error] = t('webauthn_credentials.otp_required') + redirect_to settings_two_factor_authentication_methods_path end - def require_webauthn_enabled - unless current_user.webauthn_enabled? - flash[:error] = t('webauthn_credentials.not_enabled') - redirect_to settings_two_factor_authentication_methods_path - end + def redirect_invalid_webauthn + flash[:error] = t('webauthn_credentials.not_enabled') + redirect_to settings_two_factor_authentication_methods_path end end end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index b8277ee17e..110a53e4e1 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -31,22 +31,26 @@ module AccountsHelper Setting.hide_followers_count || account.user&.settings&.[]('hide_followers_count') end + def account_formatted_stat(value) + number_to_human(value, precision: 3, strip_insignificant_zeros: true) + end + def account_description(account) prepend_stats = [ [ - number_to_human(account.statuses_count, precision: 3, strip_insignificant_zeros: true), + account_formatted_stat(account.statuses_count), I18n.t('accounts.posts', count: account.statuses_count), ].join(' '), [ - number_to_human(account.following_count, precision: 3, strip_insignificant_zeros: true), + account_formatted_stat(account.following_count), I18n.t('accounts.following', count: account.following_count), ].join(' '), ] unless hide_followers_count?(account) prepend_stats << [ - number_to_human(account.followers_count, precision: 3, strip_insignificant_zeros: true), + account_formatted_stat(account.followers_count), I18n.t('accounts.followers', count: account.followers_count), ].join(' ') end diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index ce3ff094f6..b3d0d032c4 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -155,7 +155,7 @@ module JsonLdHelper end end - def fetch_resource(uri, id, on_behalf_of = nil) + def fetch_resource(uri, id, on_behalf_of = nil, request_options: {}) unless id json = fetch_resource_without_id_validation(uri, on_behalf_of) @@ -164,14 +164,14 @@ module JsonLdHelper uri = json['id'] end - json = fetch_resource_without_id_validation(uri, on_behalf_of) + json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options) json.present? && json['id'] == uri ? json : nil end - def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false) + def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {}) on_behalf_of ||= Account.representative - build_request(uri, on_behalf_of).perform do |response| + build_request(uri, on_behalf_of, options: request_options).perform do |response| raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error body_to_json(response.body_with_limit) if response.code == 200 @@ -204,8 +204,8 @@ module JsonLdHelper response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code)) end - def build_request(uri, on_behalf_of = nil) - Request.new(:get, uri).tap do |request| + def build_request(uri, on_behalf_of = nil, options: {}) + Request.new(:get, uri, **options).tap do |request| request.on_behalf_of(on_behalf_of) if on_behalf_of request.add_headers('Accept' => 'application/activity+json, application/ld+json') end diff --git a/app/helpers/mascot_helper.rb b/app/helpers/mascot_helper.rb index 8ee04383ec..34b656411e 100644 --- a/app/helpers/mascot_helper.rb +++ b/app/helpers/mascot_helper.rb @@ -2,7 +2,7 @@ module MascotHelper def mascot_url - full_asset_url(instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg')) + full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg')) end def instance_presenter diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb index 2fb9ce72cb..15d988f64d 100644 --- a/app/helpers/routing_helper.rb +++ b/app/helpers/routing_helper.rb @@ -24,8 +24,12 @@ module RoutingHelper Rails.configuration.action_controller.asset_host || root_url end - def full_pack_url(source, **options) - full_asset_url(asset_pack_path(source, **options)) + def frontend_asset_path(source, **options) + asset_pack_path("media/#{source}", **options) + end + + def frontend_asset_url(source, **options) + full_asset_url(frontend_asset_path(source, **options)) end def use_storage? diff --git a/app/javascript/__mocks__/svg.js b/app/javascript/__mocks__/svg.js index e725dc2da6..762bc165d0 100644 --- a/app/javascript/__mocks__/svg.js +++ b/app/javascript/__mocks__/svg.js @@ -1,3 +1,3 @@ -// eslint-disable-next-line import/no-anonymous-default-export -export default 'SvgrURL'; -export const ReactComponent = 'div'; +const ReactComponent = 'div'; + +export default ReactComponent; diff --git a/app/javascript/core/inert.js b/app/javascript/core/inert.js new file mode 100644 index 0000000000..7c04a97faf --- /dev/null +++ b/app/javascript/core/inert.js @@ -0,0 +1,4 @@ +/* Placeholder file to have `inert.scss` compiled by Webpack + This is used by the `wicg-inert` polyfill */ + +import '../styles/inert.scss'; diff --git a/app/javascript/core/theme.yml b/app/javascript/core/theme.yml index 1b2bfb98f1..f6f653c0a9 100644 --- a/app/javascript/core/theme.yml +++ b/app/javascript/core/theme.yml @@ -10,6 +10,9 @@ pack: embed: embed.js error: home: + inert: + filename: inert.js + stylesheet: true mailer: filename: mailer.js stylesheet: true diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js index 5bb3aa3a79..7e54740d52 100644 --- a/app/javascript/flavours/glitch/actions/search.js +++ b/app/javascript/flavours/glitch/actions/search.js @@ -170,6 +170,11 @@ export const openURL = routerHistory => (dispatch, getState) => { export const clickSearchResult = (q, type) => (dispatch, getState) => { const previous = getState().getIn(['search', 'recent']); + + if (previous.some(x => x.get('q') === q && x.get('type') === type)) { + return; + } + const me = getState().getIn(['meta', 'me']); const current = previous.add(fromJS({ type, q })).takeLast(4); @@ -198,4 +203,4 @@ export const hydrateSearch = () => (dispatch, getState) => { if (history !== null) { dispatch(updateSearchHistory(history)); } -}; \ No newline at end of file +}; diff --git a/app/javascript/flavours/glitch/components/account.jsx b/app/javascript/flavours/glitch/components/account.jsx index 109e0daddd..266a3ca995 100644 --- a/app/javascript/flavours/glitch/components/account.jsx +++ b/app/javascript/flavours/glitch/components/account.jsx @@ -17,7 +17,7 @@ import { Avatar } from './avatar'; import { Button } from './button'; import { FollowersCounter } from './counters'; import { DisplayName } from './display_name'; -import Permalink from './permalink'; +import { Permalink } from './permalink'; import { RelativeTimestamp } from './relative_timestamp'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/components/admin/Trends.jsx b/app/javascript/flavours/glitch/components/admin/Trends.jsx index 975ea6e0f2..d7755fcdbb 100644 --- a/app/javascript/flavours/glitch/components/admin/Trends.jsx +++ b/app/javascript/flavours/glitch/components/admin/Trends.jsx @@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import api from 'flavours/glitch/api'; -import Hashtag from 'flavours/glitch/components/hashtag'; +import { Hashtag } from 'flavours/glitch/components/hashtag'; export default class Trends extends PureComponent { diff --git a/app/javascript/flavours/glitch/components/attachment_list.jsx b/app/javascript/flavours/glitch/components/attachment_list.jsx index e7b259b200..44b8bf78ee 100644 --- a/app/javascript/flavours/glitch/components/attachment_list.jsx +++ b/app/javascript/flavours/glitch/components/attachment_list.jsx @@ -7,10 +7,10 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg'; - +import LinkIcon from '@/material-icons/400-24px/link.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; export default class AttachmentList extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/components/badge.jsx b/app/javascript/flavours/glitch/components/badge.jsx index cf225a6a0c..646655c249 100644 --- a/app/javascript/flavours/glitch/components/badge.jsx +++ b/app/javascript/flavours/glitch/components/badge.jsx @@ -2,9 +2,9 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as GroupsIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg'; -import { ReactComponent as SmartToyIcon } from '@material-symbols/svg-600/outlined/smart_toy.svg'; +import GroupsIcon from '@/material-icons/400-24px/group.svg?react'; +import PersonIcon from '@/material-icons/400-24px/person.svg?react'; +import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react'; export const Badge = ({ icon, label, domain }) => ( diff --git a/app/javascript/flavours/glitch/components/column_back_button.tsx b/app/javascript/flavours/glitch/components/column_back_button.tsx index 9f90f1dddc..75c594c20a 100644 --- a/app/javascript/flavours/glitch/components/column_back_button.tsx +++ b/app/javascript/flavours/glitch/components/column_back_button.tsx @@ -2,8 +2,7 @@ import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg'; - +import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context'; diff --git a/app/javascript/flavours/glitch/components/column_header.jsx b/app/javascript/flavours/glitch/components/column_header.jsx index 8cc9fde9d3..ec99698d0c 100644 --- a/app/javascript/flavours/glitch/components/column_header.jsx +++ b/app/javascript/flavours/glitch/components/column_header.jsx @@ -6,17 +6,17 @@ import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; import { withRouter } from 'react-router-dom'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg'; -import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg'; - +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import TuneIcon from '@/material-icons/400-24px/tune.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { ButtonInTabsBar, useColumnsContext } from 'flavours/glitch/features/ui/util/columns_context'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; + import { useAppHistory } from './router'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/components/dismissable_banner.tsx b/app/javascript/flavours/glitch/components/dismissable_banner.tsx index 2edc4e8e66..650befb349 100644 --- a/app/javascript/flavours/glitch/components/dismissable_banner.tsx +++ b/app/javascript/flavours/glitch/components/dismissable_banner.tsx @@ -8,8 +8,7 @@ import { useCallback, useState, useEffect } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { changeSetting } from 'flavours/glitch/actions/settings'; import { bannerSettings } from 'flavours/glitch/settings'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; diff --git a/app/javascript/flavours/glitch/components/domain.tsx b/app/javascript/flavours/glitch/components/domain.tsx index 34c376f530..ed5e8e7e4c 100644 --- a/app/javascript/flavours/glitch/components/domain.tsx +++ b/app/javascript/flavours/glitch/components/domain.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg'; +import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; import { IconButton } from './icon_button'; diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.jsx b/app/javascript/flavours/glitch/components/dropdown_menu.jsx index f00961dcad..bfaa53f6e5 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.jsx +++ b/app/javascript/flavours/glitch/components/dropdown_menu.jsx @@ -6,10 +6,10 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import { supportsPassiveEvents } from 'detect-passive-events'; import Overlay from 'react-overlays/Overlay'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { CircularProgress } from 'flavours/glitch/components/circular_progress'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; diff --git a/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx b/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx index f7186e1325..29a418dfb1 100644 --- a/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx +++ b/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx @@ -5,8 +5,8 @@ import { FormattedMessage, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as ArrowDropDownIcon } from '@material-symbols/svg-600/outlined/arrow_drop_down.svg'; +import ArrowDropDownIcon from '@/material-icons/400-24px/arrow_drop_down.svg?react'; import { openModal } from 'flavours/glitch/actions/modal'; import { Icon } from 'flavours/glitch/components/icon'; import InlineAccount from 'flavours/glitch/components/inline_account'; diff --git a/app/javascript/flavours/glitch/components/hashtag.jsx b/app/javascript/flavours/glitch/components/hashtag.jsx deleted file mode 100644 index 956834b47a..0000000000 --- a/app/javascript/flavours/glitch/components/hashtag.jsx +++ /dev/null @@ -1,123 +0,0 @@ -// @ts-check -import PropTypes from 'prop-types'; -import { Component } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -import classNames from 'classnames'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; - -import { Sparklines, SparklinesCurve } from 'react-sparklines'; - -import { ShortNumber } from 'flavours/glitch/components/short_number'; -import { Skeleton } from 'flavours/glitch/components/skeleton'; - -import Permalink from './permalink'; - -class SilentErrorBoundary extends Component { - - static propTypes = { - children: PropTypes.node, - }; - - state = { - error: false, - }; - - componentDidCatch() { - this.setState({ error: true }); - } - - render() { - if (this.state.error) { - return null; - } - - return this.props.children; - } - -} - -/** - * Used to render counter of how much people are talking about hashtag - * @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element} - */ -export const accountsCountRenderer = (displayNumber, pluralReady) => ( - {displayNumber}, - days: 2, - }} - /> -); - -// @ts-expect-error -export const ImmutableHashtag = ({ hashtag }) => ( - day.get('uses')).toArray()} - /> -); - -ImmutableHashtag.propTypes = { - hashtag: ImmutablePropTypes.map.isRequired, -}; - -// @ts-expect-error -const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => ( -
-
- - {name ? <>#{name} : } - - - {description ? ( - {description} - ) : ( - typeof people !== 'undefined' ? : - )} -
- - {typeof uses !== 'undefined' && ( -
- -
- )} - - {withGraph && ( -
- - 0)}> - - - -
- )} -
-); - -Hashtag.propTypes = { - name: PropTypes.string, - href: PropTypes.string, - to: PropTypes.string, - people: PropTypes.number, - description: PropTypes.node, - uses: PropTypes.number, - history: PropTypes.arrayOf(PropTypes.number), - className: PropTypes.string, - withGraph: PropTypes.bool, -}; - -Hashtag.defaultProps = { - withGraph: true, -}; - -export default Hashtag; diff --git a/app/javascript/flavours/glitch/components/hashtag.tsx b/app/javascript/flavours/glitch/components/hashtag.tsx new file mode 100644 index 0000000000..f9a7455b8f --- /dev/null +++ b/app/javascript/flavours/glitch/components/hashtag.tsx @@ -0,0 +1,149 @@ +import type { JSX } from 'react'; +import { Component } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import type Immutable from 'immutable'; + +import { Sparklines, SparklinesCurve } from 'react-sparklines'; + +import { ShortNumber } from 'flavours/glitch/components/short_number'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; + +import { Permalink } from './permalink'; + +interface SilentErrorBoundaryProps { + children: React.ReactNode; +} + +class SilentErrorBoundary extends Component { + state = { + error: false, + }; + + componentDidCatch() { + this.setState({ error: true }); + } + + render() { + if (this.state.error) { + return null; + } + + return this.props.children; + } +} + +/** + * Used to render counter of how much people are talking about hashtag + * @param displayNumber Counter number to display + * @param pluralReady Whether the count is plural + * @returns Formatted counter of how much people are talking about hashtag + */ +export const accountsCountRenderer = ( + displayNumber: JSX.Element, + pluralReady: number, +) => ( + {displayNumber}, + days: 2, + }} + /> +); + +interface ImmutableHashtagProps { + hashtag: Immutable.Map; +} + +export const ImmutableHashtag = ({ hashtag }: ImmutableHashtagProps) => ( + + > + ) + .reverse() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .map((day) => day.get('uses')!) + .toArray()} + /> +); + +export interface HashtagProps { + className?: string; + description?: React.ReactNode; + history?: number[]; + href: string; + name: string; + people: number; + to: string; + uses?: number; + withGraph?: boolean; +} + +export const Hashtag: React.FC = ({ + name, + href, + to, + people, + uses, + history, + className, + description, + withGraph = true, +}) => ( +
+
+ + {name ? ( + <> + #{name} + + ) : ( + + )} + + + {description ? ( + {description} + ) : typeof people !== 'undefined' ? ( + + ) : ( + + )} +
+ + {typeof uses !== 'undefined' && ( +
+ +
+ )} + + {withGraph && ( +
+ + 0)} + > + + + +
+ )} +
+); diff --git a/app/javascript/flavours/glitch/components/icon.tsx b/app/javascript/flavours/glitch/components/icon.tsx index dbf5839606..052833fedd 100644 --- a/app/javascript/flavours/glitch/components/icon.tsx +++ b/app/javascript/flavours/glitch/components/icon.tsx @@ -1,6 +1,7 @@ import classNames from 'classnames'; -import { ReactComponent as CheckBoxOutlineBlankIcon } from '@material-symbols/svg-600/outlined/check_box_outline_blank.svg'; +import CheckBoxOutlineBlankIcon from '@/material-icons/400-24px/check_box_outline_blank.svg?react'; +import { isProduction } from 'flavours/glitch/utils/environment'; interface SVGPropsWithTitle extends React.SVGProps { title?: string; @@ -24,7 +25,7 @@ export const Icon: React.FC = ({ }) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!IconComponent) { - if (process.env.NODE_ENV !== 'production') { + if (!isProduction()) { throw new Error( ` is missing an "icon" prop.`, ); diff --git a/app/javascript/flavours/glitch/components/load_gap.tsx b/app/javascript/flavours/glitch/components/load_gap.tsx index 14ad26f8c7..f0d15d3776 100644 --- a/app/javascript/flavours/glitch/components/load_gap.tsx +++ b/app/javascript/flavours/glitch/components/load_gap.tsx @@ -2,8 +2,7 @@ import { useCallback } from 'react'; import { useIntl, defineMessages } from 'react-intl'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; - +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/components/logo.jsx b/app/javascript/flavours/glitch/components/logo.jsx index 16ca9f80fd..73a94af9ed 100644 --- a/app/javascript/flavours/glitch/components/logo.jsx +++ b/app/javascript/flavours/glitch/components/logo.jsx @@ -1,4 +1,4 @@ -import logo from 'mastodon/../images/logo.svg'; +import logo from '@/images/logo.svg'; export const WordmarkLogo = () => ( diff --git a/app/javascript/flavours/glitch/components/media_gallery.jsx b/app/javascript/flavours/glitch/components/media_gallery.jsx index 575851543a..9db2a43009 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.jsx +++ b/app/javascript/flavours/glitch/components/media_gallery.jsx @@ -8,9 +8,9 @@ import classNames from 'classnames'; import { is } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; import { debounce } from 'lodash'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Blurhash } from 'flavours/glitch/components/blurhash'; import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state'; @@ -123,7 +123,7 @@ class Item extends PureComponent { } if (attachment.get('description')?.length > 0) { - badges.push(ALT); + badges.push(ALT); } const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); diff --git a/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx b/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx index ddbfb1d44a..b171738322 100644 --- a/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx +++ b/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx @@ -12,10 +12,10 @@ import classNames from 'classnames'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as DeleteIcon } from '@material-symbols/svg-600/outlined/delete.svg'; - +import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + const messages = defineMessages({ btnAll : { id: 'notification_purge.btn_all', defaultMessage: 'Select\nall' }, btnNone : { id: 'notification_purge.btn_none', defaultMessage: 'Select\nnone' }, diff --git a/app/javascript/flavours/glitch/components/permalink.jsx b/app/javascript/flavours/glitch/components/permalink.jsx deleted file mode 100644 index 5226895415..0000000000 --- a/app/javascript/flavours/glitch/components/permalink.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router'; - -class Permalink extends PureComponent { - - static propTypes = { - className: PropTypes.string, - href: PropTypes.string.isRequired, - to: PropTypes.string.isRequired, - children: PropTypes.node, - onInterceptClick: PropTypes.func, - ...WithOptionalRouterPropTypes, - }; - - handleClick = (e) => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - if (this.props.onInterceptClick && this.props.onInterceptClick()) { - e.preventDefault(); - return; - } - - if (this.props.history) { - e.preventDefault(); - this.props.history.push(this.props.to); - } - } - }; - - render () { - const { - children, - className, - href, - to, - onInterceptClick, - ...other - } = this.props; - - return ( - - {children} - - ); - } - -} - -export default withOptionalRouter(Permalink); diff --git a/app/javascript/flavours/glitch/components/permalink.tsx b/app/javascript/flavours/glitch/components/permalink.tsx new file mode 100644 index 0000000000..9068efb06d --- /dev/null +++ b/app/javascript/flavours/glitch/components/permalink.tsx @@ -0,0 +1,41 @@ +import { useCallback } from 'react'; + +import { useAppHistory } from './router'; + +interface Props extends React.AnchorHTMLAttributes { + to: string; +} + +export const Permalink: React.FC = ({ + className, + href, + to, + children, + ...props +}) => { + const history = useAppHistory(); + + const handleClick = useCallback>( + (e) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- history can actually be undefined as the component can be mounted outside a router context + if (e.button === 0 && !(e.ctrlKey || e.metaKey) && history) { + e.preventDefault(); + history.push(to); + } + }, + [history, to], + ); + + return ( + + {children} + + ); +}; diff --git a/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.jsx b/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.jsx index 685f3c2738..10a0a7f5bb 100644 --- a/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.jsx +++ b/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.jsx @@ -5,8 +5,8 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as CancelPresentationIcon } from '@material-symbols/svg-600/outlined/cancel_presentation.svg'; +import CancelPresentationIcon from '@/material-icons/400-24px/cancel_presentation.svg?react'; import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; import { Icon } from 'flavours/glitch/components/icon'; diff --git a/app/javascript/flavours/glitch/components/poll.jsx b/app/javascript/flavours/glitch/components/poll.jsx index 4fcd56a0f3..f9e008bd72 100644 --- a/app/javascript/flavours/glitch/components/poll.jsx +++ b/app/javascript/flavours/glitch/components/poll.jsx @@ -7,10 +7,10 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; import escapeTextContentForBrowser from 'escape-html'; import spring from 'react-motion/lib/spring'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import emojify from 'flavours/glitch/features/emoji/emoji'; import Motion from 'flavours/glitch/features/ui/util/optional_motion'; diff --git a/app/javascript/flavours/glitch/components/router.tsx b/app/javascript/flavours/glitch/components/router.tsx index 22f22c65cc..0637c35ada 100644 --- a/app/javascript/flavours/glitch/components/router.tsx +++ b/app/javascript/flavours/glitch/components/router.tsx @@ -11,6 +11,7 @@ import type { import { createBrowserHistory } from 'history'; import { layoutFromWindow } from 'flavours/glitch/is_mobile'; +import { isDevelopment } from 'flavours/glitch/utils/environment'; interface MastodonLocationState { fromMastodon?: boolean; @@ -40,7 +41,7 @@ function normalizePath( } else if ( location.state !== undefined && state !== undefined && - process.env.NODE_ENV === 'development' + isDevelopment() ) { // eslint-disable-next-line no-console console.log( diff --git a/app/javascript/flavours/glitch/components/status_action_bar.jsx b/app/javascript/flavours/glitch/components/status_action_bar.jsx index 5d48cfafd4..c10122d234 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar.jsx @@ -8,21 +8,22 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg'; -import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; -import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg'; -import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg'; - +import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; +import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; +import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; +import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; +import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg'; +import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg'; +import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; -import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg'; -import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg'; import DropdownMenuContainer from '../containers/dropdown_menu_container'; import { me } from '../initial_state'; @@ -304,7 +305,7 @@ class StatusActionBar extends ImmutablePureComponent { if (status.get('reblogged')) { reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon; + reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; } else if (publicStatus) { reblogTitle = intl.formatMessage(messages.reblog); reblogIconComponent = RepeatIcon; diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index a8e069426e..f5d5ccc70a 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -9,17 +9,17 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; -import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg'; -import { ReactComponent as MovieIcon } from '@material-symbols/svg-600/outlined/movie.svg'; -import { ReactComponent as MusicNoteIcon } from '@material-symbols/svg-600/outlined/music_note.svg'; - +import ImageIcon from '@/material-icons/400-24px/image.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import LinkIcon from '@/material-icons/400-24px/link.svg?react'; +import MovieIcon from '@/material-icons/400-24px/movie.svg?react'; +import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; -import Permalink from './permalink'; + +import { Permalink } from './permalink'; const textMatchesTarget = (text, origin, host) => { return (text === origin || text === host diff --git a/app/javascript/flavours/glitch/components/status_icons.jsx b/app/javascript/flavours/glitch/components/status_icons.jsx index 5e7aa6e638..2727d3410a 100644 --- a/app/javascript/flavours/glitch/components/status_icons.jsx +++ b/app/javascript/flavours/glitch/components/status_icons.jsx @@ -6,18 +6,18 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as ExpandLessIcon } from '@material-symbols/svg-600/outlined/expand_less.svg'; -import { ReactComponent as ForumIcon } from '@material-symbols/svg-600/outlined/forum.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home.svg'; -import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; -import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg'; -import { ReactComponent as MovieIcon } from '@material-symbols/svg-600/outlined/movie.svg'; -import { ReactComponent as MusicNoteIcon } from '@material-symbols/svg-600/outlined/music_note.svg'; - +import ExpandLessIcon from '@/material-icons/400-24px/expand_less.svg?react'; +import ForumIcon from '@/material-icons/400-24px/forum.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home.svg?react'; +import ImageIcon from '@/material-icons/400-24px/image.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import LinkIcon from '@/material-icons/400-24px/link.svg?react'; +import MovieIcon from '@/material-icons/400-24px/movie.svg?react'; +import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { languages } from 'flavours/glitch/initial_state'; + import { IconButton } from './icon_button'; import { VisibilityIcon } from './visibility_icon'; diff --git a/app/javascript/flavours/glitch/components/status_prepend.jsx b/app/javascript/flavours/glitch/components/status_prepend.jsx index d4a94405dc..355f65a3e2 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.jsx +++ b/app/javascript/flavours/glitch/components/status_prepend.jsx @@ -6,16 +6,16 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; -import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; - +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { me } from 'flavours/glitch/initial_state'; + export default class StatusPrepend extends PureComponent { static propTypes = { diff --git a/app/javascript/flavours/glitch/components/verified_badge.tsx b/app/javascript/flavours/glitch/components/verified_badge.tsx index e96bf82563..626cc500d6 100644 --- a/app/javascript/flavours/glitch/components/verified_badge.tsx +++ b/app/javascript/flavours/glitch/components/verified_badge.tsx @@ -1,4 +1,4 @@ -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from './icon'; diff --git a/app/javascript/flavours/glitch/components/visibility_icon.tsx b/app/javascript/flavours/glitch/components/visibility_icon.tsx index 3a8a54d173..baf134c0ae 100644 --- a/app/javascript/flavours/glitch/components/visibility_icon.tsx +++ b/app/javascript/flavours/glitch/components/visibility_icon.tsx @@ -1,9 +1,9 @@ import { defineMessages, useIntl } from 'react-intl'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; -import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg'; -import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; +import MailIcon from '@/material-icons/400-24px/mail.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { Icon } from './icon'; diff --git a/app/javascript/flavours/glitch/containers/mastodon.jsx b/app/javascript/flavours/glitch/containers/mastodon.jsx index 070e94fe8f..1704336f2c 100644 --- a/app/javascript/flavours/glitch/containers/mastodon.jsx +++ b/app/javascript/flavours/glitch/containers/mastodon.jsx @@ -18,8 +18,9 @@ import UI from 'flavours/glitch/features/ui'; import initialState, { title as siteTitle } from 'flavours/glitch/initial_state'; import { IntlProvider } from 'flavours/glitch/locales'; import { store } from 'flavours/glitch/store'; +import { isProduction } from 'flavours/glitch/utils/environment'; -const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`; +const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`; const hydrateAction = hydrateStore(initialState); diff --git a/app/javascript/flavours/glitch/features/about/index.jsx b/app/javascript/flavours/glitch/features/about/index.jsx index 9510f46f55..e5544c8a74 100644 --- a/app/javascript/flavours/glitch/features/about/index.jsx +++ b/app/javascript/flavours/glitch/features/about/index.jsx @@ -10,9 +10,9 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; -import { ReactComponent as ExpandMoreIcon } from '@material-symbols/svg-600/outlined/expand_more.svg'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; import Column from 'flavours/glitch/components/column'; import { Icon } from 'flavours/glitch/components/icon'; diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.jsx b/app/javascript/flavours/glitch/features/account/components/action_bar.jsx index 96a5ec2712..3fcf0440c0 100644 --- a/app/javascript/flavours/glitch/features/account/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/account/components/action_bar.jsx @@ -6,10 +6,10 @@ import { NavLink } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg'; - +import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + class ActionBar extends PureComponent { static propTypes = { @@ -30,7 +30,8 @@ class ActionBar extends PureComponent { return (
- + @@ -44,14 +45,17 @@ class ActionBar extends PureComponent { if (account.get('acct') !== account.get('username')) { extraInfo = (
- - {' '} - - - + +
+ + {' '} + + + +
); } diff --git a/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx b/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx index 1b9e3572db..38b3edfb56 100644 --- a/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx +++ b/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx @@ -5,7 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import Hashtag from 'flavours/glitch/components/hashtag'; +import { Hashtag } from 'flavours/glitch/components/hashtag'; const messages = defineMessages({ lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' }, diff --git a/app/javascript/flavours/glitch/features/account/components/follow_request_note.jsx b/app/javascript/flavours/glitch/features/account/components/follow_request_note.jsx index da150ff8fb..37d1508528 100644 --- a/app/javascript/flavours/glitch/features/account/components/follow_request_note.jsx +++ b/app/javascript/flavours/glitch/features/account/components/follow_request_note.jsx @@ -3,11 +3,11 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + export default class FollowRequestNote extends ImmutablePureComponent { static propTypes = { diff --git a/app/javascript/flavours/glitch/features/account/components/header.jsx b/app/javascript/flavours/glitch/features/account/components/header.jsx index 8e96e84ba3..81ce989e99 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.jsx +++ b/app/javascript/flavours/glitch/features/account/components/header.jsx @@ -9,12 +9,12 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg'; -import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active-fill.svg'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; +import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; import { Avatar } from 'flavours/glitch/components/avatar'; import { Badge, AutomatedBadge, GroupBadge } from 'flavours/glitch/components/badge'; import { Button } from 'flavours/glitch/components/button'; diff --git a/app/javascript/flavours/glitch/features/account/components/profile_column_header.jsx b/app/javascript/flavours/glitch/features/account/components/profile_column_header.jsx index b3b07f273f..0628773497 100644 --- a/app/javascript/flavours/glitch/features/account/components/profile_column_header.jsx +++ b/app/javascript/flavours/glitch/features/account/components/profile_column_header.jsx @@ -3,7 +3,7 @@ import { PureComponent } from 'react'; import { injectIntl, defineMessages } from 'react-intl'; -import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg'; +import PersonIcon from '@/material-icons/400-24px/person.svg?react'; import ColumnHeader from '../../../components/column_header'; diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.jsx b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.jsx index c16cd8ddd0..ebd156a4f9 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.jsx +++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.jsx @@ -5,14 +5,14 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AudiotrackIcon } from '@material-symbols/svg-600/outlined/music_note.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; - +import AudiotrackIcon from '@/material-icons/400-24px/music_note.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Icon } from 'flavours/glitch/components/icon'; import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; + export default class MediaItem extends ImmutablePureComponent { static propTypes = { diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx index 90048472a1..9ccf5bd17a 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx @@ -5,8 +5,8 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as TripIcon } from '@material-symbols/svg-600/outlined/trip.svg'; +import TripIcon from '@/material-icons/400-24px/trip.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; diff --git a/app/javascript/flavours/glitch/features/audio/index.jsx b/app/javascript/flavours/glitch/features/audio/index.jsx index 3e949dca42..dc9d76222d 100644 --- a/app/javascript/flavours/glitch/features/audio/index.jsx +++ b/app/javascript/flavours/glitch/features/audio/index.jsx @@ -7,17 +7,18 @@ import classNames from 'classnames'; import { is } from 'immutable'; -import { ReactComponent as DownloadIcon } from '@material-symbols/svg-600/outlined/download.svg'; -import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg'; -import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg'; import { throttle, debounce } from 'lodash'; +import DownloadIcon from '@/material-icons/400-24px/download.svg?react'; +import PauseIcon from '@/material-icons/400-24px/pause.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off-fill.svg?react'; +import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; + import { Blurhash } from '../../components/blurhash'; import { displayMedia, useBlurhash } from '../../initial_state'; diff --git a/app/javascript/flavours/glitch/features/blocks/index.jsx b/app/javascript/flavours/glitch/features/blocks/index.jsx index 615e4c8be2..1a631d3d07 100644 --- a/app/javascript/flavours/glitch/features/blocks/index.jsx +++ b/app/javascript/flavours/glitch/features/blocks/index.jsx @@ -6,9 +6,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block-fill.svg'; import { debounce } from 'lodash'; +import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react'; + import { fetchBlocks, expandBlocks } from '../../actions/blocks'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx index 2dc0a3b9c9..81d2e79a12 100644 --- a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg'; import { debounce } from 'lodash'; +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import ColumnHeader from 'flavours/glitch/components/column_header'; diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.jsx b/app/javascript/flavours/glitch/features/community_timeline/index.jsx index 8667f7d53d..e2b982aaf8 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/community_timeline/index.jsx @@ -7,8 +7,8 @@ import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { domain } from 'flavours/glitch/initial_state'; diff --git a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx index 737bb6c622..de5d2369c3 100644 --- a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx @@ -5,10 +5,10 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg'; - +import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; import { preferencesLink, profileLink } from 'flavours/glitch/utils/backend_links'; + import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/compose/components/header.jsx b/app/javascript/flavours/glitch/features/compose/components/header.jsx index 021fcb3bb2..bdcb51cd92 100644 --- a/app/javascript/flavours/glitch/features/compose/components/header.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/header.jsx @@ -7,18 +7,18 @@ import { Link } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as LogoutIcon } from '@material-symbols/svg-600/outlined/logout.svg'; -import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg'; -import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; - +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import LogoutIcon from '@/material-icons/400-24px/logout.svg?react'; +import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react'; +import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; +import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { signOutLink } from 'flavours/glitch/utils/backend_links'; import { conditionalRender } from 'flavours/glitch/utils/react_helpers'; + const messages = defineMessages({ community: { defaultMessage: 'Local timeline', diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx index 9f88b0e92a..ca8e1222e6 100644 --- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx @@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import { profileLink } from 'flavours/glitch/utils/backend_links'; import { Avatar } from '../../../components/avatar'; diff --git a/app/javascript/flavours/glitch/features/compose/components/options.jsx b/app/javascript/flavours/glitch/features/compose/components/options.jsx index cdcc0dc1e7..314b6e638f 100644 --- a/app/javascript/flavours/glitch/features/compose/components/options.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/options.jsx @@ -6,19 +6,20 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as AttachFileIcon } from '@material-symbols/svg-600/outlined/attach_file.svg'; -import { ReactComponent as BrushIcon } from '@material-symbols/svg-600/outlined/brush.svg'; -import { ReactComponent as CodeIcon } from '@material-symbols/svg-600/outlined/code.svg'; -import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description.svg'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; -import { ReactComponent as MarkdownIcon } from '@material-symbols/svg-600/outlined/markdown.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as UploadFileIcon } from '@material-symbols/svg-600/outlined/upload_file.svg'; import Toggle from 'react-toggle'; +import AttachFileIcon from '@/material-icons/400-24px/attach_file.svg?react'; +import BrushIcon from '@/material-icons/400-24px/brush.svg?react'; +import CodeIcon from '@/material-icons/400-24px/code.svg?react'; +import DescriptionIcon from '@/material-icons/400-24px/description.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import MarkdownIcon from '@/material-icons/400-24px/markdown.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { pollLimits } from 'flavours/glitch/initial_state'; + import DropdownContainer from '../containers/dropdown_container'; import LanguageDropdown from '../containers/language_dropdown_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; diff --git a/app/javascript/flavours/glitch/features/compose/components/poll_form.jsx b/app/javascript/flavours/glitch/features/compose/components/poll_form.jsx index e8c22fbd0e..053b02673d 100644 --- a/app/javascript/flavours/glitch/features/compose/components/poll_form.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/poll_form.jsx @@ -8,9 +8,9 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import AutosuggestInput from 'flavours/glitch/components/autosuggest_input'; import { Icon } from 'flavours/glitch/components/icon'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx index c0bc4bf85e..7980c1007e 100644 --- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx @@ -3,10 +3,10 @@ import { PureComponent } from 'react'; import { injectIntl, defineMessages } from 'react-intl'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; -import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg'; -import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; +import MailIcon from '@/material-icons/400-24px/mail.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import Dropdown from './dropdown'; diff --git a/app/javascript/flavours/glitch/features/compose/components/publisher.jsx b/app/javascript/flavours/glitch/features/compose/components/publisher.jsx index b8d81b7f96..ff825ca0c9 100644 --- a/app/javascript/flavours/glitch/features/compose/components/publisher.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/publisher.jsx @@ -4,14 +4,14 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; -import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg'; -import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; - +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; +import MailIcon from '@/material-icons/400-24px/mail.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { Button } from 'flavours/glitch/components/button'; import { Icon } from 'flavours/glitch/components/icon'; + const messages = defineMessages({ publish: { defaultMessage: 'Publish', diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx index 48ded3dcd4..8af8cd441a 100644 --- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx @@ -5,8 +5,8 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import { WithOptionalRouterPropTypes, withOptionalRouter } from 'flavours/glitch/utils/react_router'; diff --git a/app/javascript/flavours/glitch/features/compose/components/search.jsx b/app/javascript/flavours/glitch/features/compose/components/search.jsx index ccd56af365..fa995663f0 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/search.jsx @@ -8,10 +8,10 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as CancelIcon } from '@material-symbols/svg-600/outlined/cancel-fill.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; +import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { domain, searchEnabled } from 'flavours/glitch/initial_state'; import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags'; @@ -63,14 +63,14 @@ class Search extends PureComponent { }; defaultOptions = [ - { label: <>has: , action: e => { e.preventDefault(); this._insertText('has:'); } }, - { label: <>is: , action: e => { e.preventDefault(); this._insertText('is:'); } }, - { label: <>language: , action: e => { e.preventDefault(); this._insertText('language:'); } }, - { label: <>from: , action: e => { e.preventDefault(); this._insertText('from:'); } }, - { label: <>before: , action: e => { e.preventDefault(); this._insertText('before:'); } }, - { label: <>during: , action: e => { e.preventDefault(); this._insertText('during:'); } }, - { label: <>after: , action: e => { e.preventDefault(); this._insertText('after:'); } }, - { label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } + { key: 'prompt-has', label: <>has: , action: e => { e.preventDefault(); this._insertText('has:'); } }, + { key: 'prompt-is', label: <>is: , action: e => { e.preventDefault(); this._insertText('is:'); } }, + { key: 'prompt-language', label: <>language: , action: e => { e.preventDefault(); this._insertText('language:'); } }, + { key: 'prompt-from', label: <>from: , action: e => { e.preventDefault(); this._insertText('from:'); } }, + { key: 'prompt-before', label: <>before: , action: e => { e.preventDefault(); this._insertText('before:'); } }, + { key: 'prompt-during', label: <>during: , action: e => { e.preventDefault(); this._insertText('during:'); } }, + { key: 'prompt-after', label: <>after: , action: e => { e.preventDefault(); this._insertText('after:'); } }, + { key: 'prompt-in', label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } ]; setRef = c => { @@ -263,6 +263,8 @@ class Search extends PureComponent { const { recent } = this.props; return recent.toArray().map(search => ({ + key: `${search.get('type')}/${search.get('q')}`, + label: labelForRecentSearch(search), action: () => this.handleRecentSearchClick(search), @@ -347,8 +349,8 @@ class Search extends PureComponent {

- {recent.size > 0 ? this._getOptions().map(({ label, action, forget }, i) => ( - diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.jsx b/app/javascript/flavours/glitch/features/compose/components/search_results.jsx index 503afd432a..3c0aec1956 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.jsx @@ -5,15 +5,15 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as FindInPageIcon } from '@material-symbols/svg-600/outlined/find_in_page.svg'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { LoadMore } from 'flavours/glitch/components/load_more'; import { SearchSection } from 'flavours/glitch/features/explore/components/search_section'; + import { ImmutableHashtag as Hashtag } from '../../../components/hashtag'; import AccountContainer from '../../../containers/account_container'; import StatusContainer from '../../../containers/status_container'; diff --git a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.jsx b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.jsx index 334efdac87..74706833d9 100644 --- a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.jsx @@ -5,11 +5,11 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as ForumIcon } from '@material-symbols/svg-600/outlined/forum.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; - +import ForumIcon from '@/material-icons/400-24px/forum.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + const messages = defineMessages({ localOnly: { defaultMessage: 'This post is local-only', diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.jsx b/app/javascript/flavours/glitch/features/compose/components/upload.jsx index ac26fb1c6a..c317fc4fc1 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/upload.jsx @@ -5,13 +5,14 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; -import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg'; import spring from 'react-motion/lib/spring'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + import Motion from '../../ui/util/optional_motion'; export default class Upload extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.jsx b/app/javascript/flavours/glitch/features/compose/components/upload_progress.jsx index 5169fbf783..f4408b5460 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload_progress.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.jsx @@ -3,9 +3,9 @@ import { PureComponent } from 'react'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as UploadFileIcon } from '@material-symbols/svg-600/outlined/upload_file.svg'; import spring from 'react-motion/lib/spring'; +import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import Motion from '../../ui/util/optional_motion'; diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_container.js index 17be30edcc..93b8645ac6 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/search_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/search_container.js @@ -1,3 +1,4 @@ +import { createSelector } from '@reduxjs/toolkit'; import { connect } from 'react-redux'; import { @@ -12,10 +13,15 @@ import { import Search from '../components/search'; +const getRecentSearches = createSelector( + state => state.getIn(['search', 'recent']), + recent => recent.reverse(), +); + const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), - recent: state.getIn(['search', 'recent']).reverse(), + recent: getRecentSearches(state), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx index 5266a8012a..458a547d02 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx @@ -1,26 +1,33 @@ import PropTypes from 'prop-types'; +import { useCallback, useState } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; -import { withRouter } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; +import { createSelector } from '@reduxjs/toolkit'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import { useDispatch, useSelector } from 'react-redux'; + -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; import { HotKeys } from 'react-hotkeys'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import { replyCompose } from 'flavours/glitch/actions/compose'; +import { markConversationRead, deleteConversation } from 'flavours/glitch/actions/conversations'; +import { openModal } from 'flavours/glitch/actions/modal'; +import { muteStatus, unmuteStatus, revealStatus, hideStatus } from 'flavours/glitch/actions/statuses'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import AvatarComposite from 'flavours/glitch/components/avatar_composite'; import { IconButton } from 'flavours/glitch/components/icon_button'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import StatusContent from 'flavours/glitch/components/status_content'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import { autoPlayGif } from 'flavours/glitch/initial_state'; -import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; +import { makeGetStatus } from 'flavours/glitch/selectors'; const messages = defineMessages({ more: { id: 'status.more', defaultMessage: 'More' }, @@ -30,45 +37,48 @@ const messages = defineMessages({ delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, + replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, + replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, }); -class Conversation extends ImmutablePureComponent { +const getAccounts = createSelector( + (state) => state.get('accounts'), + (_, accountIds) => accountIds, + (accounts, accountIds) => + accountIds.map(id => accounts.get(id)) +); - static propTypes = { - conversationId: PropTypes.string.isRequired, - accounts: ImmutablePropTypes.list.isRequired, - lastStatus: ImmutablePropTypes.map, - unread:PropTypes.bool.isRequired, - scrollKey: PropTypes.string, - onMoveUp: PropTypes.func, - onMoveDown: PropTypes.func, - markRead: PropTypes.func.isRequired, - delete: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - ...WithRouterPropTypes, - }; +const getStatus = makeGetStatus(); - state = { - isExpanded: undefined, - }; +export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown }) => { + const id = conversation.get('id'); + const unread = conversation.get('unread'); + const lastStatusId = conversation.get('last_status'); + const accountIds = conversation.get('accounts'); + const intl = useIntl(); + const dispatch = useDispatch(); + const history = useHistory(); + const lastStatus = useSelector(state => getStatus(state, { id: lastStatusId })); + const accounts = useSelector(state => getAccounts(state, accountIds)); - parseClick = (e, destination) => { - const { history, lastStatus, unread, markRead } = this.props; - if (!history) return; + // glitch-soc additions + const sharedCWState = useSelector(state => state.getIn(['state', 'content_warnings', 'shared_state'])); + const [expanded, setExpanded] = useState(undefined); + const parseClick = useCallback((e, destination) => { if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) { if (destination === undefined) { if (unread) { - markRead(); + dispatch(markConversationRead(id)); } destination = `/statuses/${lastStatus.get('id')}`; } history.push(destination); e.preventDefault(); } - }; + }, [dispatch, history, unread, id, lastStatus]); - handleMouseEnter = ({ currentTarget }) => { + const handleMouseEnter = useCallback(({ currentTarget }) => { if (autoPlayGif) { return; } @@ -79,9 +89,9 @@ class Conversation extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-original'); } - }; + }, []); - handleMouseLeave = ({ currentTarget }) => { + const handleMouseLeave = useCallback(({ currentTarget }) => { if (autoPlayGif) { return; } @@ -92,145 +102,164 @@ class Conversation extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-static'); } - }; - - handleClick = () => { - if (!this.props.history) { - return; - } - - const { lastStatus, unread, markRead } = this.props; + }, []); + const handleClick = useCallback(() => { if (unread) { - markRead(); + dispatch(markConversationRead(id)); } - this.props.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); - }; + history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); + }, [dispatch, history, unread, id, lastStatus]); - handleMarkAsRead = () => { - this.props.markRead(); - }; + const handleMarkAsRead = useCallback(() => { + dispatch(markConversationRead(id)); + }, [dispatch, id]); - handleReply = () => { - this.props.reply(this.props.lastStatus, this.props.history); - }; + const handleReply = useCallback(() => { + dispatch((_, getState) => { + let state = getState(); - handleDelete = () => { - this.props.delete(); - }; + if (state.getIn(['compose', 'text']).trim().length !== 0) { + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(lastStatus, history)), + }, + })); + } else { + dispatch(replyCompose(lastStatus, history)); + } + }); + }, [dispatch, lastStatus, history, intl]); - handleHotkeyMoveUp = () => { - this.props.onMoveUp(this.props.conversationId); - }; + const handleDelete = useCallback(() => { + dispatch(deleteConversation(id)); + }, [dispatch, id]); - handleHotkeyMoveDown = () => { - this.props.onMoveDown(this.props.conversationId); - }; + const handleHotkeyMoveUp = useCallback(() => { + onMoveUp(id); + }, [id, onMoveUp]); - handleConversationMute = () => { - this.props.onMute(this.props.lastStatus); - }; + const handleHotkeyMoveDown = useCallback(() => { + onMoveDown(id); + }, [id, onMoveDown]); - handleShowMore = () => { - this.props.onToggleHidden(this.props.lastStatus); - - if (this.props.lastStatus.get('spoiler_text')) { - this.setExpansion(!this.state.isExpanded); + const handleConversationMute = useCallback(() => { + if (lastStatus.get('muted')) { + dispatch(unmuteStatus(lastStatus.get('id'))); + } else { + dispatch(muteStatus(lastStatus.get('id'))); } - }; + }, [dispatch, lastStatus]); - setExpansion = value => { - this.setState({ isExpanded: value }); - }; - - render () { - const { accounts, lastStatus, unread, scrollKey, intl } = this.props; - - if (lastStatus === null) { - return null; + const handleShowMore = useCallback(() => { + if (lastStatus.get('hidden')) { + dispatch(revealStatus(lastStatus.get('id'))); + } else { + dispatch(hideStatus(lastStatus.get('id'))); } - const isExpanded = this.props.settings.getIn(['content_warnings', 'shared_state']) ? !lastStatus.get('hidden') : this.state.isExpanded; - - const menu = [ - { text: intl.formatMessage(messages.open), action: this.handleClick }, - null, - ]; - - menu.push({ text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute }); - - if (unread) { - menu.push({ text: intl.formatMessage(messages.markAsRead), action: this.handleMarkAsRead }); - menu.push(null); + if (lastStatus.get('spoiler_text')) { + setExpanded(!expanded); } + }, [dispatch, lastStatus, expanded]); - menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete }); + if (!lastStatus) { + return null; + } - const names = accounts.map(a => ).reduce((prev, cur) => [prev, ', ', cur]); + const menu = [ + { text: intl.formatMessage(messages.open), action: handleClick }, + null, + { text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: handleConversationMute }, + ]; - const handlers = { - reply: this.handleReply, - open: this.handleClick, - moveUp: this.handleHotkeyMoveUp, - moveDown: this.handleHotkeyMoveDown, - toggleHidden: this.handleShowMore, - }; + if (unread) { + menu.push({ text: intl.formatMessage(messages.markAsRead), action: handleMarkAsRead }); + menu.push(null); + } - let media = null; - if (lastStatus.get('media_attachments').size > 0) { - media = ; - } + menu.push({ text: intl.formatMessage(messages.delete), action: handleDelete }); - return ( - -
-
- -
+ const names = accounts.map(a => ( + + + + + + )).reduce((prev, cur) => [prev, ', ', cur]); -
-
-
- {unread && } -
+ const handlers = { + reply: handleReply, + open: handleClick, + moveUp: handleHotkeyMoveUp, + moveDown: handleHotkeyMoveDown, + toggleHidden: handleShowMore, + }; -
- {names} }} /> -
+ let media = null; + if (lastStatus.get('media_attachments').size > 0) { + media = ; + } + + return ( + +
+
+ +
+ +
+
+
+ {unread && }
- +
+ {names} }} /> +
+
-
- + -
- -
+
+ + +
+
- - ); - } +
+ + ); +}; -} - -export default withRouter(injectIntl(Conversation)); +Conversation.propTypes = { + conversation: ImmutablePropTypes.map.isRequired, + scrollKey: PropTypes.string, + onMoveUp: PropTypes.func, + onMoveDown: PropTypes.func, +}; diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.jsx index 8c12ea9e5f..b1a8fd09b6 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.jsx @@ -1,77 +1,72 @@ import PropTypes from 'prop-types'; +import { useRef, useMemo, useCallback } from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import { useSelector, useDispatch } from 'react-redux'; import { debounce } from 'lodash'; -import ScrollableList from '../../../components/scrollable_list'; -import ConversationContainer from '../containers/conversation_container'; +import { expandConversations } from 'flavours/glitch/actions/conversations'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; -export default class ConversationsList extends ImmutablePureComponent { +import { Conversation } from './conversation'; - static propTypes = { - conversations: ImmutablePropTypes.list.isRequired, - scrollKey: PropTypes.string.isRequired, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - onLoadMore: PropTypes.func, - }; +const focusChild = (node, index, alignTop) => { + const element = node.querySelector(`article:nth-of-type(${index + 1}) .focusable`); - getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id); - - handleMoveUp = id => { - const elementIndex = this.getCurrentIndex(id) - 1; - this._selectChild(elementIndex, true); - }; - - handleMoveDown = id => { - const elementIndex = this.getCurrentIndex(id) + 1; - this._selectChild(elementIndex, false); - }; - - _selectChild (index, align_top) { - const container = this.node.node; - const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); - - if (element) { - if (align_top && container.scrollTop > element.offsetTop) { - element.scrollIntoView(true); - } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { - element.scrollIntoView(false); - } - element.focus(); + if (element) { + if (alignTop && node.scrollTop > element.offsetTop) { + element.scrollIntoView(true); + } else if (!alignTop && node.scrollTop + node.clientHeight < element.offsetTop + element.offsetHeight) { + element.scrollIntoView(false); } + + element.focus(); } +}; - setRef = c => { - this.node = c; - }; +export const ConversationsList = ({ scrollKey, ...other }) => { + const listRef = useRef(); + const conversations = useSelector(state => state.getIn(['conversations', 'items'])); + const isLoading = useSelector(state => state.getIn(['conversations', 'isLoading'], true)); + const hasMore = useSelector(state => state.getIn(['conversations', 'hasMore'], false)); + const dispatch = useDispatch(); + const lastStatusId = conversations.last()?.get('last_status'); - handleLoadOlder = debounce(() => { - const last = this.props.conversations.last(); + const handleMoveUp = useCallback(id => { + const elementIndex = conversations.findIndex(x => x.get('id') === id) - 1; + focusChild(listRef.current.node, elementIndex, true); + }, [listRef, conversations]); - if (last && last.get('last_status')) { - this.props.onLoadMore(last.get('last_status')); + const handleMoveDown = useCallback(id => { + const elementIndex = conversations.findIndex(x => x.get('id') === id) + 1; + focusChild(listRef.current.node, elementIndex, false); + }, [listRef, conversations]); + + const debouncedLoadMore = useMemo(() => debounce(id => { + dispatch(expandConversations({ maxId: id })); + }, 300, { leading: true }), [dispatch]); + + const handleLoadMore = useCallback(() => { + if (lastStatusId) { + debouncedLoadMore(lastStatusId); } - }, 300, { leading: true }); + }, [debouncedLoadMore, lastStatusId]); - render () { - const { conversations, isLoading, onLoadMore, ...other } = this.props; + return ( + + {conversations.map(item => ( + + ))} + + ); +}; - return ( - - {conversations.map(item => ( - - ))} - - ); - } - -} +ConversationsList.propTypes = { + scrollKey: PropTypes.string.isRequired, +}; diff --git a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js deleted file mode 100644 index 207d3ebb65..0000000000 --- a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js +++ /dev/null @@ -1,81 +0,0 @@ -import { defineMessages, injectIntl } from 'react-intl'; - -import { connect } from 'react-redux'; - -import { replyCompose } from 'flavours/glitch/actions/compose'; -import { markConversationRead, deleteConversation } from 'flavours/glitch/actions/conversations'; -import { openModal } from 'flavours/glitch/actions/modal'; -import { muteStatus, unmuteStatus, hideStatus, revealStatus } from 'flavours/glitch/actions/statuses'; -import { makeGetStatus } from 'flavours/glitch/selectors'; - -import Conversation from '../components/conversation'; - -const messages = defineMessages({ - replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, - replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, -}); - -const mapStateToProps = () => { - const getStatus = makeGetStatus(); - - return (state, { conversationId }) => { - const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId); - const lastStatusId = conversation.get('last_status', null); - - return { - accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)), - unread: conversation.get('unread'), - lastStatus: lastStatusId && getStatus(state, { id: lastStatusId }), - settings: state.get('local_settings'), - }; - }; -}; - -const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({ - - markRead () { - dispatch(markConversationRead(conversationId)); - }, - - reply (status, router) { - dispatch((_, getState) => { - let state = getState(); - - if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status, router)), - }, - })); - } else { - dispatch(replyCompose(status, router)); - } - }); - }, - - delete () { - dispatch(deleteConversation(conversationId)); - }, - - onMute (status) { - if (status.get('muted')) { - dispatch(unmuteStatus(status.get('id'))); - } else { - dispatch(muteStatus(status.get('id'))); - } - }, - - onToggleHidden (status) { - if (status.get('hidden')) { - dispatch(revealStatus(status.get('id'))); - } else { - dispatch(hideStatus(status.get('id'))); - } - }, - -}); - -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Conversation)); diff --git a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversations_list_container.js b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversations_list_container.js deleted file mode 100644 index 1dcd3ec1bd..0000000000 --- a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversations_list_container.js +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; - -import { expandConversations } from '../../../actions/conversations'; -import ConversationsList from '../components/conversations_list'; - -const mapStateToProps = state => ({ - conversations: state.getIn(['conversations', 'items']), - isLoading: state.getIn(['conversations', 'isLoading'], true), - hasMore: state.getIn(['conversations', 'hasMore'], false), -}); - -const mapDispatchToProps = dispatch => ({ - onLoadMore: maxId => dispatch(expandConversations({ maxId })), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ConversationsList); diff --git a/app/javascript/flavours/glitch/features/direct_timeline/index.jsx b/app/javascript/flavours/glitch/features/direct_timeline/index.jsx index edb5b5aa45..25f0dd9997 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/index.jsx @@ -1,14 +1,13 @@ import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useRef, useCallback, useEffect } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; -import { connect } from 'react-redux'; - -import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg'; +import { useDispatch, useSelector } from 'react-redux'; +import MailIcon from '@/material-icons/400-24px/mail.svg?react'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { mountConversations, unmountConversations, expandConversations } from 'flavours/glitch/actions/conversations'; import { connectDirectStream } from 'flavours/glitch/actions/streaming'; @@ -17,51 +16,44 @@ import Column from 'flavours/glitch/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; +import { ConversationsList } from './components/conversations_list'; import ColumnSettingsContainer from './containers/column_settings_container'; -import ConversationsListContainer from './containers/conversations_list_container'; const messages = defineMessages({ title: { id: 'column.direct', defaultMessage: 'Private mentions' }, }); -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0, - conversationsMode: state.getIn(['settings', 'direct', 'conversations']), -}); +const DirectTimeline = ({ columnId, multiColumn }) => { + const columnRef = useRef(); + const intl = useIntl(); + const dispatch = useDispatch(); + const pinned = !!columnId; -class DirectTimeline extends PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - columnId: PropTypes.string, - intl: PropTypes.object.isRequired, - hasUnread: PropTypes.bool, - multiColumn: PropTypes.bool, - conversationsMode: PropTypes.bool, - }; - - handlePin = () => { - const { columnId, dispatch } = this.props; + // glitch-soc additions + const hasUnread = useSelector(state => state.getIn(['timelines', 'direct', 'unread']) > 0); + const conversationsMode = useSelector(state => state.getIn(['settings', 'direct', 'conversations'])); + const handlePin = useCallback(() => { if (columnId) { dispatch(removeColumn(columnId)); } else { dispatch(addColumn('DIRECT', {})); } - }; + }, [dispatch, columnId]); - handleMove = (dir) => { - const { columnId, dispatch } = this.props; + const handleMove = useCallback((dir) => { dispatch(moveColumn(columnId, dir)); - }; + }, [dispatch, columnId]); - handleHeaderClick = () => { - this.column.scrollTop(); - }; + const handleHeaderClick = useCallback(() => { + columnRef.current.scrollTop(); + }, [columnRef]); - componentDidMount () { - const { dispatch, conversationsMode } = this.props; + const handleLoadMoreTimeline = useCallback(maxId => { + dispatch(expandDirectTimeline({ maxId })); + }, [dispatch]); + useEffect(() => { dispatch(mountConversations()); if (conversationsMode) { @@ -70,99 +62,67 @@ class DirectTimeline extends PureComponent { dispatch(expandDirectTimeline()); } - this.disconnect = dispatch(connectDirectStream()); - } + const disconnect = dispatch(connectDirectStream()); - componentDidUpdate(prevProps) { - const { dispatch, conversationsMode } = this.props; + return () => { + dispatch(unmountConversations()); + disconnect(); + }; + }, [dispatch, conversationsMode]); - if (prevProps.conversationsMode && !conversationsMode) { - dispatch(expandDirectTimeline()); - } else if (!prevProps.conversationsMode && conversationsMode) { - dispatch(expandConversations()); - } - } + return ( + + + + - componentWillUnmount () { - this.props.dispatch(unmountConversations()); - - if (this.disconnect) { - this.disconnect(); - this.disconnect = null; - } - } - - setRef = c => { - this.column = c; - }; - - handleLoadMoreTimeline = maxId => { - this.props.dispatch(expandDirectTimeline({ maxId })); - }; - - handleLoadMoreConversations = maxId => { - this.props.dispatch(expandConversations({ maxId })); - }; - - render () { - const { intl, hasUnread, columnId, multiColumn, conversationsMode } = this.props; - const pinned = !!columnId; - - let contents; - if (conversationsMode) { - contents = ( - } bindToDocument={!multiColumn} - onLoadMore={this.handleLoadMore} prepend={
} alwaysPrepend - emptyMessage={} /> - ); - } else { - contents = ( + ) : (
} + onLoadMore={handleLoadMoreTimeline} + prepend={ +
+ +
+ } alwaysPrepend emptyMessage={} /> - ); - } + )} - return ( - - - - + + {intl.formatMessage(messages.title)} + + + + ); +}; - {contents} +DirectTimeline.propTypes = { + columnId: PropTypes.string, + multiColumn: PropTypes.bool, +}; - - {intl.formatMessage(messages.title)} - - - - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(DirectTimeline)); +export default DirectTimeline; diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.jsx b/app/javascript/flavours/glitch/features/directory/components/account_card.jsx index 27b363bd08..60927f81c3 100644 --- a/app/javascript/flavours/glitch/features/directory/components/account_card.jsx +++ b/app/javascript/flavours/glitch/features/directory/components/account_card.jsx @@ -19,7 +19,7 @@ import { Avatar } from 'flavours/glitch/components/avatar'; import { Button } from 'flavours/glitch/components/button'; import { DisplayName } from 'flavours/glitch/components/display_name'; import { IconButton } from 'flavours/glitch/components/icon_button'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import { ShortNumber } from 'flavours/glitch/components/short_number'; import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/initial_state'; import { makeGetAccount } from 'flavours/glitch/selectors'; diff --git a/app/javascript/flavours/glitch/features/directory/index.jsx b/app/javascript/flavours/glitch/features/directory/index.jsx index a3c97506cf..0d8742b255 100644 --- a/app/javascript/flavours/glitch/features/directory/index.jsx +++ b/app/javascript/flavours/glitch/features/directory/index.jsx @@ -9,8 +9,8 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'flavours/glitch/actions/columns'; import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directory'; import Column from 'flavours/glitch/components/column'; diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.jsx b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx index 142f14bf71..964eada9c1 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.jsx +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx @@ -8,9 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block-fill.svg'; import { debounce } from 'lodash'; +import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react'; + import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/flavours/glitch/features/explore/index.jsx b/app/javascript/flavours/glitch/features/explore/index.jsx index 8e0d8f71b1..9a18ccb72c 100644 --- a/app/javascript/flavours/glitch/features/explore/index.jsx +++ b/app/javascript/flavours/glitch/features/explore/index.jsx @@ -8,9 +8,9 @@ import { NavLink, Switch, Route } from 'react-router-dom'; import { connect } from 'react-redux'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import Column from 'flavours/glitch/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; import Search from 'flavours/glitch/features/compose/containers/search_container'; diff --git a/app/javascript/flavours/glitch/features/explore/results.jsx b/app/javascript/flavours/glitch/features/explore/results.jsx index d2b84f801d..53285988c3 100644 --- a/app/javascript/flavours/glitch/features/explore/results.jsx +++ b/app/javascript/flavours/glitch/features/explore/results.jsx @@ -9,10 +9,10 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as FindInPageIcon } from '@material-symbols/svg-600/outlined/find_in_page.svg'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; +import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { submitSearch, expandSearch } from 'flavours/glitch/actions/search'; import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import { Icon } from 'flavours/glitch/components/icon'; diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx index 61f2faa2da..119fd247b6 100644 --- a/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; import { debounce } from 'lodash'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'flavours/glitch/actions/favourites'; import ColumnHeader from 'flavours/glitch/components/column_header'; diff --git a/app/javascript/flavours/glitch/features/favourites/index.jsx b/app/javascript/flavours/glitch/features/favourites/index.jsx index b6aa0f42e7..c21166a16e 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.jsx +++ b/app/javascript/flavours/glitch/features/favourites/index.jsx @@ -8,10 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as RefreshIcon } from '@material-symbols/svg-600/outlined/refresh.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; import { debounce } from 'lodash'; +import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { fetchFavourites, expandFavourites } from 'flavours/glitch/actions/interactions'; import ColumnHeader from 'flavours/glitch/components/column_header'; import { Icon } from 'flavours/glitch/components/icon'; diff --git a/app/javascript/flavours/glitch/features/filters/select_filter.jsx b/app/javascript/flavours/glitch/features/filters/select_filter.jsx index 2cda219e48..f409cc2d70 100644 --- a/app/javascript/flavours/glitch/features/filters/select_filter.jsx +++ b/app/javascript/flavours/glitch/features/filters/select_filter.jsx @@ -5,9 +5,9 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; import fuzzysort from 'fuzzysort'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { toServerSideType } from 'flavours/glitch/utils/filters'; import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons'; diff --git a/app/javascript/flavours/glitch/features/firehose/index.jsx b/app/javascript/flavours/glitch/features/firehose/index.jsx index 45e17939d2..29219974a9 100644 --- a/app/javascript/flavours/glitch/features/firehose/index.jsx +++ b/app/javascript/flavours/glitch/features/firehose/index.jsx @@ -6,8 +6,8 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; import { NavLink } from 'react-router-dom'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { addColumn } from 'flavours/glitch/actions/columns'; import { changeSetting } from 'flavours/glitch/actions/settings'; import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming'; diff --git a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx index a252ab25bd..877d84c632 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx @@ -5,13 +5,13 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from '../../../components/avatar'; import { DisplayName } from '../../../components/display_name'; import { IconButton } from '../../../components/icon_button'; -import Permalink from '../../../components/permalink'; +import { Permalink } from '../../../components/permalink'; const messages = defineMessages({ authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.jsx b/app/javascript/flavours/glitch/features/follow_requests/index.jsx index 3b98791926..7d651f2ca6 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/index.jsx @@ -8,9 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; import { debounce } from 'lodash'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; + import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import ScrollableList from '../../components/scrollable_list'; import { me } from '../../initial_state'; diff --git a/app/javascript/flavours/glitch/features/followed_tags/index.jsx b/app/javascript/flavours/glitch/features/followed_tags/index.jsx index 477a51d409..5809d848bc 100644 --- a/app/javascript/flavours/glitch/features/followed_tags/index.jsx +++ b/app/javascript/flavours/glitch/features/followed_tags/index.jsx @@ -8,12 +8,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; import { debounce } from 'lodash'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { expandFollowedHashtags, fetchFollowedHashtags } from 'flavours/glitch/actions/tags'; import ColumnHeader from 'flavours/glitch/components/column_header'; -import Hashtag from 'flavours/glitch/components/hashtag'; +import { Hashtag } from 'flavours/glitch/components/hashtag'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; import Column from 'flavours/glitch/features/ui/components/column'; diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx index 27548ba86b..27470d9938 100644 --- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx +++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx @@ -9,13 +9,14 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; import TransitionMotion from 'react-motion/lib/TransitionMotion'; import spring from 'react-motion/lib/spring'; import ReactSwipeableViews from 'react-swipeable-views'; +import elephantUIPlane from '@/images/elephant_ui_plane.svg'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { AnimatedNumber } from 'flavours/glitch/components/animated_number'; import { Icon } from 'flavours/glitch/components/icon'; import { IconButton } from 'flavours/glitch/components/icon_button'; @@ -24,7 +25,6 @@ import { unicodeMapping } from 'flavours/glitch/features/emoji/emoji_unicode_map import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state'; import { assetHost } from 'flavours/glitch/utils/config'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; -import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, diff --git a/app/javascript/flavours/glitch/features/getting_started/index.jsx b/app/javascript/flavours/glitch/features/getting_started/index.jsx index 7abf26594e..866904eafb 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.jsx +++ b/app/javascript/flavours/glitch/features/getting_started/index.jsx @@ -10,20 +10,19 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; -import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg'; -import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg'; -import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; -import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; +import MailIcon from '@/material-icons/400-24px/mail.svg?react'; +import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react'; +import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { fetchLists } from 'flavours/glitch/actions/lists'; import { openModal } from 'flavours/glitch/actions/modal'; @@ -31,6 +30,7 @@ import Column from 'flavours/glitch/features/ui/components/column'; import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; import { preferencesLink } from 'flavours/glitch/utils/backend_links'; + import { me, showTrends } from '../../initial_state'; import NavigationBar from '../compose/components/navigation_bar'; import ColumnLink from '../ui/components/column_link'; diff --git a/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx index aa07b64c90..7c4979bd63 100644 --- a/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx +++ b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx @@ -5,18 +5,19 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block.svg'; -import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg'; -import { ReactComponent as PersonCheckIcon } from '@material-symbols/svg-600/outlined/person_check.svg'; -import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg'; - +import BlockIcon from '@/material-icons/400-24px/block.svg?react'; +import InfoIcon from '@/material-icons/400-24px/info.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import PersonCheckIcon from '@/material-icons/400-24px/person_check.svg?react'; +import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react'; import { openModal } from 'flavours/glitch/actions/modal'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; + const messages = defineMessages({ heading: { id: 'column.heading', defaultMessage: 'Misc' }, subheading: { id: 'column.subheading', defaultMessage: 'Miscellaneous options' }, @@ -51,7 +52,7 @@ class GettingStartedMisc extends ImmutablePureComponent { const { signedIn } = this.context.identity; return ( - +
{signedIn && ()} diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx b/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx index 11f09ffdb2..c2a7574307 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx @@ -8,9 +8,9 @@ import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; import { isEqual } from 'lodash'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { connectHashtagStream } from 'flavours/glitch/actions/streaming'; import { fetchHashtag, followHashtag, unfollowHashtag } from 'flavours/glitch/actions/tags'; diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.tsx index f33135621a..73ef8a5fe1 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.tsx +++ b/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.tsx @@ -2,8 +2,8 @@ import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router-dom'; +import background from '@/images/friends-cropped.png'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; -import background from 'mastodon/../images/friends-cropped.png'; export const ExplorePrompt = () => ( diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.jsx b/app/javascript/flavours/glitch/features/home_timeline/index.jsx index ee23c65276..093d8a7602 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/home_timeline/index.jsx @@ -10,9 +10,9 @@ import { createSelector } from '@reduxjs/toolkit'; import { List as ImmutableList } from 'immutable'; import { connect } from 'react-redux'; -import { ReactComponent as CampaignIcon } from '@material-symbols/svg-600/outlined/campaign.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; diff --git a/app/javascript/flavours/glitch/features/interaction_modal/index.jsx b/app/javascript/flavours/glitch/features/interaction_modal/index.jsx index 627fbef970..71cdf8409c 100644 --- a/app/javascript/flavours/glitch/features/interaction_modal/index.jsx +++ b/app/javascript/flavours/glitch/features/interaction_modal/index.jsx @@ -7,12 +7,12 @@ import classNames from 'classnames'; import { connect } from 'react-redux'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg'; import { throttle, escapeRegExp } from 'lodash'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { openModal, closeModal } from 'flavours/glitch/actions/modal'; import api from 'flavours/glitch/api'; import { Button } from 'flavours/glitch/components/button'; diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx index 466f2cb561..98f65d9b50 100644 --- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx +++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx @@ -7,8 +7,8 @@ import { Helmet } from 'react-helmet'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg'; +import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import Column from 'flavours/glitch/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; diff --git a/app/javascript/flavours/glitch/features/list_adder/components/list.jsx b/app/javascript/flavours/glitch/features/list_adder/components/list.jsx index 8c0333b5ad..adbb16fcd6 100644 --- a/app/javascript/flavours/glitch/features/list_adder/components/list.jsx +++ b/app/javascript/flavours/glitch/features/list_adder/components/list.jsx @@ -6,12 +6,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; - +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + import { removeFromListAdder, addToListAdder } from '../../../actions/lists'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/list_editor/components/account.jsx b/app/javascript/flavours/glitch/features/list_editor/components/account.jsx index 18d5e905cb..77d32af80a 100644 --- a/app/javascript/flavours/glitch/features/list_editor/components/account.jsx +++ b/app/javascript/flavours/glitch/features/list_editor/components/account.jsx @@ -6,8 +6,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { removeFromListEditor, addToListEditor } from '../../../actions/lists'; import { Avatar } from '../../../components/avatar'; diff --git a/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.jsx b/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.jsx index 1e2446f92b..89f596636e 100644 --- a/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.jsx +++ b/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.jsx @@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { changeListEditorTitle, submitListEditor } from '../../../actions/lists'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/list_editor/components/search.jsx b/app/javascript/flavours/glitch/features/list_editor/components/search.jsx index 58fb6566d6..6e9cd44835 100644 --- a/app/javascript/flavours/glitch/features/list_editor/components/search.jsx +++ b/app/javascript/flavours/glitch/features/list_editor/components/search.jsx @@ -7,11 +7,11 @@ import classNames from 'classnames'; import { connect } from 'react-redux'; -import { ReactComponent as CancelIcon } from '@material-symbols/svg-600/outlined/cancel.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; - +import CancelIcon from '@/material-icons/400-24px/cancel.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.jsx b/app/javascript/flavours/glitch/features/list_timeline/index.jsx index 48100a0a4c..ae9f75318f 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/list_timeline/index.jsx @@ -9,11 +9,11 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as DeleteIcon } from '@material-symbols/svg-600/outlined/delete.svg'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; import Toggle from 'react-toggle'; +import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { fetchList, deleteList, updateList } from 'flavours/glitch/actions/lists'; import { openModal } from 'flavours/glitch/actions/modal'; diff --git a/app/javascript/flavours/glitch/features/lists/index.jsx b/app/javascript/flavours/glitch/features/lists/index.jsx index 89aea03321..9c24d82f03 100644 --- a/app/javascript/flavours/glitch/features/lists/index.jsx +++ b/app/javascript/flavours/glitch/features/lists/index.jsx @@ -9,8 +9,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { fetchLists } from 'flavours/glitch/actions/lists'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx index 32b25e0c07..00744fa2ae 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx @@ -4,15 +4,15 @@ import { PureComponent } from 'react'; import { injectIntl, defineMessages } from 'react-intl'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; -import { ReactComponent as ExpandLessIcon } from '@material-symbols/svg-600/outlined/expand_less.svg'; -import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg'; -import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg'; -import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import ExpandLessIcon from '@/material-icons/400-24px/expand_less.svg?react'; +import ImageIcon from '@/material-icons/400-24px/image.svg?react'; +import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; import { preferencesLink } from 'flavours/glitch/utils/backend_links'; + import LocalSettingsNavigationItem from './item'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/mutes/index.jsx b/app/javascript/flavours/glitch/features/mutes/index.jsx index 7f66edc03d..3b50244ea4 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.jsx +++ b/app/javascript/flavours/glitch/features/mutes/index.jsx @@ -8,9 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg'; import { debounce } from 'lodash'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react'; + import { fetchMutes, expandMutes } from '../../actions/mutes'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx b/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx index 70e8a4e343..d16a775ea5 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx @@ -8,11 +8,11 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as FlagIcon } from '@material-symbols/svg-600/outlined/flag-fill.svg'; import { HotKeys } from 'react-hotkeys'; +import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import NotificationOverlayContainer from '../containers/overlay_container'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx index ee547e05b1..60affd444b 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx @@ -8,11 +8,11 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add-fill.svg'; import { HotKeys } from 'react-hotkeys'; +import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import AccountContainer from 'flavours/glitch/containers/account_container'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.jsx b/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.jsx index 373f20e51d..6e29709b59 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.jsx @@ -3,10 +3,10 @@ import { PureComponent } from 'react'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as DeleteForeverIcon } from '@material-symbols/svg-600/outlined/delete_forever.svg'; - +import DeleteForeverIcon from '@/material-icons/400-24px/delete_forever.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + export default class ClearColumnButton extends PureComponent { static propTypes = { diff --git a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.jsx b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.jsx index 615a27dc4a..7d4a28a2ef 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.jsx @@ -3,15 +3,15 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg'; - +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + const tooltips = defineMessages({ mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' }, favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' }, diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.jsx b/app/javascript/flavours/glitch/features/notifications/components/follow.jsx index 981d14aaf3..85ccde094b 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/follow.jsx @@ -8,11 +8,11 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add-fill.svg'; import { HotKeys } from 'react-hotkeys'; +import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import AccountContainer from 'flavours/glitch/containers/account_container'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx index b3b415a2a4..6607279983 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx @@ -8,16 +8,16 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person-fill.svg'; import { HotKeys } from 'react-hotkeys'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react'; import { Avatar } from 'flavours/glitch/components/avatar'; import { DisplayName } from 'flavours/glitch/components/display_name'; import { Icon } from 'flavours/glitch/components/icon'; import { IconButton } from 'flavours/glitch/components/icon_button'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import NotificationOverlayContainer from '../containers/overlay_container'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.jsx b/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.jsx index 4dc8ca9f5c..82c08ea175 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.jsx @@ -5,9 +5,9 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import TuneIcon from '@/material-icons/400-24px/tune.svg?react'; import { requestBrowserPermission } from 'flavours/glitch/actions/notifications'; import { changeSetting } from 'flavours/glitch/actions/settings'; import { Button } from 'flavours/glitch/components/button'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx b/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx index 1ed052f83e..faf7e42e71 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx @@ -9,10 +9,10 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; - +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + const messages = defineMessages({ markForDeletion: { id: 'notification.markForDeletion', defaultMessage: 'Mark for deletion' }, }); diff --git a/app/javascript/flavours/glitch/features/notifications/index.jsx b/app/javascript/flavours/glitch/features/notifications/index.jsx index 38544676d9..783c35a43e 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.jsx +++ b/app/javascript/flavours/glitch/features/notifications/index.jsx @@ -11,11 +11,11 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as DeleteForeverIcon } from '@material-symbols/svg-600/outlined/delete_forever.svg'; -import { ReactComponent as DoneAllIcon } from '@material-symbols/svg-600/outlined/done_all.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; import { debounce } from 'lodash'; +import DeleteForeverIcon from '@/material-icons/400-24px/delete_forever.svg?react'; +import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react'; +import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; import { compareId } from 'flavours/glitch/compare_id'; import { Icon } from 'flavours/glitch/components/icon'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; diff --git a/app/javascript/flavours/glitch/features/onboarding/components/step.jsx b/app/javascript/flavours/glitch/features/onboarding/components/step.jsx index 179e688b9c..4dcd3c9ce5 100644 --- a/app/javascript/flavours/glitch/features/onboarding/components/step.jsx +++ b/app/javascript/flavours/glitch/features/onboarding/components/step.jsx @@ -2,11 +2,11 @@ import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; -import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg'; - +import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react'; +import CheckIcon from '@/material-icons/400-24px/done.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => { const content = ( <> diff --git a/app/javascript/flavours/glitch/features/onboarding/index.jsx b/app/javascript/flavours/glitch/features/onboarding/index.jsx index dbf1ba1f61..fb9df29aeb 100644 --- a/app/javascript/flavours/glitch/features/onboarding/index.jsx +++ b/app/javascript/flavours/glitch/features/onboarding/index.jsx @@ -8,19 +8,19 @@ import { Link, Switch, Route, useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { ReactComponent as AccountCircleIcon } from '@material-symbols/svg-600/outlined/account_circle.svg'; -import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; -import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg'; -import { ReactComponent as EditNoteIcon } from '@material-symbols/svg-600/outlined/edit_note.svg'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; +import illustration from '@/images/elephant_ui_conversation.svg'; +import AccountCircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; +import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react'; +import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react'; +import EditNoteIcon from '@/material-icons/400-24px/edit_note.svg?react'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; import { focusCompose } from 'flavours/glitch/actions/compose'; import { Icon } from 'flavours/glitch/components/icon'; import Column from 'flavours/glitch/features/ui/components/column'; import { me } from 'flavours/glitch/initial_state'; import { useAppSelector } from 'flavours/glitch/store'; import { assetHost } from 'flavours/glitch/utils/config'; -import illustration from 'mastodon/../images/elephant_ui_conversation.svg'; import { Step } from './components/step'; import { Follows } from './follows'; diff --git a/app/javascript/flavours/glitch/features/onboarding/profile.jsx b/app/javascript/flavours/glitch/features/onboarding/profile.jsx index fb7c515f00..75e179ee1e 100644 --- a/app/javascript/flavours/glitch/features/onboarding/profile.jsx +++ b/app/javascript/flavours/glitch/features/onboarding/profile.jsx @@ -8,10 +8,10 @@ import { useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; import Toggle from 'react-toggle'; +import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import { updateAccount } from 'flavours/glitch/actions/accounts'; import { Button } from 'flavours/glitch/components/button'; import { ColumnBackButton } from 'flavours/glitch/components/column_back_button'; diff --git a/app/javascript/flavours/glitch/features/onboarding/share.jsx b/app/javascript/flavours/glitch/features/onboarding/share.jsx index b5732c0abc..0c7d579c10 100644 --- a/app/javascript/flavours/glitch/features/onboarding/share.jsx +++ b/app/javascript/flavours/glitch/features/onboarding/share.jsx @@ -7,10 +7,10 @@ import classNames from 'classnames'; import { Link } from 'react-router-dom'; -import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; -import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg'; import SwipeableViews from 'react-swipeable-views'; +import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react'; +import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react'; import { ColumnBackButton } from 'flavours/glitch/components/column_back_button'; import { Icon } from 'flavours/glitch/components/icon'; import { me, domain } from 'flavours/glitch/initial_state'; diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx index ee8179fc3b..83e53d9263 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx @@ -9,12 +9,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as OpenInNewIcon } from '@material-symbols/svg-600/outlined/open_in_new.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg'; +import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { initBoostModal } from 'flavours/glitch/actions/boosts'; import { replyCompose } from 'flavours/glitch/actions/compose'; import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions'; diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx index 6ed1f4fbdd..dd90cd3522 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx @@ -8,8 +8,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from 'flavours/glitch/components/avatar'; import { DisplayName } from 'flavours/glitch/components/display_name'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx index b888ab6850..94d5f6cb4c 100644 --- a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx @@ -8,10 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg'; - +import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; import { getStatusList } from 'flavours/glitch/selectors'; + import { fetchPinnedStatuses } from '../../actions/pin_statuses'; import StatusList from '../../components/status_list'; import Column from '../ui/components/column'; diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.jsx b/app/javascript/flavours/glitch/features/public_timeline/index.jsx index 00e023eaac..0062a9f719 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/public_timeline/index.jsx @@ -7,8 +7,8 @@ import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { domain } from 'flavours/glitch/initial_state'; diff --git a/app/javascript/flavours/glitch/features/reblogs/index.jsx b/app/javascript/flavours/glitch/features/reblogs/index.jsx index 57ccf9cfb1..52e9a35833 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.jsx +++ b/app/javascript/flavours/glitch/features/reblogs/index.jsx @@ -8,12 +8,13 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as RefreshIcon } from '@material-symbols/svg-600/outlined/refresh.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; import { debounce } from 'lodash'; +import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + import { fetchReblogs, expandReblogs } from '../../actions/interactions'; import ColumnHeader from '../../components/column_header'; import { LoadingIndicator } from '../../components/loading_indicator'; diff --git a/app/javascript/flavours/glitch/features/report/components/option.jsx b/app/javascript/flavours/glitch/features/report/components/option.jsx index af296528b3..3b40de8cba 100644 --- a/app/javascript/flavours/glitch/features/report/components/option.jsx +++ b/app/javascript/flavours/glitch/features/report/components/option.jsx @@ -3,10 +3,10 @@ import { PureComponent } from 'react'; import classNames from 'classnames'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg'; - +import CheckIcon from '@/material-icons/400-24px/done.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; + export default class Option extends PureComponent { static propTypes = { diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx index 4d8adb173e..1d8707ccbf 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx @@ -8,20 +8,21 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg'; -import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; -import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg'; - +import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; +import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; +import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; +import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg'; +import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg'; +import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; -import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg'; -import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg'; import { IconButton } from '../../../components/icon_button'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; @@ -236,7 +237,7 @@ class ActionBar extends PureComponent { if (status.get('reblogged')) { reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon; + reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; } else if (publicStatus) { reblogTitle = intl.formatMessage(messages.reblog); reblogIconComponent = RepeatIcon; diff --git a/app/javascript/flavours/glitch/features/status/components/card.jsx b/app/javascript/flavours/glitch/features/status/components/card.jsx index 722c6acd86..3d5522ab61 100644 --- a/app/javascript/flavours/glitch/features/status/components/card.jsx +++ b/app/javascript/flavours/glitch/features/status/components/card.jsx @@ -8,10 +8,10 @@ import classNames from 'classnames'; import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description-fill.svg'; -import { ReactComponent as OpenInNewIcon } from '@material-symbols/svg-600/outlined/open_in_new.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; +import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react'; +import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Icon } from 'flavours/glitch/components/icon'; import { useBlurhash } from 'flavours/glitch/initial_state'; diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index 8b77206e8e..0638d0d646 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -8,9 +8,9 @@ import { Link, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { AnimatedNumber } from 'flavours/glitch/components/animated_number'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import EditedTimestamp from 'flavours/glitch/components/edited_timestamp'; diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index 713ea24c77..f9b8640762 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -12,11 +12,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as ChatIcon } from '@material-symbols/svg-600/outlined/chat.svg'; -import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; import { HotKeys } from 'react-hotkeys'; +import ChatIcon from '@/material-icons/400-24px/chat.svg?react'; +import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import ScrollContainer from 'flavours/glitch/containers/scroll_container'; diff --git a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx index 86c2ede894..1edfe700fe 100644 --- a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx +++ b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx @@ -8,8 +8,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { followAccount } from 'flavours/glitch/actions/accounts'; import { Button } from 'flavours/glitch/components/button'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx index a3d9423f91..f44fd5ebf4 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx @@ -9,8 +9,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import { changeBoostPrivacy } from 'flavours/glitch/actions/boosts'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import { Icon } from 'flavours/glitch/components/icon'; diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx b/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx index 44165f1417..d1c9e36883 100644 --- a/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx @@ -3,7 +3,7 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import { ReactComponent as RefreshIcon } from '@material-symbols/svg-600/outlined/refresh.svg'; +import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx index 10cd32d168..7e84a578c2 100644 --- a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx @@ -6,9 +6,9 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import escapeTextContentForBrowser from 'escape-html'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { closeModal } from 'flavours/glitch/actions/modal'; import { IconButton } from 'flavours/glitch/components/icon_button'; import InlineAccount from 'flavours/glitch/components/inline_account'; diff --git a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx index 61750a20dd..2f0c07e78b 100644 --- a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx @@ -5,9 +5,9 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg'; -import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg'; +import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; import { Button } from 'flavours/glitch/components/button'; import { Icon } from 'flavours/glitch/components/icon'; import illustration from 'flavours/glitch/images/logo_warn_glitch.svg'; diff --git a/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx index 7caef5fccf..edd2fb8940 100644 --- a/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx @@ -4,8 +4,8 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import api from 'flavours/glitch/api'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx index da9c74111e..388bc5762c 100644 --- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx @@ -8,8 +8,8 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import { Avatar } from 'flavours/glitch/components/avatar'; import { Button } from 'flavours/glitch/components/button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx index 6c347f8f6b..9ce5eec874 100644 --- a/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx @@ -5,8 +5,8 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { fetchFilters, createFilter, createFilterStatus } from 'flavours/glitch/actions/filters'; import { fetchStatus } from 'flavours/glitch/actions/statuses'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx index 29784c89e2..caed29c252 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx @@ -9,7 +9,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import Textarea from 'react-textarea-autosize'; import { length } from 'stringz'; // eslint-disable-next-line import/extensions @@ -17,6 +16,7 @@ import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js'; // eslint-disable-next-line import/no-extraneous-dependencies import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Button } from 'flavours/glitch/components/button'; import { GIFV } from 'flavours/glitch/components/gifv'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx b/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx index 612d5eace4..24fd5b6442 100644 --- a/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx @@ -6,8 +6,7 @@ import { injectIntl, defineMessages } from 'react-intl'; import { List as ImmutableList } from 'immutable'; import { connect } from 'react-redux'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; - +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; diff --git a/app/javascript/flavours/glitch/features/ui/components/header.jsx b/app/javascript/flavours/glitch/features/ui/components/header.jsx index 1efacfc1c4..618a6b63d4 100644 --- a/app/javascript/flavours/glitch/features/ui/components/header.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/header.jsx @@ -7,14 +7,13 @@ import { Link, withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; - +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import { openModal } from 'flavours/glitch/actions/modal'; import { fetchServer } from 'flavours/glitch/actions/server'; import { Avatar } from 'flavours/glitch/components/avatar'; import { Icon } from 'flavours/glitch/components/icon'; import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo'; -import Permalink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import { registrationsOpen, me, sso_redirect } from 'flavours/glitch/initial_state'; const Account = connect(state => ({ diff --git a/app/javascript/flavours/glitch/features/ui/components/image_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/image_modal.jsx index bc650802b0..0e7a3d3121 100644 --- a/app/javascript/flavours/glitch/features/ui/components/image_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/image_modal.jsx @@ -5,8 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { IconButton } from 'flavours/glitch/components/icon_button'; import ImageLoader from './image_loader'; diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx index 6284988fa2..1baedfb8b0 100644 --- a/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx @@ -5,8 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; - +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { fetchLists } from 'flavours/glitch/actions/lists'; import ColumnLink from './column_link'; diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx index 19591fc1c8..d16b274170 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx @@ -7,11 +7,11 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import ReactSwipeableViews from 'react-swipeable-views'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { getAverageFromBlurhash } from 'flavours/glitch/blurhash'; import { GIFV } from 'flavours/glitch/components/gifv'; import { Icon } from 'flavours/glitch/components/icon'; diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx index e868055f0d..0bde497c63 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx @@ -3,18 +3,17 @@ import { Component } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; -import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg'; -import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; -import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; +import MailIcon from '@/material-icons/400-24px/mail.svg?react'; +import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { NavigationPortal } from 'flavours/glitch/components/navigation_portal'; import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state'; import { transientSingleColumn } from 'flavours/glitch/is_mobile'; diff --git a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js index 78e62fbf3a..8e38acfe80 100644 --- a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js +++ b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js @@ -1,7 +1,6 @@ import { connect } from 'react-redux'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; - +import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; const mapStateToProps = state => ({ diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx index f4f8f70194..98ff00bcbf 100644 --- a/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx @@ -7,8 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { fetchRelationships } from 'flavours/glitch/actions/accounts'; import { submitReport } from 'flavours/glitch/actions/reports'; import { fetchServer } from 'flavours/glitch/actions/server'; diff --git a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx index 7271e40db8..31a25159ea 100644 --- a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx @@ -3,9 +3,8 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import { ReactComponent as FullscreenExitIcon } from '@material-symbols/svg-600/outlined/fullscreen_exit.svg'; -import { ReactComponent as RectangleIcon } from '@material-symbols/svg-600/outlined/rectangle.svg'; - +import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; +import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; import { IconButton } from 'flavours/glitch/components/icon_button'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 839e06eea2..ba7e521b75 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -15,7 +15,7 @@ import { HotKeys } from 'react-hotkeys'; import { changeLayout } from 'flavours/glitch/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers'; import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding'; -import PermaLink from 'flavours/glitch/components/permalink'; +import { Permalink } from 'flavours/glitch/components/permalink'; import PictureInPicture from 'flavours/glitch/features/picture_in_picture'; import { layoutFromWindow } from 'flavours/glitch/is_mobile'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -649,9 +649,9 @@ class UI extends PureComponent { id='moved_to_warning' defaultMessage='This account is marked as moved to {moved_to_link}, and may thus not accept new follows.' values={{ moved_to_link: ( - + @{moved.get('acct')} - + ) }} />
)} diff --git a/app/javascript/flavours/glitch/features/video/index.jsx b/app/javascript/flavours/glitch/features/video/index.jsx index d98a29a757..d6b93dcdbc 100644 --- a/app/javascript/flavours/glitch/features/video/index.jsx +++ b/app/javascript/flavours/glitch/features/video/index.jsx @@ -7,16 +7,16 @@ import classNames from 'classnames'; import { is } from 'immutable'; -import { ReactComponent as FullscreenIcon } from '@material-symbols/svg-600/outlined/fullscreen.svg'; -import { ReactComponent as FullscreenExitIcon } from '@material-symbols/svg-600/outlined/fullscreen_exit.svg'; -import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; -import { ReactComponent as RectangleIcon } from '@material-symbols/svg-600/outlined/rectangle.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg'; -import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg'; import { throttle } from 'lodash'; +import FullscreenIcon from '@/material-icons/400-24px/fullscreen.svg?react'; +import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; +import PauseIcon from '@/material-icons/400-24px/pause.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; +import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off-fill.svg?react'; +import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react'; import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Icon } from 'flavours/glitch/components/icon'; import { playerSettings } from 'flavours/glitch/settings'; diff --git a/app/javascript/flavours/glitch/locales/ar.json b/app/javascript/flavours/glitch/locales/ar.json index 20ca870f7c..a259146328 100644 --- a/app/javascript/flavours/glitch/locales/ar.json +++ b/app/javascript/flavours/glitch/locales/ar.json @@ -32,11 +32,13 @@ "compose_form.spoiler": "إخفاء النص خلف تحذير", "confirmation_modal.do_not_ask_again": "لا تطلب التأكيد مرة أخرى", "confirmations.deprecated_settings.confirm": "استخدام تفضيلات ماستدون", + "confirmations.deprecated_settings.message": "تم استبدال بعض من الجهاز الخاص بالماستدون {preferences} الذي تستخدمه {app_settings} الخاص بجهاز ماستدون سيتم تجاوزه:", "confirmations.missing_media_description.confirm": "أرسل على أيّة حال", "confirmations.missing_media_description.edit": "تعديل الوسائط", "confirmations.unfilter.author": "المؤلف", "confirmations.unfilter.confirm": "عرض", "confirmations.unfilter.edit_filter": "تعديل عامل التصفية", + "confirmations.unfilter.filters": "مطابقة {count, plural, zero {}one {فلتر} two {فلاتر} few {فلاتر} many {فلاتر} other {فلاتر}}", "content-type.change": "نوع المحتوى", "direct.group_by_conversations": "تجميع حسب المحادثة", "endorsed_accounts_editor.endorsed_accounts": "الحسابات المميزة", @@ -61,6 +63,10 @@ "notification_purge.start": "أدخل وضع تنظيف الإشعارات", "notifications.marked_clear": "مسح الإشعارات المحددة", "notifications.marked_clear_confirmation": "هل أنت متأكد من أنك تريد مسح جميع الإشعارات المحددة نهائياً؟", + "settings.always_show_spoilers_field": "تمكين دائما حقل تحذير المحتوى", + "settings.auto_collapse_height": "الارتفاع (بالبكسل) لاعتبار التبويق طويل", + "settings.auto_collapse_reblogs": "دفع", + "settings.auto_collapse_replies": "ردود {{count}}", "settings.close": "إغلاق", "settings.content_warnings": "Content warnings", "settings.preferences": "Preferences" diff --git a/app/javascript/flavours/glitch/locales/de.json b/app/javascript/flavours/glitch/locales/de.json index a83c4df787..96650a5efa 100644 --- a/app/javascript/flavours/glitch/locales/de.json +++ b/app/javascript/flavours/glitch/locales/de.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc ist freie, quelloffene Software geforkt von Mastodon.", "account.disclaimer_full": "Die folgenden Informationen könnten das Profil des Nutzers unvollständig wiedergeben.", "account.follows": "Folgt", + "account.follows_you": "Folgt dir", "account.joined": "Beigetreten am {date}", "account.suspended_disclaimer_full": "Dieser Nutzer wurde durch einen Moderator gesperrt.", "account.view_full_profile": "Vollständiges Profil anzeigen", diff --git a/app/javascript/flavours/glitch/locales/es-AR.json b/app/javascript/flavours/glitch/locales/es-AR.json index 860c2c0beb..48ae0f0683 100644 --- a/app/javascript/flavours/glitch/locales/es-AR.json +++ b/app/javascript/flavours/glitch/locales/es-AR.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc es software gratuito, de código abierto, bifurcado de Mastodon.", "account.disclaimer_full": "La información aquí presentada puede reflejar de manera incompleta el perfil del usuario.", "account.follows": "Sigue", + "account.follows_you": "Te sigue", "account.joined": "Unido el {date}", "account.suspended_disclaimer_full": "Este usuario ha sido suspendido por un moderador.", "account.view_full_profile": "Ver perfil completo", diff --git a/app/javascript/flavours/glitch/locales/es-MX.json b/app/javascript/flavours/glitch/locales/es-MX.json index 00b87e9c12..bb0a5ab74f 100644 --- a/app/javascript/flavours/glitch/locales/es-MX.json +++ b/app/javascript/flavours/glitch/locales/es-MX.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc es software gratuito, de código abierto, bifurcado de Mastodon.", "account.disclaimer_full": "La información aquí presentada puede reflejar de manera incompleta el perfil del usuario.", "account.follows": "Seguir", + "account.follows_you": "Te sigue", "account.joined": "Unido {date}", "account.suspended_disclaimer_full": "Este usuario ha sido suspendido por un moderador.", "account.view_full_profile": "Ver perfil completo", diff --git a/app/javascript/flavours/glitch/locales/es.json b/app/javascript/flavours/glitch/locales/es.json index b7f266aa3e..35f0fa8deb 100644 --- a/app/javascript/flavours/glitch/locales/es.json +++ b/app/javascript/flavours/glitch/locales/es.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc es software gratuito, de código abierto, bifurcado de Mastodon.", "account.disclaimer_full": "La información que figura a continuación puede reflejar el perfil de la cuenta de forma incompleta.", "account.follows": "Sigue", + "account.follows_you": "Te sigue", "account.joined": "Se unió el {date}", "account.suspended_disclaimer_full": "Este usuario ha sido suspendido por un moderador.", "account.view_full_profile": "Ver perfil completo", diff --git a/app/javascript/flavours/glitch/locales/fr-QC.json b/app/javascript/flavours/glitch/locales/fr-CA.json similarity index 99% rename from app/javascript/flavours/glitch/locales/fr-QC.json rename to app/javascript/flavours/glitch/locales/fr-CA.json index a9d0108ce4..6015f4097d 100644 --- a/app/javascript/flavours/glitch/locales/fr-QC.json +++ b/app/javascript/flavours/glitch/locales/fr-CA.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc est un logiciel gratuit et open source, fork de Mastodon.", "account.disclaimer_full": "Les informations ci-dessous peuvent être incomplètes.", "account.follows": "Abonnements", + "account.follows_you": "Vous suit", "account.joined": "Ici depuis {date}", "account.suspended_disclaimer_full": "Cet utilisateur a été suspendu par un modérateur.", "account.view_full_profile": "Voir le profil complet", diff --git a/app/javascript/flavours/glitch/locales/fr.json b/app/javascript/flavours/glitch/locales/fr.json index a9d0108ce4..6015f4097d 100644 --- a/app/javascript/flavours/glitch/locales/fr.json +++ b/app/javascript/flavours/glitch/locales/fr.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc est un logiciel gratuit et open source, fork de Mastodon.", "account.disclaimer_full": "Les informations ci-dessous peuvent être incomplètes.", "account.follows": "Abonnements", + "account.follows_you": "Vous suit", "account.joined": "Ici depuis {date}", "account.suspended_disclaimer_full": "Cet utilisateur a été suspendu par un modérateur.", "account.view_full_profile": "Voir le profil complet", diff --git a/app/javascript/flavours/glitch/locales/global_locale.ts b/app/javascript/flavours/glitch/locales/global_locale.ts index 2d4329c764..67e4dd9218 100644 --- a/app/javascript/flavours/glitch/locales/global_locale.ts +++ b/app/javascript/flavours/glitch/locales/global_locale.ts @@ -1,3 +1,5 @@ +import { isDevelopment } from 'flavours/glitch/utils/environment'; + export interface LocaleData { locale: string; messages: Record; @@ -11,7 +13,7 @@ export function setLocale(locale: LocaleData) { export function getLocale(): LocaleData { if (!loadedLocale) { - if (process.env.NODE_ENV === 'development') { + if (isDevelopment()) { throw new Error('getLocale() called before any locale has been set'); } else { return { locale: 'unknown', messages: {} }; diff --git a/app/javascript/flavours/glitch/locales/intl_provider.tsx b/app/javascript/flavours/glitch/locales/intl_provider.tsx index 4fa8b2247c..f508a26be7 100644 --- a/app/javascript/flavours/glitch/locales/intl_provider.tsx +++ b/app/javascript/flavours/glitch/locales/intl_provider.tsx @@ -2,12 +2,14 @@ import { useEffect, useState } from 'react'; import { IntlProvider as BaseIntlProvider } from 'react-intl'; +import { isProduction } from 'flavours/glitch/utils/environment'; + import { getLocale, isLocaleLoaded } from './global_locale'; import { loadLocale } from './load_locale'; function onProviderError(error: unknown) { // Silent the error, like upstream does - if (process.env.NODE_ENV === 'production') return; + if (isProduction()) return; // This browser does not advertise Intl support for this locale, we only print a warning // As-per the spec, the browser should select the best matching locale diff --git a/app/javascript/flavours/glitch/locales/ko.json b/app/javascript/flavours/glitch/locales/ko.json index 96f0b7aa97..49fcb0b46a 100644 --- a/app/javascript/flavours/glitch/locales/ko.json +++ b/app/javascript/flavours/glitch/locales/ko.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "글리치는 마스토돈에서 포크한 자유 오픈소스 소프트웨어입니다.", "account.disclaimer_full": "아래에 있는 정보들은 사용자의 프로필을 완벽하게 나타내지 못하고 있을 수도 있습니다.", "account.follows": "팔로우", + "account.follows_you": "날 팔로우합니다", "account.joined": "{date}에 가입함", "account.suspended_disclaimer_full": "이 사용자는 중재자에 의해 정지되었습니다.", "account.view_full_profile": "전체 프로필 보기", @@ -44,6 +45,7 @@ "direct.group_by_conversations": "대화별로 묶기", "endorsed_accounts_editor.endorsed_accounts": "추천하는 계정들", "favourite_modal.combo": "다음엔 {combo}를 눌러 건너뛸 수 있습니다", + "firehose.column_settings.allow_local_only": "\"모두\" 탭에서 로컬 전용 글 보여주기", "home.column_settings.advanced": "고급", "home.column_settings.filter_regex": "정규표현식으로 필터", "home.column_settings.show_direct": "DM 보여주기", diff --git a/app/javascript/flavours/glitch/locales/vi.json b/app/javascript/flavours/glitch/locales/vi.json index d360fed722..0967ef424b 100644 --- a/app/javascript/flavours/glitch/locales/vi.json +++ b/app/javascript/flavours/glitch/locales/vi.json @@ -1,4 +1 @@ -{ - "settings.content_warnings": "Content warnings", - "settings.preferences": "Preferences" -} +{} diff --git a/app/javascript/flavours/glitch/locales/zh-CN.json b/app/javascript/flavours/glitch/locales/zh-CN.json index 5a620c9346..742624108c 100644 --- a/app/javascript/flavours/glitch/locales/zh-CN.json +++ b/app/javascript/flavours/glitch/locales/zh-CN.json @@ -1,7 +1,8 @@ { - "about.fork_disclaimer": "Glitch-soc是从Mastodon派生的自由开源软件。", - "account.disclaimer_full": "以下信息可能无法完整代表你的个人资料。", + "about.fork_disclaimer": "Glitch-soc是从Mastodon生成的免费开源软件。", + "account.disclaimer_full": "下面的信息可能不完全反映用户的个人资料。", "account.follows": "正在关注", + "account.follows_you": "关注了你", "account.joined": "加入于 {date}", "account.suspended_disclaimer_full": "该用户已被管理员封禁。", "account.view_full_profile": "查看完整资料", diff --git a/app/javascript/flavours/glitch/locales/zh-TW.json b/app/javascript/flavours/glitch/locales/zh-TW.json index fcd4f4a3a6..414bd44f78 100644 --- a/app/javascript/flavours/glitch/locales/zh-TW.json +++ b/app/javascript/flavours/glitch/locales/zh-TW.json @@ -2,6 +2,7 @@ "about.fork_disclaimer": "Glitch-soc 是從 Mastodon 分支出來的自由開源軟體。", "account.disclaimer_full": "下面的資訊可能不完全反映使用者的個人資料。", "account.follows": "跟隨", + "account.follows_you": "跟隨了您", "account.joined": "加入於 {date}", "account.suspended_disclaimer_full": "使用者已被管理者停權。", "account.view_full_profile": "查看完整個人資料", diff --git a/app/javascript/flavours/glitch/main.jsx b/app/javascript/flavours/glitch/main.jsx index 2aef67fa3a..f76d40713d 100644 --- a/app/javascript/flavours/glitch/main.jsx +++ b/app/javascript/flavours/glitch/main.jsx @@ -7,6 +7,8 @@ import * as perf from 'flavours/glitch/performance'; import ready from 'flavours/glitch/ready'; import { store } from 'flavours/glitch/store'; +import { isProduction } from './utils/environment'; + /** * @returns {Promise} */ @@ -21,7 +23,7 @@ function main() { root.render(); store.dispatch(setupBrowserNotifications()); - if (process.env.NODE_ENV === 'production' && me && 'serviceWorker' in navigator) { + if (isProduction() && me && 'serviceWorker' in navigator) { const { Workbox } = await import('workbox-window'); const wb = new Workbox('/sw.js'); /** @type {ServiceWorkerRegistration} */ diff --git a/app/javascript/flavours/glitch/performance.js b/app/javascript/flavours/glitch/performance.js index 42849c82b1..3bca95e85e 100644 --- a/app/javascript/flavours/glitch/performance.js +++ b/app/javascript/flavours/glitch/performance.js @@ -5,7 +5,9 @@ import * as marky from 'marky'; -if (process.env.NODE_ENV === 'development') { +import { isDevelopment } from './utils/environment'; + +if (isDevelopment()) { if (typeof performance !== 'undefined' && performance.setResourceTimingBufferSize) { // Increase Firefox's performance entry limit; otherwise it's capped to 150. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1331135 @@ -18,13 +20,13 @@ if (process.env.NODE_ENV === 'development') { } export function start(name) { - if (process.env.NODE_ENV === 'development') { + if (isDevelopment()) { marky.mark(name); } } export function stop(name) { - if (process.env.NODE_ENV === 'development') { + if (isDevelopment()) { marky.stop(name); } } diff --git a/app/javascript/flavours/glitch/reducers/accounts.ts b/app/javascript/flavours/glitch/reducers/accounts.ts index 345bff06ce..c9d7b5fe12 100644 --- a/app/javascript/flavours/glitch/reducers/accounts.ts +++ b/app/javascript/flavours/glitch/reducers/accounts.ts @@ -59,25 +59,19 @@ export const accountsReducer: Reducer = ( return normalizeAccounts(state, action.payload.accounts); else if (followAccountSuccess.match(action)) { return state - .update( - action.payload.relationship.id, - (account) => account?.update('followers_count', (n) => n + 1), + .update(action.payload.relationship.id, (account) => + account?.update('followers_count', (n) => n + 1), ) - .update( - getCurrentUser(), - (account) => account?.update('following_count', (n) => n + 1), + .update(getCurrentUser(), (account) => + account?.update('following_count', (n) => n + 1), ); } else if (unfollowAccountSuccess.match(action)) return state - .update( - action.payload.relationship.id, - (account) => - account?.update('followers_count', (n) => Math.max(0, n - 1)), + .update(action.payload.relationship.id, (account) => + account?.update('followers_count', (n) => Math.max(0, n - 1)), ) - .update( - getCurrentUser(), - (account) => - account?.update('following_count', (n) => Math.max(0, n - 1)), + .update(getCurrentUser(), (account) => + account?.update('following_count', (n) => Math.max(0, n - 1)), ); else return state; }; diff --git a/app/javascript/flavours/glitch/store/typed_functions.ts b/app/javascript/flavours/glitch/store/typed_functions.ts index 46a10b8b47..4859b82651 100644 --- a/app/javascript/flavours/glitch/store/typed_functions.ts +++ b/app/javascript/flavours/glitch/store/typed_functions.ts @@ -1,12 +1,11 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import type { TypedUseSelectorHook } from 'react-redux'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useDispatch, useSelector } from 'react-redux'; import type { AppDispatch, RootState } from './store'; -export const useAppDispatch: () => AppDispatch = useDispatch; -export const useAppSelector: TypedUseSelectorHook = useSelector; +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); export const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: RootState; diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index ccaa394bf5..d1b4974fef 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -161,6 +161,7 @@ padding: 10px; gap: 5px; color: $dark-text-color; + align-items: center; strong { font-weight: 500; @@ -620,6 +621,13 @@ span { user-select: all; } + + .icon-lock { + height: 16px; + width: 16px; + position: relative; + top: 3px; + } } } } @@ -665,16 +673,13 @@ .icon { width: 18px; height: 18px; + vertical-align: middle; } dd { display: flex; align-items: center; gap: 4px; - - span { - display: flex; - } } .verified a { diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 77835ce823..6429eba810 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -241,7 +241,7 @@ $ui-header-height: 55px; align-items: center; gap: 5px; font-size: 16px; - padding: 15px; + padding: 13px; text-decoration: none; overflow: hidden; white-space: nowrap; @@ -353,7 +353,7 @@ $ui-header-height: 55px; gap: 5px; margin: 0; border: 0; - padding: 15px; + padding: 13px; color: inherit; background: transparent; font: inherit; diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss index 0669eb245c..2b777f89f9 100644 --- a/app/javascript/flavours/glitch/styles/components/drawer.scss +++ b/app/javascript/flavours/glitch/styles/components/drawer.scss @@ -52,7 +52,7 @@ .drawer__tab { display: flex; flex: 1 1 auto; - padding: 15px 5px 13px; + padding: 13px 3px 11px; color: $darker-text-color; text-decoration: none; text-align: center; diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index e0c054aae8..ac05e7742a 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -51,6 +51,7 @@ gap: 2px; } +.media-gallery__alt__label, .media-gallery__gifv__label { display: flex; align-items: center; diff --git a/app/javascript/flavours/glitch/styles/components/misc.scss b/app/javascript/flavours/glitch/styles/components/misc.scss index b72a0a1fa2..abb59d73be 100644 --- a/app/javascript/flavours/glitch/styles/components/misc.scss +++ b/app/javascript/flavours/glitch/styles/components/misc.scss @@ -171,8 +171,8 @@ .icon { flex: 0 0 auto; - width: 20px; - height: 20px; + width: 24px; + height: 24px; aspect-ratio: 1; path { @@ -184,7 +184,7 @@ display: inline-flex; color: $action-button-color; border: 0; - padding: 2px; + padding: 0; border-radius: 4px; background: transparent; cursor: pointer; diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index 9d1930ff0b..8f008a3c58 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -492,6 +492,10 @@ padding-inline-end: 10px; } + .icon { + vertical-align: middle; + } + .button { flex: 0 0 auto; } diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index 895d54c32b..005cf9c879 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -148,6 +148,7 @@ line-height: inherit; cursor: pointer; vertical-align: top; + align-items: center; &:hover { background: lighten($ui-base-color, 33%); @@ -160,6 +161,8 @@ border-inline-start: 1px solid currentColor; padding: 0; padding-inline-start: 4px; + width: 16px; + height: 16px; } } @@ -588,8 +591,7 @@ .icon { width: 15px; height: 15px; - position: relative; - top: 0.145em; + vertical-align: middle; } } @@ -998,6 +1000,7 @@ a.status-card.compact:hover { .icon { color: $dark-text-color; + vertical-align: middle; } } } diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss index 4d3d4c546c..6d72e43924 100644 --- a/app/javascript/flavours/glitch/styles/containers.scss +++ b/app/javascript/flavours/glitch/styles/containers.scss @@ -107,3 +107,59 @@ margin-inline-start: 10px; } } + +.redirect { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + font-size: 14px; + line-height: 18px; + + &__logo { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 30px; + + img { + height: 48px; + } + } + + &__message { + text-align: center; + + h1 { + font-size: 17px; + line-height: 22px; + font-weight: 700; + margin-bottom: 30px; + } + + p { + margin-bottom: 30px; + + &:last-child { + margin-bottom: 0; + } + } + + a { + color: $highlight-text-color; + font-weight: 500; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + } + + &__link { + margin-top: 15px; + } +} diff --git a/app/javascript/flavours/glitch/utils/config.js b/app/javascript/flavours/glitch/utils/config.js deleted file mode 100644 index 932cd0cbf5..0000000000 --- a/app/javascript/flavours/glitch/utils/config.js +++ /dev/null @@ -1,10 +0,0 @@ -import ready from '../ready'; - -export let assetHost = ''; - -ready(() => { - const cdnHost = document.querySelector('meta[name=cdn-host]'); - if (cdnHost) { - assetHost = cdnHost.content || ''; - } -}); diff --git a/app/javascript/flavours/glitch/utils/config.ts b/app/javascript/flavours/glitch/utils/config.ts new file mode 100644 index 0000000000..9222c89d1b --- /dev/null +++ b/app/javascript/flavours/glitch/utils/config.ts @@ -0,0 +1,13 @@ +import ready from '../ready'; + +export let assetHost = ''; + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +ready(() => { + const cdnHost = document.querySelector( + 'meta[name=cdn-host]', + ); + if (cdnHost) { + assetHost = cdnHost.content || ''; + } +}); diff --git a/app/javascript/flavours/glitch/utils/environment.ts b/app/javascript/flavours/glitch/utils/environment.ts new file mode 100644 index 0000000000..b6371499f6 --- /dev/null +++ b/app/javascript/flavours/glitch/utils/environment.ts @@ -0,0 +1,7 @@ +export function isDevelopment() { + return process.env.NODE_ENV === 'development'; +} + +export function isProduction() { + return process.env.NODE_ENV === 'production'; +} diff --git a/app/javascript/flavours/glitch/utils/html.js b/app/javascript/flavours/glitch/utils/html.js deleted file mode 100644 index 247e98c88a..0000000000 --- a/app/javascript/flavours/glitch/utils/html.js +++ /dev/null @@ -1,6 +0,0 @@ -// NB: This function can still return unsafe HTML -export const unescapeHTML = (html) => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = html.replace(//g, '\n').replace(/<\/p>

/g, '\n\n').replace(/<[^>]*>/g, ''); - return wrapper.textContent; -}; diff --git a/app/javascript/flavours/glitch/utils/html.ts b/app/javascript/flavours/glitch/utils/html.ts new file mode 100644 index 0000000000..0145a04551 --- /dev/null +++ b/app/javascript/flavours/glitch/utils/html.ts @@ -0,0 +1,9 @@ +// NB: This function can still return unsafe HTML +export const unescapeHTML = (html: string) => { + const wrapper = document.createElement('div'); + wrapper.innerHTML = html + .replace(//g, '\n') + .replace(/<\/p>

/g, '\n\n') + .replace(/<[^>]*>/g, ''); + return wrapper.textContent; +}; diff --git a/app/javascript/flavours/glitch/utils/icons.jsx b/app/javascript/flavours/glitch/utils/icons.tsx similarity index 69% rename from app/javascript/flavours/glitch/utils/icons.jsx rename to app/javascript/flavours/glitch/utils/icons.tsx index be566032e0..6e432e32fa 100644 --- a/app/javascript/flavours/glitch/utils/icons.jsx +++ b/app/javascript/flavours/glitch/utils/icons.tsx @@ -1,13 +1,23 @@ // Copied from emoji-mart for consistency with emoji picker and since // they don't export the icons in the package export const loupeIcon = ( - + ); export const deleteIcon = ( - + ); diff --git a/app/javascript/flavours/glitch/utils/log_out.js b/app/javascript/flavours/glitch/utils/log_out.ts similarity index 100% rename from app/javascript/flavours/glitch/utils/log_out.js rename to app/javascript/flavours/glitch/utils/log_out.ts diff --git a/app/javascript/flavours/glitch/utils/notifications.js b/app/javascript/flavours/glitch/utils/notifications.js deleted file mode 100644 index 42623ac7c6..0000000000 --- a/app/javascript/flavours/glitch/utils/notifications.js +++ /dev/null @@ -1,30 +0,0 @@ -// Handles browser quirks, based on -// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API - -const checkNotificationPromise = () => { - try { - // eslint-disable-next-line promise/valid-params, promise/catch-or-return - Notification.requestPermission().then(); - } catch(e) { - return false; - } - - return true; -}; - -const handlePermission = (permission, callback) => { - // Whatever the user answers, we make sure Chrome stores the information - if(!('permission' in Notification)) { - Notification.permission = permission; - } - - callback(Notification.permission); -}; - -export const requestNotificationPermission = (callback) => { - if (checkNotificationPromise()) { - Notification.requestPermission().then((permission) => handlePermission(permission, callback)).catch(console.warn); - } else { - Notification.requestPermission((permission) => handlePermission(permission, callback)); - } -}; diff --git a/app/javascript/flavours/glitch/utils/notifications.ts b/app/javascript/flavours/glitch/utils/notifications.ts new file mode 100644 index 0000000000..08f677f8fe --- /dev/null +++ b/app/javascript/flavours/glitch/utils/notifications.ts @@ -0,0 +1,13 @@ +/** + * Tries Notification.requestPermission, console warning instead of rejecting on error. + * @param callback Runs with the permission result on completion. + */ +export const requestNotificationPermission = async ( + callback: NotificationPermissionCallback, +) => { + try { + callback(await Notification.requestPermission()); + } catch (error) { + console.warn(error); + } +}; diff --git a/app/javascript/flavours/glitch/utils/react_router.jsx b/app/javascript/flavours/glitch/utils/react_router.tsx similarity index 53% rename from app/javascript/flavours/glitch/utils/react_router.jsx rename to app/javascript/flavours/glitch/utils/react_router.tsx index fa8f0db2b5..0682fee554 100644 --- a/app/javascript/flavours/glitch/utils/react_router.jsx +++ b/app/javascript/flavours/glitch/utils/react_router.tsx @@ -1,8 +1,8 @@ -import PropTypes from "prop-types"; +import PropTypes from 'prop-types'; -import { __RouterContext } from "react-router"; +import { __RouterContext } from 'react-router'; -import hoistStatics from "hoist-non-react-statics"; +import hoistStatics from 'hoist-non-react-statics'; export const WithRouterPropTypes = { match: PropTypes.object.isRequired, @@ -16,31 +16,37 @@ export const WithOptionalRouterPropTypes = { history: PropTypes.object, }; +export interface OptionalRouterProps { + ref: unknown; + wrappedComponentRef: unknown; +} + // This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js // but does not fail if called outside of a React Router context -export function withOptionalRouter(Component) { - const displayName = `withRouter(${Component.displayName || Component.name})`; - const C = props => { +export function withOptionalRouter< + ComponentType extends React.ComponentType, +>(Component: ComponentType) { + const displayName = `withRouter(${Component.displayName ?? Component.name})`; + const C = (props: React.ComponentProps) => { const { wrappedComponentRef, ...remainingProps } = props; return ( <__RouterContext.Consumer> - {context => { - if(context) + {(context) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (context) { return ( + // @ts-expect-error - Dynamic covariant generic components are tough to type. ); - else - return ( - - ); + } else { + // @ts-expect-error - Dynamic covariant generic components are tough to type. + return ; + } }} ); @@ -53,8 +59,8 @@ export function withOptionalRouter(Component) { wrappedComponentRef: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, - PropTypes.object - ]) + PropTypes.object, + ]), }; return hoistStatics(C, Component); diff --git a/app/javascript/flavours/glitch/utils/scrollbar.js b/app/javascript/flavours/glitch/utils/scrollbar.ts similarity index 72% rename from app/javascript/flavours/glitch/utils/scrollbar.js rename to app/javascript/flavours/glitch/utils/scrollbar.ts index b3f543ffb3..d505df1244 100644 --- a/app/javascript/flavours/glitch/utils/scrollbar.js +++ b/app/javascript/flavours/glitch/utils/scrollbar.ts @@ -1,9 +1,7 @@ -/** @type {number | null} */ -let cachedScrollbarWidth = null; +import { isMobile } from '../is_mobile'; + +let cachedScrollbarWidth: number | null = null; -/** - * @returns {number} - */ const getActualScrollbarWidth = () => { const outer = document.createElement('div'); outer.style.visibility = 'hidden'; @@ -14,20 +12,19 @@ const getActualScrollbarWidth = () => { outer.appendChild(inner); const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; - outer.parentNode.removeChild(outer); + outer.remove(); return scrollbarWidth; }; -/** - * @returns {number} - */ export const getScrollbarWidth = () => { if (cachedScrollbarWidth !== null) { return cachedScrollbarWidth; } - const scrollbarWidth = getActualScrollbarWidth(); + const scrollbarWidth = isMobile(window.innerWidth) + ? 0 + : getActualScrollbarWidth(); cachedScrollbarWidth = scrollbarWidth; return scrollbarWidth; diff --git a/app/javascript/fonts/inter/inter-variable-font-slnt-wght.woff2 b/app/javascript/fonts/inter/inter-variable-font-slnt-wght.woff2 new file mode 100644 index 0000000000..e6345f2e3d Binary files /dev/null and b/app/javascript/fonts/inter/inter-variable-font-slnt-wght.woff2 differ diff --git a/app/javascript/images/mailer-new/common/header-bg-end.png b/app/javascript/images/mailer-new/common/header-bg-end.png new file mode 100644 index 0000000000..900196678a Binary files /dev/null and b/app/javascript/images/mailer-new/common/header-bg-end.png differ diff --git a/app/javascript/images/mailer-new/common/header-bg-start.png b/app/javascript/images/mailer-new/common/header-bg-start.png new file mode 100644 index 0000000000..0037c1ad93 Binary files /dev/null and b/app/javascript/images/mailer-new/common/header-bg-start.png differ diff --git a/app/javascript/images/mailer-new/common/logo-footer.png b/app/javascript/images/mailer-new/common/logo-footer.png new file mode 100644 index 0000000000..2baafd8d7f Binary files /dev/null and b/app/javascript/images/mailer-new/common/logo-footer.png differ diff --git a/app/javascript/images/mailer-new/common/logo-header.png b/app/javascript/images/mailer-new/common/logo-header.png new file mode 100644 index 0000000000..46a6bddaa1 Binary files /dev/null and b/app/javascript/images/mailer-new/common/logo-header.png differ diff --git a/app/javascript/images/mailer-new/heading/2fa-disabled.png b/app/javascript/images/mailer-new/heading/2fa-disabled.png new file mode 100644 index 0000000000..b1e342a87c Binary files /dev/null and b/app/javascript/images/mailer-new/heading/2fa-disabled.png differ diff --git a/app/javascript/images/mailer-new/heading/2fa-enabled.png b/app/javascript/images/mailer-new/heading/2fa-enabled.png new file mode 100644 index 0000000000..3ce3e04f84 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/2fa-enabled.png differ diff --git a/app/javascript/images/mailer-new/heading/2fa-recovery.png b/app/javascript/images/mailer-new/heading/2fa-recovery.png new file mode 100644 index 0000000000..cefb21e1eb Binary files /dev/null and b/app/javascript/images/mailer-new/heading/2fa-recovery.png differ diff --git a/app/javascript/images/mailer-new/heading/appeal-approved.png b/app/javascript/images/mailer-new/heading/appeal-approved.png new file mode 100755 index 0000000000..b2476ec346 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/appeal-approved.png differ diff --git a/app/javascript/images/mailer-new/heading/appeal-rejected.png b/app/javascript/images/mailer-new/heading/appeal-rejected.png new file mode 100644 index 0000000000..7ae38ad0c1 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/appeal-rejected.png differ diff --git a/app/javascript/images/mailer-new/heading/archive.png b/app/javascript/images/mailer-new/heading/archive.png new file mode 100644 index 0000000000..b0c7fad84d Binary files /dev/null and b/app/javascript/images/mailer-new/heading/archive.png differ diff --git a/app/javascript/images/mailer-new/heading/boost.png b/app/javascript/images/mailer-new/heading/boost.png new file mode 100644 index 0000000000..e33b759976 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/boost.png differ diff --git a/app/javascript/images/mailer-new/heading/email.png b/app/javascript/images/mailer-new/heading/email.png new file mode 100644 index 0000000000..c922c5239e Binary files /dev/null and b/app/javascript/images/mailer-new/heading/email.png differ diff --git a/app/javascript/images/mailer-new/heading/favorite.png b/app/javascript/images/mailer-new/heading/favorite.png new file mode 100644 index 0000000000..0e483ee9b2 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/favorite.png differ diff --git a/app/javascript/images/mailer-new/heading/follow.png b/app/javascript/images/mailer-new/heading/follow.png new file mode 100644 index 0000000000..ff5b7e0042 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/follow.png differ diff --git a/app/javascript/images/mailer-new/heading/key-added.png b/app/javascript/images/mailer-new/heading/key-added.png new file mode 100755 index 0000000000..82dcd464bf Binary files /dev/null and b/app/javascript/images/mailer-new/heading/key-added.png differ diff --git a/app/javascript/images/mailer-new/heading/key-deleted.png b/app/javascript/images/mailer-new/heading/key-deleted.png new file mode 100755 index 0000000000..2930f591a0 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/key-deleted.png differ diff --git a/app/javascript/images/mailer-new/heading/key-disabled.png b/app/javascript/images/mailer-new/heading/key-disabled.png new file mode 100755 index 0000000000..e0f259359a Binary files /dev/null and b/app/javascript/images/mailer-new/heading/key-disabled.png differ diff --git a/app/javascript/images/mailer-new/heading/key-enabled.png b/app/javascript/images/mailer-new/heading/key-enabled.png new file mode 100644 index 0000000000..b2476ec346 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/key-enabled.png differ diff --git a/app/javascript/images/mailer-new/heading/login.png b/app/javascript/images/mailer-new/heading/login.png new file mode 100644 index 0000000000..89a6e9ee33 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/login.png differ diff --git a/app/javascript/images/mailer-new/heading/mention.png b/app/javascript/images/mailer-new/heading/mention.png new file mode 100644 index 0000000000..c4dccff8ef Binary files /dev/null and b/app/javascript/images/mailer-new/heading/mention.png differ diff --git a/app/javascript/images/mailer-new/heading/password.png b/app/javascript/images/mailer-new/heading/password.png new file mode 100755 index 0000000000..552c7c0687 Binary files /dev/null and b/app/javascript/images/mailer-new/heading/password.png differ diff --git a/app/javascript/images/mailer-new/heading/user.png b/app/javascript/images/mailer-new/heading/user.png new file mode 100644 index 0000000000..f1dd58a18d Binary files /dev/null and b/app/javascript/images/mailer-new/heading/user.png differ diff --git a/app/javascript/images/mailer-new/heading/warning.png b/app/javascript/images/mailer-new/heading/warning.png new file mode 100755 index 0000000000..7764837abe Binary files /dev/null and b/app/javascript/images/mailer-new/heading/warning.png differ diff --git a/app/javascript/images/mailer-new/welcome/checkbox-off.png b/app/javascript/images/mailer-new/welcome/checkbox-off.png new file mode 100644 index 0000000000..51c190efe6 Binary files /dev/null and b/app/javascript/images/mailer-new/welcome/checkbox-off.png differ diff --git a/app/javascript/images/mailer-new/welcome/checkbox-on.png b/app/javascript/images/mailer-new/welcome/checkbox-on.png new file mode 100644 index 0000000000..162095e7df Binary files /dev/null and b/app/javascript/images/mailer-new/welcome/checkbox-on.png differ diff --git a/app/javascript/images/mailer-new/welcome/step1-on.png b/app/javascript/images/mailer-new/welcome/step1-on.png new file mode 100644 index 0000000000..c3776d17df Binary files /dev/null and b/app/javascript/images/mailer-new/welcome/step1-on.png differ diff --git a/app/javascript/images/mailer-new/welcome/step2-off.png b/app/javascript/images/mailer-new/welcome/step2-off.png new file mode 100755 index 0000000000..a262454d2d Binary files /dev/null and b/app/javascript/images/mailer-new/welcome/step2-off.png differ diff --git a/app/javascript/images/mailer-new/welcome/step3-off.png b/app/javascript/images/mailer-new/welcome/step3-off.png new file mode 100755 index 0000000000..972de65a56 Binary files /dev/null and b/app/javascript/images/mailer-new/welcome/step3-off.png differ diff --git a/app/javascript/images/mailer-new/welcome/step4-off.png b/app/javascript/images/mailer-new/welcome/step4-off.png new file mode 100755 index 0000000000..f45e9a2c9a Binary files /dev/null and b/app/javascript/images/mailer-new/welcome/step4-off.png differ diff --git a/app/javascript/images/mailer-new/welcome/step5-off.png b/app/javascript/images/mailer-new/welcome/step5-off.png new file mode 100755 index 0000000000..ca270f5478 Binary files /dev/null and b/app/javascript/images/mailer-new/welcome/step5-off.png differ diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 38a089b486..a34a490e76 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -179,6 +179,11 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => { export const clickSearchResult = (q, type) => (dispatch, getState) => { const previous = getState().getIn(['search', 'recent']); + + if (previous.some(x => x.get('q') === q && x.get('type') === type)) { + return; + } + const me = getState().getIn(['meta', 'me']); const current = previous.add(fromJS({ type, q })).takeLast(4); @@ -207,4 +212,4 @@ export const hydrateSearch = () => (dispatch, getState) => { if (history !== null) { dispatch(updateSearchHistory(history)); } -}; \ No newline at end of file +}; diff --git a/app/javascript/mastodon/components/attachment_list.jsx b/app/javascript/mastodon/components/attachment_list.jsx index ebf092b83d..c5ac046751 100644 --- a/app/javascript/mastodon/components/attachment_list.jsx +++ b/app/javascript/mastodon/components/attachment_list.jsx @@ -7,8 +7,7 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg'; - +import LinkIcon from '@/material-icons/400-24px/link.svg?react'; import { Icon } from 'mastodon/components/icon'; const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; diff --git a/app/javascript/mastodon/components/badge.jsx b/app/javascript/mastodon/components/badge.jsx index 2f762fed5f..646655c249 100644 --- a/app/javascript/mastodon/components/badge.jsx +++ b/app/javascript/mastodon/components/badge.jsx @@ -2,9 +2,9 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as GroupsIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg'; -import { ReactComponent as SmartToyIcon } from '@material-symbols/svg-600/outlined/smart_toy.svg'; +import GroupsIcon from '@/material-icons/400-24px/group.svg?react'; +import PersonIcon from '@/material-icons/400-24px/person.svg?react'; +import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react'; export const Badge = ({ icon, label, domain }) => ( @@ -31,4 +31,4 @@ export const GroupBadge = () => ( export const AutomatedBadge = () => ( } label={} /> -); \ No newline at end of file +); diff --git a/app/javascript/mastodon/components/column_back_button.tsx b/app/javascript/mastodon/components/column_back_button.tsx index b835e9e6ad..af38c1e110 100644 --- a/app/javascript/mastodon/components/column_back_button.tsx +++ b/app/javascript/mastodon/components/column_back_button.tsx @@ -2,8 +2,7 @@ import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg'; - +import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; import { Icon } from 'mastodon/components/icon'; import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context'; diff --git a/app/javascript/mastodon/components/column_header.jsx b/app/javascript/mastodon/components/column_header.jsx index b78bd9a8ef..901888e750 100644 --- a/app/javascript/mastodon/components/column_header.jsx +++ b/app/javascript/mastodon/components/column_header.jsx @@ -6,13 +6,12 @@ import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; import { withRouter } from 'react-router-dom'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg'; -import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg'; - +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import TuneIcon from '@/material-icons/400-24px/tune.svg?react'; import { Icon } from 'mastodon/components/icon'; import { ButtonInTabsBar, useColumnsContext } from 'mastodon/features/ui/util/columns_context'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; diff --git a/app/javascript/mastodon/components/copy_icon_button.jsx b/app/javascript/mastodon/components/copy_icon_button.jsx index 9b1a36d83a..0c3c6c290b 100644 --- a/app/javascript/mastodon/components/copy_icon_button.jsx +++ b/app/javascript/mastodon/components/copy_icon_button.jsx @@ -7,8 +7,7 @@ import classNames from 'classnames'; import { useDispatch } from 'react-redux'; -import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg'; - +import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react'; import { showAlert } from 'mastodon/actions/alerts'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/components/dismissable_banner.tsx b/app/javascript/mastodon/components/dismissable_banner.tsx index 4e6d3bb9a7..bc40e46f8d 100644 --- a/app/javascript/mastodon/components/dismissable_banner.tsx +++ b/app/javascript/mastodon/components/dismissable_banner.tsx @@ -8,8 +8,7 @@ import { useCallback, useState, useEffect } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { changeSetting } from 'mastodon/actions/settings'; import { bannerSettings } from 'mastodon/settings'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; diff --git a/app/javascript/mastodon/components/domain.tsx b/app/javascript/mastodon/components/domain.tsx index 34c376f530..ed5e8e7e4c 100644 --- a/app/javascript/mastodon/components/domain.tsx +++ b/app/javascript/mastodon/components/domain.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg'; +import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; import { IconButton } from './icon_button'; diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx index d9cbcc32e5..04c8bb0980 100644 --- a/app/javascript/mastodon/components/dropdown_menu.jsx +++ b/app/javascript/mastodon/components/dropdown_menu.jsx @@ -6,10 +6,10 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import { supportsPassiveEvents } from 'detect-passive-events'; import Overlay from 'react-overlays/Overlay'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { CircularProgress } from 'mastodon/components/circular_progress'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; diff --git a/app/javascript/mastodon/components/edited_timestamp/index.jsx b/app/javascript/mastodon/components/edited_timestamp/index.jsx index 4375166bcd..4ca00f8dda 100644 --- a/app/javascript/mastodon/components/edited_timestamp/index.jsx +++ b/app/javascript/mastodon/components/edited_timestamp/index.jsx @@ -5,8 +5,7 @@ import { FormattedMessage, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as ArrowDropDownIcon } from '@material-symbols/svg-600/outlined/arrow_drop_down.svg'; - +import ArrowDropDownIcon from '@/material-icons/400-24px/arrow_drop_down.svg?react'; import { openModal } from 'mastodon/actions/modal'; import { Icon } from 'mastodon/components/icon'; import InlineAccount from 'mastodon/components/inline_account'; diff --git a/app/javascript/mastodon/components/icon.tsx b/app/javascript/mastodon/components/icon.tsx index f0af11f7f6..f388380c44 100644 --- a/app/javascript/mastodon/components/icon.tsx +++ b/app/javascript/mastodon/components/icon.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames'; -import { ReactComponent as CheckBoxOutlineBlankIcon } from '@material-symbols/svg-600/outlined/check_box_outline_blank.svg'; - +import CheckBoxOutlineBlankIcon from '@/material-icons/400-24px/check_box_outline_blank.svg?react'; import { isProduction } from 'mastodon/utils/environment'; interface SVGPropsWithTitle extends React.SVGProps { diff --git a/app/javascript/mastodon/components/load_gap.tsx b/app/javascript/mastodon/components/load_gap.tsx index 27ca6648c3..1d4193a359 100644 --- a/app/javascript/mastodon/components/load_gap.tsx +++ b/app/javascript/mastodon/components/load_gap.tsx @@ -2,8 +2,7 @@ import { useCallback } from 'react'; import { useIntl, defineMessages } from 'react-intl'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; - +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { Icon } from 'mastodon/components/icon'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/components/logo.tsx b/app/javascript/mastodon/components/logo.tsx index 928aa29a7c..b7f8bd6695 100644 --- a/app/javascript/mastodon/components/logo.tsx +++ b/app/javascript/mastodon/components/logo.tsx @@ -1,4 +1,4 @@ -import logo from 'mastodon/../images/logo.svg'; +import logo from '@/images/logo.svg'; export const WordmarkLogo: React.FC = () => ( diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 668c2a2a8a..91459a1285 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -8,9 +8,9 @@ import classNames from 'classnames'; import { is } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; import { debounce } from 'lodash'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Blurhash } from 'mastodon/components/blurhash'; import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state'; @@ -103,7 +103,7 @@ class Item extends PureComponent { } if (attachment.get('description')?.length > 0) { - badges.push(ALT); + badges.push(ALT); } const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx index 08a599cd42..50f91a9275 100644 --- a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx +++ b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx @@ -5,8 +5,7 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as CancelPresentationIcon } from '@material-symbols/svg-600/outlined/cancel_presentation.svg'; - +import CancelPresentationIcon from '@/material-icons/400-24px/cancel_presentation.svg?react'; import { removePictureInPicture } from 'mastodon/actions/picture_in_picture'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/components/poll.jsx b/app/javascript/mastodon/components/poll.jsx index 7cf2c57b78..c7036d111b 100644 --- a/app/javascript/mastodon/components/poll.jsx +++ b/app/javascript/mastodon/components/poll.jsx @@ -7,10 +7,10 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; import escapeTextContentForBrowser from 'escape-html'; import spring from 'react-motion/lib/spring'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from 'mastodon/components/icon'; import emojify from 'mastodon/features/emoji/emoji'; import Motion from 'mastodon/features/ui/util/optional_motion'; diff --git a/app/javascript/mastodon/components/regeneration_indicator.jsx b/app/javascript/mastodon/components/regeneration_indicator.jsx index 052e25f25f..d42a7d7c72 100644 --- a/app/javascript/mastodon/components/regeneration_indicator.jsx +++ b/app/javascript/mastodon/components/regeneration_indicator.jsx @@ -1,6 +1,6 @@ import { FormattedMessage } from 'react-intl'; -import illustration from 'mastodon/../images/elephant_ui_working.svg'; +import illustration from '@/images/elephant_ui_working.svg'; const RegenerationIndicator = () => (

diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index c141ee2da3..ff4fe92525 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -7,12 +7,12 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AlternateEmailIcon } from '@material-symbols/svg-600/outlined/alternate_email.svg'; -import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; import { HotKeys } from 'react-hotkeys'; +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import { Icon } from 'mastodon/components/icon'; import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder'; import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router'; diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index 25eab91fe3..b111a65385 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -9,18 +9,19 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg'; -import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; -import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg'; -import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg'; - -import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg'; -import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg'; +import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg'; +import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; +import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; +import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; +import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; +import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; +import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -367,7 +368,7 @@ class StatusActionBar extends ImmutablePureComponent { if (status.get('reblogged')) { reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon; + reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; } else if (publicStatus) { reblogTitle = intl.formatMessage(messages.reblog); reblogIconComponent = RepeatIcon; diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index d1f50fc8d7..4a7ba941eb 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -9,8 +9,7 @@ import { Link, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; - +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { Icon } from 'mastodon/components/icon'; import PollContainer from 'mastodon/containers/poll_container'; import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/components/verified_badge.tsx b/app/javascript/mastodon/components/verified_badge.tsx index e96bf82563..626cc500d6 100644 --- a/app/javascript/mastodon/components/verified_badge.tsx +++ b/app/javascript/mastodon/components/verified_badge.tsx @@ -1,4 +1,4 @@ -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from './icon'; diff --git a/app/javascript/mastodon/components/visibility_icon.tsx b/app/javascript/mastodon/components/visibility_icon.tsx index 3e9f36dc70..d6ad4095eb 100644 --- a/app/javascript/mastodon/components/visibility_icon.tsx +++ b/app/javascript/mastodon/components/visibility_icon.tsx @@ -1,9 +1,9 @@ import { defineMessages, useIntl } from 'react-intl'; -import { ReactComponent as AlternateEmailIcon } from '@material-symbols/svg-600/outlined/alternate_email.svg'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; -import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { Icon } from './icon'; diff --git a/app/javascript/mastodon/features/about/index.jsx b/app/javascript/mastodon/features/about/index.jsx index 380942b8ee..3287631ed1 100644 --- a/app/javascript/mastodon/features/about/index.jsx +++ b/app/javascript/mastodon/features/about/index.jsx @@ -10,9 +10,8 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; -import { ReactComponent as ExpandMoreIcon } from '@material-symbols/svg-600/outlined/expand_more.svg'; - +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server'; import Column from 'mastodon/components/column'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/account/components/follow_request_note.jsx b/app/javascript/mastodon/features/account/components/follow_request_note.jsx index 685c282df2..d57fd030b2 100644 --- a/app/javascript/mastodon/features/account/components/follow_request_note.jsx +++ b/app/javascript/mastodon/features/account/components/follow_request_note.jsx @@ -3,9 +3,8 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Icon } from 'mastodon/components/icon'; export default class FollowRequestNote extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index 97f68c2217..233d208c64 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -9,13 +9,12 @@ import { NavLink, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg'; -import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active-fill.svg'; -import { ReactComponent as ShareIcon } from '@material-symbols/svg-600/outlined/share.svg'; - +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; +import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; +import ShareIcon from '@/material-icons/400-24px/share.svg?react'; import { Avatar } from 'mastodon/components/avatar'; import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; import { Button } from 'mastodon/components/button'; diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.jsx b/app/javascript/mastodon/features/account_gallery/components/media_item.jsx index 657b17d43d..087e775753 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.jsx +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.jsx @@ -5,10 +5,9 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AudiotrackIcon } from '@material-symbols/svg-600/outlined/music_note.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; - +import AudiotrackIcon from '@/material-icons/400-24px/music_note.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; import { autoPlayGif, displayMedia, useBlurhash } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/audio/index.jsx b/app/javascript/mastodon/features/audio/index.jsx index 7a7d0910fa..fdc1b0be0f 100644 --- a/app/javascript/mastodon/features/audio/index.jsx +++ b/app/javascript/mastodon/features/audio/index.jsx @@ -7,14 +7,14 @@ import classNames from 'classnames'; import { is } from 'immutable'; -import { ReactComponent as DownloadIcon } from '@material-symbols/svg-600/outlined/download.svg'; -import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg'; -import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg'; import { throttle, debounce } from 'lodash'; +import DownloadIcon from '@/material-icons/400-24px/download.svg?react'; +import PauseIcon from '@/material-icons/400-24px/pause.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off-fill.svg?react'; +import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react'; import { Icon } from 'mastodon/components/icon'; import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/features/video'; diff --git a/app/javascript/mastodon/features/blocks/index.jsx b/app/javascript/mastodon/features/blocks/index.jsx index 615e4c8be2..1a631d3d07 100644 --- a/app/javascript/mastodon/features/blocks/index.jsx +++ b/app/javascript/mastodon/features/blocks/index.jsx @@ -6,9 +6,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block-fill.svg'; import { debounce } from 'lodash'; +import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react'; + import { fetchBlocks, expandBlocks } from '../../actions/blocks'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx index be6110b0da..5494ad44b0 100644 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg'; import { debounce } from 'lodash'; +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'mastodon/actions/bookmarks'; import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; import ColumnHeader from 'mastodon/components/column_header'; diff --git a/app/javascript/mastodon/features/community_timeline/index.jsx b/app/javascript/mastodon/features/community_timeline/index.jsx index 60f036a628..0aa1f9aa23 100644 --- a/app/javascript/mastodon/features/community_timeline/index.jsx +++ b/app/javascript/mastodon/features/community_timeline/index.jsx @@ -7,8 +7,7 @@ import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; - +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { domain } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.jsx index 3e21090925..411ad77c79 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.jsx +++ b/app/javascript/mastodon/features/compose/components/action_bar.jsx @@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg'; +import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 3b752f252d..a70c774c37 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -8,9 +8,9 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; import { length } from 'stringz'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import { Icon } from 'mastodon/components/icon'; import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router'; diff --git a/app/javascript/mastodon/features/compose/components/poll_button.jsx b/app/javascript/mastodon/features/compose/components/poll_button.jsx index f722815b51..4900d38119 100644 --- a/app/javascript/mastodon/features/compose/components/poll_button.jsx +++ b/app/javascript/mastodon/features/compose/components/poll_button.jsx @@ -3,7 +3,7 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/mastodon/features/compose/components/poll_form.jsx b/app/javascript/mastodon/features/compose/components/poll_form.jsx index a7f933ff7c..e242f1bb3d 100644 --- a/app/javascript/mastodon/features/compose/components/poll_form.jsx +++ b/app/javascript/mastodon/features/compose/components/poll_form.jsx @@ -8,9 +8,8 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import AutosuggestInput from 'mastodon/components/autosuggest_input'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx index 060eb6767c..c3446b8d60 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx @@ -6,14 +6,13 @@ import { injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; -import { ReactComponent as AlternateEmailIcon } from '@material-symbols/svg-600/outlined/alternate_email.svg'; -import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; -import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; import { supportsPassiveEvents } from 'detect-passive-events'; import Overlay from 'react-overlays/Overlay'; - +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx index af7a9b6be5..8051e01db4 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx @@ -5,8 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import AttachmentList from 'mastodon/components/attachment_list'; import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router'; diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx index 5d55330dcb..ca02c23fc4 100644 --- a/app/javascript/mastodon/features/compose/components/search.jsx +++ b/app/javascript/mastodon/features/compose/components/search.jsx @@ -8,10 +8,9 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as CancelIcon } from '@material-symbols/svg-600/outlined/cancel-fill.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; - +import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import { Icon } from 'mastodon/components/icon'; import { domain, searchEnabled } from 'mastodon/initial_state'; import { HASHTAG_REGEX } from 'mastodon/utils/hashtags'; @@ -63,14 +62,14 @@ class Search extends PureComponent { }; defaultOptions = [ - { label: <>has: , action: e => { e.preventDefault(); this._insertText('has:'); } }, - { label: <>is: , action: e => { e.preventDefault(); this._insertText('is:'); } }, - { label: <>language: , action: e => { e.preventDefault(); this._insertText('language:'); } }, - { label: <>from: , action: e => { e.preventDefault(); this._insertText('from:'); } }, - { label: <>before: , action: e => { e.preventDefault(); this._insertText('before:'); } }, - { label: <>during: , action: e => { e.preventDefault(); this._insertText('during:'); } }, - { label: <>after: , action: e => { e.preventDefault(); this._insertText('after:'); } }, - { label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } + { key: 'prompt-has', label: <>has: , action: e => { e.preventDefault(); this._insertText('has:'); } }, + { key: 'prompt-is', label: <>is: , action: e => { e.preventDefault(); this._insertText('is:'); } }, + { key: 'prompt-language', label: <>language: , action: e => { e.preventDefault(); this._insertText('language:'); } }, + { key: 'prompt-from', label: <>from: , action: e => { e.preventDefault(); this._insertText('from:'); } }, + { key: 'prompt-before', label: <>before: , action: e => { e.preventDefault(); this._insertText('before:'); } }, + { key: 'prompt-during', label: <>during: , action: e => { e.preventDefault(); this._insertText('during:'); } }, + { key: 'prompt-after', label: <>after: , action: e => { e.preventDefault(); this._insertText('after:'); } }, + { key: 'prompt-in', label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } ]; setRef = c => { @@ -263,6 +262,8 @@ class Search extends PureComponent { const { recent } = this.props; return recent.toArray().map(search => ({ + key: `${search.get('type')}/${search.get('q')}`, + label: labelForRecentSearch(search), action: () => this.handleRecentSearchClick(search), @@ -347,8 +348,8 @@ class Search extends PureComponent {

- {recent.size > 0 ? this._getOptions().map(({ label, action, forget }, i) => ( - diff --git a/app/javascript/mastodon/features/compose/components/search_results.jsx b/app/javascript/mastodon/features/compose/components/search_results.jsx index 2667ed5437..694deea04e 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.jsx +++ b/app/javascript/mastodon/features/compose/components/search_results.jsx @@ -5,11 +5,10 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as FindInPageIcon } from '@material-symbols/svg-600/outlined/find_in_page.svg'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { Icon } from 'mastodon/components/icon'; import { LoadMore } from 'mastodon/components/load_more'; import { SearchSection } from 'mastodon/features/explore/components/search_section'; diff --git a/app/javascript/mastodon/features/compose/components/upload.jsx b/app/javascript/mastodon/features/compose/components/upload.jsx index a443741d5c..76d394af7a 100644 --- a/app/javascript/mastodon/features/compose/components/upload.jsx +++ b/app/javascript/mastodon/features/compose/components/upload.jsx @@ -5,11 +5,11 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; -import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg'; import spring from 'react-motion/lib/spring'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import { Icon } from 'mastodon/components/icon'; import Motion from '../../ui/util/optional_motion'; diff --git a/app/javascript/mastodon/features/compose/components/upload_button.jsx b/app/javascript/mastodon/features/compose/components/upload_button.jsx index dda8a42ab0..923d6a3c47 100644 --- a/app/javascript/mastodon/features/compose/components/upload_button.jsx +++ b/app/javascript/mastodon/features/compose/components/upload_button.jsx @@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg'; +import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.jsx b/app/javascript/mastodon/features/compose/components/upload_progress.jsx index 90c5142e24..e4973b24fd 100644 --- a/app/javascript/mastodon/features/compose/components/upload_progress.jsx +++ b/app/javascript/mastodon/features/compose/components/upload_progress.jsx @@ -3,9 +3,9 @@ import { PureComponent } from 'react'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as UploadFileIcon } from '@material-symbols/svg-600/outlined/upload_file.svg'; import spring from 'react-motion/lib/spring'; +import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react'; import { Icon } from 'mastodon/components/icon'; import Motion from '../../ui/util/optional_motion'; diff --git a/app/javascript/mastodon/features/compose/containers/search_container.js b/app/javascript/mastodon/features/compose/containers/search_container.js index 758b6b07db..616b91369c 100644 --- a/app/javascript/mastodon/features/compose/containers/search_container.js +++ b/app/javascript/mastodon/features/compose/containers/search_container.js @@ -1,3 +1,4 @@ +import { createSelector } from '@reduxjs/toolkit'; import { connect } from 'react-redux'; import { @@ -12,10 +13,15 @@ import { import Search from '../components/search'; +const getRecentSearches = createSelector( + state => state.getIn(['search', 'recent']), + recent => recent.reverse(), +); + const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), - recent: state.getIn(['search', 'recent']).reverse(), + recent: getRecentSearches(state), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/mastodon/features/compose/index.jsx b/app/javascript/mastodon/features/compose/index.jsx index 65650ffe02..ea3a149501 100644 --- a/app/javascript/mastodon/features/compose/index.jsx +++ b/app/javascript/mastodon/features/compose/index.jsx @@ -9,15 +9,15 @@ import { Link } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as LogoutIcon } from '@material-symbols/svg-600/outlined/logout.svg'; -import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; -import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg'; import spring from 'react-motion/lib/spring'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import LogoutIcon from '@/material-icons/400-24px/logout.svg?react'; +import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; +import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; import { openModal } from 'mastodon/actions/modal'; import Column from 'mastodon/components/column'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index 005edc5c2b..3af89f9974 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -1,17 +1,24 @@ import PropTypes from 'prop-types'; +import { useCallback } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; -import { Link, withRouter } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; +import { createSelector } from '@reduxjs/toolkit'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import { useDispatch, useSelector } from 'react-redux'; + -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; import { HotKeys } from 'react-hotkeys'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import { replyCompose } from 'mastodon/actions/compose'; +import { markConversationRead, deleteConversation } from 'mastodon/actions/conversations'; +import { openModal } from 'mastodon/actions/modal'; +import { muteStatus, unmuteStatus, revealStatus, hideStatus } from 'mastodon/actions/statuses'; import AttachmentList from 'mastodon/components/attachment_list'; import AvatarComposite from 'mastodon/components/avatar_composite'; import { IconButton } from 'mastodon/components/icon_button'; @@ -19,7 +26,7 @@ import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import StatusContent from 'mastodon/components/status_content'; import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { autoPlayGif } from 'mastodon/initial_state'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; +import { makeGetStatus } from 'mastodon/selectors'; const messages = defineMessages({ more: { id: 'status.more', defaultMessage: 'More' }, @@ -29,25 +36,31 @@ const messages = defineMessages({ delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, + replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, + replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, }); -class Conversation extends ImmutablePureComponent { +const getAccounts = createSelector( + (state) => state.get('accounts'), + (_, accountIds) => accountIds, + (accounts, accountIds) => + accountIds.map(id => accounts.get(id)) +); - static propTypes = { - conversationId: PropTypes.string.isRequired, - accounts: ImmutablePropTypes.list.isRequired, - lastStatus: ImmutablePropTypes.map, - unread:PropTypes.bool.isRequired, - scrollKey: PropTypes.string, - onMoveUp: PropTypes.func, - onMoveDown: PropTypes.func, - markRead: PropTypes.func.isRequired, - delete: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - ...WithRouterPropTypes, - }; +const getStatus = makeGetStatus(); - handleMouseEnter = ({ currentTarget }) => { +export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown }) => { + const id = conversation.get('id'); + const unread = conversation.get('unread'); + const lastStatusId = conversation.get('last_status'); + const accountIds = conversation.get('accounts'); + const intl = useIntl(); + const dispatch = useDispatch(); + const history = useHistory(); + const lastStatus = useSelector(state => getStatus(state, { id: lastStatusId })); + const accounts = useSelector(state => getAccounts(state, accountIds)); + + const handleMouseEnter = useCallback(({ currentTarget }) => { if (autoPlayGif) { return; } @@ -58,9 +71,9 @@ class Conversation extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-original'); } - }; + }, []); - handleMouseLeave = ({ currentTarget }) => { + const handleMouseLeave = useCallback(({ currentTarget }) => { if (autoPlayGif) { return; } @@ -71,136 +84,161 @@ class Conversation extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-static'); } - }; - - handleClick = () => { - if (!this.props.history) { - return; - } - - const { lastStatus, unread, markRead } = this.props; + }, []); + const handleClick = useCallback(() => { if (unread) { - markRead(); + dispatch(markConversationRead(id)); } - this.props.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); - }; + history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); + }, [dispatch, history, unread, id, lastStatus]); - handleMarkAsRead = () => { - this.props.markRead(); - }; + const handleMarkAsRead = useCallback(() => { + dispatch(markConversationRead(id)); + }, [dispatch, id]); - handleReply = () => { - this.props.reply(this.props.lastStatus, this.props.history); - }; + const handleReply = useCallback(() => { + dispatch((_, getState) => { + let state = getState(); - handleDelete = () => { - this.props.delete(); - }; + if (state.getIn(['compose', 'text']).trim().length !== 0) { + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(lastStatus, history)), + }, + })); + } else { + dispatch(replyCompose(lastStatus, history)); + } + }); + }, [dispatch, lastStatus, history, intl]); - handleHotkeyMoveUp = () => { - this.props.onMoveUp(this.props.conversationId); - }; + const handleDelete = useCallback(() => { + dispatch(deleteConversation(id)); + }, [dispatch, id]); - handleHotkeyMoveDown = () => { - this.props.onMoveDown(this.props.conversationId); - }; + const handleHotkeyMoveUp = useCallback(() => { + onMoveUp(id); + }, [id, onMoveUp]); - handleConversationMute = () => { - this.props.onMute(this.props.lastStatus); - }; + const handleHotkeyMoveDown = useCallback(() => { + onMoveDown(id); + }, [id, onMoveDown]); - handleShowMore = () => { - this.props.onToggleHidden(this.props.lastStatus); - }; - - render () { - const { accounts, lastStatus, unread, scrollKey, intl } = this.props; - - if (lastStatus === null) { - return null; + const handleConversationMute = useCallback(() => { + if (lastStatus.get('muted')) { + dispatch(unmuteStatus(lastStatus.get('id'))); + } else { + dispatch(muteStatus(lastStatus.get('id'))); } + }, [dispatch, lastStatus]); - const menu = [ - { text: intl.formatMessage(messages.open), action: this.handleClick }, - null, - ]; - - menu.push({ text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute }); - - if (unread) { - menu.push({ text: intl.formatMessage(messages.markAsRead), action: this.handleMarkAsRead }); - menu.push(null); + const handleShowMore = useCallback(() => { + if (lastStatus.get('hidden')) { + dispatch(revealStatus(lastStatus.get('id'))); + } else { + dispatch(hideStatus(lastStatus.get('id'))); } + }, [dispatch, lastStatus]); - menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete }); + if (!lastStatus) { + return null; + } - const names = accounts.map(a => ).reduce((prev, cur) => [prev, ', ', cur]); + const menu = [ + { text: intl.formatMessage(messages.open), action: handleClick }, + null, + { text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: handleConversationMute }, + ]; - const handlers = { - reply: this.handleReply, - open: this.handleClick, - moveUp: this.handleHotkeyMoveUp, - moveDown: this.handleHotkeyMoveDown, - toggleHidden: this.handleShowMore, - }; + if (unread) { + menu.push({ text: intl.formatMessage(messages.markAsRead), action: handleMarkAsRead }); + menu.push(null); + } - return ( - -
-
- -
+ menu.push({ text: intl.formatMessage(messages.delete), action: handleDelete }); -
-
-
- {unread && } -
+ const names = accounts.map(a => ( + + + + + + )).reduce((prev, cur) => [prev, ', ', cur]); -
- {names} }} /> -
+ const handlers = { + reply: handleReply, + open: handleClick, + moveUp: handleHotkeyMoveUp, + moveDown: handleHotkeyMoveDown, + toggleHidden: handleShowMore, + }; + + return ( + +
+
+ +
+ +
+
+
+ {unread && }
- + {names} }} /> +
+
+ + + + {lastStatus.get('media_attachments').size > 0 && ( + + )} - {lastStatus.get('media_attachments').size > 0 && ( - + + +
+ - )} - -
- - -
- -
-
- ); - } +
+ + ); +}; -} - -export default withRouter(injectIntl(Conversation)); +Conversation.propTypes = { + conversation: ImmutablePropTypes.map.isRequired, + scrollKey: PropTypes.string, + onMoveUp: PropTypes.func, + onMoveDown: PropTypes.func, +}; diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx index 8c12ea9e5f..c9fc098a52 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx @@ -1,77 +1,72 @@ import PropTypes from 'prop-types'; +import { useRef, useMemo, useCallback } from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import { useSelector, useDispatch } from 'react-redux'; import { debounce } from 'lodash'; -import ScrollableList from '../../../components/scrollable_list'; -import ConversationContainer from '../containers/conversation_container'; +import { expandConversations } from 'mastodon/actions/conversations'; +import ScrollableList from 'mastodon/components/scrollable_list'; -export default class ConversationsList extends ImmutablePureComponent { +import { Conversation } from './conversation'; - static propTypes = { - conversations: ImmutablePropTypes.list.isRequired, - scrollKey: PropTypes.string.isRequired, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - onLoadMore: PropTypes.func, - }; +const focusChild = (node, index, alignTop) => { + const element = node.querySelector(`article:nth-of-type(${index + 1}) .focusable`); - getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id); - - handleMoveUp = id => { - const elementIndex = this.getCurrentIndex(id) - 1; - this._selectChild(elementIndex, true); - }; - - handleMoveDown = id => { - const elementIndex = this.getCurrentIndex(id) + 1; - this._selectChild(elementIndex, false); - }; - - _selectChild (index, align_top) { - const container = this.node.node; - const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); - - if (element) { - if (align_top && container.scrollTop > element.offsetTop) { - element.scrollIntoView(true); - } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { - element.scrollIntoView(false); - } - element.focus(); + if (element) { + if (alignTop && node.scrollTop > element.offsetTop) { + element.scrollIntoView(true); + } else if (!alignTop && node.scrollTop + node.clientHeight < element.offsetTop + element.offsetHeight) { + element.scrollIntoView(false); } + + element.focus(); } +}; - setRef = c => { - this.node = c; - }; +export const ConversationsList = ({ scrollKey, ...other }) => { + const listRef = useRef(); + const conversations = useSelector(state => state.getIn(['conversations', 'items'])); + const isLoading = useSelector(state => state.getIn(['conversations', 'isLoading'], true)); + const hasMore = useSelector(state => state.getIn(['conversations', 'hasMore'], false)); + const dispatch = useDispatch(); + const lastStatusId = conversations.last()?.get('last_status'); - handleLoadOlder = debounce(() => { - const last = this.props.conversations.last(); + const handleMoveUp = useCallback(id => { + const elementIndex = conversations.findIndex(x => x.get('id') === id) - 1; + focusChild(listRef.current.node, elementIndex, true); + }, [listRef, conversations]); - if (last && last.get('last_status')) { - this.props.onLoadMore(last.get('last_status')); + const handleMoveDown = useCallback(id => { + const elementIndex = conversations.findIndex(x => x.get('id') === id) + 1; + focusChild(listRef.current.node, elementIndex, false); + }, [listRef, conversations]); + + const debouncedLoadMore = useMemo(() => debounce(id => { + dispatch(expandConversations({ maxId: id })); + }, 300, { leading: true }), [dispatch]); + + const handleLoadMore = useCallback(() => { + if (lastStatusId) { + debouncedLoadMore(lastStatusId); } - }, 300, { leading: true }); + }, [debouncedLoadMore, lastStatusId]); - render () { - const { conversations, isLoading, onLoadMore, ...other } = this.props; + return ( + + {conversations.map(item => ( + + ))} + + ); +}; - return ( - - {conversations.map(item => ( - - ))} - - ); - } - -} +ConversationsList.propTypes = { + scrollKey: PropTypes.string.isRequired, +}; diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js deleted file mode 100644 index 456fc7d7cc..0000000000 --- a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js +++ /dev/null @@ -1,80 +0,0 @@ -import { defineMessages, injectIntl } from 'react-intl'; - -import { connect } from 'react-redux'; - -import { replyCompose } from 'mastodon/actions/compose'; -import { markConversationRead, deleteConversation } from 'mastodon/actions/conversations'; -import { openModal } from 'mastodon/actions/modal'; -import { muteStatus, unmuteStatus, hideStatus, revealStatus } from 'mastodon/actions/statuses'; -import { makeGetStatus } from 'mastodon/selectors'; - -import Conversation from '../components/conversation'; - -const messages = defineMessages({ - replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, - replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, -}); - -const mapStateToProps = () => { - const getStatus = makeGetStatus(); - - return (state, { conversationId }) => { - const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId); - const lastStatusId = conversation.get('last_status', null); - - return { - accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)), - unread: conversation.get('unread'), - lastStatus: lastStatusId && getStatus(state, { id: lastStatusId }), - }; - }; -}; - -const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({ - - markRead () { - dispatch(markConversationRead(conversationId)); - }, - - reply (status, router) { - dispatch((_, getState) => { - let state = getState(); - - if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status, router)), - }, - })); - } else { - dispatch(replyCompose(status, router)); - } - }); - }, - - delete () { - dispatch(deleteConversation(conversationId)); - }, - - onMute (status) { - if (status.get('muted')) { - dispatch(unmuteStatus(status.get('id'))); - } else { - dispatch(muteStatus(status.get('id'))); - } - }, - - onToggleHidden (status) { - if (status.get('hidden')) { - dispatch(revealStatus(status.get('id'))); - } else { - dispatch(hideStatus(status.get('id'))); - } - }, - -}); - -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Conversation)); diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js deleted file mode 100644 index 1dcd3ec1bd..0000000000 --- a/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; - -import { expandConversations } from '../../../actions/conversations'; -import ConversationsList from '../components/conversations_list'; - -const mapStateToProps = state => ({ - conversations: state.getIn(['conversations', 'items']), - isLoading: state.getIn(['conversations', 'isLoading'], true), - hasMore: state.getIn(['conversations', 'hasMore'], false), -}); - -const mapDispatchToProps = dispatch => ({ - onLoadMore: maxId => dispatch(expandConversations({ maxId })), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ConversationsList); diff --git a/app/javascript/mastodon/features/direct_timeline/index.jsx b/app/javascript/mastodon/features/direct_timeline/index.jsx index 4e6d0c3e72..7aee83ec10 100644 --- a/app/javascript/mastodon/features/direct_timeline/index.jsx +++ b/app/javascript/mastodon/features/direct_timeline/index.jsx @@ -1,117 +1,92 @@ import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useRef, useCallback, useEffect } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; -import { connect } from 'react-redux'; - -import { ReactComponent as AlternateEmailIcon } from '@material-symbols/svg-600/outlined/alternate_email.svg'; +import { useDispatch } from 'react-redux'; +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; import { mountConversations, unmountConversations, expandConversations } from 'mastodon/actions/conversations'; import { connectDirectStream } from 'mastodon/actions/streaming'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; -import ConversationsListContainer from './containers/conversations_list_container'; +import { ConversationsList } from './components/conversations_list'; const messages = defineMessages({ title: { id: 'column.direct', defaultMessage: 'Private mentions' }, }); -class DirectTimeline extends PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - columnId: PropTypes.string, - intl: PropTypes.object.isRequired, - hasUnread: PropTypes.bool, - multiColumn: PropTypes.bool, - }; - - handlePin = () => { - const { columnId, dispatch } = this.props; +const DirectTimeline = ({ columnId, multiColumn }) => { + const columnRef = useRef(); + const intl = useIntl(); + const dispatch = useDispatch(); + const pinned = !!columnId; + const handlePin = useCallback(() => { if (columnId) { dispatch(removeColumn(columnId)); } else { dispatch(addColumn('DIRECT', {})); } - }; + }, [dispatch, columnId]); - handleMove = (dir) => { - const { columnId, dispatch } = this.props; + const handleMove = useCallback((dir) => { dispatch(moveColumn(columnId, dir)); - }; + }, [dispatch, columnId]); - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - componentDidMount () { - const { dispatch } = this.props; + const handleHeaderClick = useCallback(() => { + columnRef.current.scrollTop(); + }, [columnRef]); + useEffect(() => { dispatch(mountConversations()); dispatch(expandConversations()); - this.disconnect = dispatch(connectDirectStream()); - } - componentWillUnmount () { - this.props.dispatch(unmountConversations()); + const disconnect = dispatch(connectDirectStream()); - if (this.disconnect) { - this.disconnect(); - this.disconnect = null; - } - } + return () => { + dispatch(unmountConversations()); + disconnect(); + }; + }, [dispatch]); - setRef = c => { - this.column = c; - }; + return ( + + - handleLoadMore = maxId => { - this.props.dispatch(expandConversations({ maxId })); - }; + } + bindToDocument={!multiColumn} + prepend={
} + alwaysPrepend + /> - render () { - const { intl, hasUnread, columnId, multiColumn } = this.props; - const pinned = !!columnId; + + {intl.formatMessage(messages.title)} + + +
+ ); +}; - return ( - - +DirectTimeline.propTypes = { + columnId: PropTypes.string, + multiColumn: PropTypes.bool, +}; -
} - alwaysPrepend - emptyMessage={} - /> - - - {intl.formatMessage(messages.title)} - - - - ); - } - -} - -export default connect()(injectIntl(DirectTimeline)); +export default DirectTimeline; diff --git a/app/javascript/mastodon/features/directory/index.jsx b/app/javascript/mastodon/features/directory/index.jsx index 20c84d4df0..0d3408146e 100644 --- a/app/javascript/mastodon/features/directory/index.jsx +++ b/app/javascript/mastodon/features/directory/index.jsx @@ -9,8 +9,7 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; - +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'mastodon/actions/columns'; import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory'; import Column from 'mastodon/components/column'; diff --git a/app/javascript/mastodon/features/domain_blocks/index.jsx b/app/javascript/mastodon/features/domain_blocks/index.jsx index 142f14bf71..964eada9c1 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.jsx +++ b/app/javascript/mastodon/features/domain_blocks/index.jsx @@ -8,9 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block-fill.svg'; import { debounce } from 'lodash'; +import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react'; + import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/mastodon/features/explore/index.jsx b/app/javascript/mastodon/features/explore/index.jsx index 80825e4563..8ebaccd013 100644 --- a/app/javascript/mastodon/features/explore/index.jsx +++ b/app/javascript/mastodon/features/explore/index.jsx @@ -8,9 +8,8 @@ import { NavLink, Switch, Route } from 'react-router-dom'; import { connect } from 'react-redux'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; import Search from 'mastodon/features/compose/containers/search_container'; diff --git a/app/javascript/mastodon/features/explore/results.jsx b/app/javascript/mastodon/features/explore/results.jsx index 8c172c134a..355c0f1c4c 100644 --- a/app/javascript/mastodon/features/explore/results.jsx +++ b/app/javascript/mastodon/features/explore/results.jsx @@ -9,10 +9,9 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as FindInPageIcon } from '@material-symbols/svg-600/outlined/find_in_page.svg'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { submitSearch, expandSearch } from 'mastodon/actions/search'; import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/favourited_statuses/index.jsx b/app/javascript/mastodon/features/favourited_statuses/index.jsx index d3c3cc9f09..8e65ff5b68 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.jsx +++ b/app/javascript/mastodon/features/favourited_statuses/index.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; import { debounce } from 'lodash'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'mastodon/actions/favourites'; import ColumnHeader from 'mastodon/components/column_header'; diff --git a/app/javascript/mastodon/features/favourites/index.jsx b/app/javascript/mastodon/features/favourites/index.jsx index 637a9d6994..27ca169409 100644 --- a/app/javascript/mastodon/features/favourites/index.jsx +++ b/app/javascript/mastodon/features/favourites/index.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as RefreshIcon } from '@material-symbols/svg-600/outlined/refresh.svg'; import { debounce } from 'lodash'; +import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions'; import ColumnHeader from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/filters/select_filter.jsx b/app/javascript/mastodon/features/filters/select_filter.jsx index 9e8f87e005..5b2eb64952 100644 --- a/app/javascript/mastodon/features/filters/select_filter.jsx +++ b/app/javascript/mastodon/features/filters/select_filter.jsx @@ -5,9 +5,9 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; import fuzzysort from 'fuzzysort'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; import { Icon } from 'mastodon/components/icon'; import { toServerSideType } from 'mastodon/utils/filters'; import { loupeIcon, deleteIcon } from 'mastodon/utils/icons'; diff --git a/app/javascript/mastodon/features/firehose/index.jsx b/app/javascript/mastodon/features/firehose/index.jsx index 0ed8aa11aa..6355efbfe0 100644 --- a/app/javascript/mastodon/features/firehose/index.jsx +++ b/app/javascript/mastodon/features/firehose/index.jsx @@ -6,8 +6,7 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; import { NavLink } from 'react-router-dom'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; - +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { addColumn } from 'mastodon/actions/columns'; import { changeSetting } from 'mastodon/actions/settings'; import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx index ca2b454143..dd308c87cb 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx @@ -7,8 +7,8 @@ import { Link } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from '../../../components/avatar'; import { DisplayName } from '../../../components/display_name'; diff --git a/app/javascript/mastodon/features/follow_requests/index.jsx b/app/javascript/mastodon/features/follow_requests/index.jsx index 3b98791926..7d651f2ca6 100644 --- a/app/javascript/mastodon/features/follow_requests/index.jsx +++ b/app/javascript/mastodon/features/follow_requests/index.jsx @@ -8,9 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; import { debounce } from 'lodash'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; + import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import ScrollableList from '../../components/scrollable_list'; import { me } from '../../initial_state'; diff --git a/app/javascript/mastodon/features/followed_tags/index.jsx b/app/javascript/mastodon/features/followed_tags/index.jsx index dec53f0121..21248e6de9 100644 --- a/app/javascript/mastodon/features/followed_tags/index.jsx +++ b/app/javascript/mastodon/features/followed_tags/index.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; import { debounce } from 'lodash'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags'; import ColumnHeader from 'mastodon/components/column_header'; import { Hashtag } from 'mastodon/components/hashtag'; diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx index 1999316a8e..ea36cefd7d 100644 --- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx +++ b/app/javascript/mastodon/features/getting_started/components/announcements.jsx @@ -9,14 +9,14 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; import TransitionMotion from 'react-motion/lib/TransitionMotion'; import spring from 'react-motion/lib/spring'; import ReactSwipeableViews from 'react-swipeable-views'; -import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; +import elephantUIPlane from '@/images/elephant_ui_plane.svg'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { AnimatedNumber } from 'mastodon/components/animated_number'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/getting_started/index.jsx b/app/javascript/mastodon/features/getting_started/index.jsx index 5994e88edf..53bb22ddb2 100644 --- a/app/javascript/mastodon/features/getting_started/index.jsx +++ b/app/javascript/mastodon/features/getting_started/index.jsx @@ -9,18 +9,17 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as AlternateEmailIcon } from '@material-symbols/svg-600/outlined/alternate_email.svg'; -import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg'; -import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; -import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; -import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; +import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; +import StarIcon from '@/material-icons/400-24px/star.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { fetchFollowRequests } from 'mastodon/actions/accounts'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.jsx b/app/javascript/mastodon/features/hashtag_timeline/index.jsx index d39d550a1b..f431a7e9b7 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.jsx +++ b/app/javascript/mastodon/features/hashtag_timeline/index.jsx @@ -8,9 +8,9 @@ import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; import { isEqual } from 'lodash'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; import { connectHashtagStream } from 'mastodon/actions/streaming'; import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags'; diff --git a/app/javascript/mastodon/features/home_timeline/components/explore_prompt.tsx b/app/javascript/mastodon/features/home_timeline/components/explore_prompt.tsx index 9eeec00e34..960d30e2ca 100644 --- a/app/javascript/mastodon/features/home_timeline/components/explore_prompt.tsx +++ b/app/javascript/mastodon/features/home_timeline/components/explore_prompt.tsx @@ -2,7 +2,7 @@ import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router-dom'; -import background from 'mastodon/../images/friends-cropped.png'; +import background from '@/images/friends-cropped.png'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; export const ExplorePrompt = () => ( diff --git a/app/javascript/mastodon/features/home_timeline/index.jsx b/app/javascript/mastodon/features/home_timeline/index.jsx index 613eb4b896..069f52b0be 100644 --- a/app/javascript/mastodon/features/home_timeline/index.jsx +++ b/app/javascript/mastodon/features/home_timeline/index.jsx @@ -10,9 +10,8 @@ import { createSelector } from '@reduxjs/toolkit'; import { List as ImmutableList } from 'immutable'; import { connect } from 'react-redux'; -import { ReactComponent as CampaignIcon } from '@material-symbols/svg-600/outlined/campaign.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; - +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/announcements'; import { IconWithBadge } from 'mastodon/components/icon_with_badge'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx index 216c63a7e6..07f1e6fe5a 100644 --- a/app/javascript/mastodon/features/interaction_modal/index.jsx +++ b/app/javascript/mastodon/features/interaction_modal/index.jsx @@ -7,12 +7,12 @@ import classNames from 'classnames'; import { connect } from 'react-redux'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg'; import { throttle, escapeRegExp } from 'lodash'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { openModal, closeModal } from 'mastodon/actions/modal'; import api from 'mastodon/api'; import { Button } from 'mastodon/components/button'; diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx index 7552e1799b..622ca525c1 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx @@ -6,8 +6,7 @@ import { Helmet } from 'react-helmet'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg'; - +import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; diff --git a/app/javascript/mastodon/features/list_adder/components/list.jsx b/app/javascript/mastodon/features/list_adder/components/list.jsx index 6c5aab85da..a7cfd60bf3 100644 --- a/app/javascript/mastodon/features/list_adder/components/list.jsx +++ b/app/javascript/mastodon/features/list_adder/components/list.jsx @@ -6,10 +6,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; - +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { Icon } from 'mastodon/components/icon'; import { removeFromListAdder, addToListAdder } from '../../../actions/lists'; diff --git a/app/javascript/mastodon/features/list_editor/components/account.jsx b/app/javascript/mastodon/features/list_editor/components/account.jsx index 18d5e905cb..77d32af80a 100644 --- a/app/javascript/mastodon/features/list_editor/components/account.jsx +++ b/app/javascript/mastodon/features/list_editor/components/account.jsx @@ -6,8 +6,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { removeFromListEditor, addToListEditor } from '../../../actions/lists'; import { Avatar } from '../../../components/avatar'; diff --git a/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx b/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx index 1e2446f92b..89f596636e 100644 --- a/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx +++ b/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx @@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { changeListEditorTitle, submitListEditor } from '../../../actions/lists'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/mastodon/features/list_editor/components/search.jsx b/app/javascript/mastodon/features/list_editor/components/search.jsx index 093af5cd4e..097d4f3f41 100644 --- a/app/javascript/mastodon/features/list_editor/components/search.jsx +++ b/app/javascript/mastodon/features/list_editor/components/search.jsx @@ -7,9 +7,8 @@ import classNames from 'classnames'; import { connect } from 'react-redux'; -import { ReactComponent as CancelIcon } from '@material-symbols/svg-600/outlined/cancel.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; - +import CancelIcon from '@/material-icons/400-24px/cancel.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import { Icon } from 'mastodon/components/icon'; import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists'; diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx index 55579c2fd1..24bf122fac 100644 --- a/app/javascript/mastodon/features/list_timeline/index.jsx +++ b/app/javascript/mastodon/features/list_timeline/index.jsx @@ -9,11 +9,11 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as DeleteIcon } from '@material-symbols/svg-600/outlined/delete.svg'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; import Toggle from 'react-toggle'; +import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; import { fetchList, deleteList, updateList } from 'mastodon/actions/lists'; import { openModal } from 'mastodon/actions/modal'; diff --git a/app/javascript/mastodon/features/lists/index.jsx b/app/javascript/mastodon/features/lists/index.jsx index 9014394351..a7648f55b2 100644 --- a/app/javascript/mastodon/features/lists/index.jsx +++ b/app/javascript/mastodon/features/lists/index.jsx @@ -9,8 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; - +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { fetchLists } from 'mastodon/actions/lists'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; diff --git a/app/javascript/mastodon/features/mutes/index.jsx b/app/javascript/mastodon/features/mutes/index.jsx index 7f66edc03d..3b50244ea4 100644 --- a/app/javascript/mastodon/features/mutes/index.jsx +++ b/app/javascript/mastodon/features/mutes/index.jsx @@ -8,9 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg'; import { debounce } from 'lodash'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react'; + import { fetchMutes, expandMutes } from '../../actions/mutes'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx b/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx index 54fa16fb67..73eaecba59 100644 --- a/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx +++ b/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx @@ -3,8 +3,7 @@ import { PureComponent } from 'react'; import { FormattedMessage } from 'react-intl'; -import { ReactComponent as DeleteForeverIcon } from '@material-symbols/svg-600/outlined/delete_forever.svg'; - +import DeleteForeverIcon from '@/material-icons/400-24px/delete_forever.svg?react'; import { Icon } from 'mastodon/components/icon'; export default class ClearColumnButton extends PureComponent { diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.jsx b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx index 84bd4791ca..c288c2c0de 100644 --- a/app/javascript/mastodon/features/notifications/components/filter_bar.jsx +++ b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx @@ -3,13 +3,12 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg'; - +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { Icon } from 'mastodon/components/icon'; const tooltips = defineMessages({ diff --git a/app/javascript/mastodon/features/notifications/components/follow_request.jsx b/app/javascript/mastodon/features/notifications/components/follow_request.jsx index 03420b6c01..4024455cbd 100644 --- a/app/javascript/mastodon/features/notifications/components/follow_request.jsx +++ b/app/javascript/mastodon/features/notifications/components/follow_request.jsx @@ -7,9 +7,8 @@ import { Link } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from 'mastodon/components/avatar'; import { DisplayName } from 'mastodon/components/display_name'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index ad7308b26f..d7101f8384 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -8,16 +8,16 @@ import { Link, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; -import { ReactComponent as FlagIcon } from '@material-symbols/svg-600/outlined/flag-fill.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; -import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person-fill.svg'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add-fill.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; import { HotKeys } from 'react-hotkeys'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react'; +import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { Icon } from 'mastodon/components/icon'; import AccountContainer from 'mastodon/containers/account_container'; import StatusContainer from 'mastodon/containers/status_container'; diff --git a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx index b7ebb4c467..1cdf5b5dfe 100644 --- a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx +++ b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx @@ -5,9 +5,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; -import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import TuneIcon from '@/material-icons/400-24px/tune.svg?react'; import { requestBrowserPermission } from 'mastodon/actions/notifications'; import { changeSetting } from 'mastodon/actions/settings'; import { Button } from 'mastodon/components/button'; diff --git a/app/javascript/mastodon/features/notifications/index.jsx b/app/javascript/mastodon/features/notifications/index.jsx index 762c96ccca..30c63ed32a 100644 --- a/app/javascript/mastodon/features/notifications/index.jsx +++ b/app/javascript/mastodon/features/notifications/index.jsx @@ -10,10 +10,10 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as DoneAllIcon } from '@material-symbols/svg-600/outlined/done_all.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; import { debounce } from 'lodash'; +import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react'; +import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; import { compareId } from 'mastodon/compare_id'; import { Icon } from 'mastodon/components/icon'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; diff --git a/app/javascript/mastodon/features/onboarding/components/step.jsx b/app/javascript/mastodon/features/onboarding/components/step.jsx index 1f83f20801..a2a1653b8a 100644 --- a/app/javascript/mastodon/features/onboarding/components/step.jsx +++ b/app/javascript/mastodon/features/onboarding/components/step.jsx @@ -2,9 +2,8 @@ import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; -import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg'; - +import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react'; +import CheckIcon from '@/material-icons/400-24px/done.svg?react'; import { Icon } from 'mastodon/components/icon'; export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => { diff --git a/app/javascript/mastodon/features/onboarding/index.jsx b/app/javascript/mastodon/features/onboarding/index.jsx index 51677fbc7a..5900b9ec76 100644 --- a/app/javascript/mastodon/features/onboarding/index.jsx +++ b/app/javascript/mastodon/features/onboarding/index.jsx @@ -8,13 +8,12 @@ import { Link, Switch, Route, useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { ReactComponent as AccountCircleIcon } from '@material-symbols/svg-600/outlined/account_circle.svg'; -import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; -import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg'; -import { ReactComponent as EditNoteIcon } from '@material-symbols/svg-600/outlined/edit_note.svg'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; - -import illustration from 'mastodon/../images/elephant_ui_conversation.svg'; +import illustration from '@/images/elephant_ui_conversation.svg'; +import AccountCircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; +import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react'; +import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react'; +import EditNoteIcon from '@/material-icons/400-24px/edit_note.svg?react'; +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; import { focusCompose } from 'mastodon/actions/compose'; import { Icon } from 'mastodon/components/icon'; import Column from 'mastodon/features/ui/components/column'; diff --git a/app/javascript/mastodon/features/onboarding/profile.jsx b/app/javascript/mastodon/features/onboarding/profile.jsx index daaef6065c..14250ae39b 100644 --- a/app/javascript/mastodon/features/onboarding/profile.jsx +++ b/app/javascript/mastodon/features/onboarding/profile.jsx @@ -8,10 +8,10 @@ import { useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg'; -import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; import Toggle from 'react-toggle'; +import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import { updateAccount } from 'mastodon/actions/accounts'; import { Button } from 'mastodon/components/button'; import { ColumnBackButton } from 'mastodon/components/column_back_button'; diff --git a/app/javascript/mastodon/features/onboarding/share.jsx b/app/javascript/mastodon/features/onboarding/share.jsx index adc0f9cba3..32a86ab6cc 100644 --- a/app/javascript/mastodon/features/onboarding/share.jsx +++ b/app/javascript/mastodon/features/onboarding/share.jsx @@ -7,10 +7,10 @@ import classNames from 'classnames'; import { Link } from 'react-router-dom'; -import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; -import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg'; import SwipeableViews from 'react-swipeable-views'; +import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react'; +import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react'; import { ColumnBackButton } from 'mastodon/components/column_back_button'; import { Icon } from 'mastodon/components/icon'; import { me, domain } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx index ed86d19f5c..8dfbf54cb6 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx @@ -9,12 +9,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as OpenInNewIcon } from '@material-symbols/svg-600/outlined/open_in_new.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg'; - +import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { initBoostModal } from 'mastodon/actions/boosts'; import { replyCompose } from 'mastodon/actions/compose'; import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions'; diff --git a/app/javascript/mastodon/features/picture_in_picture/components/header.jsx b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx index 80a13bd2e3..31073d7387 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/header.jsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx @@ -8,8 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from 'mastodon/components/avatar'; import { DisplayName } from 'mastodon/components/display_name'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/pinned_statuses/index.jsx b/app/javascript/mastodon/features/pinned_statuses/index.jsx index 82398ccda9..921e9a6072 100644 --- a/app/javascript/mastodon/features/pinned_statuses/index.jsx +++ b/app/javascript/mastodon/features/pinned_statuses/index.jsx @@ -8,8 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg'; - +import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; import { getStatusList } from 'mastodon/selectors'; import { fetchPinnedStatuses } from '../../actions/pin_statuses'; diff --git a/app/javascript/mastodon/features/public_timeline/index.jsx b/app/javascript/mastodon/features/public_timeline/index.jsx index 09a9f6821f..3601dfeae8 100644 --- a/app/javascript/mastodon/features/public_timeline/index.jsx +++ b/app/javascript/mastodon/features/public_timeline/index.jsx @@ -7,8 +7,7 @@ import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; - +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { domain } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/reblogs/index.jsx b/app/javascript/mastodon/features/reblogs/index.jsx index be17668418..3d1fc94cb7 100644 --- a/app/javascript/mastodon/features/reblogs/index.jsx +++ b/app/javascript/mastodon/features/reblogs/index.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as RefreshIcon } from '@material-symbols/svg-600/outlined/refresh.svg'; import { debounce } from 'lodash'; +import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; import { Icon } from 'mastodon/components/icon'; import { fetchReblogs, expandReblogs } from '../../actions/interactions'; diff --git a/app/javascript/mastodon/features/report/components/option.jsx b/app/javascript/mastodon/features/report/components/option.jsx index b3602219f5..7aa0dd1379 100644 --- a/app/javascript/mastodon/features/report/components/option.jsx +++ b/app/javascript/mastodon/features/report/components/option.jsx @@ -3,8 +3,7 @@ import { PureComponent } from 'react'; import classNames from 'classnames'; -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg'; - +import CheckIcon from '@/material-icons/400-24px/done.svg?react'; import { Icon } from 'mastodon/components/icon'; export default class Option extends PureComponent { diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index 663bce7431..c243a49129 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -9,17 +9,18 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg'; -import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg'; -import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; -import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg'; - -import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg'; -import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg'; +import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; +import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; +import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; +import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; +import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; +import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -297,7 +298,7 @@ class ActionBar extends PureComponent { if (status.get('reblogged')) { reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon; + reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; } else if (publicStatus) { reblogTitle = intl.formatMessage(messages.reblog); reblogIconComponent = RepeatIcon; diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index d7d688952d..f37b558c4c 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -10,10 +10,9 @@ import classNames from 'classnames'; import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description-fill.svg'; -import { ReactComponent as OpenInNewIcon } from '@material-symbols/svg-600/outlined/open_in_new.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; - +import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react'; +import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index d8d9559127..437e9e86bf 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -8,10 +8,9 @@ import { Link, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as AlternateEmailIcon } from '@material-symbols/svg-600/outlined/alternate_email.svg'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; - +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { AnimatedNumber } from 'mastodon/components/animated_number'; import EditedTimestamp from 'mastodon/components/edited_timestamp'; import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar'; diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 3832489764..a3034bb570 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -12,10 +12,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; import { HotKeys } from 'react-hotkeys'; +import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import ScrollContainer from 'mastodon/containers/scroll_container'; diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx index ac34f7986c..0531346f91 100644 --- a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx +++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx @@ -8,8 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { followAccount } from 'mastodon/actions/accounts'; import { Button } from 'mastodon/components/button'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.jsx b/app/javascript/mastodon/features/ui/components/boost_modal.jsx index c6fa8ce363..3b3e1e3f97 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx @@ -9,8 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; - +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import { changeBoostPrivacy } from 'mastodon/actions/boosts'; import AttachmentList from 'mastodon/components/attachment_list'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx b/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx index 44165f1417..d1c9e36883 100644 --- a/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx +++ b/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx @@ -3,7 +3,7 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import { ReactComponent as RefreshIcon } from '@material-symbols/svg-600/outlined/refresh.svg'; +import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; import { IconButton } from '../../../components/icon_button'; diff --git a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx index 413c5069f1..4227c74131 100644 --- a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx @@ -6,9 +6,9 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import escapeTextContentForBrowser from 'escape-html'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { closeModal } from 'mastodon/actions/modal'; import { IconButton } from 'mastodon/components/icon_button'; import InlineAccount from 'mastodon/components/inline_account'; diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.jsx b/app/javascript/mastodon/features/ui/components/embed_modal.jsx index e5c0b5cae9..a4e5fc9dfb 100644 --- a/app/javascript/mastodon/features/ui/components/embed_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/embed_modal.jsx @@ -4,8 +4,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import api from 'mastodon/api'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/ui/components/filter_modal.jsx b/app/javascript/mastodon/features/ui/components/filter_modal.jsx index d90edddec4..477575bd7b 100644 --- a/app/javascript/mastodon/features/ui/components/filter_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/filter_modal.jsx @@ -5,8 +5,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { fetchFilters, createFilter, createFilterStatus } from 'mastodon/actions/filters'; import { fetchStatus } from 'mastodon/actions/statuses'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx index 91dca26718..25ad1e2e9f 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx @@ -9,7 +9,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import Textarea from 'react-textarea-autosize'; import { length } from 'stringz'; // eslint-disable-next-line import/extensions @@ -17,6 +16,7 @@ import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js'; // eslint-disable-next-line import/no-extraneous-dependencies import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Button } from 'mastodon/components/button'; import { GIFV } from 'mastodon/components/gifv'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx b/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx index 314b2a2652..4aa0092631 100644 --- a/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx +++ b/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx @@ -6,8 +6,7 @@ import { injectIntl, defineMessages } from 'react-intl'; import { List as ImmutableList } from 'immutable'; import { connect } from 'react-redux'; -import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; - +import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; import { fetchFollowRequests } from 'mastodon/actions/accounts'; import { IconWithBadge } from 'mastodon/components/icon_with_badge'; import ColumnLink from 'mastodon/features/ui/components/column_link'; diff --git a/app/javascript/mastodon/features/ui/components/header.jsx b/app/javascript/mastodon/features/ui/components/header.jsx index 150647ffb3..2f8636b12a 100644 --- a/app/javascript/mastodon/features/ui/components/header.jsx +++ b/app/javascript/mastodon/features/ui/components/header.jsx @@ -7,8 +7,7 @@ import { Link, withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; - +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import { openModal } from 'mastodon/actions/modal'; import { fetchServer } from 'mastodon/actions/server'; import { Avatar } from 'mastodon/components/avatar'; diff --git a/app/javascript/mastodon/features/ui/components/image_modal.jsx b/app/javascript/mastodon/features/ui/components/image_modal.jsx index c534bf1636..f08ce15342 100644 --- a/app/javascript/mastodon/features/ui/components/image_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/image_modal.jsx @@ -5,8 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { IconButton } from 'mastodon/components/icon_button'; import ImageLoader from './image_loader'; diff --git a/app/javascript/mastodon/features/ui/components/list_panel.jsx b/app/javascript/mastodon/features/ui/components/list_panel.jsx index ff600730a0..fec21f14ca 100644 --- a/app/javascript/mastodon/features/ui/components/list_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/list_panel.jsx @@ -5,8 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; - +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { fetchLists } from 'mastodon/actions/lists'; import ColumnLink from './column_link'; diff --git a/app/javascript/mastodon/features/ui/components/media_modal.jsx b/app/javascript/mastodon/features/ui/components/media_modal.jsx index 8c06a96531..0f6e8a727b 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx @@ -7,11 +7,11 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg'; -import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; import ReactSwipeableViews from 'react-swipeable-views'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { getAverageFromBlurhash } from 'mastodon/blurhash'; import { GIFV } from 'mastodon/components/gifv'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index d1b2a0910b..d3f46c6d46 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -5,17 +5,16 @@ import { defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { ReactComponent as AlternateEmailIcon } from '@material-symbols/svg-600/outlined/alternate_email.svg'; -import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg'; -import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; -import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; -import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg'; -import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg'; -import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg'; -import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; -import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg'; - +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import SearchIcon from '@/material-icons/400-24px/search.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { WordmarkLogo } from 'mastodon/components/logo'; import { NavigationPortal } from 'mastodon/components/navigation_portal'; import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js b/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js index b3e9950e93..7d59d616d8 100644 --- a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js +++ b/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js @@ -1,7 +1,6 @@ import { connect } from 'react-redux'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; - +import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; import { IconWithBadge } from 'mastodon/components/icon_with_badge'; diff --git a/app/javascript/mastodon/features/ui/components/report_modal.jsx b/app/javascript/mastodon/features/ui/components/report_modal.jsx index 3fd8ff127d..6584364609 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/report_modal.jsx @@ -7,8 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg'; - +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { submitReport } from 'mastodon/actions/reports'; import { fetchServer } from 'mastodon/actions/server'; import { expandAccountTimeline } from 'mastodon/actions/timelines'; diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.jsx b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx index 5e71da9d96..272a3cff00 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.jsx +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx @@ -3,9 +3,8 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import { ReactComponent as FullscreenExitIcon } from '@material-symbols/svg-600/outlined/fullscreen_exit.svg'; -import { ReactComponent as RectangleIcon } from '@material-symbols/svg-600/outlined/rectangle.svg'; - +import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; +import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; import { IconButton } from 'mastodon/components/icon_button'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx index e908715e91..89a8ba560a 100644 --- a/app/javascript/mastodon/features/video/index.jsx +++ b/app/javascript/mastodon/features/video/index.jsx @@ -7,16 +7,16 @@ import classNames from 'classnames'; import { is } from 'immutable'; -import { ReactComponent as FullscreenIcon } from '@material-symbols/svg-600/outlined/fullscreen.svg'; -import { ReactComponent as FullscreenExitIcon } from '@material-symbols/svg-600/outlined/fullscreen_exit.svg'; -import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; -import { ReactComponent as RectangleIcon } from '@material-symbols/svg-600/outlined/rectangle.svg'; -import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg'; -import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg'; import { throttle } from 'lodash'; +import FullscreenIcon from '@/material-icons/400-24px/fullscreen.svg?react'; +import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; +import PauseIcon from '@/material-icons/400-24px/pause.svg?react'; +import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; +import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off-fill.svg?react'; +import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; import { playerSettings } from 'mastodon/settings'; diff --git a/app/javascript/mastodon/locales/af.json b/app/javascript/mastodon/locales/af.json index 6c37cdf5ca..d1873d6dce 100644 --- a/app/javascript/mastodon/locales/af.json +++ b/app/javascript/mastodon/locales/af.json @@ -3,6 +3,7 @@ "about.contact": "Kontak:", "about.disclaimer": "Mastodon is gratis oopbronsagteware en ’n handelsmerk van Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Rede nie beskikbaar nie", + "about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.", "about.domain_blocks.silenced.title": "Beperk", "about.domain_blocks.suspended.title": "Opgeskort", "about.not_available": "Hierdie inligting is nie op hierdie bediener beskikbaar gestel nie.", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index c4238edcd4..1467f8891e 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -13,14 +13,12 @@ "about.rules": "Normes del sirvidor", "account.account_note_header": "Nota", "account.add_or_remove_from_list": "Amestar o quitar de les llistes", - "account.badges.bot": "Automatizáu", "account.badges.group": "Grupu", "account.block": "Bloquiar a @{name}", "account.block_domain": "Bloquiar el dominiu {domain}", "account.block_short": "Bloquiar", "account.blocked": "Perfil bloquiáu", "account.browse_more_on_origin_server": "Restolar más nel perfil orixinal", - "account.cancel_follow_request": "Atayar siguimientu", "account.copy": "Copiar I'enllaz al perfil", "account.direct": "Mentar a @{name} per privao", "account.disable_notifications": "Dexar d'avisame cuando @{name} espublice artículos", @@ -28,13 +26,11 @@ "account.edit_profile": "Editar el perfil", "account.enable_notifications": "Avisame cuando @{name} espublice artículos", "account.endorse": "Destacar nel perfil", - "account.featured_tags.last_status_at": "Últimu estáu en {date}", - "account.featured_tags.last_status_never": "Sin estaos", + "account.featured_tags.last_status_never": "Nun hai nengún artículu", "account.featured_tags.title": "Etiquetes destacaes de: {name}", "account.follow": "Siguir", "account.followers": "Siguidores", "account.followers.empty": "Naide sigue a esti perfil.", - "account.followers_counter": "{count, plural, one {{counter} Siguíu} other {{counter} Siguíos}}", "account.following": "Siguiendo", "account.following_counter": "{count, plural,one {Sigue a {counter}} other {Sigue a {counter}}}", "account.follows.empty": "Esti perfil nun sigue a naide.", @@ -42,7 +38,6 @@ "account.hide_reblogs": "Anubrir los artículos compartíos de @{name}", "account.in_memoriam": "N'alcordanza.", "account.joined_short": "Data de xunión", - "account.languages": "Camudar llingües suscrites", "account.link_verified_on": "La propiedá d'esti enllaz foi comprobada'l {date}", "account.media": "Multimedia", "account.mention": "Mentar a @{name}", @@ -108,6 +103,7 @@ "community.column_settings.remote_only": "Namás lo remoto", "compose.language.change": "Camudar la llingua", "compose.language.search": "Buscar llingües…", + "compose.published.body": "Espublizóse l'artículu.", "compose_form.direct_message_warning_learn_more": "Saber más", "compose_form.encryption_warning": "Los artículos de Mastodon nun tán cifraos de puntu a puntu. Nun compartas nengún tipu d'información sensible per Mastodon.", "compose_form.lock_disclaimer": "La to cuenta nun ye {locked}. Cualesquier perfil pue siguite pa ver los artículos que son namás pa siguidores.", @@ -120,7 +116,6 @@ "compose_form.publish_form": "Artículu nuevu", "compose_form.publish_loud": "¡{publish}!", "compose_form.save_changes": "Guardar los cambeos", - "compose_form.spoiler.unmarked": "Text is not hidden", "confirmation_modal.cancel": "Encaboxar", "confirmations.block.block_and_report": "Bloquiar ya informar", "confirmations.block.confirm": "Bloquiar", @@ -150,6 +145,7 @@ "dismissable_banner.community_timeline": "Esta seición contién los artículos públicos más actuales de los perfiles agospiaos nel dominiu {domain}.", "dismissable_banner.dismiss": "Escartar", "dismissable_banner.explore_tags": "Esta seición contién les etiquetes del fediversu que tán ganando popularidá güei. Les etiquetes más usaes polos perfiles apaecen no cimero.", + "dismissable_banner.public_timeline": "Esta seición contién los artículos más nuevos de les persones na web social que les persones de {domain} siguen.", "embed.instructions": "Empotra esti artículu nel to sitiu web pente la copia del códigu d'abaxo.", "embed.preview": "Va apaecer asina:", "emoji_button.activity": "Actividá", @@ -159,6 +155,7 @@ "emoji_button.not_found": "Nun s'atoparon fustaxes que concasen", "emoji_button.objects": "Oxetos", "emoji_button.people": "Persones", + "emoji_button.recent": "D'usu frecuente", "emoji_button.search": "Buscar…", "emoji_button.search_results": "Resultaos de la busca", "emoji_button.symbols": "Símbolos", @@ -221,7 +218,6 @@ "hashtag.column_header.tag_mode.any": "o {additional}", "hashtag.column_header.tag_mode.none": "ensin {additional}", "hashtag.column_settings.select.no_options_message": "Nun s'atopó nenguna suxerencia", - "hashtag.column_settings.tag_toggle": "Include additional tags in this column", "hashtag.counter_by_accounts": "{count, plural, one {{counter} participante} other {{counter} participantes}}", "hashtag.follow": "Siguir a la etiqueta", "hashtag.unfollow": "Dexar de siguir a la etiqueta", @@ -263,7 +259,6 @@ "keyboard_shortcuts.reply": "Responder a un artículu", "keyboard_shortcuts.requests": "Abrir la llista de solicitúes de siguimientu", "keyboard_shortcuts.search": "Enfocar la barra de busca", - "keyboard_shortcuts.spoilers": "to show/hide CW field", "keyboard_shortcuts.start": "Abrir la columna «Entamar»", "keyboard_shortcuts.toggle_sensitivity": "Amosar/anubrir el conteníu multimedia", "keyboard_shortcuts.toot": "Comenzar un artículu nuevu", @@ -299,6 +294,7 @@ "navigation_bar.lists": "Llistes", "navigation_bar.logout": "Zarrar la sesión", "navigation_bar.mutes": "Perfiles colos avisos desactivaos", + "navigation_bar.opened_in_classic_interface": "Los artículos, les cuentes ya otres páxines específiques ábrense por defeutu na interfaz web clásica.", "navigation_bar.pins": "Artículos fixaos", "navigation_bar.preferences": "Preferencies", "navigation_bar.public_timeline": "Llinia de tiempu federada", @@ -334,7 +330,7 @@ "notifications.group": "{count} avisos", "notifications.mark_as_read": "Marcar tolos avisos como lleíos", "notifications.permission_required": "Los avisos d'escritoriu nun tán disponibles porque nun se concedió'l permisu riquíu.", - "onboarding.actions.go_to_explore": "See what's trending", + "onboarding.profile.note_hint": "Pues @mentar a otros perfiles o poner #etiquetes…", "onboarding.start.lead": "Xá yes parte de Mastodon, una plataforma social multimedia descentralizada onde tu ya non un algoritmu, personalices la to esperiencia. Vamos presentate esti llugar social nuevu:", "onboarding.start.skip": "¿Nun precises ayuda pa comenzar?", "onboarding.steps.follow_people.body": "Mastodon trata namás de siguir a cuentes interesantes.", @@ -415,12 +411,16 @@ "search.quick_action.go_to_hashtag": "Dir a la etiqueta {x}", "search.quick_action.status_search": "Artículos que concasen con {x}", "search.search_or_paste": "Busca o apiega una URL", + "search_popout.language_code": "códigu de llingua ISO", "search_popout.quick_actions": "Aiciones rápides", "search_popout.recent": "Busques de recién", + "search_popout.specific_date": "data específica", + "search_popout.user": "perfil", "search_results.accounts": "Perfiles", "search_results.all": "Too", "search_results.hashtags": "Etiquetes", "search_results.nothing_found": "Nun se pudo atopar nada con esos términos de busca", + "search_results.see_all": "Ver too", "search_results.statuses": "Artículos", "search_results.title": "Busca de: {q}", "server_banner.introduction": "{domain} ye parte de la rede social descentralizada que tien la teunoloxía de {mastodon}.", @@ -463,6 +463,7 @@ "status.replied_to": "En rempuesta a {name}", "status.reply": "Responder", "status.replyAll": "Responder al filu", + "status.report": "Informar de @{name}", "status.sensitive_warning": "Conteníu sensible", "status.show_filter_reason": "Amosar de toes toes", "status.show_less": "Amosar menos", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 290b364a52..c763a32ba8 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -150,7 +150,7 @@ "compose_form.poll.duration": "Durada de l'enquesta", "compose_form.poll.option_placeholder": "Opció {number}", "compose_form.poll.remove_option": "Elimina aquesta opció", - "compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre diverses opcions", + "compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre múltiples opcions", "compose_form.poll.switch_to_single": "Canvia l’enquesta per a permetre una única opció", "compose_form.publish": "Tut", "compose_form.publish_form": "Nou tut", @@ -521,7 +521,7 @@ "poll.total_people": "{count, plural, one {# persona} other {# persones}}", "poll.total_votes": "{count, plural, one {# vot} other {# vots}}", "poll.vote": "Vota", - "poll.voted": "Vas votar per aquesta resposta", + "poll.voted": "Vau votar aquesta resposta", "poll.votes": "{votes, plural, one {# vot} other {# vots}}", "poll_button.add_poll": "Afegeix una enquesta", "poll_button.remove_poll": "Elimina l'enquesta", @@ -607,7 +607,7 @@ "search.quick_action.status_search": "Tuts coincidint amb {x}", "search.search_or_paste": "Cerca o escriu l'URL", "search_popout.full_text_search_disabled_message": "No disponible a {domain}.", - "search_popout.full_text_search_logged_out_message": "Només disponible en iniciar la sessió.", + "search_popout.full_text_search_logged_out_message": "Només disponible amb la sessió iniciada.", "search_popout.language_code": "Codi de llengua ISO", "search_popout.options": "Opcions de cerca", "search_popout.quick_actions": "Accions ràpides", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 2608b91563..462352750c 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -489,7 +489,7 @@ "onboarding.profile.lead": "Du kannst das später in den Einstellungen vervollständigen, wo noch mehr Anpassungsmöglichkeiten zur Verfügung stehen.", "onboarding.profile.note": "Über mich", "onboarding.profile.note_hint": "Du kannst andere @Profile erwähnen oder #Hashtags verwenden …", - "onboarding.profile.save_and_continue": "Speichern und fortsetzen", + "onboarding.profile.save_and_continue": "Speichern und fortfahren", "onboarding.profile.title": "Profil einrichten", "onboarding.profile.upload_avatar": "Profilbild hochladen", "onboarding.profile.upload_header": "Titelbild hochladen", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 9259f20154..94d13e8d91 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -393,6 +393,7 @@ "lists.search": "Leita millum fólk, sum tú fylgir", "lists.subheading": "Tínir listar", "load_pending": "{count, plural, one {# nýtt evni} other {# nýggj evni}}", + "loading_indicator.label": "Innlesur…", "media_gallery.toggle_visible": "{number, plural, one {Fjal mynd} other {Fjal myndir}}", "moved_to_account_banner.text": "Konta tín {disabledAccount} er í løtuni óvirkin, tí tú flutti til {movedToAccount}.", "mute_modal.duration": "Tíðarbil", @@ -483,6 +484,15 @@ "onboarding.follows.title": "Vælumtókt á Mastodon", "onboarding.profile.discoverable": "Ger tað møguligt hjá øðrum at finna vangan hjá mær", "onboarding.profile.discoverable_hint": "Tá tú játtar at onnur skulu kunna finna teg á Mastodon, so kann henda, at postar tínir síggjast í leitiúrslitum og rákum, og vangin hjá tær kann vera skotin upp fyri fólki við áhugamálum sum minna um tíni.", + "onboarding.profile.display_name": "Navn, sum skal vísast", + "onboarding.profile.display_name_hint": "Títt fulla navn ella títt stuttliga navn…", + "onboarding.profile.lead": "Tú kanst altíð gera hetta liðugt seinni í stillingunum, har enn fleiri tillagingarmøguleikar eru tøkir.", + "onboarding.profile.note": "Ævilýsing", + "onboarding.profile.note_hint": "Tú kanst @umrøða onnur fólk ella #frámerki…", + "onboarding.profile.save_and_continue": "Goym og halt fram", + "onboarding.profile.title": "Vangauppsetan", + "onboarding.profile.upload_avatar": "Legg vangamynd upp", + "onboarding.profile.upload_header": "Legg vangahøvd upp", "onboarding.share.lead": "Lat fólk vita, hvussu tey kunnu finna teg á Mastodon!", "onboarding.share.message": "Eg eri {username} á #Mastodon! Kom og fylg mær á {url}", "onboarding.share.next_steps": "Møgulig næstu stig:", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 48bbb6b307..e857b69554 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -20,7 +20,7 @@ "account.block_short": "Bloquear", "account.blocked": "Bloqueada", "account.browse_more_on_origin_server": "Busca máis no perfil orixinal", - "account.cancel_follow_request": "Cancelar a solicitude de seguimento", + "account.cancel_follow_request": "Desbotar a solicitude de seguimento", "account.copy": "Copiar ligazón ao perfil", "account.direct": "Mencionar de xeito privado a @{name}", "account.disable_notifications": "Deixar de notificarme cando @{name} publica", @@ -184,7 +184,7 @@ "confirmations.mute.explanation": "Isto agochará as súas publicacións ou as que a mencionen, mais poderá ler as túas publicacións e ser seguidora túa.", "confirmations.mute.message": "Tes a certeza de querer acalar a {name}?", "confirmations.redraft.confirm": "Eliminar e reescribir", - "confirmations.redraft.message": "Tes a certeza de querer eliminar esta publicación e reescribila? Perderás as comparticións e favoritas, e as respostas á publicación orixinal ficarán orfas.", + "confirmations.redraft.message": "Tes a certeza de querer eliminar esta publicación e reescribila? Perderás as promocións e favorecementos, e as respostas á publicación orixinal ficarán orfas.", "confirmations.reply.confirm": "Responder", "confirmations.reply.message": "Ao responder sobrescribirás a mensaxe que estás a compor. Tes a certeza de que queres continuar?", "confirmations.unfollow.confirm": "Deixar de seguir", @@ -204,7 +204,7 @@ "disabled_account_banner.text": "Actualmente a túa conta {disabledAccount} está desactivada.", "dismissable_banner.community_timeline": "Estas son as publicacións máis recentes das persoas que teñen a súa conta en {domain}.", "dismissable_banner.dismiss": "Desbotar", - "dismissable_banner.explore_links": "As persoas deste servidor e da rede descentralizada están a falar destas historias agora mesmo.", + "dismissable_banner.explore_links": "Estas son as novas historias más compartidas hoxe na web social. Aparecen primeiro as novas compartidas por máis persoas diferentes.", "dismissable_banner.explore_statuses": "Estas son as publicacións da web social que hoxe están gañando popularidade. As publicacións con máis promocións e favorecemento teñen puntuación máis alta.", "dismissable_banner.explore_tags": "Estes cancelos están gañando popularidade entre as persoas deste servidor e noutros servidores da rede descentralizada.", "dismissable_banner.public_timeline": "Estas son as publicacións públicas máis recentes das persoas que as usuarias de {domain} están a seguir.", @@ -240,7 +240,7 @@ "empty_column.follow_requests": "Non tes peticións de seguimento. Cando recibas unha, amosarase aquí.", "empty_column.followed_tags": "Aínda non seguiches ningún cancelo. Cando o fagas aparecerán aquí.", "empty_column.hashtag": "Aínda non hai nada con este cancelo.", - "empty_column.home": "A túa cronoloxía inicial está baleira! Segue a outras usuarias para enchela. {suggestions}", + "empty_column.home": "A túa cronoloxía inicial está baleira! Sigue a outras usuarias para enchela.", "empty_column.list": "Aínda non hai nada nesta listaxe. Cando as usuarias incluídas na listaxe publiquen mensaxes, amosaranse aquí.", "empty_column.lists": "Aínda non tes listaxes. Cando crees unha, amosarase aquí.", "empty_column.mutes": "Aínda non silenciaches a ningúnha usuaria.", @@ -346,7 +346,7 @@ "keyboard_shortcuts.down": "Para mover cara abaixo na listaxe", "keyboard_shortcuts.enter": "Para abrir publicación", "keyboard_shortcuts.favourite": "Marcar como favorita", - "keyboard_shortcuts.favourites": "Para abrir a listaxe das favoritas", + "keyboard_shortcuts.favourites": "Para abrir a lista das favoritas", "keyboard_shortcuts.federated": "Para abrir a cronoloxía federada", "keyboard_shortcuts.heading": "Atallos do teclado", "keyboard_shortcuts.home": "Para abrir a cronoloxía inicial", @@ -383,7 +383,7 @@ "lists.delete": "Eliminar listaxe", "lists.edit": "Editar listaxe", "lists.edit.submit": "Mudar o título", - "lists.exclusive": "Agocha estas publicacións no inicio", + "lists.exclusive": "Agocha estas publicacións no Inicio", "lists.new.create": "Engadir listaxe", "lists.new.title_placeholder": "Título da nova listaxe", "lists.replies_policy.followed": "Toda usuaria seguida", @@ -480,8 +480,8 @@ "onboarding.actions.go_to_home": "Vai á cronoloxía de inicio", "onboarding.compose.template": "Ola #Mastodon!", "onboarding.follows.empty": "Desgraciadamente agora mesmo non hai nada que mostrar. Podes intentalo coa busca ou na páxina descubrir para atopar persoas ás que seguir, ou intentalo máis tarde.", - "onboarding.follows.lead": "Podes facer que a túa cronoloxía de inicio sexa como ti a queres. Canta máis xente sigas máis interesante será. Estes perfís poderían axudarche a comezar —sempre poderás deixar de seguilos despois!", - "onboarding.follows.title": "Popular en Mastodon", + "onboarding.follows.lead": "A cronoloxía de Inicio é o principal xeito de desfrutar Mastodon. Cantas máis persoas sigas mais interesante e activa será. Para comezar, aquí tes algunhas suxestións:", + "onboarding.follows.title": "Personaliza a cronoloxía de inicio", "onboarding.profile.discoverable": "Que o meu perfil se poida atopar", "onboarding.profile.discoverable_hint": "Cando elixes que poidan atoparte en Mastodon as túas publicacións aparecerán nos resultados das buscas e nos temas en voga, e o teu perfil podería ser suxerido para seguimento a persoas con intereses semellantes aos teus.", "onboarding.profile.display_name": "Nome público", @@ -494,20 +494,20 @@ "onboarding.profile.upload_avatar": "Subir imaxe do perfil", "onboarding.profile.upload_header": "Subir cabeceira para o perfil", "onboarding.share.lead": "Fai que as persoas saiban como atoparte en Mastodon!", - "onboarding.share.message": "Son {username} en #Mastodon! Ségueme en {url}", + "onboarding.share.message": "Son {username} en #Mastodon! Sígueme en {url}", "onboarding.share.next_steps": "Seguintes pasos:", "onboarding.share.title": "Comparte o teu perfil", - "onboarding.start.lead": "A túa nova conta en Mastodon está preparada. Mira de que xeito lle podes sacar proveito:", + "onboarding.start.lead": "Xa formas parte de Mastodon, unha plataforma de relacións sociais descentralizada, única, onde ti —e non un algoritmo— elixes o que les. Axudámosche cos primeiros pasos:", "onboarding.start.skip": "Queres omitir todo isto?", "onboarding.start.title": "Pois xa está!", - "onboarding.steps.follow_people.body": "Constrúes a túa cronoloxía. Énchea con persoas interesantes.", - "onboarding.steps.follow_people.title": "Segue a {count, plural, one {unha persoa} other {# persoas}}", - "onboarding.steps.publish_status.body": "Saúda a todo o mundo.", + "onboarding.steps.follow_people.body": "Mastodon consiste en seguir a persoas interesantes.", + "onboarding.steps.follow_people.title": "Personaliza a túa cronoloxía", + "onboarding.steps.publish_status.body": "Exprésate con texto, fotos, vídeos ou enquisas {emoji}", "onboarding.steps.publish_status.title": "Escribe a túa primeira publicación", "onboarding.steps.setup_profile.body": "Ao engadir información ao teu perfil é máis probable que teñas máis interaccións.", "onboarding.steps.setup_profile.title": "Personaliza o perfil", "onboarding.steps.share_profile.body": "Dille ás amizades como poden atoparte en Mastodon!", - "onboarding.steps.share_profile.title": "Comparte o teu perfil", + "onboarding.steps.share_profile.title": "Comparte o teu perfil en Mastodon", "onboarding.tips.2fa": "Sabes que? Podes protexer a túa conta configurando un segundo factor de autenticación nos axustes. Funciona con calquera app TOTP, non precisas un número de teléfono!", "onboarding.tips.accounts_from_other_servers": "Sabes que? Como Mastodon é descentralizado, algúns perfís que atopes estarán en servidores diferentes ao teu. Pero podes interactuar igualmente con eles! O seu servidor é o que ven despois da @ no seu identificador!", "onboarding.tips.migration": "Sabes que? Se cres que {domain} non é o servidor axeitado para ti, podes mover a conta a outro servidor Mastodon sen perder as túas seguidoras. Incluso podes hospedar o teu propio servidor!", @@ -573,7 +573,7 @@ "report.reasons.dislike": "Non me gusta", "report.reasons.dislike_description": "Non é algo que queiras ver", "report.reasons.legal": "É ilegal", - "report.reasons.legal_description": "Cres que atenta contra as leis do país do teu servidor", + "report.reasons.legal_description": "Cres que atenta contra as leis do país do teu pais ou servidor", "report.reasons.other": "É outra cousa", "report.reasons.other_description": "O problema non cae dentro de outras categorías", "report.reasons.spam": "É spam", diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index 76d7c1d381..412159bef9 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -386,6 +386,7 @@ "navigation_bar.security": "सुरक्षा", "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", "notification.reblog": "{name} boosted your status", + "notification.status": "{name} ने अभी पोस्ट किया", "notifications.clear": "सूचनाएं हटाए", "notifications.column_settings.admin.report": "नई रिपोर्ट:", "notifications.column_settings.favourite": "पसंदीदा:", @@ -399,9 +400,12 @@ "notifications.column_settings.show": "कॉलम में दिखाएँ", "notifications.column_settings.sound": "ध्वनि चलाएँ", "notifications.column_settings.status": "New toots:", + "notifications.column_settings.unread_notifications.category": "अपठित सूचनाएं", + "notifications.column_settings.unread_notifications.highlight": "अपठित सूचनाओं को हाइलाइट करें", "notifications.column_settings.update": "संपादन:", "notifications.filter.all": "सभी", "notifications.filter.boosts": "बूस्ट", + "notifications.filter.favourites": "पसंदीदा", "notifications.filter.follows": "फॉलो", "notifications.filter.mentions": "उल्लेख", "notifications.filter.polls": "चुनाव परिणाम", @@ -416,6 +420,7 @@ "onboarding.follows.title": "Popular on Mastodon", "onboarding.profile.discoverable": "अपना प्रोफाइल खोजने योग्य बनाएं", "onboarding.profile.discoverable_hint": "जब आप मॅस्टोडॉन पर डिस्कवरेबिलिटी चुनते हैं तो आपके पोस्ट ट्रेंडिंग और सर्च में दिख सकते हैं और आपका प्रोफाइल आपके ही जैसे अकाउंट्स को सुझाया जा सकता है।", + "onboarding.profile.display_name": "प्रदर्शित नाम", "onboarding.share.message": "मैं {username} मॅस्टोडॉन पर हूं! मुझे यहां {url} फॉलो करें", "onboarding.share.next_steps": "आगे कि संभवित विधि", "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", @@ -445,6 +450,9 @@ "recommended": "अनुशंसित", "refresh": "रीफ्रेश करें", "regeneration_indicator.label": "लोड हो रहा है...", + "relative_time.days": "{number}दिन", + "relative_time.full.days": "{number, plural, one {# दिन} other {# दिन}} पहले", + "relative_time.full.hours": "{number, plural,one {# घंटा} other {# घंटे}} पहले", "relative_time.full.just_now": "अभी-अभी", "relative_time.full.minutes": "{number, plural, one {# मिनट} other {# मिनट}} पहले", "relative_time.full.seconds": "{number, plural, one {# सेकंड} other {# सेकंड}} पहले", @@ -454,15 +462,23 @@ "relative_time.seconds": "{number} सेकंड", "relative_time.today": "आज", "reply_indicator.cancel": "रद्द करें", + "report.block": "ब्लॉक", "report.block_explanation": "आपको उनकी पोस्टें नहीं दिखेंगे। वे आपकी पोस्टें को देख नहीं पाएंगे और आपको फ़ॉलो नहीं कर पाएंगे। उन्हे पता लगेगा कि वे blocked हैं।", "report.categories.other": "अन्य", "report.categories.spam": "अवांछित", "report.category.title_account": "रूपरेखा", "report.close": "स्वीकार करें", "report.comment.title": "क्या और कुछ है जिसके बारे में आपको लगता है कि हमें सूचित होना चाहिए?", + "report.next": "आगे", "report.placeholder": "Type or paste additional comments", "report.reasons.dislike": "मुझे यह पसंद नहीं है", + "report.reasons.legal": "यह अवैध है", + "report.reasons.legal_description": "आप मानते हैं कि यह आपके या सर्वर के देश के कानून का उल्लंघन करता है", "report.reasons.other": "कुछ और है।", + "report.reasons.violation": "यह सर्वर नियमों का उल्लंघन करता है", + "report.rules.title": "किन नियमों का उल्लंघन हो रहा है?", + "report.statuses.subtitle": "लागू होने वाले सभी का चयन करें", + "report.statuses.title": "क्या ऐसे कोई पोस्ट हैं जो इस रिपोर्ट का समर्थन करते हों?", "report.submit": "सबमिट करें", "report.target": "Report {target}", "report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached", @@ -480,11 +496,24 @@ "search_results.statuses": "Toots", "sign_in_banner.sign_in": "Sign in", "status.admin_status": "Open this status in the moderation interface", + "status.block": "@{name} को ब्लॉक करें", + "status.bookmark": "बुकमार्क", + "status.cannot_reblog": "रिपोस्ट को बूस्ट नहीं किया जा सकता", "status.copy": "Copy link to status", + "status.delete": "हटाएं", + "status.detailed_status": "विस्तृत वार्ता दृश्य", "status.direct": "निजी संदेश @{name} से", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", + "status.media.open": "खोलने के लिए क्लिक करें", + "status.media.show": "दिखाने के लिए क्लिक करें", + "status.mention": "@{name} का उल्लेख करें", + "status.more": "अतिरिक्त", + "status.mute": "@{name} म्यूट करें", + "status.mute_conversation": "इस वार्तालाप को म्यूट करें", "status.open": "Expand this status", + "status.pin": "प्रोफ़ाइल पर पिन करें", "status.pinned": "Pinned toot", + "status.read_more": "और पढ़ें", "status.reblog": "बूस्ट", "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.", "status.replied_to": "{name} का उत्तर दें", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index e790306e9c..a660eb77ff 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -9,14 +9,20 @@ "account.block": "Blocar @{name}", "account.block_short": "Blocar", "account.blocked": "Blocate", + "account.browse_more_on_origin_server": "Navigar plus sur le profilo original", "account.copy": "Copiar ligamine a profilo", + "account.domain_blocked": "Dominio blocate", "account.edit_profile": "Modificar profilo", + "account.enable_notifications": "Notifica me quando @{name} publica", "account.endorse": "Evidentiar sur le profilo", "account.featured_tags.last_status_at": "Ultime message in {date}", "account.featured_tags.last_status_never": "Necun messages", + "account.featured_tags.title": "Hashtags eminente de {name}", "account.follow": "Sequer", "account.follow_back": "Sequer etiam", "account.followers": "Sequitores", + "account.followers.empty": "Iste usator ancora non ha sequitores.", + "account.followers_counter": "{count, plural, one {{counter} sequitor} other {{counter} sequitores}}", "account.following": "Sequente", "account.go_to_profile": "Vader al profilo", "account.hide_reblogs": "Celar boosts de @{name}", @@ -30,6 +36,7 @@ "account.mute_short": "Silentiar", "account.muted": "Silentiate", "account.no_bio": "Nulle description fornite.", + "account.open_original_page": "Aperir le pagina original", "account.posts": "Messages", "account.posts_with_replies": "Messages e responsas", "account.share": "Compartir profilo de @{name}", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 264781baa3..70ce6611d6 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -683,7 +683,7 @@ "status.show_more": "펼치기", "status.show_more_all": "모두 펼치기", "status.show_original": "원본 보기", - "status.title.with_attachments": "{user} 님이 {attachmentCount, plural, one {첨부} other {{attachmentCount}개 첨부}}하여 게시", + "status.title.with_attachments": "{user} 님이 {attachmentCount, plural, one {첨부파일} other {{attachmentCount}개의 첨부파일}}과 함께 게시함", "status.translate": "번역", "status.translated_from_with": "{provider}에 의해 {lang}에서 번역됨", "status.uncached_media_warning": "마리보기 허용되지 않음", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index 2a911483de..8fde687427 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -328,6 +328,7 @@ "interaction_modal.on_another_server": "En otro sirvidor", "interaction_modal.on_this_server": "En este sirvidor", "interaction_modal.sign_in": "No estas konektado kon este sirvidor. Ande tyenes tu kuento?", + "interaction_modal.sign_in_hint": "Konsejo: Akel es el sitio adonde te enrejistrates. Si no lo akodras, bushka el mesaj de posta elektronika de bienvenida en tu kuti de arivo. Tambien puedes eskrivir tu nombre de utilizador kompleto (por enshemplo @Mastodon@mastodon.social)", "interaction_modal.title.favourite": "Endika ke te plaze publikasyon de {name}", "interaction_modal.title.follow": "Sige a {name}", "interaction_modal.title.reblog": "Repartaja publikasyon de {name}", @@ -478,6 +479,7 @@ "onboarding.actions.go_to_explore": "Va a los trendes", "onboarding.actions.go_to_home": "Va a tu linya prinsipala", "onboarding.compose.template": "Ke haber, #Mastodon?", + "onboarding.follows.empty": "Malorozamente, no se pueden amostrar rezultados en este momento. Puedes aprovar uzar la bushkeda o navigar por la pajina de eksplorasyon para topar personas a las que segir, o aprovarlo de muevo mas tadre.", "onboarding.follows.title": "Personaliza tu linya prinsipala", "onboarding.profile.discoverable": "Faz ke mi profil apareska en bushkedas", "onboarding.profile.display_name": "Nombre amostrado", @@ -497,7 +499,9 @@ "onboarding.start.title": "Lo logrates!", "onboarding.steps.follow_people.body": "El buto de Mastodon es segir a djente interesante.", "onboarding.steps.follow_people.title": "Personaliza tu linya prinsipala", + "onboarding.steps.publish_status.body": "Puedes introdusirte al mundo con teksto, fotos, videos o anketas {emoji}", "onboarding.steps.publish_status.title": "Eskrive tu primera publikasyon", + "onboarding.steps.setup_profile.body": "Kompleta tu profil para aumentar tus enteraksyones.", "onboarding.steps.setup_profile.title": "Personaliza tu profil", "onboarding.steps.share_profile.body": "Informe a tus amigos komo toparte en Mastodon", "onboarding.steps.share_profile.title": "Partaja tu profil de Mastodon", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index ec0d30363c..623ff2248f 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -9,7 +9,7 @@ "about.domain_blocks.suspended.explanation": "Jokie duomenys iš šio serverio nebus apdorojami, saugomi ar keičiami, todėl bet kokia sąveika ar bendravimas su šio serverio naudotojais bus neįmanomas.", "about.domain_blocks.suspended.title": "Uždrausta", "about.not_available": "Ši informacija nebuvo pateikta šiame serveryje.", - "about.powered_by": "Decentralizuota socialinė žiniasklaida, kurią valdo {mastodon}", + "about.powered_by": "Decentralizuota socialinė medija, kurią valdo {mastodon}", "about.rules": "Serverio taisyklės", "account.account_note_header": "Pastaba", "account.add_or_remove_from_list": "Pridėti arba ištrinti iš sąrašų", @@ -27,24 +27,24 @@ "account.domain_blocked": "Užblokuotas domenas", "account.edit_profile": "Redaguoti profilį", "account.enable_notifications": "Pranešti man, kai @{name} paskelbia", - "account.endorse": "Savybė profilyje", + "account.endorse": "Rekomenduoti profilyje", "account.featured_tags.last_status_at": "Paskutinį kartą paskelbta {date}", "account.featured_tags.last_status_never": "Nėra įrašų", - "account.featured_tags.title": "{name} rekomenduojamos grotažymės", + "account.featured_tags.title": "{name} rekomenduojami saitažodžiai", "account.follow": "Sekti", "account.follow_back": "Sekti atgal", "account.followers": "Sekėjai", "account.followers.empty": "Šio naudotojo dar niekas neseka.", "account.followers_counter": "{count, plural, one {{counter} sekėjas} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}", - "account.following": "Seka", - "account.following_counter": "{count, plural, one {{counter} Seka} few {{counter} Seka} many {{counter} Seka} other {{counter} Seka}}", + "account.following": "Sekama", + "account.following_counter": "{count, plural, one {{counter} sekimas} few {{counter} sekimai} many {{counter} sekimo} other {{counter} sekimų}}", "account.follows.empty": "Šis (-i) naudotojas (-a) dar nieko neseka.", "account.go_to_profile": "Eiti į profilį", "account.hide_reblogs": "Slėpti pakėlimus iš @{name}", "account.in_memoriam": "Atminimui.", "account.joined_short": "Prisijungė", "account.languages": "Keisti prenumeruojamas kalbas", - "account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}", + "account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}.", "account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.", "account.media": "Medija", "account.mention": "Paminėti @{name}", @@ -58,8 +58,8 @@ "account.open_original_page": "Atidaryti originalinį puslapį", "account.posts": "Įrašai", "account.posts_with_replies": "Įrašai ir atsakymai", - "account.report": "Pranešti @{name}", - "account.requested": "Laukiama patvirtinimo. Spausk, kad atšaukti sekimo užklausą", + "account.report": "Pranešti apie @{name}", + "account.requested": "Laukiama patvirtinimo. Spustelėk, jei nori atšaukti sekimo prašymą.", "account.requested_follow": "{name} paprašė tave sekti", "account.share": "Bendrinti @{name} profilį", "account.show_reblogs": "Rodyti pakėlimus iš @{name}", @@ -69,28 +69,28 @@ "account.unblock_short": "Atblokuoti", "account.unendorse": "Nerodyti profilyje", "account.unfollow": "Nebesekti", - "account.unmute": "Atitildyti @{name}", - "account.unmute_notifications_short": "Atitildyti pranešimus", - "account.unmute_short": "Atitildyti", - "account_note.placeholder": "Spausk norėdamas (-a) pridėti pastabą", - "admin.dashboard.daily_retention": "Vartotojų išbuvimo rodiklis pagal dieną po registracijos", - "admin.dashboard.monthly_retention": "Naudotojų išlaikymo rodiklis pagal mėnesį po registracijos", + "account.unmute": "Atšaukti nutildymą @{name}", + "account.unmute_notifications_short": "Atšaukti nutildymą pranešimams", + "account.unmute_short": "Atšaukti nutildymą", + "account_note.placeholder": "Spustelėk norėdamas (-a) pridėti pastabą", + "admin.dashboard.daily_retention": "Naudotojų pasilikimo rodiklis pagal dieną po registracijos", + "admin.dashboard.monthly_retention": "Naudotojų pasilikimo rodiklis pagal mėnesį po registracijos", "admin.dashboard.retention.average": "Vidurkis", - "admin.dashboard.retention.cohort": "Registravimo mėnuo", + "admin.dashboard.retention.cohort": "Registracijos mėnuo", "admin.dashboard.retention.cohort_size": "Nauji naudotojai", "admin.impact_report.instance_accounts": "Paskyrų profiliai, kuriuos tai ištrintų", "admin.impact_report.instance_followers": "Sekėjai, kuriuos prarastų mūsų naudotojai", "admin.impact_report.instance_follows": "Sekėjai, kuriuos prarastų jų naudotojai", "admin.impact_report.title": "Poveikio apibendrinimas", "alert.rate_limited.message": "Pabandyk vėliau po {retry_time, time, medium}.", - "alert.rate_limited.title": "Spartos ribojimas", + "alert.rate_limited.title": "Sparta ribota", "alert.unexpected.message": "Įvyko netikėta klaida.", "alert.unexpected.title": "Ups!", "announcement.announcement": "Skelbimas", "attachments_list.unprocessed": "(neapdorotas)", "audio.hide": "Slėpti garsą", "autosuggest_hashtag.per_week": "{count} per savaitę", - "boost_modal.combo": "Gali spausti {combo}, kad praleisti kitą kartą", + "boost_modal.combo": "Gali paspausti {combo}, kad praleisti kitą kartą", "bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitą", "bundle_column_error.error.body": "Užklausos puslapio nepavyko atvaizduoti. Tai gali būti dėl mūsų kodo klaidos arba naršyklės suderinamumo problemos.", "bundle_column_error.error.title": "O, ne!", @@ -104,10 +104,10 @@ "bundle_modal_error.message": "Kraunant šį komponentą kažkas nepavyko.", "bundle_modal_error.retry": "Bandyti dar kartą", "closed_registrations.other_server_instructions": "Kadangi Mastodon yra decentralizuotas, gali susikurti paskyrą kitame serveryje ir vis tiek bendrauti su šiuo serveriu.", - "closed_registrations_modal.description": "Sukurti paskyrą {domain} šiuo metu neįmanoma, tačiau nepamiršk, kad norint naudotis Mastodon nebūtina turėti paskyrą {domain}.", + "closed_registrations_modal.description": "Sukurti paskyrą {domain} šiuo metu neįmanoma, tačiau nepamiršk, kad norint naudotis Mastodon nebūtina turėti paskyrą domene {domain}.", "closed_registrations_modal.find_another_server": "Rasti kitą serverį", "closed_registrations_modal.preamble": "Mastodon yra decentralizuotas, todėl nesvarbu, kur susikursi paskyrą, galėsi sekti ir bendrauti su bet kuriuo šiame serveryje esančiu asmeniu. Jį gali net savarankiškai talpinti!", - "closed_registrations_modal.title": "Užsiregistravimas į Mastodon", + "closed_registrations_modal.title": "Užsiregistruoti Mastodon", "column.about": "Apie", "column.blocks": "Užblokuoti naudotojai", "column.bookmarks": "Žymės", @@ -116,15 +116,15 @@ "column.directory": "Naršyti profilius", "column.domain_blocks": "Užblokuoti domenai", "column.favourites": "Mėgstamiausi", - "column.firehose": "Tiesioginiai padavimai", - "column.follow_requests": "Sekti prašymus", + "column.firehose": "Tiesioginiai srautai", + "column.follow_requests": "Sekimo prašymus", "column.home": "Pagrindinis", "column.lists": "Sąrašai", "column.mutes": "Nutildyti naudotojai", "column.notifications": "Pranešimai", "column.pins": "Prisegti įrašai", "column.public": "Federacinė laiko skalė", - "column_back_button.label": "Atgal", + "column_back_button.label": "Grįžti", "column_header.hide_settings": "Slėpti nustatymus", "column_header.moveLeft_settings": "Judinti stulpelį į kairę", "column_header.moveRight_settings": "Judinti stulpelį į dešinę", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 833bfe6ace..1ecfbcaf06 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -18,6 +18,7 @@ "account.blocked": "Blocat", "account.browse_more_on_origin_server": "Navigar sul perfil original", "account.cancel_follow_request": "Retirar la demanda d’abonament", + "account.copy": "Copiar lo ligam del perfil", "account.direct": "Mencionar @{name} en privat", "account.disable_notifications": "Quitar de m’avisar quand @{name} publica quicòm", "account.domain_blocked": "Domeni amagat", @@ -28,6 +29,7 @@ "account.featured_tags.last_status_never": "Cap de publicacion", "account.featured_tags.title": "Etiquetas en avant de {name}", "account.follow": "Sègre", + "account.follow_back": "Sègre en retorn", "account.followers": "Seguidors", "account.followers.empty": "Degun sèc pas aqueste utilizaire pel moment.", "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidors}}", @@ -48,6 +50,7 @@ "account.mute_notifications_short": "Amudir las notificacions", "account.mute_short": "Amudir", "account.muted": "Mes en silenci", + "account.mutual": "Mutual", "account.no_bio": "Cap de descripcion pas fornida.", "account.open_original_page": "Dobrir la pagina d’origina", "account.posts": "Tuts", @@ -172,6 +175,7 @@ "conversation.mark_as_read": "Marcar coma legida", "conversation.open": "Veire la conversacion", "conversation.with": "Amb {names}", + "copy_icon_button.copied": "Copiat al quichapapièr", "copypaste.copied": "Copiat", "copypaste.copy_to_clipboard": "Copiar al quichapapièr", "directory.federated": "Del fediverse conegut", @@ -294,6 +298,8 @@ "keyboard_shortcuts.direct": "to open direct messages column", "keyboard_shortcuts.down": "far davalar dins la lista", "keyboard_shortcuts.enter": "dobrir los estatuts", + "keyboard_shortcuts.favourite": "Marcar coma favorit", + "keyboard_shortcuts.favourites": "Dobrir la lista dels favorits", "keyboard_shortcuts.federated": "dobrir lo flux public global", "keyboard_shortcuts.heading": "Acorchis clavièr", "keyboard_shortcuts.home": "dobrir lo flux public local", @@ -339,6 +345,7 @@ "lists.search": "Cercar demest lo mond que seguètz", "lists.subheading": "Vòstras listas", "load_pending": "{count, plural, one {# nòu element} other {# nòu elements}}", + "loading_indicator.label": "Cargament…", "media_gallery.toggle_visible": "Modificar la visibilitat", "mute_modal.duration": "Durada", "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", @@ -371,6 +378,7 @@ "not_signed_in_indicator.not_signed_in": "Devètz vos connectar per accedir a aquesta ressorsa.", "notification.admin.report": "{name} senhalèt {target}", "notification.admin.sign_up": "{name} se marquèt", + "notification.favourite": "{name} a mes vòstre estatut en favorit", "notification.follow": "{name} vos sèc", "notification.follow_request": "{name} a demandat a vos sègre", "notification.mention": "{name} vos a mencionat", @@ -423,6 +431,8 @@ "onboarding.compose.template": "Adiu #Mastodon !", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.title": "Popular on Mastodon", + "onboarding.profile.display_name": "Nom d’afichatge", + "onboarding.profile.note": "Biografia", "onboarding.share.title": "Partejar vòstre perfil", "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", "onboarding.start.skip": "Want to skip right ahead?", @@ -504,6 +514,7 @@ "report_notification.categories.spam": "Messatge indesirable", "report_notification.categories.violation": "Violacion de las règlas", "report_notification.open": "Dobrir lo senhalament", + "search.no_recent_searches": "Cap de recèrcas recentas", "search.placeholder": "Recercar", "search.search_or_paste": "Recercar o picar una URL", "search_popout.language_code": "Còdi ISO de lenga", @@ -536,6 +547,7 @@ "status.copy": "Copiar lo ligam de l’estatut", "status.delete": "Escafar", "status.detailed_status": "Vista detalhada de la convèrsa", + "status.direct": "Mencionar @{name} en privat", "status.direct_indicator": "Mencion privada", "status.edit": "Modificar", "status.edited": "Modificat {date}", @@ -626,6 +638,7 @@ "upload_modal.preview_label": "Apercebut ({ratio})", "upload_progress.label": "Mandadís…", "upload_progress.processing": "Tractament…", + "username.taken": "Aqueste nom d’utilizaire es pres. Ensajatz-ne un autre", "video.close": "Tampar la vidèo", "video.download": "Telecargar lo fichièr", "video.exit_fullscreen": "Sortir plen ecran", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 482cc8ee73..b8e18e1229 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -32,6 +32,7 @@ "account.featured_tags.last_status_never": "Sem publicações", "account.featured_tags.title": "Hashtags em destaque de {name}", "account.follow": "Seguir", + "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", "account.followers.empty": "Nada aqui.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", @@ -52,6 +53,7 @@ "account.mute_notifications_short": "Silenciar notificações", "account.mute_short": "Silenciar", "account.muted": "Silenciado", + "account.mutual": "Mútuo", "account.no_bio": "Nenhuma descrição fornecida.", "account.open_original_page": "Abrir a página original", "account.posts": "Toots", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index b108e581a4..65f27ef061 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -314,7 +314,7 @@ "home.explore_prompt.body": "ฟีดหน้าแรกของคุณจะมีการผสมผสานของโพสต์จากแฮชแท็กที่คุณได้เลือกติดตาม, ผู้คนที่คุณได้เลือกติดตาม และโพสต์ที่เขาดัน หากนั่นรู้สึกเงียบเกินไป คุณอาจต้องการ:", "home.explore_prompt.title": "นี่คือฐานหน้าแรกของคุณภายใน Mastodon", "home.hide_announcements": "ซ่อนประกาศ", - "home.pending_critical_update.body": "โปรดอัปเดตเซิร์ฟเวอร์ Mastodon ของคุณโดยเร็วที่สุดเท่าที่จะทำได้!", + "home.pending_critical_update.body": "โปรดอัปเดตเซิร์ฟเวอร์ Mastodon ของคุณโดยเร็วที่สุดเท่าที่จะเป็นไปได้!", "home.pending_critical_update.link": "ดูการอัปเดต", "home.pending_critical_update.title": "มีการอัปเดตความปลอดภัยสำคัญพร้อมใช้งาน!", "home.show_announcements": "แสดงประกาศ", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 9de043bb20..c623caa3fb 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -358,7 +358,7 @@ "keyboard_shortcuts.my_profile": "mở hồ sơ của bạn", "keyboard_shortcuts.notifications": "mở thông báo", "keyboard_shortcuts.open_media": "mở ảnh hoặc video", - "keyboard_shortcuts.pinned": "mở những tút đã ghim", + "keyboard_shortcuts.pinned": "Open pinned posts list", "keyboard_shortcuts.profile": "mở trang của người đăng tút", "keyboard_shortcuts.reply": "trả lời", "keyboard_shortcuts.requests": "mở danh sách yêu cầu theo dõi", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 9983936953..cc8b583120 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -48,7 +48,7 @@ "account.locked_info": "此帳號的隱私狀態設定為鎖定。該擁有者會手動審核能跟隨此帳號的人。", "account.media": "媒體", "account.mention": "提及 @{name}", - "account.moved_to": "{name} 現在的新帳號為:", + "account.moved_to": "{name} 目前的新帳號為:", "account.mute": "靜音 @{name}", "account.mute_notifications_short": "靜音推播通知", "account.mute_short": "靜音", @@ -59,7 +59,7 @@ "account.posts": "嘟文", "account.posts_with_replies": "嘟文與回覆", "account.report": "檢舉 @{name}", - "account.requested": "正在等待核准。按一下以取消跟隨請求", + "account.requested": "正在等候審核。按一下以取消跟隨請求", "account.requested_follow": "{name} 要求跟隨您", "account.share": "分享 @{name} 的個人檔案", "account.show_reblogs": "顯示來自 @{name} 的嘟文", @@ -84,7 +84,7 @@ "admin.impact_report.title": "影響總結", "alert.rate_limited.message": "請於 {retry_time, time, medium} 後重試。", "alert.rate_limited.title": "已限速", - "alert.unexpected.message": "發生了非預期的錯誤。", + "alert.unexpected.message": "發生非預期的錯誤。", "alert.unexpected.title": "哎呀!", "announcement.announcement": "公告", "attachments_list.unprocessed": "(未經處理)", @@ -241,7 +241,7 @@ "empty_column.followed_tags": "您還沒有跟隨任何主題標籤。當您跟隨主題標籤時,它們將於此顯示。", "empty_column.hashtag": "這個主題標籤下什麼也沒有。", "empty_column.home": "您的首頁時間軸是空的!跟隨更多人來將它填滿吧!", - "empty_column.list": "這份列表下什麼也沒有。當此列表的成員嘟出了新的嘟文時,它們將顯示於此。", + "empty_column.list": "這份列表下什麼也沒有。當此列表的成員嘟出新的嘟文時,它們將顯示於此。", "empty_column.lists": "您還沒有建立任何列表。當您建立列表時,它將於此顯示。", "empty_column.mutes": "您尚未靜音任何使用者。", "empty_column.notifications": "您還沒有收到任何通知,當您與別人開始互動時,它將於此顯示。", @@ -303,8 +303,8 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} 名} other {{counter} 名}}參與者", "hashtag.counter_by_uses": "{count, plural, one {{counter} 則} other {{counter} 則}}嘟文", "hashtag.counter_by_uses_today": "本日有 {count, plural, one {{counter} 則} other {{counter} 則}}嘟文", - "hashtag.follow": "追蹤主題標籤", - "hashtag.unfollow": "取消追蹤主題標籤", + "hashtag.follow": "跟隨主題標籤", + "hashtag.unfollow": "取消跟隨主題標籤", "hashtags.and_other": "…及其他 {count, plural, other {# 個}}", "home.actions.go_to_explore": "看看發生什麼新鮮事", "home.actions.go_to_suggestions": "尋找一些人來跟隨", diff --git a/app/javascript/mastodon/reducers/accounts.ts b/app/javascript/mastodon/reducers/accounts.ts index e6f07340c0..5a9cc7220c 100644 --- a/app/javascript/mastodon/reducers/accounts.ts +++ b/app/javascript/mastodon/reducers/accounts.ts @@ -59,25 +59,19 @@ export const accountsReducer: Reducer = ( return normalizeAccounts(state, action.payload.accounts); else if (followAccountSuccess.match(action)) { return state - .update( - action.payload.relationship.id, - (account) => account?.update('followers_count', (n) => n + 1), + .update(action.payload.relationship.id, (account) => + account?.update('followers_count', (n) => n + 1), ) - .update( - getCurrentUser(), - (account) => account?.update('following_count', (n) => n + 1), + .update(getCurrentUser(), (account) => + account?.update('following_count', (n) => n + 1), ); } else if (unfollowAccountSuccess.match(action)) return state - .update( - action.payload.relationship.id, - (account) => - account?.update('followers_count', (n) => Math.max(0, n - 1)), + .update(action.payload.relationship.id, (account) => + account?.update('followers_count', (n) => Math.max(0, n - 1)), ) - .update( - getCurrentUser(), - (account) => - account?.update('following_count', (n) => Math.max(0, n - 1)), + .update(getCurrentUser(), (account) => + account?.update('following_count', (n) => Math.max(0, n - 1)), ); else return state; }; diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index 46a10b8b47..4859b82651 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -1,12 +1,11 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import type { TypedUseSelectorHook } from 'react-redux'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useDispatch, useSelector } from 'react-redux'; import type { AppDispatch, RootState } from './store'; -export const useAppDispatch: () => AppDispatch = useDispatch; -export const useAppSelector: TypedUseSelectorHook = useSelector; +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); export const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: RootState; diff --git a/app/javascript/material-icons/400-24px/account_circle-fill.svg b/app/javascript/material-icons/400-24px/account_circle-fill.svg new file mode 100644 index 0000000000..1bf9d57a31 --- /dev/null +++ b/app/javascript/material-icons/400-24px/account_circle-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/account_circle.svg b/app/javascript/material-icons/400-24px/account_circle.svg new file mode 100644 index 0000000000..ce59194be0 --- /dev/null +++ b/app/javascript/material-icons/400-24px/account_circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/add-fill.svg b/app/javascript/material-icons/400-24px/add-fill.svg new file mode 100644 index 0000000000..f8bc9309ce --- /dev/null +++ b/app/javascript/material-icons/400-24px/add-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/add.svg b/app/javascript/material-icons/400-24px/add.svg new file mode 100644 index 0000000000..f8bc9309ce --- /dev/null +++ b/app/javascript/material-icons/400-24px/add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/add_photo_alternate-fill.svg b/app/javascript/material-icons/400-24px/add_photo_alternate-fill.svg new file mode 100644 index 0000000000..deb3f8e0d9 --- /dev/null +++ b/app/javascript/material-icons/400-24px/add_photo_alternate-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/add_photo_alternate.svg b/app/javascript/material-icons/400-24px/add_photo_alternate.svg new file mode 100644 index 0000000000..0ae8ad841c --- /dev/null +++ b/app/javascript/material-icons/400-24px/add_photo_alternate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/alternate_email-fill.svg b/app/javascript/material-icons/400-24px/alternate_email-fill.svg new file mode 100644 index 0000000000..7648cf9755 --- /dev/null +++ b/app/javascript/material-icons/400-24px/alternate_email-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/alternate_email.svg b/app/javascript/material-icons/400-24px/alternate_email.svg new file mode 100644 index 0000000000..7648cf9755 --- /dev/null +++ b/app/javascript/material-icons/400-24px/alternate_email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/arrow_back-fill.svg b/app/javascript/material-icons/400-24px/arrow_back-fill.svg new file mode 100644 index 0000000000..cba0c8b2a8 --- /dev/null +++ b/app/javascript/material-icons/400-24px/arrow_back-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/arrow_back.svg b/app/javascript/material-icons/400-24px/arrow_back.svg new file mode 100644 index 0000000000..cba0c8b2a8 --- /dev/null +++ b/app/javascript/material-icons/400-24px/arrow_back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/arrow_drop_down-fill.svg b/app/javascript/material-icons/400-24px/arrow_drop_down-fill.svg new file mode 100644 index 0000000000..48c72546df --- /dev/null +++ b/app/javascript/material-icons/400-24px/arrow_drop_down-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/arrow_drop_down.svg b/app/javascript/material-icons/400-24px/arrow_drop_down.svg new file mode 100644 index 0000000000..48c72546df --- /dev/null +++ b/app/javascript/material-icons/400-24px/arrow_drop_down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/arrow_right_alt-fill.svg b/app/javascript/material-icons/400-24px/arrow_right_alt-fill.svg new file mode 100644 index 0000000000..4bf73bb6da --- /dev/null +++ b/app/javascript/material-icons/400-24px/arrow_right_alt-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/arrow_right_alt.svg b/app/javascript/material-icons/400-24px/arrow_right_alt.svg new file mode 100644 index 0000000000..4bf73bb6da --- /dev/null +++ b/app/javascript/material-icons/400-24px/arrow_right_alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/attach_file-fill.svg b/app/javascript/material-icons/400-24px/attach_file-fill.svg new file mode 100644 index 0000000000..e719e8fd06 --- /dev/null +++ b/app/javascript/material-icons/400-24px/attach_file-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/attach_file.svg b/app/javascript/material-icons/400-24px/attach_file.svg new file mode 100644 index 0000000000..e719e8fd06 --- /dev/null +++ b/app/javascript/material-icons/400-24px/attach_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/block-fill.svg b/app/javascript/material-icons/400-24px/block-fill.svg new file mode 100644 index 0000000000..20e9889ae8 --- /dev/null +++ b/app/javascript/material-icons/400-24px/block-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/block.svg b/app/javascript/material-icons/400-24px/block.svg new file mode 100644 index 0000000000..20e9889ae8 --- /dev/null +++ b/app/javascript/material-icons/400-24px/block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/bookmark-fill.svg b/app/javascript/material-icons/400-24px/bookmark-fill.svg new file mode 100644 index 0000000000..3a7b4d2e8b --- /dev/null +++ b/app/javascript/material-icons/400-24px/bookmark-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/bookmark.svg b/app/javascript/material-icons/400-24px/bookmark.svg new file mode 100644 index 0000000000..a8226a6d87 --- /dev/null +++ b/app/javascript/material-icons/400-24px/bookmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/bookmarks-fill.svg b/app/javascript/material-icons/400-24px/bookmarks-fill.svg new file mode 100644 index 0000000000..f5231f925a --- /dev/null +++ b/app/javascript/material-icons/400-24px/bookmarks-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/bookmarks.svg b/app/javascript/material-icons/400-24px/bookmarks.svg new file mode 100644 index 0000000000..67dffd6857 --- /dev/null +++ b/app/javascript/material-icons/400-24px/bookmarks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/brush-fill.svg b/app/javascript/material-icons/400-24px/brush-fill.svg new file mode 100644 index 0000000000..f92d77ab32 --- /dev/null +++ b/app/javascript/material-icons/400-24px/brush-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/brush.svg b/app/javascript/material-icons/400-24px/brush.svg new file mode 100644 index 0000000000..d583fce54d --- /dev/null +++ b/app/javascript/material-icons/400-24px/brush.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/campaign-fill.svg b/app/javascript/material-icons/400-24px/campaign-fill.svg new file mode 100644 index 0000000000..3df7275bf6 --- /dev/null +++ b/app/javascript/material-icons/400-24px/campaign-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/campaign.svg b/app/javascript/material-icons/400-24px/campaign.svg new file mode 100644 index 0000000000..a6d893fed4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/campaign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cancel-fill.svg b/app/javascript/material-icons/400-24px/cancel-fill.svg new file mode 100644 index 0000000000..f7d476f253 --- /dev/null +++ b/app/javascript/material-icons/400-24px/cancel-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cancel.svg b/app/javascript/material-icons/400-24px/cancel.svg new file mode 100644 index 0000000000..8504fbfdad --- /dev/null +++ b/app/javascript/material-icons/400-24px/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cancel_presentation-fill.svg b/app/javascript/material-icons/400-24px/cancel_presentation-fill.svg new file mode 100644 index 0000000000..8e8e6a1ee6 --- /dev/null +++ b/app/javascript/material-icons/400-24px/cancel_presentation-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cancel_presentation.svg b/app/javascript/material-icons/400-24px/cancel_presentation.svg new file mode 100644 index 0000000000..c0da419cd0 --- /dev/null +++ b/app/javascript/material-icons/400-24px/cancel_presentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chat-fill.svg b/app/javascript/material-icons/400-24px/chat-fill.svg new file mode 100644 index 0000000000..3a74e02f16 --- /dev/null +++ b/app/javascript/material-icons/400-24px/chat-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chat.svg b/app/javascript/material-icons/400-24px/chat.svg new file mode 100644 index 0000000000..4d910a87f1 --- /dev/null +++ b/app/javascript/material-icons/400-24px/chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/check-fill.svg b/app/javascript/material-icons/400-24px/check-fill.svg new file mode 100644 index 0000000000..1655d12bf3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/check-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/check.svg b/app/javascript/material-icons/400-24px/check.svg new file mode 100644 index 0000000000..1655d12bf3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/check_box_outline_blank-fill.svg b/app/javascript/material-icons/400-24px/check_box_outline_blank-fill.svg new file mode 100644 index 0000000000..3f7df315a5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/check_box_outline_blank-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/check_box_outline_blank.svg b/app/javascript/material-icons/400-24px/check_box_outline_blank.svg new file mode 100644 index 0000000000..3f7df315a5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/check_box_outline_blank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chevron_left-fill.svg b/app/javascript/material-icons/400-24px/chevron_left-fill.svg new file mode 100644 index 0000000000..53783746ae --- /dev/null +++ b/app/javascript/material-icons/400-24px/chevron_left-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chevron_left.svg b/app/javascript/material-icons/400-24px/chevron_left.svg new file mode 100644 index 0000000000..53783746ae --- /dev/null +++ b/app/javascript/material-icons/400-24px/chevron_left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chevron_right-fill.svg b/app/javascript/material-icons/400-24px/chevron_right-fill.svg new file mode 100644 index 0000000000..4100467365 --- /dev/null +++ b/app/javascript/material-icons/400-24px/chevron_right-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chevron_right.svg b/app/javascript/material-icons/400-24px/chevron_right.svg new file mode 100644 index 0000000000..4100467365 --- /dev/null +++ b/app/javascript/material-icons/400-24px/chevron_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/close-fill.svg b/app/javascript/material-icons/400-24px/close-fill.svg new file mode 100644 index 0000000000..5a60c58e77 --- /dev/null +++ b/app/javascript/material-icons/400-24px/close-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/close.svg b/app/javascript/material-icons/400-24px/close.svg new file mode 100644 index 0000000000..5a60c58e77 --- /dev/null +++ b/app/javascript/material-icons/400-24px/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/code-fill.svg b/app/javascript/material-icons/400-24px/code-fill.svg new file mode 100644 index 0000000000..8ef5c55cd4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/code-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/code.svg b/app/javascript/material-icons/400-24px/code.svg new file mode 100644 index 0000000000..8ef5c55cd4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/content_copy-fill.svg b/app/javascript/material-icons/400-24px/content_copy-fill.svg new file mode 100644 index 0000000000..dabf094503 --- /dev/null +++ b/app/javascript/material-icons/400-24px/content_copy-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/content_copy.svg b/app/javascript/material-icons/400-24px/content_copy.svg new file mode 100644 index 0000000000..d875c84491 --- /dev/null +++ b/app/javascript/material-icons/400-24px/content_copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/delete-fill.svg b/app/javascript/material-icons/400-24px/delete-fill.svg new file mode 100644 index 0000000000..59d1abb8bc --- /dev/null +++ b/app/javascript/material-icons/400-24px/delete-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/delete.svg b/app/javascript/material-icons/400-24px/delete.svg new file mode 100644 index 0000000000..560d174b9b --- /dev/null +++ b/app/javascript/material-icons/400-24px/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/delete_forever-fill.svg b/app/javascript/material-icons/400-24px/delete_forever-fill.svg new file mode 100644 index 0000000000..40fe4497f5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/delete_forever-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/delete_forever.svg b/app/javascript/material-icons/400-24px/delete_forever.svg new file mode 100644 index 0000000000..763f517d1d --- /dev/null +++ b/app/javascript/material-icons/400-24px/delete_forever.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/description-fill.svg b/app/javascript/material-icons/400-24px/description-fill.svg new file mode 100644 index 0000000000..07998b29d6 --- /dev/null +++ b/app/javascript/material-icons/400-24px/description-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/description.svg b/app/javascript/material-icons/400-24px/description.svg new file mode 100644 index 0000000000..309a4f5b38 --- /dev/null +++ b/app/javascript/material-icons/400-24px/description.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/done-fill.svg b/app/javascript/material-icons/400-24px/done-fill.svg new file mode 100644 index 0000000000..1655d12bf3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/done-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/done.svg b/app/javascript/material-icons/400-24px/done.svg new file mode 100644 index 0000000000..1655d12bf3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/done.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/done_all-fill.svg b/app/javascript/material-icons/400-24px/done_all-fill.svg new file mode 100644 index 0000000000..8f05228c40 --- /dev/null +++ b/app/javascript/material-icons/400-24px/done_all-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/done_all.svg b/app/javascript/material-icons/400-24px/done_all.svg new file mode 100644 index 0000000000..8f05228c40 --- /dev/null +++ b/app/javascript/material-icons/400-24px/done_all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/download-fill.svg b/app/javascript/material-icons/400-24px/download-fill.svg new file mode 100644 index 0000000000..6a171ea822 --- /dev/null +++ b/app/javascript/material-icons/400-24px/download-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/download.svg b/app/javascript/material-icons/400-24px/download.svg new file mode 100644 index 0000000000..6a171ea822 --- /dev/null +++ b/app/javascript/material-icons/400-24px/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/edit-fill.svg b/app/javascript/material-icons/400-24px/edit-fill.svg new file mode 100644 index 0000000000..278e79978e --- /dev/null +++ b/app/javascript/material-icons/400-24px/edit-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/edit.svg b/app/javascript/material-icons/400-24px/edit.svg new file mode 100644 index 0000000000..cb81b11302 --- /dev/null +++ b/app/javascript/material-icons/400-24px/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/edit_note-fill.svg b/app/javascript/material-icons/400-24px/edit_note-fill.svg new file mode 100644 index 0000000000..b18db1df8e --- /dev/null +++ b/app/javascript/material-icons/400-24px/edit_note-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/edit_note.svg b/app/javascript/material-icons/400-24px/edit_note.svg new file mode 100644 index 0000000000..cf7e98405a --- /dev/null +++ b/app/javascript/material-icons/400-24px/edit_note.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/expand_less-fill.svg b/app/javascript/material-icons/400-24px/expand_less-fill.svg new file mode 100644 index 0000000000..453729655d --- /dev/null +++ b/app/javascript/material-icons/400-24px/expand_less-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/expand_less.svg b/app/javascript/material-icons/400-24px/expand_less.svg new file mode 100644 index 0000000000..453729655d --- /dev/null +++ b/app/javascript/material-icons/400-24px/expand_less.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/expand_more-fill.svg b/app/javascript/material-icons/400-24px/expand_more-fill.svg new file mode 100644 index 0000000000..0c8f273596 --- /dev/null +++ b/app/javascript/material-icons/400-24px/expand_more-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/expand_more.svg b/app/javascript/material-icons/400-24px/expand_more.svg new file mode 100644 index 0000000000..0c8f273596 --- /dev/null +++ b/app/javascript/material-icons/400-24px/expand_more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/find_in_page-fill.svg b/app/javascript/material-icons/400-24px/find_in_page-fill.svg new file mode 100644 index 0000000000..146f838a27 --- /dev/null +++ b/app/javascript/material-icons/400-24px/find_in_page-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/find_in_page.svg b/app/javascript/material-icons/400-24px/find_in_page.svg new file mode 100644 index 0000000000..f21c2786ca --- /dev/null +++ b/app/javascript/material-icons/400-24px/find_in_page.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/flag-fill.svg b/app/javascript/material-icons/400-24px/flag-fill.svg new file mode 100644 index 0000000000..e44a94d90b --- /dev/null +++ b/app/javascript/material-icons/400-24px/flag-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/flag.svg b/app/javascript/material-icons/400-24px/flag.svg new file mode 100644 index 0000000000..cb4c810e08 --- /dev/null +++ b/app/javascript/material-icons/400-24px/flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/forum-fill.svg b/app/javascript/material-icons/400-24px/forum-fill.svg new file mode 100644 index 0000000000..2459058aa3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/forum-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/forum.svg b/app/javascript/material-icons/400-24px/forum.svg new file mode 100644 index 0000000000..6f5d8d7a4d --- /dev/null +++ b/app/javascript/material-icons/400-24px/forum.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/fullscreen-fill.svg b/app/javascript/material-icons/400-24px/fullscreen-fill.svg new file mode 100644 index 0000000000..940c878a7d --- /dev/null +++ b/app/javascript/material-icons/400-24px/fullscreen-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/fullscreen.svg b/app/javascript/material-icons/400-24px/fullscreen.svg new file mode 100644 index 0000000000..940c878a7d --- /dev/null +++ b/app/javascript/material-icons/400-24px/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/fullscreen_exit-fill.svg b/app/javascript/material-icons/400-24px/fullscreen_exit-fill.svg new file mode 100644 index 0000000000..d9d45a6c6a --- /dev/null +++ b/app/javascript/material-icons/400-24px/fullscreen_exit-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/fullscreen_exit.svg b/app/javascript/material-icons/400-24px/fullscreen_exit.svg new file mode 100644 index 0000000000..d9d45a6c6a --- /dev/null +++ b/app/javascript/material-icons/400-24px/fullscreen_exit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/group-fill.svg b/app/javascript/material-icons/400-24px/group-fill.svg new file mode 100644 index 0000000000..c0d6cef5c5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/group-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/group.svg b/app/javascript/material-icons/400-24px/group.svg new file mode 100644 index 0000000000..dbc2c937e4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/home-fill.svg b/app/javascript/material-icons/400-24px/home-fill.svg new file mode 100644 index 0000000000..e254416380 --- /dev/null +++ b/app/javascript/material-icons/400-24px/home-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/home.svg b/app/javascript/material-icons/400-24px/home.svg new file mode 100644 index 0000000000..d700ddea7b --- /dev/null +++ b/app/javascript/material-icons/400-24px/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/image-fill.svg b/app/javascript/material-icons/400-24px/image-fill.svg new file mode 100644 index 0000000000..ba28ad3534 --- /dev/null +++ b/app/javascript/material-icons/400-24px/image-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/image.svg b/app/javascript/material-icons/400-24px/image.svg new file mode 100644 index 0000000000..0e7c11d402 --- /dev/null +++ b/app/javascript/material-icons/400-24px/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/info-fill.svg b/app/javascript/material-icons/400-24px/info-fill.svg new file mode 100644 index 0000000000..0232e17ad0 --- /dev/null +++ b/app/javascript/material-icons/400-24px/info-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/info.svg b/app/javascript/material-icons/400-24px/info.svg new file mode 100644 index 0000000000..05606f4e59 --- /dev/null +++ b/app/javascript/material-icons/400-24px/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/insert_chart-fill.svg b/app/javascript/material-icons/400-24px/insert_chart-fill.svg new file mode 100644 index 0000000000..12d137ca89 --- /dev/null +++ b/app/javascript/material-icons/400-24px/insert_chart-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/insert_chart.svg b/app/javascript/material-icons/400-24px/insert_chart.svg new file mode 100644 index 0000000000..4f2a10be59 --- /dev/null +++ b/app/javascript/material-icons/400-24px/insert_chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/link-fill.svg b/app/javascript/material-icons/400-24px/link-fill.svg new file mode 100644 index 0000000000..319a0681c4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/link-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/link.svg b/app/javascript/material-icons/400-24px/link.svg new file mode 100644 index 0000000000..319a0681c4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/list_alt-fill.svg b/app/javascript/material-icons/400-24px/list_alt-fill.svg new file mode 100644 index 0000000000..6aa8b50823 --- /dev/null +++ b/app/javascript/material-icons/400-24px/list_alt-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/list_alt.svg b/app/javascript/material-icons/400-24px/list_alt.svg new file mode 100644 index 0000000000..cca8ab1955 --- /dev/null +++ b/app/javascript/material-icons/400-24px/list_alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/lock-fill.svg b/app/javascript/material-icons/400-24px/lock-fill.svg new file mode 100644 index 0000000000..0815d78418 --- /dev/null +++ b/app/javascript/material-icons/400-24px/lock-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/lock.svg b/app/javascript/material-icons/400-24px/lock.svg new file mode 100644 index 0000000000..20b9e3984e --- /dev/null +++ b/app/javascript/material-icons/400-24px/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/lock_open-fill.svg b/app/javascript/material-icons/400-24px/lock_open-fill.svg new file mode 100644 index 0000000000..60309dce5e --- /dev/null +++ b/app/javascript/material-icons/400-24px/lock_open-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/lock_open.svg b/app/javascript/material-icons/400-24px/lock_open.svg new file mode 100644 index 0000000000..824c70b7c4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/lock_open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/logout-fill.svg b/app/javascript/material-icons/400-24px/logout-fill.svg new file mode 100644 index 0000000000..4881453501 --- /dev/null +++ b/app/javascript/material-icons/400-24px/logout-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/logout.svg b/app/javascript/material-icons/400-24px/logout.svg new file mode 100644 index 0000000000..4881453501 --- /dev/null +++ b/app/javascript/material-icons/400-24px/logout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/mail-fill.svg b/app/javascript/material-icons/400-24px/mail-fill.svg new file mode 100644 index 0000000000..5e7e4a2fb2 --- /dev/null +++ b/app/javascript/material-icons/400-24px/mail-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/mail.svg b/app/javascript/material-icons/400-24px/mail.svg new file mode 100644 index 0000000000..15e1d12d4e --- /dev/null +++ b/app/javascript/material-icons/400-24px/mail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/manufacturing-fill.svg b/app/javascript/material-icons/400-24px/manufacturing-fill.svg new file mode 100644 index 0000000000..f19180759c --- /dev/null +++ b/app/javascript/material-icons/400-24px/manufacturing-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/manufacturing.svg b/app/javascript/material-icons/400-24px/manufacturing.svg new file mode 100644 index 0000000000..f19180759c --- /dev/null +++ b/app/javascript/material-icons/400-24px/manufacturing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/markdown-fill.svg b/app/javascript/material-icons/400-24px/markdown-fill.svg new file mode 100644 index 0000000000..18a0670518 --- /dev/null +++ b/app/javascript/material-icons/400-24px/markdown-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/markdown.svg b/app/javascript/material-icons/400-24px/markdown.svg new file mode 100644 index 0000000000..3396c2f99a --- /dev/null +++ b/app/javascript/material-icons/400-24px/markdown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/menu-fill.svg b/app/javascript/material-icons/400-24px/menu-fill.svg new file mode 100644 index 0000000000..2f427e91c8 --- /dev/null +++ b/app/javascript/material-icons/400-24px/menu-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/menu.svg b/app/javascript/material-icons/400-24px/menu.svg new file mode 100644 index 0000000000..2f427e91c8 --- /dev/null +++ b/app/javascript/material-icons/400-24px/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/more_horiz-fill.svg b/app/javascript/material-icons/400-24px/more_horiz-fill.svg new file mode 100644 index 0000000000..e777154892 --- /dev/null +++ b/app/javascript/material-icons/400-24px/more_horiz-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/more_horiz.svg b/app/javascript/material-icons/400-24px/more_horiz.svg new file mode 100644 index 0000000000..e777154892 --- /dev/null +++ b/app/javascript/material-icons/400-24px/more_horiz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/movie-fill.svg b/app/javascript/material-icons/400-24px/movie-fill.svg new file mode 100644 index 0000000000..d295408958 --- /dev/null +++ b/app/javascript/material-icons/400-24px/movie-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/movie.svg b/app/javascript/material-icons/400-24px/movie.svg new file mode 100644 index 0000000000..e98fa48473 --- /dev/null +++ b/app/javascript/material-icons/400-24px/movie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/music_note-fill.svg b/app/javascript/material-icons/400-24px/music_note-fill.svg new file mode 100644 index 0000000000..b10ad1921a --- /dev/null +++ b/app/javascript/material-icons/400-24px/music_note-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/music_note.svg b/app/javascript/material-icons/400-24px/music_note.svg new file mode 100644 index 0000000000..b10ad1921a --- /dev/null +++ b/app/javascript/material-icons/400-24px/music_note.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/notifications-fill.svg b/app/javascript/material-icons/400-24px/notifications-fill.svg new file mode 100644 index 0000000000..0730efefca --- /dev/null +++ b/app/javascript/material-icons/400-24px/notifications-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/notifications.svg b/app/javascript/material-icons/400-24px/notifications.svg new file mode 100644 index 0000000000..dbfe0e0409 --- /dev/null +++ b/app/javascript/material-icons/400-24px/notifications.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/notifications_active-fill.svg b/app/javascript/material-icons/400-24px/notifications_active-fill.svg new file mode 100644 index 0000000000..856a0ed8a5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/notifications_active-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/notifications_active.svg b/app/javascript/material-icons/400-24px/notifications_active.svg new file mode 100644 index 0000000000..1389a10e0a --- /dev/null +++ b/app/javascript/material-icons/400-24px/notifications_active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/open_in_new-fill.svg b/app/javascript/material-icons/400-24px/open_in_new-fill.svg new file mode 100644 index 0000000000..42895ffd13 --- /dev/null +++ b/app/javascript/material-icons/400-24px/open_in_new-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/open_in_new.svg b/app/javascript/material-icons/400-24px/open_in_new.svg new file mode 100644 index 0000000000..42895ffd13 --- /dev/null +++ b/app/javascript/material-icons/400-24px/open_in_new.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/pause-fill.svg b/app/javascript/material-icons/400-24px/pause-fill.svg new file mode 100644 index 0000000000..fc9a8074de --- /dev/null +++ b/app/javascript/material-icons/400-24px/pause-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/pause.svg b/app/javascript/material-icons/400-24px/pause.svg new file mode 100644 index 0000000000..95bc792fc3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person-fill.svg b/app/javascript/material-icons/400-24px/person-fill.svg new file mode 100644 index 0000000000..73ef1efc10 --- /dev/null +++ b/app/javascript/material-icons/400-24px/person-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person.svg b/app/javascript/material-icons/400-24px/person.svg new file mode 100644 index 0000000000..a3f6b246c8 --- /dev/null +++ b/app/javascript/material-icons/400-24px/person.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person_add-fill.svg b/app/javascript/material-icons/400-24px/person_add-fill.svg new file mode 100644 index 0000000000..3fa7f65288 --- /dev/null +++ b/app/javascript/material-icons/400-24px/person_add-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person_add.svg b/app/javascript/material-icons/400-24px/person_add.svg new file mode 100644 index 0000000000..39b592bf04 --- /dev/null +++ b/app/javascript/material-icons/400-24px/person_add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person_check-fill.svg b/app/javascript/material-icons/400-24px/person_check-fill.svg new file mode 100644 index 0000000000..21d91f1933 --- /dev/null +++ b/app/javascript/material-icons/400-24px/person_check-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person_check.svg b/app/javascript/material-icons/400-24px/person_check.svg new file mode 100644 index 0000000000..fbe013ac7b --- /dev/null +++ b/app/javascript/material-icons/400-24px/person_check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/play_arrow-fill.svg b/app/javascript/material-icons/400-24px/play_arrow-fill.svg new file mode 100644 index 0000000000..6465b90222 --- /dev/null +++ b/app/javascript/material-icons/400-24px/play_arrow-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/play_arrow.svg b/app/javascript/material-icons/400-24px/play_arrow.svg new file mode 100644 index 0000000000..52f0fcc9c4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/play_arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/public-fill.svg b/app/javascript/material-icons/400-24px/public-fill.svg new file mode 100644 index 0000000000..1e9e79de4d --- /dev/null +++ b/app/javascript/material-icons/400-24px/public-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/public.svg b/app/javascript/material-icons/400-24px/public.svg new file mode 100644 index 0000000000..1e9e79de4d --- /dev/null +++ b/app/javascript/material-icons/400-24px/public.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/push_pin-fill.svg b/app/javascript/material-icons/400-24px/push_pin-fill.svg new file mode 100644 index 0000000000..6095ba77ee --- /dev/null +++ b/app/javascript/material-icons/400-24px/push_pin-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/push_pin.svg b/app/javascript/material-icons/400-24px/push_pin.svg new file mode 100644 index 0000000000..e1abd900a7 --- /dev/null +++ b/app/javascript/material-icons/400-24px/push_pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/rectangle-fill.svg b/app/javascript/material-icons/400-24px/rectangle-fill.svg new file mode 100644 index 0000000000..64b038f268 --- /dev/null +++ b/app/javascript/material-icons/400-24px/rectangle-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/rectangle.svg b/app/javascript/material-icons/400-24px/rectangle.svg new file mode 100644 index 0000000000..ada92f2cf5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/rectangle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/refresh-fill.svg b/app/javascript/material-icons/400-24px/refresh-fill.svg new file mode 100644 index 0000000000..a7a6bc801b --- /dev/null +++ b/app/javascript/material-icons/400-24px/refresh-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/refresh.svg b/app/javascript/material-icons/400-24px/refresh.svg new file mode 100644 index 0000000000..a7a6bc801b --- /dev/null +++ b/app/javascript/material-icons/400-24px/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/repeat-fill.svg b/app/javascript/material-icons/400-24px/repeat-fill.svg new file mode 100644 index 0000000000..c1b09d8026 --- /dev/null +++ b/app/javascript/material-icons/400-24px/repeat-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/repeat.svg b/app/javascript/material-icons/400-24px/repeat.svg new file mode 100644 index 0000000000..c1b09d8026 --- /dev/null +++ b/app/javascript/material-icons/400-24px/repeat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/reply-fill.svg b/app/javascript/material-icons/400-24px/reply-fill.svg new file mode 100644 index 0000000000..eb661f2823 --- /dev/null +++ b/app/javascript/material-icons/400-24px/reply-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/reply.svg b/app/javascript/material-icons/400-24px/reply.svg new file mode 100644 index 0000000000..eb661f2823 --- /dev/null +++ b/app/javascript/material-icons/400-24px/reply.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/reply_all-fill.svg b/app/javascript/material-icons/400-24px/reply_all-fill.svg new file mode 100644 index 0000000000..74c9573eaf --- /dev/null +++ b/app/javascript/material-icons/400-24px/reply_all-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/reply_all.svg b/app/javascript/material-icons/400-24px/reply_all.svg new file mode 100644 index 0000000000..74c9573eaf --- /dev/null +++ b/app/javascript/material-icons/400-24px/reply_all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/search-fill.svg b/app/javascript/material-icons/400-24px/search-fill.svg new file mode 100644 index 0000000000..ef0d0521eb --- /dev/null +++ b/app/javascript/material-icons/400-24px/search-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/search.svg b/app/javascript/material-icons/400-24px/search.svg new file mode 100644 index 0000000000..ef0d0521eb --- /dev/null +++ b/app/javascript/material-icons/400-24px/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/settings-fill.svg b/app/javascript/material-icons/400-24px/settings-fill.svg new file mode 100644 index 0000000000..f133479502 --- /dev/null +++ b/app/javascript/material-icons/400-24px/settings-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/settings.svg b/app/javascript/material-icons/400-24px/settings.svg new file mode 100644 index 0000000000..817c782f05 --- /dev/null +++ b/app/javascript/material-icons/400-24px/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/share-fill.svg b/app/javascript/material-icons/400-24px/share-fill.svg new file mode 100644 index 0000000000..5a6b0d0a8d --- /dev/null +++ b/app/javascript/material-icons/400-24px/share-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/share.svg b/app/javascript/material-icons/400-24px/share.svg new file mode 100644 index 0000000000..6876cd42da --- /dev/null +++ b/app/javascript/material-icons/400-24px/share.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/smart_toy-fill.svg b/app/javascript/material-icons/400-24px/smart_toy-fill.svg new file mode 100644 index 0000000000..df417f5ff7 --- /dev/null +++ b/app/javascript/material-icons/400-24px/smart_toy-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/smart_toy.svg b/app/javascript/material-icons/400-24px/smart_toy.svg new file mode 100644 index 0000000000..b84efc73b1 --- /dev/null +++ b/app/javascript/material-icons/400-24px/smart_toy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/star-fill.svg b/app/javascript/material-icons/400-24px/star-fill.svg new file mode 100644 index 0000000000..cb2231e634 --- /dev/null +++ b/app/javascript/material-icons/400-24px/star-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/star.svg b/app/javascript/material-icons/400-24px/star.svg new file mode 100644 index 0000000000..1736e085d0 --- /dev/null +++ b/app/javascript/material-icons/400-24px/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/tag-fill.svg b/app/javascript/material-icons/400-24px/tag-fill.svg new file mode 100644 index 0000000000..ce76d537b3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/tag-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/tag.svg b/app/javascript/material-icons/400-24px/tag.svg new file mode 100644 index 0000000000..ce76d537b3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/tag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/trip-fill.svg b/app/javascript/material-icons/400-24px/trip-fill.svg new file mode 100644 index 0000000000..97c8c46dad --- /dev/null +++ b/app/javascript/material-icons/400-24px/trip-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/trip.svg b/app/javascript/material-icons/400-24px/trip.svg new file mode 100644 index 0000000000..f158a738c6 --- /dev/null +++ b/app/javascript/material-icons/400-24px/trip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/tune-fill.svg b/app/javascript/material-icons/400-24px/tune-fill.svg new file mode 100644 index 0000000000..887f8bd498 --- /dev/null +++ b/app/javascript/material-icons/400-24px/tune-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/tune.svg b/app/javascript/material-icons/400-24px/tune.svg new file mode 100644 index 0000000000..887f8bd498 --- /dev/null +++ b/app/javascript/material-icons/400-24px/tune.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/upload_file-fill.svg b/app/javascript/material-icons/400-24px/upload_file-fill.svg new file mode 100644 index 0000000000..639d77af36 --- /dev/null +++ b/app/javascript/material-icons/400-24px/upload_file-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/upload_file.svg b/app/javascript/material-icons/400-24px/upload_file.svg new file mode 100644 index 0000000000..40ce5b65e5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/upload_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/visibility-fill.svg b/app/javascript/material-icons/400-24px/visibility-fill.svg new file mode 100644 index 0000000000..44b5f4c606 --- /dev/null +++ b/app/javascript/material-icons/400-24px/visibility-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/visibility.svg b/app/javascript/material-icons/400-24px/visibility.svg new file mode 100644 index 0000000000..8fe45d09af --- /dev/null +++ b/app/javascript/material-icons/400-24px/visibility.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/visibility_off-fill.svg b/app/javascript/material-icons/400-24px/visibility_off-fill.svg new file mode 100644 index 0000000000..e21fbd88df --- /dev/null +++ b/app/javascript/material-icons/400-24px/visibility_off-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/visibility_off.svg b/app/javascript/material-icons/400-24px/visibility_off.svg new file mode 100644 index 0000000000..d98cf8d942 --- /dev/null +++ b/app/javascript/material-icons/400-24px/visibility_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/volume_off-fill.svg b/app/javascript/material-icons/400-24px/volume_off-fill.svg new file mode 100644 index 0000000000..b3d12d4d98 --- /dev/null +++ b/app/javascript/material-icons/400-24px/volume_off-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/volume_off.svg b/app/javascript/material-icons/400-24px/volume_off.svg new file mode 100644 index 0000000000..a0acf63747 --- /dev/null +++ b/app/javascript/material-icons/400-24px/volume_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/volume_up-fill.svg b/app/javascript/material-icons/400-24px/volume_up-fill.svg new file mode 100644 index 0000000000..dd5771215e --- /dev/null +++ b/app/javascript/material-icons/400-24px/volume_up-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/volume_up.svg b/app/javascript/material-icons/400-24px/volume_up.svg new file mode 100644 index 0000000000..fd9006a6d2 --- /dev/null +++ b/app/javascript/material-icons/400-24px/volume_up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/LICENSE b/app/javascript/material-icons/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/app/javascript/material-icons/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/javascript/material-icons/README.md b/app/javascript/material-icons/README.md new file mode 100644 index 0000000000..1479cb2255 --- /dev/null +++ b/app/javascript/material-icons/README.md @@ -0,0 +1 @@ +Files in this directory are Material Symbols icons fetched using the `icons:download` task. diff --git a/app/javascript/styles/fonts/inter.scss b/app/javascript/styles/fonts/inter.scss new file mode 100644 index 0000000000..bb4899b701 --- /dev/null +++ b/app/javascript/styles/fonts/inter.scss @@ -0,0 +1,8 @@ +@font-face { + font-family: Inter; + src: url('../fonts/inter/inter-variable-font-slnt-wght.woff2') + format('woff2-variations'); + font-weight: 100 900; + font-style: normal; + mso-generic-font-family: swiss; /* stylelint-disable-line property-no-unknown -- Proprietary property for Outlook on Windows. */ +} diff --git a/public/inert.css b/app/javascript/styles/inert.scss similarity index 78% rename from public/inert.css rename to app/javascript/styles/inert.scss index 54e10616d2..a60045d7be 100644 --- a/public/inert.css +++ b/app/javascript/styles/inert.scss @@ -1,3 +1,5 @@ +/* This is needed for the wicg-inert polyfill */ + [inert] { pointer-events: none; cursor: default; diff --git a/app/javascript/styles/mailer.scss b/app/javascript/styles/mailer.scss index 92d00cae8b..a2cbb494b4 100644 --- a/app/javascript/styles/mailer.scss +++ b/app/javascript/styles/mailer.scss @@ -1,574 +1,620 @@ -@import 'mastodon/variables'; -@import 'fonts/roboto'; +@import 'fonts/inter'; -table, -td, -div { - box-sizing: border-box; -} - -html, body { - width: 100% !important; - min-width: 100%; + accent-color: #6364ff; + word-break: break-word; margin: 0; + background-color: #f3f2f5; padding: 0; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -.email-body { - td, - div, - a, - span { - line-height: inherit; - } -} - -a { - &, - &:visited, - span { - text-decoration: none; - color: $ui-highlight-color; - } - - #outlook & { - padding: 0; - } -} - -img { - outline: none; - border: 0; - text-decoration: none; - -ms-interpolation-mode: bicubic; - clear: both; - line-height: 100%; -} - -table { - border-spacing: 0; - mso-table-lspace: 0; - mso-table-rspace: 0; -} - -td { - vertical-align: top; -} - -.auto-dir { - p { - unicode-bidi: plaintext; - } - - a { - unicode-bidi: isolate; - } -} - -.email-table, -.content-section, -.column, -.column-cell { - width: 100%; - min-width: 100%; -} - -.email-body { - font-size: 0 !important; - line-height: 100%; - text-align: center; - padding-left: 16px; - padding-right: 16px; -} - -.email-start { - padding-top: 32px; -} - -.email-end { - padding-bottom: 32px; -} - -.email-body, -html, -body { - background-color: lighten($ui-base-color, 4%); -} - -.email-container, -.email-row, -.col-0, -.col-1, -.col-2, -.col-3, -.col-4, -.col-5, -.col-6 { - font-size: 0; - display: inline-block; - width: 100%; - min-width: 100%; - min-width: 0 !important; - vertical-align: top; -} - -.content-cell { - width: 100%; - min-width: 100%; - min-width: 0 !important; -} - -.column-cell { - padding-top: 16px; - padding-bottom: 16px; - vertical-align: top; - - &.button-cell { - padding-top: 0; - } -} - -.email-container { - max-width: 632px; - margin: 0 auto; - text-align: center; -} - -.email-row { - display: block; - max-width: 600px !important; - margin: 0 auto; - text-align: center; - clear: both; -} - -.col-0 { - max-width: 50px; -} - -.col-1 { - max-width: 100px; -} - -.col-2 { - max-width: 200px; -} - -.col-3 { - max-width: 300px; -} - -.col-4 { - max-width: 400px; -} - -.col-5 { - max-width: 500px; -} - -.col-6 { - max-width: 600px; -} - -.column-cell, -.column-cell td, -p { - font-family: Helvetica, Arial, sans-serif; - - @media only screen { - font-family: $font-sans-serif, sans-serif !important; - } -} - -.email-body .column-cell, -.column-cell, -p { - font-size: 15px; - line-height: 23px; - color: $ui-primary-color; - mso-line-height-rule: exactly; - text-rendering: optimizelegibility; -} - -p { - display: block; - margin-top: 0; - margin-bottom: 16px; - - &.small { - font-size: 13px; - } - - &.lead { - font-size: 19px; - line-height: 27px; - } + -webkit-text-size-adjust: none; + text-size-adjust: none; } +p, h1, h2, h3, h4, h5, h6 { - color: $ui-secondary-color; - margin-left: 0; - margin-right: 0; - margin-top: 20px; - margin-bottom: 8px; + margin: 0; + background-color: transparent; padding: 0; - font-weight: 500; + border: none; + font-family: Inter, 'Lucida Grande', sans-serif; } -h1 { - font-size: 26px; - line-height: 36px; +img { + max-width: 100%; + height: auto; + border: none; + text-indent: 0; + vertical-align: middle; + color: inherit; + font-family: inherit; } -h2 { - font-size: 23px; - line-height: 30px; +table { + border: none; } -h3 { - font-size: 19px; - line-height: 25px; +table + p { + margin-top: 16px; } -h5 { - font-size: 16px; - line-height: 21px; - font-weight: 700; - color: lighten($ui-base-color, 34%); +.email { + min-width: 280px; + font-family: Inter, 'Lucida Grande', sans-serif; + word-break: break-word; + color: #17063b; + background-color: #f3f2f5; } -.input-cell { - h5 { - margin-top: 4px; - } -} - -.input { - td { - background: darken($ui-base-color, 8%); - border-radius: 4px; - padding: 16px; - line-height: 20px; - mso-line-height-rule: exactly; - text-align: center; - font-weight: 500; - font-size: 17px; - } -} - -.content-cell, -.blank-cell { +.email-container { + max-width: 740px; + margin: 0 auto; width: 100%; - font-size: 0; - text-align: center; - vertical-align: top; - padding-left: 16px; - padding-right: 16px; } -.content-cell { - background-color: darken($ui-base-color, 4%); - - &.darker { - background-color: darken($ui-base-color, 8%); - } +// Outer email card +.email-card-table { + border-collapse: collapse; + width: 100%; } -.hero { - background-color: $ui-base-color; - padding-top: 20px; +.email-card-td { + overflow: hidden; + box-shadow: 0 4px 16px 0 rgba(23, 6, 59, 4%); + background-color: #fff; } -.hero-with-button { - padding-bottom: 16px; - - h1 { - margin-bottom: 4px; - } - - p.lead { - margin-bottom: 32px; - } +// Inner email card +.email-inner-card-table { + border-collapse: separate; + width: 100%; + border-radius: 12px; } -.header { - border-radius: 5px 5px 0 0; - background-color: darken($ui-base-color, 8%); - - .column-cell { - text-align: center; - padding-top: 20px; - padding-bottom: 8px; - } +.email-inner-card-td-without-padding, +.email-inner-card-td { + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 16px 0 rgba(23, 6, 59, 8%); + background-color: #fff; + border: 1px solid #dfdee3; } -.content-start { - padding-top: 32px; +.email-inner-card-td { + padding: 24px; } -.content-end { - border-radius: 0 0 5px 5px; - padding-top: 16px; +// Account +.email-account-banner-table { + background-color: #f3f2f5; + border-top-left-radius: 12px; + border-top-right-radius: 12px; } -.footer { - .column-cell, - p { - color: lighten($ui-base-color, 34%); - } +.email-account-banner-td { + border-top-left-radius: 12px; + border-top-right-radius: 12px; + height: 140px; + vertical-align: bottom; + background-position: center !important; + background-size: cover !important; +} - p { - margin-bottom: 0; - font-size: 13px; +.email-account-banner-inner-td { + padding: 24px 24px 0; + mso-padding-alt: 24px; +} - &.small { - margin-bottom: 0; - } - } +.email-account-banner-overlap-div { + max-height: 42px; +} - a { - color: lighten($ui-base-color, 34%); - text-decoration: underline; - } +.email-account-banner-icon-table { + width: auto; + margin: 0; + overflow: hidden; + border-radius: 8px; + border-collapse: separate; + background-color: #fff; + border: 2px solid #fff; img { - opacity: 0.3; - } -} - -.logo { - position: relative; - left: -4px; -} - -.button { - display: table; - margin-left: auto; - margin-right: auto; - - td { - line-height: 20px; - mso-line-height-rule: exactly; - border-radius: 4px; - text-align: center; - font-weight: 500; - font-size: 17px; - padding: 0 !important; - - a, - a span { - color: $primary-text-color; - display: block !important; - text-align: center !important; - vertical-align: top !important; - line-height: inherit !important; - } - - a { - padding: 10px 22px !important; - line-height: 26px !important; - font-weight: 500 !important; - } - } - - &.button-small { - td { - border-radius: 4px; - font-size: 14px; - padding: 8px 16px; - - a { - padding: 5px 16px !important; - line-height: 26px !important; - } - } - } -} - -.button-default { - background-color: darken($ui-base-color, 8%); -} - -.button-primary { - background-color: darken($ui-highlight-color, 3%); -} - -.text-center { - text-align: center; -} - -.text-right { - text-align: right; -} - -.padded { - padding-left: 16px; - padding-right: 16px; -} - -.padded-bottom { - padding-bottom: 32px; -} - -.margin-bottom { - margin-bottom: 20px; -} - -.hero-icon { - width: 64px; - - td { - text-align: center; - vertical-align: middle; - line-height: 100%; - mso-line-height-rule: exactly; - padding: 16px; - border-radius: 80px; - background: $success-green; - } - - &.warning-icon td { - background: $gold-star; - } - - &.alert-icon td { - background: $error-red; - } - - img { - max-width: 32px; - width: 32px; - height: 32px; display: block; - line-height: 100%; + max-width: 100%; + border: none; + border-radius: 6px; } } -.hr { - width: 100%; +.email-account-body-td { + padding: 56px 24px 24px; + mso-padding-alt: 24px; +} +.email-account-name { + font-size: 16px; + font-weight: 600; + line-height: 24px; + color: #17063b; +} + +.email-account-handle { + font-size: 14px; + line-height: 20px; + color: #746a89; +} + +.email-account-stats-table { td { - font-size: 0; - line-height: 1px; - mso-line-height-rule: exactly; - min-height: 1px; - overflow: hidden; - height: 2px; - background-color: transparent !important; - border-top: 1px solid lighten($ui-base-color, 8%); + padding-right: 16px; + font-size: 14px; + line-height: 20px; + color: #746a89; + } + + b { + font-weight: 600; + color: #17063b; + } + + span { + white-space: nowrap; } } -.status { - padding-bottom: 32px; - - &--highlighted { - border: 1px solid lighten($ui-base-color, 8%); - border-radius: 4px; - padding-bottom: 16px; - margin-bottom: 16px; - } - - .status-header { - td { - font-size: 14px; - padding-bottom: 15px; - } - - bdi { - color: $white; - font-size: 16px; - display: block; - font-weight: 500; - } - - td:first-child { - padding-right: 10px; - } - - img { - width: 48px; - height: 48px; - border-radius: 4px; - } - } +// Utility classes +.email-w-full { + width: 100%; +} +.email-prose { p { - font-size: 19px; - margin-bottom: 20px; + color: #17063b; + font-size: 14px; + line-height: 20px; - &.status-footer { - color: lighten($ui-base-color, 26%); - font-size: 14px; - margin-bottom: 0; + &:not(:last-child) { + margin-bottom: 16px; + } - a { - color: lighten($ui-base-color, 26%); + a:not([class]) { + color: #6364ff; + text-decoration: none; + + &:hover { + color: #563acc !important; } } } } -.border-top { - border-top: 1px solid lighten($ui-base-color, 8%); +.email-padding-24 { + padding: 24px; } -ul { - padding-left: 15px; - margin-top: 0; - margin-bottom: 0; +.email-padding-top-24 { + padding-top: 24px; +} + +.email-padding-top-16 { padding-top: 16px; - - li { - margin-bottom: 16px; - color: lighten($ui-base-color, 26%); - - span { - color: $ui-primary-color; - } - } } -ul.rules-list { +.email-padding-top-0 { padding-top: 0; } -@media only screen and (device-width >= 768px) and (device-width <= 1024px) and (orientation: landscape) { - body { - min-height: 1024px !important; +.email-border-top { + border-top: 1px solid #dfdee3; +} + +.email-border-bottom { + border-bottom: 1px solid #dfdee3; +} + +// Header +.email-header-td { + padding: 16px 32px; + background-color: #1b001f; + background-image: url('../images/mailer-new/common/header-bg-start.png'); + background-position: left top; + background-repeat: repeat; +} + +.email-header-logo-table { + width: auto; + margin: 0; +} + +.email-header-logo-td { + padding: 16px 0; + font-size: 0; + + img { + color: #fff; + font-size: 16px; + font-weight: bold; + max-height: 40px; } } -@media (width <= 697px) { - .email-container, - .col-1, - .col-2, - .col-3, - .col-4, - .col-5, - .col-6 { - width: 100% !important; - max-width: none !important; - } +.email-header-logo-a { + display: inline-block; - .email-start { - padding-top: 16px !important; - } - - .email-end { - padding-bottom: 16px !important; - } - - .padded { - padding-left: 0 !important; - padding-right: 0 !important; + img { + display: inline-block; + color: #fff; + } +} + +.email-header-logo-div { + max-height: 0; +} + +.email-header-logo-p { + word-break: break-all; + padding-left: 40px; + padding-top: 26px; + font-size: 11px; + line-height: 13px; + color: #8d808f; + text-align: left; +} + +.email-header-logo-span { + display: block; + text-align: right; +} + +.email-header-heading-td { + padding: 16px 0; +} + +.email-header-heading-img-td { + width: 56px; + text-align: left; + vertical-align: top; + + img { + width: 56px; + height: 56px; + border-radius: 12px; + } +} + +.email-header-heading-txt-td { + vertical-align: middle; + padding-left: 16px; + padding-right: 16px; + + h1 { + margin-bottom: 5px; + color: #fff; + font-size: 24px; + line-height: 28px; + font-weight: 600; + } + + p { + color: #a399a5; + font-size: 18px; + line-height: 21.6px; + font-weight: 500; + } + + &:only-child { + padding-left: 0; + padding-right: 0; + } +} + +// To make the design work with images off +// we create an empty div that overlaps with +// the rest of the content with a dark background. +.email-header-after-div { + max-height: 0; +} + +.email-header-after-inside-div { + height: 30px; + background-color: #1b001f; +} + +// Body content +.email-body-td { + background-image: url('../images/mailer-new/common/header-bg-end.png'); + background-position: left top; + background-repeat: no-repeat; +} + +.email-body-padding-td { + padding: 0 32px 32px; + mso-padding-alt: 32px; +} + +.email-body-padding-td { + & > p { + font-size: 14px; + line-height: 20px; + color: #17063b; + + a { + color: #6364ff; + text-decoration: none; + + &:hover { + color: #563acc !important; + } + } + } +} + +// Footer +.email-footer-td { + padding: 28px 32px 32px; + text-align: center; +} + +.email-footer-logo-a { + display: inline-block; +} + +.email-footer-p { + color: #9b94ab; + text-align: center; + font-size: 12px; + line-height: 20px; + + a { + color: #9b94ab; + text-decoration: underline; + } + + &:first-child { + margin-bottom: 12px; + } +} + +// Button +.email-btn-table { + margin: 0; + max-width: 100%; + border-collapse: separate; + border-radius: 8px; + background-color: #6364ff; +} + +.email-btn-td { + height: 40px; + text-align: center; + mso-padding-alt: 0 35px; +} + +.email-btn-a { + display: block; + border-radius: 8px; + padding-left: 35px; + padding-right: 35px; + padding-top: 10px; + padding-bottom: 10px; + text-align: center; + font-family: Inter, 'Lucida Grande', sans-serif; + font-size: 14px; + font-weight: 600; + line-height: 20px; + color: #fff; + text-decoration: none; + transition: background-color 0.3s ease-in-out; +} + +// Status +.email-status-header-img { + vertical-align: top; + width: 48px; + + img { + width: 48px; + height: 48px; + border-radius: 8px; + overflow: hidden; + } +} + +.email-status-header-text { + padding-left: 16px; + padding-right: 16px; + vertical-align: middle; +} + +.email-status-header-name { + font-size: 16px; + font-weight: 600; + line-height: 24px; + color: #17063b; +} + +.email-status-header-handle { + font-size: 14px; + line-height: 20px; + color: #746a89; +} + +.email-status-content { + padding-top: 24px; +} + +.email-status-spoiler { + color: #746a89; + font-style: italic; + margin-bottom: 8px; +} + +.email-status-prose { + p { + font-size: 14px; + line-height: 20px; + color: #17063b; + } + + a { + color: #6364ff; + text-decoration: none; + + &:hover { + color: #563acc !important; + } + } +} + +.email-status-media { + margin-top: 16px; + font-size: 14px; + line-height: 20px; + color: #17063b; + + img { + border-radius: 8px; + } + + a { + color: #6364ff; + text-decoration: none; + + &:hover { + color: #563acc !important; + } + } +} + +.email-status-footer { + margin-top: 16px; + font-size: 12px; + line-height: 16px; + color: #746a89; + + a { + color: #746a89; + } + + a:hover { + color: #746a89 !important; + text-decoration: underline !important; + } +} + +// Purple frame for emphasis +.email-frame-table { + background-color: #efefff; + border-radius: 8px; +} + +.email-frame-td { + padding: 16px; +} + +.email-frame-wrapper-td { + padding-bottom: 16px; +} + +.email-frame-td > p { + text-align: center; + font-size: 16px; + line-height: 24px; +} + +// Checklist item +.email-checklist-wrapper-td { + padding: 4px 0; +} + +.email-checklist-table { + border-radius: 12px; + border-width: 1px; + border-style: solid; + border-color: #efefff; + background-color: #fff; +} + +.email-checklist-td { + padding: 16px; +} + +.email-checklist-icons-td { + width: 84px; + vertical-align: top; +} + +.email-checklist-icons-checkbox-td { + width: 20px; + vertical-align: middle; + + img { + max-width: 100%; + width: 20px; + } +} + +.email-checklist-icons-step-td { + width: 64px; + text-align: center; + vertical-align: middle; + + img { + max-width: 100%; + width: 40px; + } +} + +.email-checklist-text-td { + h3 { + margin: 0 0 4px; + color: #17063b; + font-size: 14px; + font-weight: 600; + line-height: 16.8px; + } + + p { + margin: 0 0 2px; + color: #746a89; + font-size: 14px; + line-height: 16.8px; + } + + .email-btn-table { + width: 100px; + } + + .email-btn-td { + mso-padding-alt: 10px; + } + + .email-btn-a { + padding-left: 10px; + padding-right: 10px; + } +} + +// Responsive +/* stylelint-disable-next-line media-feature-range-notation -- Basic media queries have better support across email clients. */ +@media only screen and (min-width: 740px) { + .email-desktop-p-8 { + padding: 32px !important; + } + + .email-desktop-rounded-16px { + border-radius: 16px !important; + } + + .email-header-td { + border-radius: 16px 16px 0 0 !important; + } + + .email-desktop-flex { + display: flex; } } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 47433c289f..803fd50053 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -187,8 +187,8 @@ .icon { flex: 0 0 auto; - width: 20px; - height: 20px; + width: 24px; + height: 24px; aspect-ratio: 1; path { @@ -200,7 +200,7 @@ display: inline-flex; color: $action-button-color; border: 0; - padding: 2px; + padding: 0; border-radius: 4px; background: transparent; cursor: pointer; @@ -1398,8 +1398,7 @@ body > [data-popper-placement] { .icon { width: 15px; height: 15px; - position: relative; - top: 0.145em; + vertical-align: middle; } } @@ -2322,7 +2321,7 @@ $ui-header-height: 55px; .drawer__tab { display: flex; flex: 1 1 auto; - padding: 15px 5px 13px; + padding: 13px 3px 11px; color: $darker-text-color; text-decoration: none; text-align: center; @@ -3205,7 +3204,7 @@ $ui-header-height: 55px; align-items: center; gap: 5px; font-size: 16px; - padding: 15px; + padding: 13px; text-decoration: none; overflow: hidden; white-space: nowrap; @@ -3804,7 +3803,7 @@ a.status-card { gap: 5px; margin: 0; border: 0; - padding: 15px; + padding: 13px; padding-inline-end: 0; color: inherit; background: transparent; @@ -5544,6 +5543,10 @@ a.status-card { padding-inline-end: 10px; } + .icon { + vertical-align: middle; + } + .button { flex: 0 0 auto; } @@ -6094,6 +6097,7 @@ a.status-card { gap: 2px; } +.media-gallery__alt__label, .media-gallery__gifv__label { display: flex; align-items: center; @@ -6170,6 +6174,7 @@ a.status-card { .icon { color: $dark-text-color; + vertical-align: middle; } } } @@ -7415,6 +7420,13 @@ noscript { span { user-select: all; } + + .icon-lock { + height: 16px; + width: 16px; + position: relative; + top: 3px; + } } } } diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 3d646da239..b6e995787d 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -104,3 +104,59 @@ margin-inline-start: 10px; } } + +.redirect { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + font-size: 14px; + line-height: 18px; + + &__logo { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 30px; + + img { + height: 48px; + } + } + + &__message { + text-align: center; + + h1 { + font-size: 17px; + line-height: 22px; + font-weight: 700; + margin-bottom: 30px; + } + + p { + margin-bottom: 30px; + + &:last-child { + margin-bottom: 0; + } + } + + a { + color: $highlight-text-color; + font-weight: 500; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + } + + &__link { + margin-top: 15px; + } +} diff --git a/app/javascript/svg-icons/repeat_active.svg b/app/javascript/svg-icons/repeat_active.svg new file mode 100644 index 0000000000..a5bbb8fc4f --- /dev/null +++ b/app/javascript/svg-icons/repeat_active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/javascript/svg-icons/repeat_disabled.svg b/app/javascript/svg-icons/repeat_disabled.svg old mode 100755 new mode 100644 diff --git a/app/javascript/svg-icons/repeat_private.svg b/app/javascript/svg-icons/repeat_private.svg old mode 100755 new mode 100644 diff --git a/app/javascript/svg-icons/repeat_private_active.svg b/app/javascript/svg-icons/repeat_private_active.svg new file mode 100644 index 0000000000..cf2a05c84e --- /dev/null +++ b/app/javascript/svg-icons/repeat_private_active.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/javascript/types/image.d.ts b/app/javascript/types/image.d.ts index 07d1929555..8a08eca9f6 100644 --- a/app/javascript/types/image.d.ts +++ b/app/javascript/types/image.d.ts @@ -20,16 +20,20 @@ declare module '*.png' { } declare module '*.svg' { + const path: string; + export default path; +} + +declare module '*.svg?react' { import type React from 'react'; interface SVGPropsWithTitle extends React.SVGProps { title?: string; } - export const ReactComponent: React.FC; + const ReactComponent: React.FC; - const path: string; - export default path; + export default ReactComponent; } declare module '*.webp' { diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 8eff9fdf40..22b2becea7 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -108,7 +108,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def process_status_params - @status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: @account.followers_url) + @status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: @account.followers_url, object: @object) attachment_ids = process_attachments.take(4).map(&:id) @@ -283,6 +283,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id) rescue Seahorse::Client::NetworkingError => e Rails.logger.warn "Error storing media attachment: #{e}" + RedownloadMediaWorker.perform_async(media_attachment.id) end end @@ -319,7 +320,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity already_voted = true with_redis_lock("vote:#{replied_to_status.poll_id}:#{@account.id}") do - already_voted = poll.votes.where(account: @account).exists? + already_voted = poll.votes.exists?(account: @account) poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri) end @@ -405,7 +406,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return false if local_usernames.empty? - Account.local.where(username: local_usernames).exists? + Account.local.exists?(username: local_usernames) end def tombstone_exists? diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb index 510f00f075..ea9b8a473c 100644 --- a/app/lib/activitypub/parser/status_parser.rb +++ b/app/lib/activitypub/parser/status_parser.rb @@ -4,12 +4,13 @@ class ActivityPub::Parser::StatusParser include JsonLdHelper # @param [Hash] json - # @param [Hash] magic_values - # @option magic_values [String] :followers_collection - def initialize(json, magic_values = {}) - @json = json - @object = json['object'] || json - @magic_values = magic_values + # @param [Hash] options + # @option options [String] :followers_collection + # @option options [Hash] :object + def initialize(json, **options) + @json = json + @object = options[:object] || json['object'] || json + @options = options end def uri @@ -78,7 +79,7 @@ class ActivityPub::Parser::StatusParser :public elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) } :unlisted - elsif audience_to.include?(@magic_values[:followers_collection]) + elsif audience_to.include?(@options[:followers_collection]) :private elsif direct_message == false :limited diff --git a/app/lib/admin/system_check/media_privacy_check.rb b/app/lib/admin/system_check/media_privacy_check.rb index 1df05b120e..2ddc8e8b07 100644 --- a/app/lib/admin/system_check/media_privacy_check.rb +++ b/app/lib/admin/system_check/media_privacy_check.rb @@ -78,7 +78,7 @@ class Admin::SystemCheck::MediaPrivacyCheck < Admin::SystemCheck::BaseCheck @media_attachment ||= begin attachment = Account.representative.media_attachments.first if attachment.present? - attachment.touch # rubocop:disable Rails/SkipsModelValidations + attachment.touch attachment else create_test_attachment! diff --git a/app/lib/annual_report.rb b/app/lib/annual_report.rb new file mode 100644 index 0000000000..cf4297f2a4 --- /dev/null +++ b/app/lib/annual_report.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class AnnualReport + include DatabaseHelper + + SOURCES = [ + AnnualReport::Archetype, + AnnualReport::TypeDistribution, + AnnualReport::TopStatuses, + AnnualReport::MostUsedApps, + AnnualReport::CommonlyInteractedWithAccounts, + AnnualReport::TimeSeries, + AnnualReport::TopHashtags, + AnnualReport::MostRebloggedAccounts, + AnnualReport::Percentiles, + ].freeze + + SCHEMA = 1 + + def initialize(account, year) + @account = account + @year = year + end + + def generate + return if GeneratedAnnualReport.exists?(account: @account, year: @year) + + GeneratedAnnualReport.create( + account: @account, + year: @year, + schema_version: SCHEMA, + data: data + ) + end + + private + + def data + with_read_replica do + SOURCES.each_with_object({}) { |klass, hsh| hsh.merge!(klass.new(@account, @year).generate) } + end + end +end diff --git a/app/lib/annual_report/archetype.rb b/app/lib/annual_report/archetype.rb new file mode 100644 index 0000000000..ea9ef366df --- /dev/null +++ b/app/lib/annual_report/archetype.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class AnnualReport::Archetype < AnnualReport::Source + # Average number of posts (including replies and reblogs) made by + # each active user in a single year (2023) + AVERAGE_PER_YEAR = 113 + + def generate + { + archetype: archetype, + } + end + + private + + def archetype + if (standalone_count + replies_count + reblogs_count) < AVERAGE_PER_YEAR + :lurker + elsif reblogs_count > (standalone_count * 2) + :booster + elsif polls_count > (standalone_count * 0.1) # standalone_count includes posts with polls + :pollster + elsif replies_count > (standalone_count * 2) + :replier + else + :oracle + end + end + + def polls_count + @polls_count ||= base_scope.where.not(poll_id: nil).count + end + + def reblogs_count + @reblogs_count ||= base_scope.where.not(reblog_of_id: nil).count + end + + def replies_count + @replies_count ||= base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count + end + + def standalone_count + @standalone_count ||= base_scope.without_replies.without_reblogs.count + end + + def base_scope + @account.statuses.where(id: year_as_snowflake_range) + end +end diff --git a/app/lib/annual_report/commonly_interacted_with_accounts.rb b/app/lib/annual_report/commonly_interacted_with_accounts.rb new file mode 100644 index 0000000000..af5e854c22 --- /dev/null +++ b/app/lib/annual_report/commonly_interacted_with_accounts.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source + SET_SIZE = 40 + + def generate + { + commonly_interacted_with_accounts: commonly_interacted_with_accounts.map do |(account_id, count)| + { + account_id: account_id, + count: count, + } + end, + } + end + + private + + def commonly_interacted_with_accounts + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('in_reply_to_account_id, count(*) AS total')) + end +end diff --git a/app/lib/annual_report/most_reblogged_accounts.rb b/app/lib/annual_report/most_reblogged_accounts.rb new file mode 100644 index 0000000000..e3e8a7c90b --- /dev/null +++ b/app/lib/annual_report/most_reblogged_accounts.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::MostRebloggedAccounts < AnnualReport::Source + SET_SIZE = 10 + + def generate + { + most_reblogged_accounts: most_reblogged_accounts.map do |(account_id, count)| + { + account_id: account_id, + count: count, + } + end, + } + end + + private + + def most_reblogged_accounts + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(reblog_of_id: nil).joins(reblog: :account).group('accounts.id').having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('accounts.id, count(*) as total')) + end +end diff --git a/app/lib/annual_report/most_used_apps.rb b/app/lib/annual_report/most_used_apps.rb new file mode 100644 index 0000000000..85ff1ff86e --- /dev/null +++ b/app/lib/annual_report/most_used_apps.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::MostUsedApps < AnnualReport::Source + SET_SIZE = 10 + + def generate + { + most_used_apps: most_used_apps.map do |(name, count)| + { + name: name, + count: count, + } + end, + } + end + + private + + def most_used_apps + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).joins(:application).group('oauth_applications.name').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('oauth_applications.name, count(*) as total')) + end +end diff --git a/app/lib/annual_report/percentiles.rb b/app/lib/annual_report/percentiles.rb new file mode 100644 index 0000000000..9fe4698ee5 --- /dev/null +++ b/app/lib/annual_report/percentiles.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class AnnualReport::Percentiles < AnnualReport::Source + def generate + { + percentiles: { + followers: (total_with_fewer_followers / (total_with_any_followers + 1.0)) * 100, + statuses: (total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100, + }, + } + end + + private + + def followers_gained + @followers_gained ||= @account.passive_relationships.where("date_part('year', follows.created_at) = ?", @year).count + end + + def statuses_created + @statuses_created ||= @account.statuses.where(id: year_as_snowflake_range).count + end + + def total_with_fewer_followers + @total_with_fewer_followers ||= Follow.find_by_sql([<<~SQL.squish, { year: @year, comparison: followers_gained }]).first.total + WITH tmp0 AS ( + SELECT follows.target_account_id + FROM follows + INNER JOIN accounts ON accounts.id = follows.target_account_id + WHERE date_part('year', follows.created_at) = :year + AND accounts.domain IS NULL + GROUP BY follows.target_account_id + HAVING COUNT(*) < :comparison + ) + SELECT count(*) AS total + FROM tmp0 + SQL + end + + def total_with_fewer_statuses + @total_with_fewer_statuses ||= Status.find_by_sql([<<~SQL.squish, { comparison: statuses_created, min_id: year_as_snowflake_range.first, max_id: year_as_snowflake_range.last }]).first.total + WITH tmp0 AS ( + SELECT statuses.account_id + FROM statuses + INNER JOIN accounts ON accounts.id = statuses.account_id + WHERE statuses.id BETWEEN :min_id AND :max_id + AND accounts.domain IS NULL + GROUP BY statuses.account_id + HAVING count(*) < :comparison + ) + SELECT count(*) AS total + FROM tmp0 + SQL + end + + def total_with_any_followers + @total_with_any_followers ||= Follow.where("date_part('year', follows.created_at) = ?", @year).joins(:target_account).merge(Account.local).count('distinct follows.target_account_id') + end + + def total_with_any_statuses + @total_with_any_statuses ||= Status.where(id: year_as_snowflake_range).joins(:account).merge(Account.local).count('distinct statuses.account_id') + end +end diff --git a/app/lib/annual_report/source.rb b/app/lib/annual_report/source.rb new file mode 100644 index 0000000000..1ccb622676 --- /dev/null +++ b/app/lib/annual_report/source.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AnnualReport::Source + attr_reader :account, :year + + def initialize(account, year) + @account = account + @year = year + end + + protected + + def year_as_snowflake_range + (Mastodon::Snowflake.id_at(DateTime.new(year, 1, 1))..Mastodon::Snowflake.id_at(DateTime.new(year, 12, 31))) + end +end diff --git a/app/lib/annual_report/time_series.rb b/app/lib/annual_report/time_series.rb new file mode 100644 index 0000000000..a144bac0d1 --- /dev/null +++ b/app/lib/annual_report/time_series.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class AnnualReport::TimeSeries < AnnualReport::Source + def generate + { + time_series: (1..12).map do |month| + { + month: month, + statuses: statuses_per_month[month] || 0, + following: following_per_month[month] || 0, + followers: followers_per_month[month] || 0, + } + end, + } + end + + private + + def statuses_per_month + @statuses_per_month ||= @account.statuses.reorder(nil).where(id: year_as_snowflake_range).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end + + def following_per_month + @following_per_month ||= @account.active_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end + + def followers_per_month + @followers_per_month ||= @account.passive_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end +end diff --git a/app/lib/annual_report/top_hashtags.rb b/app/lib/annual_report/top_hashtags.rb new file mode 100644 index 0000000000..488dacb1b4 --- /dev/null +++ b/app/lib/annual_report/top_hashtags.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::TopHashtags < AnnualReport::Source + SET_SIZE = 40 + + def generate + { + top_hashtags: top_hashtags.map do |(name, count)| + { + name: name, + count: count, + } + end, + } + end + + private + + def top_hashtags + Tag.joins(:statuses).where(statuses: { id: @account.statuses.where(id: year_as_snowflake_range).reorder(nil).select(:id) }).group(:id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('COALESCE(tags.display_name, tags.name), count(*) AS total')) + end +end diff --git a/app/lib/annual_report/top_statuses.rb b/app/lib/annual_report/top_statuses.rb new file mode 100644 index 0000000000..112e5591ce --- /dev/null +++ b/app/lib/annual_report/top_statuses.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class AnnualReport::TopStatuses < AnnualReport::Source + def generate + top_reblogs = base_scope.order(reblogs_count: :desc).first&.id + top_favourites = base_scope.where.not(id: top_reblogs).order(favourites_count: :desc).first&.id + top_replies = base_scope.where.not(id: [top_reblogs, top_favourites]).order(replies_count: :desc).first&.id + + { + top_statuses: { + by_reblogs: top_reblogs, + by_favourites: top_favourites, + by_replies: top_replies, + }, + } + end + + def base_scope + @account.statuses.with_public_visibility.joins(:status_stat).where(id: year_as_snowflake_range).reorder(nil) + end +end diff --git a/app/lib/annual_report/type_distribution.rb b/app/lib/annual_report/type_distribution.rb new file mode 100644 index 0000000000..fc12a6f1f4 --- /dev/null +++ b/app/lib/annual_report/type_distribution.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AnnualReport::TypeDistribution < AnnualReport::Source + def generate + { + type_distribution: { + total: base_scope.count, + reblogs: base_scope.where.not(reblog_of_id: nil).count, + replies: base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count, + standalone: base_scope.without_replies.without_reblogs.count, + }, + } + end + + private + + def base_scope + @account.statuses.where(id: year_as_snowflake_range) + end +end diff --git a/app/lib/attachment_batch.rb b/app/lib/attachment_batch.rb index b28f5c3d7f..32ccb0b13c 100644 --- a/app/lib/attachment_batch.rb +++ b/app/lib/attachment_batch.rb @@ -37,7 +37,7 @@ class AttachmentBatch def clear remove_files - batch.update_all(nullified_attributes) # rubocop:disable Rails/SkipsModelValidations + batch.update_all(nullified_attributes) end private diff --git a/app/lib/delivery_failure_tracker.rb b/app/lib/delivery_failure_tracker.rb index d938269829..e17b45d667 100644 --- a/app/lib/delivery_failure_tracker.rb +++ b/app/lib/delivery_failure_tracker.rb @@ -28,7 +28,7 @@ class DeliveryFailureTracker end def available? - !UnavailableDomain.where(domain: @host).exists? + !UnavailableDomain.exists?(domain: @host) end def exhausted_deliveries_days diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index a7b8456918..cdf4cdd228 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -470,8 +470,8 @@ class FeedManager check_for_blocks = status.active_mentions.pluck(:account_id) check_for_blocks.push(status.in_reply_to_account) if status.reply? && !status.in_reply_to_account_id.nil? - should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) - should_filter ||= status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists? # of if the account is silenced and I'm not following them + should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) + should_filter ||= status.account.silenced? && !Follow.exists?(account_id: receiver_id, target_account_id: status.account_id) # Filter if the account is silenced and I'm not following them should_filter end @@ -494,7 +494,7 @@ class FeedManager if status.reply? && status.in_reply_to_account_id != status.account_id should_filter = status.in_reply_to_account_id != list.account_id should_filter &&= !list.show_followed? - should_filter &&= !(list.show_list? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?) + should_filter &&= !(list.show_list? && ListAccount.exists?(list_id: list.id, account_id: status.in_reply_to_account_id)) return !!should_filter end diff --git a/app/lib/permalink_redirector.rb b/app/lib/permalink_redirector.rb index 0dd37483e2..f551f69db8 100644 --- a/app/lib/permalink_redirector.rb +++ b/app/lib/permalink_redirector.rb @@ -5,17 +5,46 @@ class PermalinkRedirector def initialize(path) @path = path + @object = nil + end + + def object + @object ||= begin + if at_username_status_request? || statuses_status_request? + status = Status.find_by(id: second_segment) + status if status&.distributable? && !status&.local? + elsif at_username_request? + username, domain = first_segment.delete_prefix('@').split('@') + domain = nil if TagManager.instance.local_domain?(domain) + account = Account.find_remote(username, domain) + account unless account&.local? + elsif accounts_request? && record_integer_id_request? + account = Account.find_by(id: second_segment) + account unless account&.local? + end + end end def redirect_path - if at_username_status_request? || statuses_status_request? - find_status_url_by_id(second_segment) - elsif at_username_request? - find_account_url_by_name(first_segment) - elsif accounts_request? && record_integer_id_request? - find_account_url_by_id(second_segment) - elsif @path.start_with?('/deck') - @path.delete_prefix('/deck') + return ActivityPub::TagManager.instance.url_for(object) if object.present? + + @path.delete_prefix('/deck') if @path.start_with?('/deck') + end + + def redirect_uri + return ActivityPub::TagManager.instance.uri_for(object) if object.present? + + @path.delete_prefix('/deck') if @path.start_with?('/deck') + end + + def redirect_confirmation_path + case object.class.name + when 'Account' + redirect_account_path(object.id) + when 'Status' + redirect_status_path(object.id) + else + @path.delete_prefix('/deck') if @path.start_with?('/deck') end end @@ -56,22 +85,4 @@ class PermalinkRedirector def path_segments @path_segments ||= @path.delete_prefix('/deck').delete_prefix('/').split('/') end - - def find_status_url_by_id(id) - status = Status.find_by(id: id) - ActivityPub::TagManager.instance.url_for(status) if status&.distributable? && !status.account.local? - end - - def find_account_url_by_id(id) - account = Account.find_by(id: id) - ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local? - end - - def find_account_url_by_name(name) - username, domain = name.gsub(/\A@/, '').split('@') - domain = nil if TagManager.instance.local_domain?(domain) - account = Account.find_remote(username, domain) - - ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local? - end end diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index 45b50cb379..34f6199ec0 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -26,11 +26,11 @@ class StatusCacheHydrator def hydrate_non_reblog_payload(empty_payload, account_id) empty_payload.tap do |payload| - payload[:favourited] = Favourite.where(account_id: account_id, status_id: @status.id).exists? - payload[:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.id).exists? - payload[:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.conversation_id).exists? - payload[:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.id).exists? - payload[:pinned] = StatusPin.where(account_id: account_id, status_id: @status.id).exists? if @status.account_id == account_id + payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.id) + payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.id) + payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.conversation_id) + payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.id) + payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.id) if @status.account_id == account_id payload[:filtered] = mapped_applied_custom_filter(account_id, @status) if payload[:poll] @@ -51,11 +51,11 @@ class StatusCacheHydrator # used to create the status, we need to hydrate it here too payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id - payload[:reblog][:favourited] = Favourite.where(account_id: account_id, status_id: @status.reblog_of_id).exists? - payload[:reblog][:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.reblog_of_id).exists? - payload[:reblog][:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.reblog.conversation_id).exists? - payload[:reblog][:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.reblog_of_id).exists? - payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? if @status.reblog.account_id == account_id + payload[:reblog][:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.reblog_of_id) + payload[:reblog][:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.reblog_of_id) + payload[:reblog][:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.reblog.conversation_id) + payload[:reblog][:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.reblog_of_id) + payload[:reblog][:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.reblog_of_id) if @status.reblog.account_id == account_id payload[:reblog][:filtered] = payload[:filtered] if payload[:reblog][:poll] diff --git a/app/lib/status_reach_finder.rb b/app/lib/status_reach_finder.rb index 36fb0e80fb..17e42e3ec3 100644 --- a/app/lib/status_reach_finder.rb +++ b/app/lib/status_reach_finder.rb @@ -16,28 +16,28 @@ class StatusReachFinder private def reached_account_inboxes + Account.where(id: reached_account_ids).inboxes + end + + def reached_account_ids # When the status is a reblog, there are no interactions with it # directly, we assume all interactions are with the original one if @status.reblog? - [] + [reblog_of_account_id] else - Account.where(id: reached_account_ids).inboxes - end - end - - def reached_account_ids - [ - replied_to_account_id, - reblog_of_account_id, - mentioned_account_ids, - reblogs_account_ids, - favourites_account_ids, - replies_account_ids, - ].tap do |arr| - arr.flatten! - arr.compact! - arr.uniq! + [ + replied_to_account_id, + reblog_of_account_id, + mentioned_account_ids, + reblogs_account_ids, + favourites_account_ids, + replies_account_ids, + ].tap do |arr| + arr.flatten! + arr.compact! + arr.uniq! + end end end diff --git a/app/lib/suspicious_sign_in_detector.rb b/app/lib/suspicious_sign_in_detector.rb index 1af5188c65..74f49aa558 100644 --- a/app/lib/suspicious_sign_in_detector.rb +++ b/app/lib/suspicious_sign_in_detector.rb @@ -19,7 +19,7 @@ class SuspiciousSignInDetector end def previously_seen_ip?(request) - @user.ips.where('ip <<= ?', masked_ip(request)).exists? + @user.ips.exists?(['ip <<= ?', masked_ip(request)]) end def freshly_signed_up? diff --git a/app/lib/vacuum/media_attachments_vacuum.rb b/app/lib/vacuum/media_attachments_vacuum.rb index ab7ea4092f..e558195290 100644 --- a/app/lib/vacuum/media_attachments_vacuum.rb +++ b/app/lib/vacuum/media_attachments_vacuum.rb @@ -27,11 +27,17 @@ class Vacuum::MediaAttachmentsVacuum end def media_attachments_past_retention_period - MediaAttachment.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago)) + MediaAttachment + .remote + .cached + .created_before(@retention_period.ago) + .updated_before(@retention_period.ago) end def orphaned_media_attachments - MediaAttachment.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago)) + MediaAttachment + .unattached + .created_before(TTL.ago) end def retention_period? diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 5eecfed104..4eb38ec340 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -12,6 +12,8 @@ class NotificationMailer < ApplicationMailer default to: -> { email_address_with_name(@user.email, @me.username) } + layout 'mailer' + def mention return unless @user.functional? && @status.present? diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 432b851b5e..3b1a085cb8 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -191,6 +191,18 @@ class UserMailer < Devise::Mailer end end + def failed_2fa(user, remote_ip, user_agent, timestamp) + @resource = user + @remote_ip = remote_ip + @user_agent = user_agent + @detection = Browser.new(user_agent) + @timestamp = timestamp.to_time.utc + + I18n.with_locale(locale) do + mail subject: default_i18n_subject + end + end + private def default_devise_subject diff --git a/app/models/account.rb b/app/models/account.rb index 0e38e07be8..55eee725d2 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -127,13 +127,13 @@ class Account < ApplicationRecord scope :bots, -> { where(actor_type: %w(Application Service)) } scope :groups, -> { where(actor_type: 'Group') } scope :alphabetic, -> { order(domain: :asc, username: :asc) } + scope :matches_uri_prefix, ->(value) { where(arel_table[:uri].matches("#{sanitize_sql_like(value)}/%", false, true)).or(where(uri: value)) } scope :matches_username, ->(value) { where('lower((username)::text) LIKE lower(?)', "#{value}%") } scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) } - scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } scope :without_unapproved, -> { left_outer_joins(:user).merge(User.approved.confirmed).or(remote) } + scope :auditable, -> { where(id: Admin::ActionLog.select(:account_id).distinct) } scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) } scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat) } - scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) } scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.order('last_status_at DESC NULLS LAST')).references(:account_stat) } scope :by_recent_activity, -> { left_joins(:user, :account_stat).order(coalesced_activity_timestamps.desc).order(id: :desc) } scope :popular, -> { order('account_stats.followers_count desc') } diff --git a/app/models/account_suggestions.rb b/app/models/account_suggestions.rb index d62176c7ca..25c8b04d50 100644 --- a/app/models/account_suggestions.rb +++ b/app/models/account_suggestions.rb @@ -29,7 +29,7 @@ class AccountSuggestions # a complicated query on this end. account_ids = account_ids_with_sources[offset, limit] - accounts_map = Account.where(id: account_ids.map(&:first)).includes(:account_stat).index_by(&:id) + accounts_map = Account.where(id: account_ids.map(&:first)).includes(:account_stat, :user).index_by(&:id) account_ids.filter_map do |(account_id, source)| next unless accounts_map.key?(account_id) diff --git a/app/models/account_suggestions/source.rb b/app/models/account_suggestions/source.rb index ee93a1342f..d83f5e3773 100644 --- a/app/models/account_suggestions/source.rb +++ b/app/models/account_suggestions/source.rb @@ -8,11 +8,31 @@ class AccountSuggestions::Source protected def base_account_scope(account) - Account.searchable - .followable_by(account) - .not_excluded_by_account(account) - .not_domain_blocked_by_account(account) - .where.not(id: account.id) - .joins("LEFT OUTER JOIN follow_recommendation_mutes ON follow_recommendation_mutes.target_account_id = accounts.id AND follow_recommendation_mutes.account_id = #{account.id}").where(follow_recommendation_mutes: { target_account_id: nil }) + Account + .searchable + .where.not(follows_sql, id: account.id) + .where.not(follow_requests_sql, id: account.id) + .not_excluded_by_account(account) + .not_domain_blocked_by_account(account) + .where.not(id: account.id) + .where.not(follow_recommendation_mutes_sql, id: account.id) + end + + def follows_sql + <<~SQL.squish + EXISTS (SELECT 1 FROM follows WHERE follows.target_account_id = accounts.id AND follows.account_id = :id) + SQL + end + + def follow_requests_sql + <<~SQL.squish + EXISTS (SELECT 1 FROM follow_requests WHERE follow_requests.target_account_id = accounts.id AND follow_requests.account_id = :id) + SQL + end + + def follow_recommendation_mutes_sql + <<~SQL.squish + EXISTS (SELECT 1 FROM follow_recommendation_mutes WHERE follow_recommendation_mutes.target_account_id = accounts.id AND follow_recommendation_mutes.account_id = :id) + SQL end end diff --git a/app/models/account_summary.rb b/app/models/account_summary.rb index 0d8835b83c..2a21d09a8b 100644 --- a/app/models/account_summary.rb +++ b/app/models/account_summary.rb @@ -12,9 +12,11 @@ class AccountSummary < ApplicationRecord self.primary_key = :account_id + has_many :follow_recommendation_suppressions, primary_key: :account_id, foreign_key: :account_id, inverse_of: false + scope :safe, -> { where(sensitive: false) } scope :localized, ->(locale) { where(language: locale) } - scope :filtered, -> { joins(arel_table.join(FollowRecommendationSuppression.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:account_id].eq(FollowRecommendationSuppression.arel_table[:account_id])).join_sources).where(FollowRecommendationSuppression.arel_table[:id].eq(nil)) } + scope :filtered, -> { where.missing(:follow_recommendation_suppressions) } def self.refresh Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false) diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb index d413cb386d..f581af74e8 100644 --- a/app/models/admin/action_log_filter.rb +++ b/app/models/admin/action_log_filter.rb @@ -72,7 +72,7 @@ class Admin::ActionLogFilter end def results - scope = latest_action_logs.includes(:target) + scope = latest_action_logs.includes(:target, :account) params.each do |key, value| next if key.to_s == 'page' diff --git a/app/models/announcement.rb b/app/models/announcement.rb index 86f7037a56..e630570020 100644 --- a/app/models/announcement.rb +++ b/app/models/announcement.rb @@ -75,22 +75,41 @@ class Announcement < ApplicationRecord end def reactions(account = nil) - records = begin - scope = announcement_reactions.group(:announcement_id, :name, :custom_emoji_id).order(Arel.sql('MIN(created_at) ASC')) - - if account.nil? - scope.select('name, custom_emoji_id, count(*) as count, false as me') - else - scope.select("name, custom_emoji_id, count(*) as count, exists(select 1 from announcement_reactions r where r.account_id = #{account.id} and r.announcement_id = announcement_reactions.announcement_id and r.name = announcement_reactions.name) as me") + grouped_ordered_announcement_reactions.select( + [:name, :custom_emoji_id, 'COUNT(*) as count'].tap do |values| + values << value_for_reaction_me_column(account) end - end.to_a - - ActiveRecord::Associations::Preloader.new(records: records, associations: :custom_emoji).call - records + ).to_a.tap do |records| + ActiveRecord::Associations::Preloader.new(records: records, associations: :custom_emoji).call + end end private + def grouped_ordered_announcement_reactions + announcement_reactions + .group(:announcement_id, :name, :custom_emoji_id) + .order( + Arel.sql('MIN(created_at)').asc + ) + end + + def value_for_reaction_me_column(account) + if account.nil? + 'FALSE AS me' + else + <<~SQL.squish + EXISTS( + SELECT 1 + FROM announcement_reactions inner_reactions + WHERE inner_reactions.account_id = #{account.id} + AND inner_reactions.announcement_id = announcement_reactions.announcement_id + AND inner_reactions.name = announcement_reactions.name + ) AS me + SQL + end + end + def set_published return unless scheduled_at.blank? || scheduled_at.past? diff --git a/app/models/appeal.rb b/app/models/appeal.rb index f1290ad01a..395056b76f 100644 --- a/app/models/appeal.rb +++ b/app/models/appeal.rb @@ -20,8 +20,11 @@ class Appeal < ApplicationRecord belongs_to :account belongs_to :strike, class_name: 'AccountWarning', foreign_key: 'account_warning_id', inverse_of: :appeal - belongs_to :approved_by_account, class_name: 'Account', optional: true - belongs_to :rejected_by_account, class_name: 'Account', optional: true + + with_options class_name: 'Account', optional: true do + belongs_to :approved_by_account + belongs_to :rejected_by_account + end validates :text, presence: true, length: { maximum: 2_000 } validates :account_warning_id, uniqueness: true diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb index 810e471849..406fb2aba2 100644 --- a/app/models/bulk_import.rb +++ b/app/models/bulk_import.rb @@ -44,8 +44,8 @@ class BulkImport < ApplicationRecord def self.progress!(bulk_import_id, imported: false) # Use `increment_counter` so that the incrementation is done atomically in the database - BulkImport.increment_counter(:processed_items, bulk_import_id) # rubocop:disable Rails/SkipsModelValidations - BulkImport.increment_counter(:imported_items, bulk_import_id) if imported # rubocop:disable Rails/SkipsModelValidations + BulkImport.increment_counter(:processed_items, bulk_import_id) + BulkImport.increment_counter(:imported_items, bulk_import_id) if imported # Since the incrementation has been done atomically, concurrent access to `bulk_import` is now bening bulk_import = BulkImport.find(bulk_import_id) diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index 351530c2f0..5b05c31e03 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -183,7 +183,7 @@ module Account::Interactions end def following?(other_account) - active_relationships.where(target_account: other_account).exists? + active_relationships.exists?(target_account: other_account) end def following_anyone? @@ -199,51 +199,51 @@ module Account::Interactions end def blocking?(other_account) - block_relationships.where(target_account: other_account).exists? + block_relationships.exists?(target_account: other_account) end def domain_blocking?(other_domain) - domain_blocks.where(domain: other_domain).exists? + domain_blocks.exists?(domain: other_domain) end def muting?(other_account) - mute_relationships.where(target_account: other_account).exists? + mute_relationships.exists?(target_account: other_account) end def muting_conversation?(conversation) - conversation_mutes.where(conversation: conversation).exists? + conversation_mutes.exists?(conversation: conversation) end def muting_notifications?(other_account) - mute_relationships.where(target_account: other_account, hide_notifications: true).exists? + mute_relationships.exists?(target_account: other_account, hide_notifications: true) end def muting_reblogs?(other_account) - active_relationships.where(target_account: other_account, show_reblogs: false).exists? + active_relationships.exists?(target_account: other_account, show_reblogs: false) end def requested?(other_account) - follow_requests.where(target_account: other_account).exists? + follow_requests.exists?(target_account: other_account) end def favourited?(status) - status.proper.favourites.where(account: self).exists? + status.proper.favourites.exists?(account: self) end def bookmarked?(status) - status.proper.bookmarks.where(account: self).exists? + status.proper.bookmarks.exists?(account: self) end def reblogged?(status) - status.proper.reblogs.where(account: self).exists? + status.proper.reblogs.exists?(account: self) end def pinned?(status) - status_pins.where(status: status).exists? + status_pins.exists?(status: status) end def endorsed?(account) - account_pins.where(target_account: account).exists? + account_pins.exists?(target_account: account) end def status_matches_filters(status) diff --git a/app/models/concerns/user/omniauthable.rb b/app/models/concerns/user/omniauthable.rb index 6d1d1b8cc3..113bfda230 100644 --- a/app/models/concerns/user/omniauthable.rb +++ b/app/models/concerns/user/omniauthable.rb @@ -61,7 +61,7 @@ module User::Omniauthable user.account.avatar_remote_url = nil end - user.confirm! if email_is_verified + user.mark_email_as_confirmed! if email_is_verified user.save! user end diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 371267fc28..c8120c2395 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -68,16 +68,7 @@ class CustomFilter < ApplicationRecord scope = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account_id: account_id }).where(Arel.sql('expires_at IS NULL OR expires_at > NOW()')) scope.to_a.group_by(&:custom_filter).each do |filter, keywords| - keywords.map! do |keyword| - if keyword.whole_word - sb = /\A[[:word:]]/.match?(keyword.keyword) ? '\b' : '' - eb = /[[:word:]]\z/.match?(keyword.keyword) ? '\b' : '' - - /(?mix:#{sb}#{Regexp.escape(keyword.keyword)}#{eb})/ - else - /#{Regexp.escape(keyword.keyword)}/i - end - end + keywords.map!(&:to_regex) filters_hash[filter.id] = { keywords: Regexp.union(keywords), filter: filter } end.to_h diff --git a/app/models/custom_filter_keyword.rb b/app/models/custom_filter_keyword.rb index 3158b3b79a..979d0b822e 100644 --- a/app/models/custom_filter_keyword.rb +++ b/app/models/custom_filter_keyword.rb @@ -23,8 +23,24 @@ class CustomFilterKeyword < ApplicationRecord before_destroy :prepare_cache_invalidation! after_commit :invalidate_cache! + def to_regex + if whole_word? + /(?mix:#{to_regex_sb}#{Regexp.escape(keyword)}#{to_regex_eb})/ + else + /#{Regexp.escape(keyword)}/i + end + end + private + def to_regex_sb + /\A[[:word:]]/.match?(keyword) ? '\b' : '' + end + + def to_regex_eb + /[[:word:]]\z/.match?(keyword) ? '\b' : '' + end + def prepare_cache_invalidation! custom_filter.prepare_cache_invalidation! end diff --git a/app/models/domain_allow.rb b/app/models/domain_allow.rb index ce9597b4d1..47ada7ac23 100644 --- a/app/models/domain_allow.rb +++ b/app/models/domain_allow.rb @@ -17,8 +17,6 @@ class DomainAllow < ApplicationRecord validates :domain, presence: true, uniqueness: true, domain: true - scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } - def to_log_human_identifier domain end diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 8da099256a..a05db099a8 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -28,7 +28,6 @@ class DomainBlock < ApplicationRecord has_many :accounts, foreign_key: :domain, primary_key: :domain, inverse_of: false, dependent: nil delegate :count, to: :accounts, prefix: true - scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]) } scope :with_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) } scope :by_severity, -> { in_order_of(:severity, %w(noop silence suspend)).order(:domain) } diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb index f1b14c8b08..40be59420a 100644 --- a/app/models/email_domain_block.rb +++ b/app/models/email_domain_block.rb @@ -21,8 +21,10 @@ class EmailDomainBlock < ApplicationRecord include DomainNormalizable include Paginable - belongs_to :parent, class_name: 'EmailDomainBlock', optional: true - has_many :children, class_name: 'EmailDomainBlock', foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy + with_options class_name: 'EmailDomainBlock' do + belongs_to :parent, optional: true + has_many :children, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy + end validates :domain, presence: true, uniqueness: true, domain: true diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb index 7c36aa8b0b..ea8aa4787c 100644 --- a/app/models/featured_tag.rb +++ b/app/models/featured_tag.rb @@ -45,7 +45,7 @@ class FeaturedTag < ApplicationRecord end def decrement(deleted_status_id) - update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at) + update(statuses_count: [0, statuses_count - 1].max, last_status_at: visible_tagged_account_statuses.where.not(id: deleted_status_id).select(:created_at).first&.created_at) end private @@ -55,8 +55,8 @@ class FeaturedTag < ApplicationRecord end def reset_data - self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count - self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at + self.statuses_count = visible_tagged_account_statuses.count + self.last_status_at = visible_tagged_account_statuses.select(:created_at).first&.created_at end def validate_featured_tags_limit @@ -66,6 +66,14 @@ class FeaturedTag < ApplicationRecord end def validate_tag_uniqueness - errors.add(:name, :taken) if FeaturedTag.by_name(name).where(account_id: account_id).exists? + errors.add(:name, :taken) if tag_already_featured_for_account? + end + + def tag_already_featured_for_account? + FeaturedTag.by_name(name).exists?(account_id: account_id) + end + + def visible_tagged_account_statuses + account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag) end end diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 3c5e8f96f0..c13cc718d8 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -33,7 +33,7 @@ class FollowRequest < ApplicationRecord def authorize! follow = account.follow!(target_account, reblogs: show_reblogs, notify: notify, languages: languages, uri: uri, bypass_limit: true) - ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id) # rubocop:disable Rails/SkipsModelValidations + ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id) MergeWorker.perform_async(target_account.id, account.id) if account.local? destroy! end diff --git a/app/models/form/import.rb b/app/models/form/import.rb index 712acf3706..fc83d9c58c 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -69,7 +69,7 @@ class Form::Import ApplicationRecord.transaction do now = Time.now.utc @bulk_import = current_account.bulk_imports.create(type: type, overwrite: overwrite || false, state: :unconfirmed, original_filename: data.original_filename, likely_mismatched: likely_mismatched?) - nb_items = BulkImportRow.insert_all(parsed_rows.map { |row| { bulk_import_id: bulk_import.id, data: row, created_at: now, updated_at: now } }).length # rubocop:disable Rails/SkipsModelValidations + nb_items = BulkImportRow.insert_all(parsed_rows.map { |row| { bulk_import_id: bulk_import.id, data: row, created_at: now, updated_at: now } }).length @bulk_import.update(total_items: nb_items) end end diff --git a/app/models/generated_annual_report.rb b/app/models/generated_annual_report.rb new file mode 100644 index 0000000000..43c97d7108 --- /dev/null +++ b/app/models/generated_annual_report.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: generated_annual_reports +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# year :integer not null +# data :jsonb not null +# schema_version :integer not null +# viewed_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# + +class GeneratedAnnualReport < ApplicationRecord + belongs_to :account + + scope :pending, -> { where(viewed_at: nil) } + + def viewed? + viewed_at.present? + end + + def view! + update!(viewed_at: Time.now.utc) + end + + def account_ids + data['most_reblogged_accounts'].pluck('account_id') + data['commonly_interacted_with_accounts'].pluck('account_id') + end + + def status_ids + data['top_statuses'].values + end +end diff --git a/app/models/instance.rb b/app/models/instance.rb index 17ee0cbb1e..0fd31c8097 100644 --- a/app/models/instance.rb +++ b/app/models/instance.rb @@ -13,22 +13,37 @@ class Instance < ApplicationRecord attr_accessor :failure_days - has_many :accounts, foreign_key: :domain, primary_key: :domain, inverse_of: false - with_options foreign_key: :domain, primary_key: :domain, inverse_of: false do belongs_to :domain_block belongs_to :domain_allow - belongs_to :unavailable_domain # skipcq: RB-RL1031 + belongs_to :unavailable_domain + + has_many :accounts, dependent: nil end scope :searchable, -> { where.not(domain: DomainBlock.select(:domain)) } scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } + scope :domain_starts_with, ->(value) { where(arel_table[:domain].matches("#{sanitize_sql_like(value)}%", false, true)) } scope :by_domain_and_subdomains, ->(domain) { where("reverse('.' || domain) LIKE reverse(?)", "%.#{domain}") } + scope :with_domain_follows, ->(domains) { where(domain: domains).where(domain_account_follows) } def self.refresh Scenic.database.refresh_materialized_view(table_name, concurrently: true, cascade: false) end + def self.domain_account_follows + Arel.sql( + <<~SQL.squish + EXISTS ( + SELECT 1 + FROM follows + JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id + WHERE accounts.domain = instances.domain + ) + SQL + ) + end + def readonly? true end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index f6b5745696..12cc69bfd6 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -202,12 +202,14 @@ class MediaAttachment < ApplicationRecord validates :account, presence: true validates :description, length: { maximum: (ENV['MAX_AUDIO_LENGTH'] || 420).to_i }, if: :local? - scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } - scope :cached, -> { remote.where.not(file_file_name: nil) } - scope :local, -> { where(remote_url: '') } - scope :ordered, -> { order(id: :asc) } - scope :remote, -> { where.not(remote_url: '') } + scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } + scope :cached, -> { remote.where.not(file_file_name: nil) } + scope :created_before, ->(value) { where(arel_table[:created_at].lt(value)) } + scope :local, -> { where(remote_url: '') } + scope :ordered, -> { order(id: :asc) } + scope :remote, -> { where.not(remote_url: '') } scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) } + scope :updated_before, ->(value) { where(arel_table[:updated_at].lt(value)) } attr_accessor :skip_download diff --git a/app/models/poll.rb b/app/models/poll.rb index 72f04f00a7..cc4184f80a 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -27,8 +27,11 @@ class Poll < ApplicationRecord belongs_to :status has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :delete_all - has_many :voters, -> { group('accounts.id') }, through: :votes, class_name: 'Account', source: :account - has_many :local_voters, -> { group('accounts.id').merge(Account.local) }, through: :votes, class_name: 'Account', source: :account + + with_options class_name: 'Account', source: :account, through: :votes do + has_many :voters, -> { group('accounts.id') } + has_many :local_voters, -> { group('accounts.id').merge(Account.local) } + end has_many :notifications, as: :activity, dependent: :destroy @@ -54,7 +57,7 @@ class Poll < ApplicationRecord end def voted?(account) - account.id == account_id || votes.where(account: account).exists? + account.id == account_id || votes.exists?(account: account) end def own_votes(account) diff --git a/app/models/privacy_policy.rb b/app/models/privacy_policy.rb index 36cbf18822..c0d6e1b76d 100644 --- a/app/models/privacy_policy.rb +++ b/app/models/privacy_policy.rb @@ -1,66 +1,7 @@ # frozen_string_literal: true class PrivacyPolicy < ActiveModelSerializers::Model - DEFAULT_PRIVACY_POLICY = <<~TXT - This privacy policy describes how %{domain} ("%{domain}", "we", "us") collects, protects and uses the personally identifiable information you may provide through the %{domain} website or its API. The policy also describes the choices available to you regarding our use of your personal information and how you can access and update this information. This policy does not apply to the practices of companies that %{domain} does not own or control, or to individuals that %{domain} does not employ or manage. - - # What information do we collect? - - - **Basic account information**: If you register on this server, you may be asked to enter a username, an e-mail address and a password. You may also enter additional profile information such as a display name and biography, and upload a profile picture and header image. The username, display name, biography, profile picture and header image are always listed publicly. - - **Posts, following and other public information**: The list of people you follow is listed publicly, the same is true for your followers. When you submit a message, the date and time is stored as well as the application you submitted the message from. Messages may contain media attachments, such as pictures and videos. Public and unlisted posts are available publicly. When you feature a post on your profile, that is also publicly available information. Your posts are delivered to your followers, in some cases it means they are delivered to different servers and copies are stored there. When you delete posts, this is likewise delivered to your followers. The action of reblogging or favouriting another post is always public. - - **Direct and followers-only posts**: All posts are stored and processed on the server. Followers-only posts are delivered to your followers and users who are mentioned in them, and direct posts are delivered only to users mentioned in them. In some cases it means they are delivered to different servers and copies are stored there. We make a good faith effort to limit the access to those posts only to authorized persons, but other servers may fail to do so. Therefore it's important to review servers your followers belong to. You may toggle an option to approve and reject new followers manually in the settings. **Please keep in mind that the operators of the server and any receiving server may view such messages**, and that recipients may screenshot, copy or otherwise re-share them. **Do not share any sensitive information over Mastodon.** - - **IPs and other metadata**: When you log in, we record the IP address you log in from, as well as the name of your browser application. All the logged in sessions are available for your review and revocation in the settings. The latest IP address used is stored for up to 12 months. We also may retain server logs which include the IP address of every request to our server. - - # What do we use your information for? - - Any of the information we collect from you may be used in the following ways: - - - To provide the core functionality of Mastodon. You can only interact with other people's content and post your own content when you are logged in. For example, you may follow other people to view their combined posts in your own personalized home timeline. - - To aid moderation of the community, for example comparing your IP address with other known ones to determine ban evasion or other violations. - - The email address you provide may be used to send you information, notifications about other people interacting with your content or sending you messages, and to respond to inquiries, and/or other requests or questions. - - # How do we protect your information? - - We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information. Among other things, your browser session, as well as the traffic between your applications and the API, are secured with SSL, and your password is hashed using a strong one-way algorithm. You may enable two-factor authentication to further secure access to your account. - - # What is our data retention policy? - - We will make a good faith effort to: - - - Retain server logs containing the IP address of all requests to this server, in so far as such logs are kept, no more than 90 days. - - Retain the IP addresses associated with registered users no more than 12 months. - - You can request and download an archive of your content, including your posts, media attachments, profile picture, and header image. - - You may irreversibly delete your account at any time. - - # Do we use cookies? - - Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account. - - We use cookies to understand and save your preferences for future visits. - - # Do we disclose any information to outside parties? - - We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. - - Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this. - - When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password. - - # Site usage by children - - If this server is in the EU or the EEA: Our site, products and services are all directed to people who are at least 16 years old. If you are under the age of 16, per the requirements of the GDPR (General Data Protection Regulation) do not use this site. - - If this server is in the USA: Our site, products and services are all directed to people who are at least 13 years old. If you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site. - - Law requirements can be different if this server is in another jurisdiction. - - ___ - - This document is CC-BY-SA. Originally adapted from the [Discourse privacy policy](https://github.com/discourse/discourse). - TXT - + DEFAULT_PRIVACY_POLICY = Rails.root.join('config', 'templates', 'privacy-policy.md').read DEFAULT_UPDATED_AT = DateTime.new(2022, 10, 7).freeze attributes :updated_at, :text diff --git a/app/models/report.rb b/app/models/report.rb index c565362cc6..38da26d7b7 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -29,16 +29,19 @@ class Report < ApplicationRecord rate_limit by: :account, family: :reports belongs_to :account - belongs_to :target_account, class_name: 'Account' - belongs_to :action_taken_by_account, class_name: 'Account', optional: true - belongs_to :assigned_account, class_name: 'Account', optional: true + + with_options class_name: 'Account' do + belongs_to :target_account + belongs_to :action_taken_by_account, optional: true + belongs_to :assigned_account, optional: true + end has_many :notes, class_name: 'ReportNote', inverse_of: :report, dependent: :destroy has_many :notifications, as: :activity, dependent: :destroy scope :unresolved, -> { where(action_taken_at: nil) } scope :resolved, -> { where.not(action_taken_at: nil) } - scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) } + scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with([:account_stat, { user: [:invite_request, :invite, :ips] }])) } # A report is considered local if the reporter is local delegate :local?, to: :account diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index 7f5f0d9a9a..c67180d3ba 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -41,7 +41,7 @@ class SessionActivation < ApplicationRecord class << self def active?(id) - id && where(session_id: id).exists? + id && exists?(session_id: id) end def activate(**options) diff --git a/app/models/status.rb b/app/models/status.rb index 8963331a9d..18c9176b3a 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -61,8 +61,10 @@ class Status < ApplicationRecord belongs_to :conversation, optional: true belongs_to :preloadable_poll, class_name: 'Poll', foreign_key: 'poll_id', optional: true, inverse_of: false - belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true - belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true + with_options class_name: 'Status', optional: true do + belongs_to :thread, foreign_key: 'in_reply_to_id', inverse_of: :replies + belongs_to :reblog, foreign_key: 'reblog_of_id', inverse_of: :reblogs + end has_many :favourites, inverse_of: :status, dependent: :destroy has_many :bookmarks, inverse_of: :status, dependent: :destroy @@ -268,7 +270,7 @@ class Status < ApplicationRecord end def reported? - @reported ||= Report.where(target_account: account).unresolved.where('? = ANY(status_ids)', id).exists? + @reported ||= Report.where(target_account: account).unresolved.exists?(['? = ANY(status_ids)', id]) end def emojis diff --git a/app/models/tag.rb b/app/models/tag.rb index 46e55d74f9..f2168ae904 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -39,6 +39,8 @@ class Tag < ApplicationRecord HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/ + RECENT_STATUS_LIMIT = 1000 + validates :name, presence: true, format: { with: HASHTAG_NAME_RE } validates :display_name, format: { with: HASHTAG_NAME_RE } validate :validate_name_change, if: -> { !new_record? && name_changed? } @@ -53,7 +55,7 @@ class Tag < ApplicationRecord scope :not_trendable, -> { where(trendable: false) } scope :recently_used, lambda { |account| joins(:statuses) - .where(statuses: { id: account.statuses.select(:id).limit(1000) }) + .where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) }) .group(:id).order(Arel.sql('count(*) desc')) } scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index diff --git a/app/models/user.rb b/app/models/user.rb index 56e575ca42..cc56c2f54e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -149,6 +149,10 @@ class User < ApplicationRecord end end + def self.skip_mx_check? + Rails.env.local? + end + def role if role_id.nil? UserRole.everyone @@ -186,37 +190,16 @@ class User < ApplicationRecord end def confirm - new_user = !confirmed? - self.approved = true if open_registrations? && !sign_up_from_ip_requires_approval? - - super - - if new_user - # Avoid extremely unlikely race condition when approving and confirming - # the user at the same time - reload unless approved? - - if approved? - prepare_new_user! - else - notify_staff_about_pending_account! - end + wrap_email_confirmation do + super end end - def confirm! - new_user = !confirmed? - self.approved = true if open_registrations? - - skip_confirmation! - save! - - if new_user - # Avoid extremely unlikely race condition when approving and confirming - # the user at the same time - reload unless approved? - - prepare_new_user! if approved? + # Mark current email as confirmed, bypassing Devise + def mark_email_as_confirmed! + wrap_email_confirmation do + skip_confirmation! + save! end end @@ -426,14 +409,53 @@ class User < ApplicationRecord end end + def grant_approval_on_confirmation? + # Re-check approval on confirmation if the server has switched to open registrations + open_registrations? && !sign_up_from_ip_requires_approval? && !sign_up_email_requires_approval? + end + + def wrap_email_confirmation + new_user = !confirmed? + self.approved = true if grant_approval_on_confirmation? + + yield + + if new_user + # Avoid extremely unlikely race condition when approving and confirming + # the user at the same time + reload unless approved? + + if approved? + prepare_new_user! + else + notify_staff_about_pending_account! + end + end + end + def sign_up_from_ip_requires_approval? - !sign_up_ip.nil? && IpBlock.where(severity: :sign_up_requires_approval).where('ip >>= ?', sign_up_ip.to_s).exists? + sign_up_ip.present? && IpBlock.sign_up_requires_approval.exists?(['ip >>= ?', sign_up_ip.to_s]) end def sign_up_email_requires_approval? - return false unless email.present? || unconfirmed_email.present? + return false if email.blank? - EmailDomainBlock.requires_approval?(email.presence || unconfirmed_email, attempt_ip: sign_up_ip) + _, domain = email.split('@', 2) + return false if domain.blank? + + records = [] + + # Doing this conditionally is not very satisfying, but this is consistent + # with the MX records validations we do and keeps the specs tractable. + unless self.class.skip_mx_check? + Resolv::DNS.open do |dns| + dns.timeouts = 5 + + records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }.compact_blank + end + end + + EmailDomainBlock.requires_approval?(records + [domain], attempt_ip: sign_up_ip) end def open_registrations? @@ -484,7 +506,7 @@ class User < ApplicationRecord end def validate_email_dns? - email_changed? && !external? && !Rails.env.local? + email_changed? && !external? && !self.class.skip_mx_check? end def validate_role_elevation diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index cb45750d76..fb60ca20ce 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -58,7 +58,7 @@ class StatusPolicy < ApplicationPolicy if record.mentions.loaded? record.mentions.any? { |mention| mention.account_id == current_account.id } else - record.mentions.where(account: current_account).exists? + record.mentions.exists?(account: current_account) end end diff --git a/app/presenters/annual_reports_presenter.rb b/app/presenters/annual_reports_presenter.rb new file mode 100644 index 0000000000..001e1d37b0 --- /dev/null +++ b/app/presenters/annual_reports_presenter.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class AnnualReportsPresenter + alias read_attribute_for_serialization send + + attr_reader :annual_reports + + def initialize(annual_reports) + @annual_reports = annual_reports + end + + def accounts + @accounts ||= Account.where(id: @annual_reports.flat_map(&:account_ids)).includes(:account_stat, :moved_to_account, user: :role) + end + + def statuses + @statuses ||= Status.where(id: @annual_reports.flat_map(&:status_ids)).with_includes + end + + def self.model_name + @model_name ||= ActiveModel::Name.new(self) + end +end diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb index 501bb788e7..1c1f7d0ad5 100644 --- a/app/serializers/manifest_serializer.rb +++ b/app/serializers/manifest_serializer.rb @@ -39,7 +39,7 @@ class ManifestSerializer < ActiveModel::Serializer def icons ICON_SIZES.map do |size| { - src: full_pack_url("media/icons/android-chrome-#{size}x#{size}.png"), + src: frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), sizes: "#{size}x#{size}", type: 'image/png', purpose: 'any maskable', diff --git a/app/serializers/rest/announcement_serializer.rb b/app/serializers/rest/announcement_serializer.rb index 23b2fa514b..8cee271272 100644 --- a/app/serializers/rest/announcement_serializer.rb +++ b/app/serializers/rest/announcement_serializer.rb @@ -23,7 +23,7 @@ class REST::AnnouncementSerializer < ActiveModel::Serializer end def read - object.announcement_mutes.where(account: current_user.account).exists? + object.announcement_mutes.exists?(account: current_user.account) end def content diff --git a/app/serializers/rest/annual_report_serializer.rb b/app/serializers/rest/annual_report_serializer.rb new file mode 100644 index 0000000000..1fb5ddb5c1 --- /dev/null +++ b/app/serializers/rest/annual_report_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class REST::AnnualReportSerializer < ActiveModel::Serializer + attributes :year, :data, :schema_version +end diff --git a/app/serializers/rest/annual_reports_serializer.rb b/app/serializers/rest/annual_reports_serializer.rb new file mode 100644 index 0000000000..ea9572be1b --- /dev/null +++ b/app/serializers/rest/annual_reports_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class REST::AnnualReportsSerializer < ActiveModel::Serializer + has_many :annual_reports, serializer: REST::AnnualReportSerializer + has_many :accounts, serializer: REST::AccountSerializer + has_many :statuses, serializer: REST::StatusSerializer +end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index 0434687999..e7d4a646d4 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -28,7 +28,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer } else { - url: full_pack_url('media/images/preview.png'), + url: frontend_asset_url('images/preview.png'), } end end diff --git a/app/serializers/rest/tag_serializer.rb b/app/serializers/rest/tag_serializer.rb index 7801e77d1f..017b572718 100644 --- a/app/serializers/rest/tag_serializer.rb +++ b/app/serializers/rest/tag_serializer.rb @@ -19,7 +19,7 @@ class REST::TagSerializer < ActiveModel::Serializer if instance_options && instance_options[:relationships] instance_options[:relationships].following_map[object.id] || false else - TagFollow.where(tag_id: object.id, account_id: current_user.account_id).exists? + TagFollow.exists?(tag_id: object.id, account_id: current_user.account_id) end end diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb index 0c21014046..c6cbaba489 100644 --- a/app/serializers/rest/v1/instance_serializer.rb +++ b/app/serializers/rest/v1/instance_serializer.rb @@ -33,7 +33,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer end def thumbnail - instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url(:'@1x')) : full_pack_url('media/images/preview.png') + instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url(:'@1x')) : frontend_asset_url('images/preview.png') end def max_toot_chars diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index d2bae08a0e..89c3a1b6c0 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -23,9 +23,9 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService case collection['type'] when 'Collection', 'CollectionPage' - collection['items'] + as_array(collection['items']) when 'OrderedCollection', 'OrderedCollectionPage' - collection['orderedItems'] + as_array(collection['orderedItems']) end end diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index a491b32b26..e3a9b60b56 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -44,7 +44,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService # If we fetched a status that already exists, then we need to treat the # activity as an update rather than create - activity_json['type'] = 'Update' if equals_or_includes_any?(activity_json['type'], %w(Create)) && Status.where(uri: object_uri, account_id: actor.id).exists? + activity_json['type'] = 'Update' if equals_or_includes_any?(activity_json['type'], %w(Create)) && Status.exists?(uri: object_uri, account_id: actor.id) with_redis do |redis| discoveries = redis.incr("status_discovery_per_request:#{@request_id}") diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index b5c7759ec5..e2ecdef165 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -26,9 +26,9 @@ class ActivityPub::FetchRepliesService < BaseService case collection['type'] when 'Collection', 'CollectionPage' - collection['items'] + as_array(collection['items']) when 'OrderedCollection', 'OrderedCollectionPage' - collection['orderedItems'] + as_array(collection['orderedItems']) end end @@ -37,7 +37,20 @@ class ActivityPub::FetchRepliesService < BaseService return unless @allow_synchronous_requests return if non_matching_uri_hosts?(@account.uri, collection_or_uri) - fetch_resource_without_id_validation(collection_or_uri, nil, true) + # NOTE: For backward compatibility reasons, Mastodon signs outgoing + # queries incorrectly by default. + # + # While this is relevant for all URLs with query strings, this is + # the only code path where this happens in practice. + # + # Therefore, retry with correct signatures if this fails. + begin + fetch_resource_without_id_validation(collection_or_uri, nil, true) + rescue Mastodon::UnexpectedResponseError => e + raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present? + + fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { with_query_string: true }) + end end def filtered_replies diff --git a/app/services/activitypub/synchronize_followers_service.rb b/app/services/activitypub/synchronize_followers_service.rb index 7ccc917309..f51d671a00 100644 --- a/app/services/activitypub/synchronize_followers_service.rb +++ b/app/services/activitypub/synchronize_followers_service.rb @@ -59,9 +59,9 @@ class ActivityPub::SynchronizeFollowersService < BaseService case collection['type'] when 'Collection', 'CollectionPage' - collection['items'] + as_array(collection['items']) when 'OrderedCollection', 'OrderedCollectionPage' - collection['orderedItems'] + as_array(collection['orderedItems']) end end diff --git a/app/services/keys/query_service.rb b/app/services/keys/query_service.rb index 14c9d9205b..33e13293f3 100644 --- a/app/services/keys/query_service.rb +++ b/app/services/keys/query_service.rb @@ -69,7 +69,7 @@ class Keys::QueryService < BaseService return if json['items'].blank? - @devices = json['items'].map do |device| + @devices = as_array(json['items']).map do |device| Device.new(device_id: device['id'], name: device['name'], identity_key: device.dig('identityKey', 'publicKeyBase64'), fingerprint_key: device.dig('fingerprintKey', 'publicKeyBase64'), claim_url: device['claim']) end rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 18e1748cea..9c3d410182 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -43,11 +43,7 @@ class ReblogService < BaseService def create_notification(reblog) reblogged_status = reblog.reblog - if reblogged_status.account.local? - LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, 'reblog') - elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account) - ActivityPub::DeliveryWorker.perform_async(build_json(reblog), reblog.account_id, reblogged_status.account.inbox_url) - end + LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, 'reblog') if reblogged_status.account.local? end def increment_statistics diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb index 3e92a1690a..878350388b 100644 --- a/app/services/vote_service.rb +++ b/app/services/vote_service.rb @@ -19,7 +19,7 @@ class VoteService < BaseService already_voted = true with_redis_lock("vote:#{@poll.id}:#{@account.id}") do - already_voted = @poll.votes.where(account: @account).exists? + already_voted = @poll.votes.exists?(account: @account) ApplicationRecord.transaction do @choices.each do |choice| diff --git a/app/validators/reaction_validator.rb b/app/validators/reaction_validator.rb index 4ed3376e8b..89d83de5a2 100644 --- a/app/validators/reaction_validator.rb +++ b/app/validators/reaction_validator.rb @@ -19,7 +19,7 @@ class ReactionValidator < ActiveModel::Validator end def new_reaction?(reaction) - !reaction.announcement.announcement_reactions.where(name: reaction.name).exists? + !reaction.announcement.announcement_reactions.exists?(name: reaction.name) end def limit_reached?(reaction) diff --git a/app/validators/vote_validator.rb b/app/validators/vote_validator.rb index fa2bd223dc..e725b4c0b8 100644 --- a/app/validators/vote_validator.rb +++ b/app/validators/vote_validator.rb @@ -35,7 +35,7 @@ class VoteValidator < ActiveModel::Validator if vote.persisted? account_votes_on_same_poll(vote).where(choice: vote.choice).where.not(poll_votes: { id: vote }).exists? else - account_votes_on_same_poll(vote).where(choice: vote.choice).exists? + account_votes_on_same_poll(vote).exists?(choice: vote.choice) end end diff --git a/app/views/application/mailer/_account.html.haml b/app/views/application/mailer/_account.html.haml new file mode 100644 index 0000000000..b0ef4f1e02 --- /dev/null +++ b/app/views/application/mailer/_account.html.haml @@ -0,0 +1,30 @@ +%table.email-w-full.email-account-banner-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-account-banner-td{ height: 140, background: full_asset_url(account.header.url) } + %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-account-banner-inner-td + .email-account-banner-overlap-div + %table.email-account-banner-icon-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td + %img{ src: full_asset_url(account.avatar.url), width: 80, height: 80, alt: '' } +%table.email-w-full.email-account-body-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-account-body-td + %p.email-account-name= display_name(account) + %p.email-account-handle= acct(account) + %table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-padding-top-16 + %table.email-w-full.email-account-stats-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td + %b= account_formatted_stat(account.statuses_count) + %span= t('accounts.posts', count: account.statuses_count) + %td + %b= account_formatted_stat(account.following_count) + %span= t('accounts.following') + %td + %b= hide_followers_count?(account) ? '-' : account_formatted_stat(account.followers_count) + %span= t('accounts.followers', count: account.followers_count) diff --git a/app/views/application/mailer/_button.html.haml b/app/views/application/mailer/_button.html.haml new file mode 100644 index 0000000000..61430732eb --- /dev/null +++ b/app/views/application/mailer/_button.html.haml @@ -0,0 +1,4 @@ +%table.email-btn-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-btn-td + = link_to "#{text} ➜", url, class: 'email-btn-a email-btn-hover' diff --git a/app/views/application/mailer/_checklist.html.haml b/app/views/application/mailer/_checklist.html.haml new file mode 100644 index 0000000000..83072bd36b --- /dev/null +++ b/app/views/application/mailer/_checklist.html.haml @@ -0,0 +1,29 @@ +%table.email-w-full.email-checklist-wrapper-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-checklist-wrapper-td + %table.email-w-full.email-checklist-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-checklist-td + %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-checklist-icons-td + %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-checklist-icons-checkbox-td + - if defined?(checked) && checked + = image_tag frontend_asset_url('images/mailer-new/welcome/checkbox-on.png'), alt: '', width: 20, height: 20 + - else + = image_tag frontend_asset_url('images/mailer-new/welcome/checkbox-off.png'), alt: '', width: 20, height: 20 + %td.email-checklist-icons-step-td + - if defined?(step_image_url) + = image_tag step_image_url, alt: '', width: 40, height: 40 + %td.email-checklist-text-td + .email-desktop-flex + %div + - if defined?(title) + %h3= title + - if defined?(text) + %p= text + %div + - if defined?(button_text) && defined?(button_url) && defined?(checked) && !checked + = render 'application/mailer/button', text: button_text, url: button_url diff --git a/app/views/application/mailer/_frame.html.haml b/app/views/application/mailer/_frame.html.haml new file mode 100644 index 0000000000..74403e7678 --- /dev/null +++ b/app/views/application/mailer/_frame.html.haml @@ -0,0 +1,4 @@ +%table.email-w-full.email-frame-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-frame-td + %p= text diff --git a/app/views/application/mailer/_heading.html.haml b/app/views/application/mailer/_heading.html.haml new file mode 100644 index 0000000000..9fc5dc7471 --- /dev/null +++ b/app/views/application/mailer/_heading.html.haml @@ -0,0 +1,13 @@ +%table.email-w-full.email-header-heading-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-header-heading-td + %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + - if defined?(heading_image_url) + %td.email-header-heading-img-td + = image_tag heading_image_url, alt: '', width: 56, height: 56 + %td.email-header-heading-txt-td + - if defined?(heading_title) + %h1= heading_title + - if defined?(heading_subtitle) + %p= heading_subtitle diff --git a/app/views/custom_css/show.css.erb b/app/views/custom_css/show.css.erb index 9cd38fb371..78da809ed6 100644 --- a/app/views/custom_css/show.css.erb +++ b/app/views/custom_css/show.css.erb @@ -1,8 +1,8 @@ -<%- if Setting.custom_css.present? %> -<%= raw Setting.custom_css %> +<%- if custom_css_styles.present? %> +<%= raw custom_css_styles %> <%- end %> -<%- UserRole.where(highlighted: true).select { |role| role.color.present? }.each do |role| %> +<%- @user_roles.each do |role| %> .user-role-<%= role.id %> { --user-role-accent: <%= role.color %>; } diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml index 62695b155e..5f72138821 100644 --- a/app/views/disputes/strikes/show.html.haml +++ b/app/views/disputes/strikes/show.html.haml @@ -21,7 +21,7 @@ .report-header .report-header__card - = render 'card', strike: @strike + = render 'disputes/strikes/card', strike: @strike .report-header__details .report-header__details__item diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 78fe6db11a..213b988ec2 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -14,12 +14,12 @@ %link{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }/ - %w(16 32 48).each do |size| - %link{ rel: 'icon', sizes: "#{size}x#{size}", href: asset_pack_path("media/icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ + %link{ rel: 'icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| - %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: asset_pack_path("media/icons/apple-touch-icon-#{size}x#{size}.png") }/ + %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ - %link{ rel: 'mask-icon', href: asset_pack_path('media/images/logo-symbol-icon.svg'), color: '#6364FF' }/ + %link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/ %link{ rel: 'manifest', href: manifest_path(format: :json) }/ %meta{ name: 'theme-color', content: '#191b22' }/ %meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/ @@ -27,6 +27,10 @@ %title= html_title = javascript_pack_tag 'common', crossorigin: 'anonymous' + + -# Needed for the wicg-inert polyfill. It needs to be on it's own