Compare commits

..

No commits in common. "686df3be2196d546b5d158e6ddc8bc22f89ff7bb" and "3b122c5662e72d014313a91bdd722de17862c074" have entirely different histories.

900 changed files with 6105 additions and 8298 deletions

View file

@ -1,6 +0,0 @@
---
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

View file

@ -5,7 +5,7 @@
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": { "features": {
"ghcr.io/devcontainers/features/sshd:1": {}, "ghcr.io/devcontainers/features/sshd:1": {}
}, },
"runServices": ["app", "db", "redis"], "runServices": ["app", "db", "redis"],
@ -15,16 +15,16 @@
"portsAttributes": { "portsAttributes": {
"3000": { "3000": {
"label": "web", "label": "web",
"onAutoForward": "notify", "onAutoForward": "notify"
}, },
"4000": { "4000": {
"label": "stream", "label": "stream",
"onAutoForward": "silent", "onAutoForward": "silent"
}, }
}, },
"otherPortsAttributes": { "otherPortsAttributes": {
"onAutoForward": "silent", "onAutoForward": "silent"
}, },
"remoteEnv": { "remoteEnv": {
@ -33,7 +33,7 @@
"STREAMING_API_BASE_URL": "https://${localEnv:CODESPACE_NAME}-4000.app.github.dev", "STREAMING_API_BASE_URL": "https://${localEnv:CODESPACE_NAME}-4000.app.github.dev",
"DISABLE_FORGERY_REQUEST_PROTECTION": "true", "DISABLE_FORGERY_REQUEST_PROTECTION": "true",
"ES_ENABLED": "", "ES_ENABLED": "",
"LIBRE_TRANSLATE_ENDPOINT": "", "LIBRE_TRANSLATE_ENDPOINT": ""
}, },
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
@ -43,7 +43,7 @@
"customizations": { "customizations": {
"vscode": { "vscode": {
"settings": {}, "settings": {},
"extensions": ["EditorConfig.EditorConfig", "webben.browserslist"], "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"]
}, }
}, }
} }

View file

@ -5,7 +5,7 @@
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": { "features": {
"ghcr.io/devcontainers/features/sshd:1": {}, "ghcr.io/devcontainers/features/sshd:1": {}
}, },
"forwardPorts": [3000, 4000], "forwardPorts": [3000, 4000],
@ -14,17 +14,17 @@
"3000": { "3000": {
"label": "web", "label": "web",
"onAutoForward": "notify", "onAutoForward": "notify",
"requireLocalPort": true, "requireLocalPort": true
}, },
"4000": { "4000": {
"label": "stream", "label": "stream",
"onAutoForward": "silent", "onAutoForward": "silent",
"requireLocalPort": true, "requireLocalPort": true
}, }
}, },
"otherPortsAttributes": { "otherPortsAttributes": {
"onAutoForward": "silent", "onAutoForward": "silent"
}, },
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
@ -34,7 +34,7 @@
"customizations": { "customizations": {
"vscode": { "vscode": {
"settings": {}, "settings": {},
"extensions": ["EditorConfig.EditorConfig", "webben.browserslist"], "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"]
}, }
}, }
} }

View file

@ -70,7 +70,7 @@ services:
hard: -1 hard: -1
libretranslate: libretranslate:
image: libretranslate/libretranslate:v1.5.4 image: libretranslate/libretranslate:v1.5.3
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- lt-data:/home/libretranslate/.local - lt-data:/home/libretranslate/.local

View file

@ -1,61 +0,0 @@
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

View file

@ -78,8 +78,23 @@ jobs:
- name: Create database - name: Create database
run: './bin/rails db:create' run: './bin/rails db:create'
- name: Run historical migrations with data population - name: Run migrations up to v2.0.0
run: './bin/rails tests:migrations:prepare_database' 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 all remaining migrations - name: Run all remaining migrations
run: './bin/rails db:migrate' run: './bin/rails db:migrate'

View file

@ -45,7 +45,6 @@ jobs:
--health-retries 5 --health-retries 5
ports: ports:
- 5432:5432 - 5432:5432
redis: redis:
image: redis:7-alpine image: redis:7-alpine
options: >- options: >-
@ -78,11 +77,28 @@ jobs:
- name: Create database - name: Create database
run: './bin/rails db:create' run: './bin/rails db:create'
- name: Run historical migrations with data population - name: Run migrations up to v2.0.0
run: './bin/rails tests:migrations:prepare_database' 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'
env: env:
SKIP_POST_DEPLOYMENT_MIGRATIONS: true 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 - name: Run all remaining pre-deployment migrations
run: './bin/rails db:migrate' run: './bin/rails db:migrate'
env: env:

View file

@ -52,7 +52,7 @@ jobs:
run: | run: |
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs*
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
if: matrix.mode == 'test' if: matrix.mode == 'test'
with: with:
path: |- path: |-
@ -117,7 +117,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
with: with:
path: './' path: './'
name: ${{ github.sha }} name: ${{ github.sha }}
@ -193,7 +193,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
with: with:
path: './public' path: './public'
name: ${{ github.sha }} name: ${{ github.sha }}
@ -213,14 +213,14 @@ jobs:
- run: bundle exec rake spec:system - run: bundle exec rake spec:system
- name: Archive logs - name: Archive logs
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
if: failure() if: failure()
with: with:
name: e2e-logs-${{ matrix.ruby-version }} name: e2e-logs-${{ matrix.ruby-version }}
path: log/ path: log/
- name: Archive test screenshots - name: Archive test screenshots
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
if: failure() if: failure()
with: with:
name: e2e-screenshots name: e2e-screenshots
@ -297,7 +297,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
with: with:
path: './public' path: './public'
name: ${{ github.sha }} name: ${{ github.sha }}
@ -317,14 +317,14 @@ jobs:
- run: bin/rspec --tag search - run: bin/rspec --tag search
- name: Archive logs - name: Archive logs
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
if: failure() if: failure()
with: with:
name: test-search-logs-${{ matrix.ruby-version }} name: test-search-logs-${{ matrix.ruby-version }}
path: log/ path: log/
- name: Archive test screenshots - name: Archive test screenshots
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
if: failure() if: failure()
with: with:
name: test-search-screenshots name: test-search-screenshots

View file

@ -103,26 +103,9 @@ Rails/Exit:
- 'config/boot.rb' - 'config/boot.rb'
- 'lib/mastodon/cli/*.rb' - 'lib/mastodon/cli/*.rb'
# Reason: Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railslexicallyscopedactionfilter
Rails/LexicallyScopedActionFilter:
Exclude:
- '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: Rails/SkipsModelValidations:
Enabled: false Exclude:
- 'db/*migrate/**/*'
# Reason: We want to preserve the ability to migrate from arbitrary old versions, # 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. # and cannot guarantee that every installation has run every migration as they upgrade.

View file

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` # `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.60.2. # using RuboCop version 1.59.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -13,6 +13,13 @@ Bundler/OrderedGems:
Exclude: Exclude:
- 'Gemfile' - '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: Lint/NonLocalExitFromIterator:
Exclude: Exclude:
- 'app/helpers/jsonld_helper.rb' - 'app/helpers/jsonld_helper.rb'
@ -57,10 +64,64 @@ Rails/HasAndBelongsToMany:
- 'app/models/status.rb' - 'app/models/status.rb'
- 'app/models/tag.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: Rails/OutputSafety:
Exclude: Exclude:
- 'config/initializers/simple_form.rb' - '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. # Configuration parameters: Include.
# Include: app/models/**/*.rb # Include: app/models/**/*.rb
Rails/UniqueValidationWithoutIndex: Rails/UniqueValidationWithoutIndex:
@ -70,6 +131,38 @@ Rails/UniqueValidationWithoutIndex:
- 'app/models/identity.rb' - 'app/models/identity.rb'
- 'app/models/webauthn_credential.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). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowedMethods, AllowedPatterns. # Configuration parameters: AllowedMethods, AllowedPatterns.
# AllowedMethods: ==, equal?, eql? # AllowedMethods: ==, equal?, eql?
@ -108,6 +201,7 @@ Style/FetchEnvVar:
# AllowedMethods: redirect # AllowedMethods: redirect
Style/FormatStringToken: Style/FormatStringToken:
Exclude: Exclude:
- 'app/models/privacy_policy.rb'
- 'config/initializers/devise.rb' - 'config/initializers/devise.rb'
- 'lib/paperclip/color_extractor.rb' - 'lib/paperclip/color_extractor.rb'
@ -121,6 +215,10 @@ Style/GlobalStdStream:
# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals.
Style/GuardClause: Style/GuardClause:
Exclude: 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/activitypub/activity/block.rb'
- 'app/lib/request.rb' - 'app/lib/request.rb'
- 'app/lib/request_pool.rb' - 'app/lib/request_pool.rb'
@ -275,6 +373,13 @@ Style/StringLiterals:
- 'config/initializers/webauthn.rb' - 'config/initializers/webauthn.rb'
- 'config/routes.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). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyleForMultiline. # Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma # SupportedStylesForMultiline: comma, consistent_comma, no_comma

View file

@ -1 +1 @@
3.2.3 3.2.2

View file

@ -7,15 +7,15 @@
ARG TARGETPLATFORM=${TARGETPLATFORM} ARG TARGETPLATFORM=${TARGETPLATFORM}
ARG BUILDPLATFORM=${BUILDPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM}
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.3"] # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.2"]
ARG RUBY_VERSION="3.2.3" ARG RUBY_VERSION="3.2.2"
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
ARG NODE_MAJOR_VERSION="20" ARG NODE_MAJOR_VERSION="20"
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
ARG DEBIAN_VERSION="bookworm" ARG DEBIAN_VERSION="bookworm"
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim) # 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 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.3-slim-bookworm) # Ruby image to use for base image based on combined variables (ex: 3.2.2-slim-bookworm)
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA

View file

@ -1,35 +1,19 @@
# Federation ## ActivityPub federation in Mastodon
## 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. 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 ActivityPub vocabulary](https://docs.joinmastodon.org/spec/activitypub/) Supported vocabulary: https://docs.joinmastodon.org/spec/activitypub/
### Required extensions ### Required extensions
#### WebFinger #### Webfinger
In Mastodon, users are identified by a `username` and `domain` pair (e.g., `Gargron@mastodon.social`). 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. 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. As a result, Mastodon requires that each ActivityPub actor uniquely maps back to an `acct:` URI that can be resolved via WebFinger.
- [WebFinger information and examples](https://docs.joinmastodon.org/spec/webfinger/) More information and examples are available at: https://docs.joinmastodon.org/spec/webfinger/
#### HTTP Signatures #### HTTP Signatures
@ -37,13 +21,11 @@ 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. Mastodon requires all `POST` requests to be signed, and MAY require `GET` requests to be signed, depending on the configuration of the Mastodon server.
- [HTTP Signatures information and examples](https://docs.joinmastodon.org/spec/security/#http) More information on HTTP Signatures, as well as examples, can be found here: https://docs.joinmastodon.org/spec/security/#http
### Optional extensions ### Optional extensions
- [Linked-Data Signatures](https://docs.joinmastodon.org/spec/security/#ld) - Linked-Data Signatures: https://docs.joinmastodon.org/spec/security/#ld
- [Bearcaps](https://docs.joinmastodon.org/spec/bearcaps/) - Bearcaps: https://docs.joinmastodon.org/spec/bearcaps/
- Followers collection synchronization: https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md
### Additional documentation - Search indexing consent for actors: https://codeberg.org/fediverse/fep/src/branch/main/fep/5feb/fep-5feb.md
- [Mastodon documentation](https://docs.joinmastodon.org/)

View file

@ -39,7 +39,8 @@ end
gem 'net-ldap', '~> 0.18' gem 'net-ldap', '~> 0.18'
gem 'omniauth-cas', '~> 3.0.0.beta.1' # TODO: Point back at released omniauth-cas gem when new version is released
gem 'omniauth-cas', github: 'dlindahl/omniauth-cas', ref: '9d9d3a91b316c55d49ab6e621977f2067010c5bf'
gem 'omniauth-saml', '~> 2.0' gem 'omniauth-saml', '~> 2.0'
gem 'omniauth_openid_connect', '~> 0.6.1' gem 'omniauth_openid_connect', '~> 0.6.1'
gem 'omniauth', '~> 2.0' gem 'omniauth', '~> 2.0'
@ -123,7 +124,7 @@ group :test do
gem 'database_cleaner-active_record' gem 'database_cleaner-active_record'
# Used to mock environment variables # Used to mock environment variables
gem 'climate_control' gem 'climate_control', '~> 0.2'
# Generating fake data for specs # Generating fake data for specs
gem 'faker', '~> 3.2' gem 'faker', '~> 3.2'

View file

@ -7,6 +7,16 @@ GIT
hkdf (~> 0.2) hkdf (~> 0.2)
jwt (~> 2.0) 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 GIT
remote: https://github.com/jhawthorn/nsa.git remote: https://github.com/jhawthorn/nsa.git
revision: e020fcc3a54d993ab45b7194d89ab720296c111b revision: e020fcc3a54d993ab45b7194d89ab720296c111b
@ -21,35 +31,35 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.3) actioncable (7.1.2)
actionpack (= 7.1.3) actionpack (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.3) actionmailbox (7.1.2)
actionpack (= 7.1.3) actionpack (= 7.1.2)
activejob (= 7.1.3) activejob (= 7.1.2)
activerecord (= 7.1.3) activerecord (= 7.1.2)
activestorage (= 7.1.3) activestorage (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.1.3) actionmailer (7.1.2)
actionpack (= 7.1.3) actionpack (= 7.1.2)
actionview (= 7.1.3) actionview (= 7.1.2)
activejob (= 7.1.3) activejob (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.1.3) actionpack (7.1.2)
actionview (= 7.1.3) actionview (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc racc
rack (>= 2.2.4) rack (>= 2.2.4)
@ -57,15 +67,15 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.3) actiontext (7.1.2)
actionpack (= 7.1.3) actionpack (= 7.1.2)
activerecord (= 7.1.3) activerecord (= 7.1.2)
activestorage (= 7.1.3) activestorage (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.3) actionview (7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@ -75,22 +85,22 @@ GEM
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3) activejob (7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.3) activemodel (7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
activerecord (7.1.3) activerecord (7.1.2)
activemodel (= 7.1.3) activemodel (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.3) activestorage (7.1.2)
actionpack (= 7.1.3) actionpack (= 7.1.2)
activejob (= 7.1.3) activejob (= 7.1.2)
activerecord (= 7.1.3) activerecord (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.3) activesupport (7.1.2)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
@ -150,12 +160,12 @@ GEM
erubi (~> 1.4) erubi (~> 1.4)
parser (>= 2.4) parser (>= 2.4)
smart_properties smart_properties
bigdecimal (3.1.6) bigdecimal (3.1.5)
bindata (2.4.15) bindata (2.4.15)
binding_of_caller (1.0.0) binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blurhash (0.1.7) blurhash (0.1.7)
bootsnap (1.17.1) bootsnap (1.17.0)
msgpack (~> 1.2) msgpack (~> 1.2)
brakeman (6.1.1) brakeman (6.1.1)
racc racc
@ -180,15 +190,15 @@ GEM
activesupport activesupport
cbor (0.5.9.6) cbor (0.5.9.6)
charlock_holmes (0.7.7) charlock_holmes (0.7.7)
chewy (7.5.0) chewy (7.4.0)
activesupport (>= 5.2) activesupport (>= 5.2)
elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch (>= 7.12.0, < 7.14.0)
elasticsearch-dsl elasticsearch-dsl
chunky_png (1.4.0) chunky_png (1.4.0)
climate_control (1.2.0) climate_control (0.2.0)
cocoon (1.2.15) cocoon (1.2.15)
color_diff (0.1) color_diff (0.1)
concurrent-ruby (1.2.3) concurrent-ruby (1.2.2)
connection_pool (2.4.1) connection_pool (2.4.1)
cose (1.3.0) cose (1.3.0)
cbor (~> 0.5.9) cbor (~> 0.5.9)
@ -257,7 +267,7 @@ GEM
tzinfo tzinfo
excon (0.109.0) excon (0.109.0)
fabrication (2.31.0) fabrication (2.31.0)
faker (3.2.3) faker (3.2.2)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (1.10.3) faraday (1.10.3)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
@ -319,7 +329,7 @@ GEM
activesupport (>= 5.1) activesupport (>= 5.1)
haml (>= 4.0.6) haml (>= 4.0.6)
railties (>= 5.1) railties (>= 5.1)
haml_lint (0.55.0) haml_lint (0.53.0)
haml (>= 5.0) haml (>= 5.0)
parallel (~> 1.10) parallel (~> 1.10)
rainbow rainbow
@ -360,7 +370,7 @@ GEM
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1) terminal-table (>= 1.5.1)
idn-ruby (0.1.5) idn-ruby (0.1.5)
io-console (0.7.2) io-console (0.7.1)
irb (1.11.1) irb (1.11.1)
rdoc rdoc
reline (>= 0.4.2) reline (>= 0.4.2)
@ -398,12 +408,12 @@ GEM
activerecord activerecord
kaminari-core (= 1.2.2) kaminari-core (= 1.2.2)
kaminari-core (1.2.2) kaminari-core (1.2.2)
kt-paperclip (7.2.2) kt-paperclip (7.2.1)
activemodel (>= 4.2.0) activemodel (>= 4.2.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
marcel (~> 1.0.1) marcel (~> 1.0.1)
mime-types mime-types
terrapin (>= 0.6.0, < 2.0) terrapin (~> 0.6.0)
language_server-protocol (3.17.0.3) language_server-protocol (3.17.0.3)
launchy (2.5.2) launchy (2.5.2)
addressable (~> 2.8) addressable (~> 2.8)
@ -445,7 +455,7 @@ GEM
mime-types-data (3.2023.1205) mime-types-data (3.2023.1205)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.5) mini_portile2 (2.8.5)
minitest (5.21.2) minitest (5.20.0)
msgpack (1.7.2) msgpack (1.7.2)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.3.0) multipart-post (2.3.0)
@ -454,7 +464,7 @@ GEM
uri uri
net-http-persistent (4.0.2) net-http-persistent (4.0.2)
connection_pool (~> 2.2) connection_pool (~> 2.2)
net-imap (0.4.9.1) net-imap (0.4.4)
date date
net-protocol net-protocol
net-ldap (0.19.0) net-ldap (0.19.0)
@ -462,7 +472,7 @@ GEM
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
timeout timeout
net-smtp (0.4.0.1) net-smtp (0.4.0)
net-protocol net-protocol
nio4r (2.5.9) nio4r (2.5.9)
nokogiri (1.16.0) nokogiri (1.16.0)
@ -474,10 +484,6 @@ GEM
hashie (>= 3.4.6) hashie (>= 3.4.6)
rack (>= 2.2.3) rack (>= 2.2.3)
rack-protection 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) omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2) actionpack (>= 4.2)
omniauth (~> 2.0) omniauth (~> 2.0)
@ -504,7 +510,7 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
ox (2.14.17) ox (2.14.17)
parallel (1.24.0) parallel (1.24.0)
parser (3.3.0.5) parser (3.2.2.4)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
parslet (2.0.0) parslet (2.0.0)
@ -552,27 +558,27 @@ GEM
rack rack
rack-proxy (0.7.6) rack-proxy (0.7.6)
rack rack
rack-session (1.0.2) rack-session (1.0.1)
rack (< 3) rack (< 3)
rack-test (2.1.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rackup (1.0.0) rackup (1.0.0)
rack (< 3) rack (< 3)
webrick webrick
rails (7.1.3) rails (7.1.2)
actioncable (= 7.1.3) actioncable (= 7.1.2)
actionmailbox (= 7.1.3) actionmailbox (= 7.1.2)
actionmailer (= 7.1.3) actionmailer (= 7.1.2)
actionpack (= 7.1.3) actionpack (= 7.1.2)
actiontext (= 7.1.3) actiontext (= 7.1.2)
actionview (= 7.1.3) actionview (= 7.1.2)
activejob (= 7.1.3) activejob (= 7.1.2)
activemodel (= 7.1.3) activemodel (= 7.1.2)
activerecord (= 7.1.3) activerecord (= 7.1.2)
activestorage (= 7.1.3) activestorage (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.3) railties (= 7.1.2)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@ -587,9 +593,9 @@ GEM
rails-i18n (7.0.8) rails-i18n (7.0.8)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.1.3) railties (7.1.2)
actionpack (= 7.1.3) actionpack (= 7.1.2)
activesupport (= 7.1.3) activesupport (= 7.1.2)
irb irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -600,8 +606,8 @@ GEM
rdf (3.3.1) rdf (3.3.1)
bcp47_spec (~> 0.2) bcp47_spec (~> 0.2)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.7.0) rdf-normalize (0.6.1)
rdf (~> 3.3) rdf (~> 3.2)
rdoc (6.6.2) rdoc (6.6.2)
psych (>= 4.0.0) psych (>= 4.0.0)
redcarpet (3.6.0) redcarpet (3.6.0)
@ -610,7 +616,7 @@ GEM
redis (>= 4) redis (>= 4)
redlock (1.3.2) redlock (1.3.2)
redis (>= 3.0.0, < 6.0) redis (>= 3.0.0, < 6.0)
regexp_parser (2.9.0) regexp_parser (2.8.3)
reline (0.4.2) reline (0.4.2)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.5.1) request_store (1.5.1)
@ -636,7 +642,7 @@ GEM
rspec-mocks (3.12.6) rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-rails (6.1.1) rspec-rails (6.1.0)
actionpack (>= 6.1) actionpack (>= 6.1)
activesupport (>= 6.1) activesupport (>= 6.1)
railties (>= 6.1) railties (>= 6.1)
@ -650,11 +656,11 @@ GEM
rspec-mocks (~> 3.0) rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8) sidekiq (>= 5, < 8)
rspec-support (3.12.1) rspec-support (3.12.1)
rubocop (1.60.2) rubocop (1.59.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.2.2.4)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0) rexml (>= 3.2.5, < 4.0)
@ -696,8 +702,7 @@ GEM
scenic (1.7.0) scenic (1.7.0)
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
railties (>= 4.0.0) railties (>= 4.0.0)
selenium-webdriver (4.17.0) selenium-webdriver (4.16.0)
base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0) websocket (~> 1.0)
@ -731,7 +736,7 @@ GEM
simplecov-lcov (0.8.0) simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
smart_properties (1.17.0) smart_properties (1.17.0)
stackprof (0.2.26) stackprof (0.2.25)
statsd-ruby (1.5.0) statsd-ruby (1.5.0)
stoplight (3.0.2) stoplight (3.0.2)
redlock (~> 1.0) redlock (~> 1.0)
@ -746,8 +751,8 @@ GEM
temple (0.10.3) temple (0.10.3)
terminal-table (3.0.2) terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3) unicode-display_width (>= 1.1.1, < 3)
terrapin (1.0.1) terrapin (0.6.0)
climate_control climate_control (>= 0.0.3, < 1.0)
test-prof (1.3.1) test-prof (1.3.1)
thor (1.3.0) thor (1.3.0)
tilt (2.3.0) tilt (2.3.0)
@ -836,7 +841,7 @@ DEPENDENCIES
capybara (~> 3.39) capybara (~> 3.39)
charlock_holmes (~> 0.7.7) charlock_holmes (~> 0.7.7)
chewy (~> 7.3) chewy (~> 7.3)
climate_control climate_control (~> 0.2)
cocoon (~> 1.2) cocoon (~> 1.2)
color_diff (~> 0.1) color_diff (~> 0.1)
concurrent-ruby concurrent-ruby
@ -889,7 +894,7 @@ DEPENDENCIES
nsa! nsa!
oj (~> 3.14) oj (~> 3.14)
omniauth (~> 2.0) omniauth (~> 2.0)
omniauth-cas (~> 3.0.0.beta.1) omniauth-cas!
omniauth-rails_csrf_protection (~> 1.0) omniauth-rails_csrf_protection (~> 1.0)
omniauth-saml (~> 2.0) omniauth-saml (~> 2.0)
omniauth_openid_connect (~> 0.6.1) omniauth_openid_connect (~> 0.6.1)
@ -956,4 +961,4 @@ RUBY VERSION
ruby 3.2.2p53 ruby 3.2.2p53
BUNDLED WITH BUNDLED WITH
2.5.4 2.4.20

View file

@ -24,7 +24,7 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
end end
def set_items def set_items
@items = @account.followers.matches_uri_prefix(uri_prefix).pluck(:uri) @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)
end end
def collection_presenter def collection_presenter

View file

@ -24,7 +24,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
def unknown_affected_account? def unknown_affected_account?
json = Oj.load(body, mode: :strict) 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.exists?(uri: json['actor']) 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?
rescue Oj::ParseError rescue Oj::ParseError
false false
end end

View file

@ -6,7 +6,7 @@ module Admin
def index def index
authorize :audit_log, :index? authorize :audit_log, :index?
@auditable_accounts = Account.auditable.select(:id, :username) @auditable_accounts = Account.where(id: Admin::ActionLog.select('distinct account_id')).select(:id, :username)
end end
private private

View file

@ -3,11 +3,11 @@
module Admin module Admin
class ConfirmationsController < BaseController class ConfirmationsController < BaseController
before_action :set_user before_action :set_user
before_action :redirect_confirmed_user, only: [:resend], if: :user_confirmed? before_action :check_confirmation, only: [:resend]
def create def create
authorize @user, :confirm? authorize @user, :confirm?
@user.mark_email_as_confirmed! @user.confirm!
log_action :confirm, @user log_action :confirm, @user
redirect_to admin_accounts_path redirect_to admin_accounts_path
end end
@ -25,13 +25,11 @@ module Admin
private private
def redirect_confirmed_user def check_confirmation
flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed') if @user.confirmed?
redirect_to admin_accounts_path flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed')
end redirect_to admin_accounts_path
end
def user_confirmed?
@user.confirmed?
end end
end end
end end

View file

@ -38,7 +38,7 @@ module Admin
log_action :create, @email_domain_block log_action :create, @email_domain_block
(@email_domain_block.other_domains || []).uniq.each do |domain| (@email_domain_block.other_domains || []).uniq.each do |domain|
next if EmailDomainBlock.exists?(domain: domain) next if EmailDomainBlock.where(domain: domain).exists?
other_email_domain_block = EmailDomainBlock.create!(domain: domain, allow_with_approval: @email_domain_block.allow_with_approval, parent: @email_domain_block) 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 log_action :create, other_email_domain_block

View file

@ -49,7 +49,7 @@ module Admin
next next
end end
@warning_domains = instances_from_imported_blocks.pluck(:domain) @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)
rescue ActionController::ParameterMissing rescue ActionController::ParameterMissing
flash.now[:alert] = I18n.t('admin.export_domain_blocks.no_file') flash.now[:alert] = I18n.t('admin.export_domain_blocks.no_file')
set_dummy_import! set_dummy_import!
@ -58,10 +58,6 @@ module Admin
private private
def instances_from_imported_blocks
Instance.with_domain_follows(@domain_blocks.map(&:domain))
end
def export_filename def export_filename
'domain_blocks.csv' 'domain_blocks.csv'
end end

View file

@ -21,7 +21,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
return [] if hide_results? return [] if hide_results?
scope = default_accounts scope = default_accounts
scope = scope.not_excluded_by_account(current_account) unless current_account.nil? || current_account.id == @account.id scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a scope.merge(paginated_follows).to_a
end end
@ -30,7 +30,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:active_relationships, :account_stat, :user).references(:active_relationships) Account.includes(:active_relationships, :account_stat).references(:active_relationships)
end end
def paginated_follows def paginated_follows

View file

@ -21,7 +21,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
return [] if hide_results? return [] if hide_results?
scope = default_accounts scope = default_accounts
scope = scope.not_excluded_by_account(current_account) unless current_account.nil? || current_account.id == @account.id scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a scope.merge(paginated_follows).to_a
end end
@ -30,7 +30,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:passive_relationships, :account_stat, :user).references(:passive_relationships) Account.includes(:passive_relationships, :account_stat).references(:passive_relationships)
end end
def paginated_follows def paginated_follows

View file

@ -1,30 +0,0 @@
# 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

View file

@ -17,7 +17,7 @@ class Api::V1::BlocksController < Api::BaseController
end end
def paginated_blocks def paginated_blocks
@paginated_blocks ||= Block.eager_load(target_account: [:account_stat, :user]) @paginated_blocks ||= Block.eager_load(target_account: :account_stat)
.joins(:target_account) .joins(:target_account)
.merge(Account.without_suspended) .merge(Account.without_suspended)
.where(account: current_account) .where(account: current_account)

View file

@ -27,7 +27,7 @@ class Api::V1::DirectoriesController < Api::BaseController
scope.merge!(local_account_scope) if local_accounts? scope.merge!(local_account_scope) if local_accounts?
scope.merge!(account_exclusion_scope) if current_account scope.merge!(account_exclusion_scope) if current_account
scope.merge!(account_domain_block_scope) if current_account && !local_accounts? scope.merge!(account_domain_block_scope) if current_account && !local_accounts?
end.includes(:account_stat, user: :role) end
end end
def local_accounts? def local_accounts?

View file

@ -25,7 +25,7 @@ class Api::V1::EndorsementsController < Api::BaseController
end end
def endorsed_accounts def endorsed_accounts
current_account.endorsed_accounts.includes(:account_stat, :user).without_suspended current_account.endorsed_accounts.includes(:account_stat).without_suspended
end end
def insert_pagination_headers def insert_pagination_headers

View file

@ -37,7 +37,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.without_suspended.includes(:follow_requests, :account_stat, :user).references(:follow_requests) Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests)
end end
def paginated_follow_requests def paginated_follow_requests

View file

@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController
def load_accounts def load_accounts
if unlimited? if unlimited?
@list.accounts.without_suspended.includes(:account_stat, :user).all @list.accounts.without_suspended.includes(:account_stat).all
else else
@list.accounts.without_suspended.includes(:account_stat, :user).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) @list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
end end
end end

View file

@ -19,7 +19,7 @@ class Api::V1::MarkersController < Api::BaseController
@markers = {} @markers = {}
resource_params.each_pair do |timeline, timeline_params| resource_params.each_pair do |timeline, timeline_params|
@markers[timeline] = current_user.markers.find_or_create_by(timeline: timeline) @markers[timeline] = current_user.markers.find_or_initialize_by(timeline: timeline)
@markers[timeline].update!(timeline_params) @markers[timeline].update!(timeline_params)
end end
end end

View file

@ -17,7 +17,7 @@ class Api::V1::MutesController < Api::BaseController
end end
def paginated_mutes def paginated_mutes
@paginated_mutes ||= Mute.eager_load(target_account: [:account_stat, :user]) @paginated_mutes ||= Mute.eager_load(:target_account)
.joins(:target_account) .joins(:target_account)
.merge(Account.without_suspended) .merge(Account.without_suspended)
.where(account: current_account) .where(account: current_account)

View file

@ -27,7 +27,7 @@ class Api::V1::Peers::SearchController < Api::BaseController
@domains = InstancesIndex.query(function_score: { @domains = InstancesIndex.query(function_score: {
query: { query: {
prefix: { prefix: {
domain: normalized_domain, domain: TagManager.instance.normalize_domain(params[:q].strip),
}, },
}, },
@ -37,18 +37,11 @@ class Api::V1::Peers::SearchController < Api::BaseController
}, },
}).limit(10).pluck(:domain) }).limit(10).pluck(:domain)
else else
domain = normalized_domain domain = params[:q].strip
@domains = Instance.searchable.domain_starts_with(domain).limit(10).pluck(:domain) 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)
end end
rescue Addressable::URI::InvalidURIError rescue Addressable::URI::InvalidURIError
@domains = [] @domains = []
end end
def normalized_domain
TagManager.instance.normalize_domain(query_value)
end
def query_value
params[:q].strip
end
end end

View file

@ -14,14 +14,14 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas
def load_accounts def load_accounts
scope = default_accounts scope = default_accounts
scope = scope.not_excluded_by_account(current_account) unless current_account.nil? scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope.merge(paginated_favourites).to_a scope.merge(paginated_favourites).to_a
end end
def default_accounts def default_accounts
Account Account
.without_suspended .without_suspended
.includes(:favourites, :account_stat, :user) .includes(:favourites, :account_stat)
.references(:favourites) .references(:favourites)
.where(favourites: { status_id: @status.id }) .where(favourites: { status_id: @status.id })
end end

View file

@ -14,12 +14,12 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base
def load_accounts def load_accounts
scope = default_accounts scope = default_accounts
scope = scope.not_excluded_by_account(current_account) unless current_account.nil? scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope.merge(paginated_statuses).to_a scope.merge(paginated_statuses).to_a
end end
def default_accounts def default_accounts
Account.without_suspended.includes(:statuses, :account_stat, :user).references(:statuses) Account.without_suspended.includes(:statuses, :account_stat).references(:statuses)
end end
def paginated_statuses def paginated_statuses

View file

@ -35,7 +35,7 @@ class Api::V2::FiltersController < Api::BaseController
private private
def set_filters def set_filters
@filters = current_account.custom_filters.includes(:keywords, :statuses) @filters = current_account.custom_filters.includes(:keywords)
end end
def set_filter def set_filter

View file

@ -8,7 +8,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
before_action :set_body_classes before_action :set_body_classes
before_action :set_pack before_action :set_pack
before_action :set_confirmation_user!, only: [:show, :confirm_captcha] before_action :set_confirmation_user!, only: [:show, :confirm_captcha]
before_action :redirect_confirmed_user, if: :signed_in_confirmed_user? before_action :require_unconfirmed!
before_action :extend_csp_for_captcha!, only: [:show, :confirm_captcha] before_action :extend_csp_for_captcha!, only: [:show, :confirm_captcha]
before_action :require_captcha_if_needed!, only: [:show] before_action :require_captcha_if_needed!, only: [:show]
@ -70,12 +70,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
use_pack 'auth' use_pack 'auth'
end end
def redirect_confirmed_user def require_unconfirmed!
redirect_to(current_user.approved? ? root_path : edit_user_registration_path) if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank?
end 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 end
def set_body_classes def set_body_classes

View file

@ -2,7 +2,7 @@
class Auth::PasswordsController < Devise::PasswordsController class Auth::PasswordsController < Devise::PasswordsController
skip_before_action :check_self_destruct! skip_before_action :check_self_destruct!
before_action :redirect_invalid_reset_token, only: :edit, unless: :reset_password_token_is_valid? before_action :check_validity_of_reset_password_token, only: :edit
before_action :set_pack before_action :set_pack
before_action :set_body_classes before_action :set_body_classes
@ -20,9 +20,11 @@ class Auth::PasswordsController < Devise::PasswordsController
private private
def redirect_invalid_reset_token def check_validity_of_reset_password_token
flash[:error] = I18n.t('auth.invalid_reset_password_token') unless reset_password_token_is_valid?
redirect_to new_password_path(resource_name) flash[:error] = I18n.t('auth.invalid_reset_password_token')
redirect_to new_password_path(resource_name)
end
end end
def set_body_classes def set_body_classes

View file

@ -1,10 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Auth::SessionsController < Devise::SessionsController class Auth::SessionsController < Devise::SessionsController
include Redisable
MAX_2FA_ATTEMPTS_PER_HOUR = 10
layout 'auth' layout 'auth'
skip_before_action :check_self_destruct! skip_before_action :check_self_destruct!
@ -139,23 +135,9 @@ class Auth::SessionsController < Devise::SessionsController
session.delete(:attempt_user_updated_at) session.delete(:attempt_user_updated_at)
end 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) def on_authentication_success(user, security_measure)
@on_authentication_success_called = true @on_authentication_success_called = true
clear_2fa_attempt_from_user(user)
clear_attempt_from_session clear_attempt_from_session
user.update_sign_in!(new_sign_in: true) user.update_sign_in!(new_sign_in: true)
@ -186,14 +168,5 @@ class Auth::SessionsController < Devise::SessionsController
ip: request.remote_ip, ip: request.remote_ip,
user_agent: request.user_agent 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
end end

View file

@ -66,11 +66,6 @@ module Auth::TwoFactorAuthenticationConcern
end end
def authenticate_with_two_factor_via_otp(user) 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) if valid_otp_attempt?(user)
on_authentication_success(user, :otp) on_authentication_success(user, :otp)
else else

View file

@ -22,20 +22,11 @@ module WebAppControllerConcern
def redirect_unauthenticated_to_permalinks! def redirect_unauthenticated_to_permalinks!
return if user_signed_in? # NOTE: Different from upstream because we allow moved users to log in return if user_signed_in? # NOTE: Different from upstream because we allow moved users to log in
permalink_redirector = PermalinkRedirector.new(request.path) redirect_path = PermalinkRedirector.new(request.path).redirect_path
return if permalink_redirector.redirect_path.blank? return if redirect_path.blank?
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in? 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 end
def set_pack def set_pack

View file

@ -1,21 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
before_action :set_user_roles
def show def show
expires_in 3.minutes, public: true expires_in 3.minutes, public: true
render content_type: 'text/css' render content_type: 'text/css'
end 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 end

View file

@ -1,10 +0,0 @@
# 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

View file

@ -1,29 +0,0 @@
# 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

View file

@ -1,10 +0,0 @@
# 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

View file

@ -6,8 +6,8 @@ module Settings
skip_before_action :check_self_destruct! skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :redirect_invalid_otp, unless: -> { current_user.otp_enabled? } before_action :require_otp_enabled
before_action :redirect_invalid_webauthn, only: [:index, :destroy], unless: -> { current_user.webauthn_enabled? } before_action :require_webauthn_enabled, only: [:index, :destroy]
def index; end def index; end
def new; end def new; end
@ -89,14 +89,18 @@ module Settings
use_pack 'auth' use_pack 'auth'
end end
def redirect_invalid_otp def require_otp_enabled
flash[:error] = t('webauthn_credentials.otp_required') unless current_user.otp_enabled?
redirect_to settings_two_factor_authentication_methods_path flash[:error] = t('webauthn_credentials.otp_required')
redirect_to settings_two_factor_authentication_methods_path
end
end end
def redirect_invalid_webauthn def require_webauthn_enabled
flash[:error] = t('webauthn_credentials.not_enabled') unless current_user.webauthn_enabled?
redirect_to settings_two_factor_authentication_methods_path flash[:error] = t('webauthn_credentials.not_enabled')
redirect_to settings_two_factor_authentication_methods_path
end
end end
end end
end end

View file

@ -31,26 +31,22 @@ module AccountsHelper
Setting.hide_followers_count || account.user&.settings&.[]('hide_followers_count') Setting.hide_followers_count || account.user&.settings&.[]('hide_followers_count')
end end
def account_formatted_stat(value)
number_to_human(value, precision: 3, strip_insignificant_zeros: true)
end
def account_description(account) def account_description(account)
prepend_stats = [ prepend_stats = [
[ [
account_formatted_stat(account.statuses_count), number_to_human(account.statuses_count, precision: 3, strip_insignificant_zeros: true),
I18n.t('accounts.posts', count: account.statuses_count), I18n.t('accounts.posts', count: account.statuses_count),
].join(' '), ].join(' '),
[ [
account_formatted_stat(account.following_count), number_to_human(account.following_count, precision: 3, strip_insignificant_zeros: true),
I18n.t('accounts.following', count: account.following_count), I18n.t('accounts.following', count: account.following_count),
].join(' '), ].join(' '),
] ]
unless hide_followers_count?(account) unless hide_followers_count?(account)
prepend_stats << [ prepend_stats << [
account_formatted_stat(account.followers_count), number_to_human(account.followers_count, precision: 3, strip_insignificant_zeros: true),
I18n.t('accounts.followers', count: account.followers_count), I18n.t('accounts.followers', count: account.followers_count),
].join(' ') ].join(' ')
end end

View file

@ -155,7 +155,7 @@ module JsonLdHelper
end end
end end
def fetch_resource(uri, id, on_behalf_of = nil, request_options: {}) def fetch_resource(uri, id, on_behalf_of = nil)
unless id unless id
json = fetch_resource_without_id_validation(uri, on_behalf_of) json = fetch_resource_without_id_validation(uri, on_behalf_of)
@ -164,14 +164,14 @@ module JsonLdHelper
uri = json['id'] uri = json['id']
end end
json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options) json = fetch_resource_without_id_validation(uri, on_behalf_of)
json.present? && json['id'] == uri ? json : nil json.present? && json['id'] == uri ? json : nil
end end
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {}) def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false)
on_behalf_of ||= Account.representative on_behalf_of ||= Account.representative
build_request(uri, on_behalf_of, options: request_options).perform do |response| build_request(uri, on_behalf_of).perform do |response|
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error 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 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)) response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
end end
def build_request(uri, on_behalf_of = nil, options: {}) def build_request(uri, on_behalf_of = nil)
Request.new(:get, uri, **options).tap do |request| Request.new(:get, uri).tap do |request|
request.on_behalf_of(on_behalf_of) if on_behalf_of request.on_behalf_of(on_behalf_of) if on_behalf_of
request.add_headers('Accept' => 'application/activity+json, application/ld+json') request.add_headers('Accept' => 'application/activity+json, application/ld+json')
end end

View file

@ -2,7 +2,7 @@
module MascotHelper module MascotHelper
def mascot_url def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg')) full_asset_url(instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'))
end end
def instance_presenter def instance_presenter

View file

@ -24,12 +24,8 @@ module RoutingHelper
Rails.configuration.action_controller.asset_host || root_url Rails.configuration.action_controller.asset_host || root_url
end end
def frontend_asset_path(source, **options) def full_pack_url(source, **options)
asset_pack_path("media/#{source}", **options) full_asset_url(asset_pack_path(source, **options))
end
def frontend_asset_url(source, **options)
full_asset_url(frontend_asset_path(source, **options))
end end
def use_storage? def use_storage?

View file

@ -1,3 +1,3 @@
const ReactComponent = 'div'; // eslint-disable-next-line import/no-anonymous-default-export
export default 'SvgrURL';
export default ReactComponent; export const ReactComponent = 'div';

View file

@ -1,4 +0,0 @@
/* Placeholder file to have `inert.scss` compiled by Webpack
This is used by the `wicg-inert` polyfill */
import '../styles/inert.scss';

View file

@ -10,9 +10,6 @@ pack:
embed: embed.js embed: embed.js
error: error:
home: home:
inert:
filename: inert.js
stylesheet: true
mailer: mailer:
filename: mailer.js filename: mailer.js
stylesheet: true stylesheet: true

View file

@ -170,11 +170,6 @@ export const openURL = routerHistory => (dispatch, getState) => {
export const clickSearchResult = (q, type) => (dispatch, getState) => { export const clickSearchResult = (q, type) => (dispatch, getState) => {
const previous = getState().getIn(['search', 'recent']); 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 me = getState().getIn(['meta', 'me']);
const current = previous.add(fromJS({ type, q })).takeLast(4); const current = previous.add(fromJS({ type, q })).takeLast(4);
@ -203,4 +198,4 @@ export const hydrateSearch = () => (dispatch, getState) => {
if (history !== null) { if (history !== null) {
dispatch(updateSearchHistory(history)); dispatch(updateSearchHistory(history));
} }
}; };

View file

@ -17,7 +17,7 @@ import { Avatar } from './avatar';
import { Button } from './button'; import { Button } from './button';
import { FollowersCounter } from './counters'; import { FollowersCounter } from './counters';
import { DisplayName } from './display_name'; import { DisplayName } from './display_name';
import { Permalink } from './permalink'; import Permalink from './permalink';
import { RelativeTimestamp } from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
const messages = defineMessages({ const messages = defineMessages({

View file

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import api from 'flavours/glitch/api'; 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 { export default class Trends extends PureComponent {

View file

@ -7,9 +7,9 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import LinkIcon from '@/material-icons/400-24px/link.svg?react'; import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; const filename = url => url.split('/').pop().split('#')[0].split('?')[0];

View file

@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import GroupsIcon from '@/material-icons/400-24px/group.svg?react'; import { ReactComponent as GroupsIcon } from '@material-symbols/svg-600/outlined/group.svg';
import PersonIcon from '@/material-icons/400-24px/person.svg?react'; import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg';
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react'; import { ReactComponent as SmartToyIcon } from '@material-symbols/svg-600/outlined/smart_toy.svg';
export const Badge = ({ icon, label, domain }) => ( export const Badge = ({ icon, label, domain }) => (

View file

@ -2,7 +2,8 @@ import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context'; import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context';

View file

@ -6,17 +6,17 @@ import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import AddIcon from '@/material-icons/400-24px/add.svg?react'; import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg';
import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import TuneIcon from '@/material-icons/400-24px/tune.svg?react'; import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { ButtonInTabsBar, useColumnsContext } from 'flavours/glitch/features/ui/util/columns_context'; import { ButtonInTabsBar, useColumnsContext } from 'flavours/glitch/features/ui/util/columns_context';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { useAppHistory } from './router'; import { useAppHistory } from './router';
const messages = defineMessages({ const messages = defineMessages({

View file

@ -8,7 +8,8 @@ import { useCallback, useState, useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { changeSetting } from 'flavours/glitch/actions/settings'; import { changeSetting } from 'flavours/glitch/actions/settings';
import { bannerSettings } from 'flavours/glitch/settings'; import { bannerSettings } from 'flavours/glitch/settings';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';

View file

@ -2,7 +2,7 @@ import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';

View file

@ -6,10 +6,10 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; 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 { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay'; 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 { CircularProgress } from 'flavours/glitch/components/circular_progress';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';

View file

@ -5,8 +5,8 @@ import { FormattedMessage, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; 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 { openModal } from 'flavours/glitch/actions/modal';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import InlineAccount from 'flavours/glitch/components/inline_account'; import InlineAccount from 'flavours/glitch/components/inline_account';

View file

@ -0,0 +1,123 @@
// @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) => (
<FormattedMessage
id='trends.counter_by_accounts'
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
days: 2,
}}
/>
);
// @ts-expect-error
export const ImmutableHashtag = ({ hashtag }) => (
<Hashtag
name={hashtag.get('name')}
href={hashtag.get('url')}
to={`/tags/${hashtag.get('name')}`}
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
// @ts-expect-error
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
/>
);
ImmutableHashtag.propTypes = {
hashtag: ImmutablePropTypes.map.isRequired,
};
// @ts-expect-error
const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Permalink href={href} to={to}>
{name ? <>#<span>{name}</span></> : <Skeleton width={50} />}
</Permalink>
{description ? (
<span>{description}</span>
) : (
typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
)}
</div>
{typeof uses !== 'undefined' && (
<div className='trends__item__current'>
<ShortNumber value={uses} />
</div>
)}
{withGraph && (
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</SilentErrorBoundary>
</div>
)}
</div>
);
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;

View file

@ -1,149 +0,0 @@
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<SilentErrorBoundaryProps> {
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,
) => (
<FormattedMessage
id='trends.counter_by_accounts'
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
days: 2,
}}
/>
);
interface ImmutableHashtagProps {
hashtag: Immutable.Map<string, unknown>;
}
export const ImmutableHashtag = ({ hashtag }: ImmutableHashtagProps) => (
<Hashtag
name={hashtag.get('name') as string}
href={hashtag.get('url') as string}
to={`/tags/${hashtag.get('name') as string}`}
people={
(hashtag.getIn(['history', 0, 'accounts']) as number) * 1 +
(hashtag.getIn(['history', 1, 'accounts']) as number) * 1
}
history={(
hashtag.get('history') as Immutable.Collection.Indexed<
Immutable.Map<string, number>
>
)
.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<HashtagProps> = ({
name,
href,
to,
people,
uses,
history,
className,
description,
withGraph = true,
}) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Permalink href={href} to={to}>
{name ? (
<>
#<span>{name}</span>
</>
) : (
<Skeleton width={50} />
)}
</Permalink>
{description ? (
<span>{description}</span>
) : typeof people !== 'undefined' ? (
<ShortNumber value={people} renderer={accountsCountRenderer} />
) : (
<Skeleton width={100} />
)}
</div>
{typeof uses !== 'undefined' && (
<div className='trends__item__current'>
<ShortNumber value={uses} />
</div>
)}
{withGraph && (
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines
width={50}
height={28}
data={history ? history : Array.from(Array(7)).map(() => 0)}
>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</SilentErrorBoundary>
</div>
)}
</div>
);

View file

@ -1,7 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import CheckBoxOutlineBlankIcon from '@/material-icons/400-24px/check_box_outline_blank.svg?react'; import { ReactComponent as CheckBoxOutlineBlankIcon } from '@material-symbols/svg-600/outlined/check_box_outline_blank.svg';
import { isProduction } from 'flavours/glitch/utils/environment';
interface SVGPropsWithTitle extends React.SVGProps<SVGSVGElement> { interface SVGPropsWithTitle extends React.SVGProps<SVGSVGElement> {
title?: string; title?: string;
@ -25,7 +24,7 @@ export const Icon: React.FC<Props> = ({
}) => { }) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!IconComponent) { if (!IconComponent) {
if (!isProduction()) { if (process.env.NODE_ENV !== 'production') {
throw new Error( throw new Error(
`<Icon id="${id}" className="${className}"> is missing an "icon" prop.`, `<Icon id="${id}" className="${className}"> is missing an "icon" prop.`,
); );

View file

@ -2,7 +2,8 @@ import { useCallback } from 'react';
import { useIntl, defineMessages } from 'react-intl'; import { useIntl, defineMessages } from 'react-intl';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({

View file

@ -1,4 +1,4 @@
import logo from '@/images/logo.svg'; import logo from 'mastodon/../images/logo.svg';
export const WordmarkLogo = () => ( export const WordmarkLogo = () => (
<svg viewBox='0 0 261 66' className='logo logo--wordmark' role='img'> <svg viewBox='0 0 261 66' className='logo logo--wordmark' role='img'>

View file

@ -8,9 +8,9 @@ import classNames from 'classnames';
import { is } from 'immutable'; import { is } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state'; import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
@ -123,7 +123,7 @@ class Item extends PureComponent {
} }
if (attachment.get('description')?.length > 0) { if (attachment.get('description')?.length > 0) {
badges.push(<span key='alt' className='media-gallery__alt__label'>ALT</span>); badges.push(<span key='alt' className='media-gallery__gifv__label'>ALT</span>);
} }
const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); const description = attachment.getIn(['translation', 'description']) || attachment.get('description');

View file

@ -12,9 +12,9 @@ import classNames from 'classnames';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; import { ReactComponent as DeleteIcon } from '@material-symbols/svg-600/outlined/delete.svg';
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
btnAll : { id: 'notification_purge.btn_all', defaultMessage: 'Select\nall' }, btnAll : { id: 'notification_purge.btn_all', defaultMessage: 'Select\nall' },

View file

@ -0,0 +1,50 @@
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 (
<a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
{children}
</a>
);
}
}
export default withOptionalRouter(Permalink);

View file

@ -1,41 +0,0 @@
import { useCallback } from 'react';
import { useAppHistory } from './router';
interface Props extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
to: string;
}
export const Permalink: React.FC<Props> = ({
className,
href,
to,
children,
...props
}) => {
const history = useAppHistory();
const handleClick = useCallback<React.MouseEventHandler<HTMLAnchorElement>>(
(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 (
<a
target='_blank'
rel='noreferrer'
href={href}
onClick={handleClick}
className={`permalink${className ? ' ' + className : ''}`}
{...props}
>
{children}
</a>
);
};

View file

@ -5,8 +5,8 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; 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 { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';

View file

@ -7,10 +7,10 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; 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 escapeTextContentForBrowser from 'escape-html';
import spring from 'react-motion/lib/spring'; 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 { Icon } from 'flavours/glitch/components/icon';
import emojify from 'flavours/glitch/features/emoji/emoji'; import emojify from 'flavours/glitch/features/emoji/emoji';
import Motion from 'flavours/glitch/features/ui/util/optional_motion'; import Motion from 'flavours/glitch/features/ui/util/optional_motion';

View file

@ -11,7 +11,6 @@ import type {
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import { layoutFromWindow } from 'flavours/glitch/is_mobile'; import { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { isDevelopment } from 'flavours/glitch/utils/environment';
interface MastodonLocationState { interface MastodonLocationState {
fromMastodon?: boolean; fromMastodon?: boolean;
@ -41,7 +40,7 @@ function normalizePath(
} else if ( } else if (
location.state !== undefined && location.state !== undefined &&
state !== undefined && state !== undefined &&
isDevelopment() process.env.NODE_ENV === 'development'
) { ) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log( console.log(

View file

@ -8,22 +8,21 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg';
import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg';
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg';
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg';
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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; 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 DropdownMenuContainer from '../containers/dropdown_menu_container';
import { me } from '../initial_state'; import { me } from '../initial_state';
@ -305,7 +304,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (status.get('reblogged')) { if (status.get('reblogged')) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private); reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
} else if (publicStatus) { } else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog); reblogTitle = intl.formatMessage(messages.reblog);
reblogIconComponent = RepeatIcon; reblogIconComponent = RepeatIcon;

View file

@ -9,17 +9,17 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ImageIcon from '@/material-icons/400-24px/image.svg?react'; import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg';
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import LinkIcon from '@/material-icons/400-24px/link.svg?react'; import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';
import MovieIcon from '@/material-icons/400-24px/movie.svg?react'; import { ReactComponent as MovieIcon } from '@material-symbols/svg-600/outlined/movie.svg';
import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; import { ReactComponent as MusicNoteIcon } from '@material-symbols/svg-600/outlined/music_note.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
import Permalink from './permalink';
import { Permalink } from './permalink';
const textMatchesTarget = (text, origin, host) => { const textMatchesTarget = (text, origin, host) => {
return (text === origin || text === host return (text === origin || text === host

View file

@ -6,18 +6,18 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ExpandLessIcon from '@/material-icons/400-24px/expand_less.svg?react'; import { ReactComponent as ExpandLessIcon } from '@material-symbols/svg-600/outlined/expand_less.svg';
import ForumIcon from '@/material-icons/400-24px/forum.svg?react'; import { ReactComponent as ForumIcon } from '@material-symbols/svg-600/outlined/forum.svg';
import HomeIcon from '@/material-icons/400-24px/home.svg?react'; import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home.svg';
import ImageIcon from '@/material-icons/400-24px/image.svg?react'; import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg';
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import LinkIcon from '@/material-icons/400-24px/link.svg?react'; import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';
import MovieIcon from '@/material-icons/400-24px/movie.svg?react'; import { ReactComponent as MovieIcon } from '@material-symbols/svg-600/outlined/movie.svg';
import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; import { ReactComponent as MusicNoteIcon } from '@material-symbols/svg-600/outlined/music_note.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { languages } from 'flavours/glitch/initial_state'; import { languages } from 'flavours/glitch/initial_state';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';
import { VisibilityIcon } from './visibility_icon'; import { VisibilityIcon } from './visibility_icon';

View file

@ -6,16 +6,16 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg';
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { me } from 'flavours/glitch/initial_state'; import { me } from 'flavours/glitch/initial_state';
export default class StatusPrepend extends PureComponent { export default class StatusPrepend extends PureComponent {
static propTypes = { static propTypes = {

View file

@ -1,4 +1,4 @@
import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { Icon } from './icon'; import { Icon } from './icon';

View file

@ -1,9 +1,9 @@
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg';
import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';
import MailIcon from '@/material-icons/400-24px/mail.svg?react'; import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import { Icon } from './icon'; import { Icon } from './icon';

View file

@ -18,9 +18,8 @@ import UI from 'flavours/glitch/features/ui';
import initialState, { title as siteTitle } from 'flavours/glitch/initial_state'; import initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales'; import { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store'; import { store } from 'flavours/glitch/store';
import { isProduction } from 'flavours/glitch/utils/environment';
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`; const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
const hydrateAction = hydrateStore(initialState); const hydrateAction = hydrateStore(initialState);

View file

@ -10,9 +10,9 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; 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 { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';

View file

@ -6,9 +6,9 @@ import { NavLink } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg';
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';
class ActionBar extends PureComponent { class ActionBar extends PureComponent {
@ -30,8 +30,7 @@ class ActionBar extends PureComponent {
return ( return (
<div> <div>
<div className='account__disclaimer'> <div className='account__disclaimer'>
<Icon id='info-circle' icon={InfoIcon} /> <Icon id='info-circle' icon={InfoIcon} /> <FormattedMessage
<FormattedMessage
id='account.suspended_disclaimer_full' id='account.suspended_disclaimer_full'
defaultMessage='This user has been suspended by a moderator.' defaultMessage='This user has been suspended by a moderator.'
/> />
@ -45,17 +44,14 @@ class ActionBar extends PureComponent {
if (account.get('acct') !== account.get('username')) { if (account.get('acct') !== account.get('username')) {
extraInfo = ( extraInfo = (
<div className='account__disclaimer'> <div className='account__disclaimer'>
<Icon id='info-circle' icon={InfoIcon} /> <Icon id='info-circle' icon={InfoIcon} /> <FormattedMessage
<div> id='account.disclaimer_full'
<FormattedMessage defaultMessage="Information below may reflect the user's profile incompletely."
id='account.disclaimer_full' />
defaultMessage="Information below may reflect the user's profile incompletely." {' '}
/> <a target='_blank' rel='noopener' href={account.get('url')}>
{' '} <FormattedMessage id='account.view_full_profile' defaultMessage='View full profile' />
<a target='_blank' rel='noopener' href={account.get('url')}> </a>
<FormattedMessage id='account.view_full_profile' defaultMessage='View full profile' />
</a>
</div>
</div> </div>
); );
} }

View file

@ -5,7 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { Hashtag } from 'flavours/glitch/components/hashtag'; import Hashtag from 'flavours/glitch/components/hashtag';
const messages = defineMessages({ const messages = defineMessages({
lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' }, lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },

View file

@ -3,10 +3,10 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';
export default class FollowRequestNote extends ImmutablePureComponent { export default class FollowRequestNote extends ImmutablePureComponent {

View file

@ -9,12 +9,12 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; 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 { Avatar } from 'flavours/glitch/components/avatar';
import { Badge, AutomatedBadge, GroupBadge } from 'flavours/glitch/components/badge'; import { Badge, AutomatedBadge, GroupBadge } from 'flavours/glitch/components/badge';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';

View file

@ -3,7 +3,7 @@ import { PureComponent } from 'react';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
import PersonIcon from '@/material-icons/400-24px/person.svg?react'; import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg';
import ColumnHeader from '../../../components/column_header'; import ColumnHeader from '../../../components/column_header';

View file

@ -5,14 +5,14 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import AudiotrackIcon from '@/material-icons/400-24px/music_note.svg?react'; import { ReactComponent as AudiotrackIcon } from '@material-symbols/svg-600/outlined/music_note.svg';
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow.svg?react'; import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg';
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
export default class MediaItem extends ImmutablePureComponent { export default class MediaItem extends ImmutablePureComponent {
static propTypes = { static propTypes = {

View file

@ -5,8 +5,8 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; 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 { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';

View file

@ -7,18 +7,17 @@ import classNames from 'classnames';
import { is } from 'immutable'; 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 { 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 { Icon } from 'flavours/glitch/components/icon';
import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
import { Blurhash } from '../../components/blurhash'; import { Blurhash } from '../../components/blurhash';
import { displayMedia, useBlurhash } from '../../initial_state'; import { displayMedia, useBlurhash } from '../../initial_state';

View file

@ -6,10 +6,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
import { fetchBlocks, expandBlocks } from '../../actions/blocks'; import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';

View file

@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks'; import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';

View file

@ -7,8 +7,8 @@ import { Helmet } from 'react-helmet';
import { connect } from 'react-redux'; 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 { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';

View file

@ -5,9 +5,9 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg';
import { preferencesLink, profileLink } from 'flavours/glitch/utils/backend_links';
import { preferencesLink, profileLink } from 'flavours/glitch/utils/backend_links';
import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container';

View file

@ -7,18 +7,18 @@ import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg';
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import LogoutIcon from '@/material-icons/400-24px/logout.svg?react'; import { ReactComponent as LogoutIcon } from '@material-symbols/svg-600/outlined/logout.svg';
import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react'; import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg';
import MenuIcon from '@/material-icons/400-24px/menu.svg?react'; import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg';
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { signOutLink } from 'flavours/glitch/utils/backend_links'; import { signOutLink } from 'flavours/glitch/utils/backend_links';
import { conditionalRender } from 'flavours/glitch/utils/react_helpers'; import { conditionalRender } from 'flavours/glitch/utils/react_helpers';
const messages = defineMessages({ const messages = defineMessages({
community: { community: {
defaultMessage: 'Local timeline', defaultMessage: 'Local timeline',

View file

@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; 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 { profileLink } from 'flavours/glitch/utils/backend_links';
import { Avatar } from '../../../components/avatar'; import { Avatar } from '../../../components/avatar';

View file

@ -6,20 +6,19 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; 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 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 { IconButton } from 'flavours/glitch/components/icon_button';
import { pollLimits } from 'flavours/glitch/initial_state'; import { pollLimits } from 'flavours/glitch/initial_state';
import DropdownContainer from '../containers/dropdown_container'; import DropdownContainer from '../containers/dropdown_container';
import LanguageDropdown from '../containers/language_dropdown_container'; import LanguageDropdown from '../containers/language_dropdown_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';

View file

@ -8,9 +8,9 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; 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 AutosuggestInput from 'flavours/glitch/components/autosuggest_input';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';

View file

@ -3,10 +3,10 @@ import { PureComponent } from 'react';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg';
import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';
import MailIcon from '@/material-icons/400-24px/mail.svg?react'; import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import Dropdown from './dropdown'; import Dropdown from './dropdown';

Some files were not shown because too many files have changed in this diff Show more