Skip to content

Commit

Permalink
Merge commit '7742f440fa2a1541cd71512cadfec1762f16cdc9' into kbtopic-…
Browse files Browse the repository at this point in the history
…follow-9.0-to-patch
kmycode committed Nov 28, 2023

Verified

This commit was signed with the committer’s verified signature.
daryllimyt Daryl Lim
2 parents 170f1e9 + 7742f44 commit c4410ac
Showing 862 changed files with 28,955 additions and 21,624 deletions.
4 changes: 2 additions & 2 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
# Install Rails
# RUN gem install rails webdrivers

ARG NODE_VERSION="16"
ARG NODE_VERSION="20"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"

# [Optional] Uncomment this section to install additional OS packages.
@@ -15,6 +15,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
RUN gem install foreman

# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1

COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
2 changes: 1 addition & 1 deletion .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -70,7 +70,7 @@ services:
hard: -1

libretranslate:
image: libretranslate/libretranslate:v1.3.12
image: libretranslate/libretranslate:v1.4.1
restart: unless-stopped
volumes:
- lt-data:/home/libretranslate/.local
3 changes: 2 additions & 1 deletion .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -11,7 +11,8 @@ bundle install
git checkout -- Gemfile.lock

# Fetch Javascript dependencies
yarn --frozen-lockfile
corepack prepare
yarn install --immutable

# [re]create, migrate, and seed the test database
RAILS_ENV=test ./bin/rails db:setup
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
public/system
public/assets
public/packs
public/packs-test
node_modules
neo4j
vendor/bundle
4 changes: 2 additions & 2 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Node.js
NODE_ENV=tests
# In test, compile the NodeJS code as if we are in production
NODE_ENV=production
# Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -236,7 +236,7 @@ module.exports = {
},
// Common React utilities
{
pattern: '{classnames,react-helmet,react-router-dom}',
pattern: '{classnames,react-helmet,react-router,react-router-dom}',
group: 'external',
position: 'before',
},
42 changes: 42 additions & 0 deletions .github/actions/setup-javascript/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: 'Setup Javascript'
description: 'Setup a Javascript environment ready to run the Mastodon code'
inputs:
onlyProduction:
description: Only install production dependencies
default: 'false'

runs:
using: 'composite'
steps:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'

# The following is needed because we can not use `cache: true` for `setup-node`, as it does not support Corepack yet and mess up with the cache location if ran after Node is installed
- name: Enable corepack
shell: bash
run: corepack enable

- name: Get yarn cache directory path
id: yarn-cache-dir-path
shell: bash
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT

- uses: actions/cache@v3
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install all yarn packages
shell: bash
run: yarn install --immutable
if: inputs.onlyProduction == 'false'

- name: Install all production yarn packages
shell: bash
run: yarn workspaces focus --production
if: inputs.onlyProduction != 'false'
23 changes: 23 additions & 0 deletions .github/actions/setup-ruby/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: 'Setup RUby'
description: 'Setup a Ruby environment ready to run the Mastodon code'
inputs:
ruby-version:
description: The Ruby version to install
default: '.ruby-version'
additional-system-dependencies:
description: 'Additional packages to install'

runs:
using: 'composite'
steps:
- name: Install system dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev ${{ inputs.additional-system-dependencies }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ inputs.ruby-version }}
bundler-cache: true
2 changes: 1 addition & 1 deletion .github/renovate.json5
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
extends: [
'config:recommended',
':labels(dependencies)',
':maintainLockFilesMonthly', // update non-direct dependencies monthly
':prConcurrentLimitNone', // Remove limit for open PRs at any time.
':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour.
],
@@ -13,6 +12,7 @@
// If we do not want a package to be grouped with others, we need to set its groupName
// to `null` after any other rule set it to something.
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
postUpdateOptions: ['yarnDedupeHighest'],
packageRules: [
{
// Require Dependency Dashboard Approval for major version bumps of these node packages
10 changes: 2 additions & 8 deletions .github/workflows/bundler-audit.yml
Original file line number Diff line number Diff line change
@@ -27,14 +27,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

- name: Run bundler-audit
run: bundle exec bundler-audit
24 changes: 5 additions & 19 deletions .github/workflows/check-i18n.yml
Original file line number Diff line number Diff line change
@@ -19,25 +19,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: Check for missing strings in English JSON
run: |
10 changes: 2 additions & 8 deletions .github/workflows/lint-css.yml
Original file line number Diff line number Diff line change
@@ -35,14 +35,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- uses: xt0rted/stylelint-problem-matcher@v1

12 changes: 2 additions & 10 deletions .github/workflows/lint-haml.yml
Original file line number Diff line number Diff line change
@@ -30,16 +30,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Install native Ruby dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

- name: Run haml-lint
run: |
10 changes: 2 additions & 8 deletions .github/workflows/lint-js.yml
Original file line number Diff line number Diff line change
@@ -39,14 +39,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: ESLint
run: yarn lint:js --max-warnings 0
10 changes: 2 additions & 8 deletions .github/workflows/lint-json.yml
Original file line number Diff line number Diff line change
@@ -31,14 +31,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: Prettier
run: yarn lint:json
10 changes: 2 additions & 8 deletions .github/workflows/lint-md.yml
Original file line number Diff line number Diff line change
@@ -31,14 +31,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: Prettier
run: yarn lint:md
10 changes: 2 additions & 8 deletions .github/workflows/lint-ruby.yml
Original file line number Diff line number Diff line change
@@ -31,14 +31,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

- name: Set-up RuboCop Problem Matcher
uses: r7kamura/rubocop-problem-matchers-action@v1
10 changes: 2 additions & 8 deletions .github/workflows/lint-yml.yml
Original file line number Diff line number Diff line change
@@ -33,14 +33,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: Prettier
run: yarn lint:yml
10 changes: 2 additions & 8 deletions .github/workflows/test-js.yml
Original file line number Diff line number Diff line change
@@ -35,14 +35,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: Jest testing
run: yarn jest --reporters github-actions summary
12 changes: 2 additions & 10 deletions .github/workflows/test-migrations-one-step.yml
Original file line number Diff line number Diff line change
@@ -72,16 +72,8 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install native Ruby dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

- name: Create database
run: './bin/rails db:create'
12 changes: 2 additions & 10 deletions .github/workflows/test-migrations-two-step.yml
Original file line number Diff line number Diff line change
@@ -71,16 +71,8 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install native Ruby dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

- name: Create database
run: './bin/rails db:create'
103 changes: 29 additions & 74 deletions .github/workflows/test-ruby.yml
Original file line number Diff line number Diff line change
@@ -34,36 +34,29 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install native Ruby dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby

- name: Set up bundler cache
uses: ruby/setup-ruby@v1
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
with:
ruby-version: .ruby-version
bundler-cache: true
onlyProduction: 'true'

- run: yarn --frozen-lockfile --production
- name: Precompile assets
# Previously had set this, but it's not supported
# export NODE_OPTIONS=--openssl-legacy-provider
run: |-
./bin/rails assets:precompile
- name: Archive asset artifacts
run: |
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs*
- uses: actions/upload-artifact@v3
if: matrix.mode == 'test'
with:
path: |-
./public/assets
./public/packs-test
./artifacts.tar.gz
name: ${{ github.sha }}
retention-days: 0

@@ -112,7 +105,7 @@ jobs:
SAML_ENABLED: true
CAS_ENABLED: true
BUNDLE_WITH: 'pam_authentication test'
CI_JOBS: ${{ matrix.ci_job }}/4
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
ES_ENABLED: false

strategy:
@@ -122,38 +115,28 @@ jobs:
- '3.0'
- '3.1'
- '.ruby-version'
ci_job:
- 1
- 2
- 3
- 4
steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v3
with:
path: './public'
path: './'
name: ${{ github.sha }}

- name: Update package index
run: sudo apt-get update

- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev

- name: Install additional system dependencies
run: sudo apt-get install -y ffmpeg imagemagick libpam-dev
- name: Expand archived asset artifacts
run: |
tar xvzf artifacts.tar.gz
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true
additional-system-dependencies: ffmpeg imagemagick libpam-dev

- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'

- run: bundle exec rake rspec_chunked
- run: bin/rspec

test-e2e:
name: End to End testing
@@ -211,28 +194,14 @@ jobs:
path: './public'
name: ${{ github.sha }}

- name: Update package index
run: sudo apt-get update

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev

- name: Install additional system dependencies
run: sudo apt-get install -y ffmpeg imagemagick

- name: Set up bundler cache
uses: ruby/setup-ruby@v1
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true
additional-system-dependencies: ffmpeg imagemagick

- run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
@@ -329,28 +298,14 @@ jobs:
path: './public'
name: ${{ github.sha }}

- name: Update package index
run: sudo apt-get update

- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'

- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev

- name: Install additional system dependencies
run: sudo apt-get install -y ffmpeg imagemagick

- name: Set up bundler cache
uses: ruby/setup-ruby@v1
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true
additional-system-dependencies: ffmpeg imagemagick

- run: yarn --frozen-lockfile
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript

- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -55,6 +55,15 @@ npm-debug.log
yarn-error.log
yarn-debug.log

# From https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Ignore vagrant log files
*-cloudimg-console.log

2 changes: 2 additions & 0 deletions .haml-lint.yml
Original file line number Diff line number Diff line change
@@ -12,3 +12,5 @@ linters:
enabled: true
MiddleDot:
enabled: true
LineLength:
max: 320
46 changes: 26 additions & 20 deletions .haml-lint_todo.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
# on 2023-10-11 11:31:24 -0400 using Haml-Lint version 0.51.0.
# on 2023-10-26 09:32:34 -0400 using Haml-Lint version 0.51.0.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again.

linters:
# Offense count: 946
# Offense count: 16
LineLength:
enabled: false

# Offense count: 22
UnnecessaryStringOutput:
enabled: false
exclude:
- 'app/views/admin/account_actions/new.html.haml'
- 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/ip_blocks/new.html.haml'
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/admin/settings/discovery/show.html.haml'
- 'app/views/auth/registrations/edit.html.haml'
- 'app/views/auth/registrations/new.html.haml'
- 'app/views/filters/_filter_fields.html.haml'
- 'app/views/media/player.html.haml'
- 'app/views/settings/applications/_fields.html.haml'
- 'app/views/settings/imports/index.html.haml'
- 'app/views/settings/preferences/appearance/show.html.haml'
- 'app/views/settings/preferences/notifications/show.html.haml'
- 'app/views/settings/preferences/other/show.html.haml'
- 'app/views/settings/preferences/reaching/show.html.haml'
- 'app/views/settings/profiles/show.html.haml'
- 'app/views/settings/privacy_extra/show.html.haml'

# Offense count: 44
# Offense count: 9
RuboCop:
enabled: false
exclude:
- 'app/views/admin/accounts/_buttons.html.haml'
- 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/home/index.html.haml'

# Offense count: 3
ViewLength:
exclude:
- 'app/views/admin/accounts/show.html.haml'
- 'app/views/admin/instances/show.html.haml'
- 'app/views/admin/reports/show.html.haml'
- 'app/views/disputes/strikes/show.html.haml'

# Offense count: 15
InstanceVariables:
exclude:
- 'app/views/application/_sidebar.html.haml'

# Offense count: 2
IdNames:
exclude:
- 'app/views/oauth/authorizations/error.html.haml'
- 'app/views/shared/_error_messages.html.haml'
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.8
20.9
19 changes: 12 additions & 7 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ AllCops:
- 'node_modules/**/*'
- 'Vagrantfile'
- 'vendor/**/*'
- 'lib/json_ld/*' # Generated files
- 'config/initializers/json_ld*' # Generated files
- 'lib/mastodon/migration_helpers.rb' # Vendored from GitLab
- 'lib/templates/**/*'

@@ -121,16 +121,11 @@ Rails/Exit:
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath
RSpec/FilePath:
CustomTransform:
ActivityPub: activitypub # Ignore the snake_case due to the amount of files to rename
ActivityPub: activitypub
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
JsonLdHelper: jsonld_helper
OEmbedController: oembed_controller
OStatus: ostatus
NodeInfoController: nodeinfo_controller # NodeInfo isn't snake_cased for any of the instances
Exclude:
- 'spec/config/initializers/rack_attack_spec.rb' # namespaces usually have separate folder
- 'spec/lib/sanitize_config_spec.rb' # namespaces usually have separate folder

# Reason:
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject
@@ -147,6 +142,16 @@ RSpec/NotToNot:
RSpec/Rails/HttpStatus:
EnforcedStyle: numeric

# Reason: Match overrides from Rspec/FilePath rule above
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecspecfilepathformat
RSpec/SpecFilePathFormat:
CustomTransform:
ActivityPub: activitypub
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
OEmbedController: oembed_controller
OStatus: ostatus

# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren
Style/ClassAndModuleChildren:
232 changes: 4 additions & 228 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.56.1.
# using RuboCop version 1.57.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -20,55 +20,10 @@ Layout/LineLength:
Exclude:
- 'app/models/account.rb'

# Configuration parameters: AllowComments, AllowEmptyLambdas.
Lint/EmptyBlock:
Exclude:
- 'spec/controllers/api/v2/search_controller_spec.rb'
- 'spec/fabricators/access_token_fabricator.rb'
- 'spec/fabricators/conversation_fabricator.rb'
- 'spec/fabricators/system_key_fabricator.rb'
- 'spec/lib/activitypub/adapter_spec.rb'
- 'spec/models/user_role_spec.rb'

Lint/NonLocalExitFromIterator:
Exclude:
- 'app/helpers/jsonld_helper.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
Lint/OrAssignmentToConstant:
Exclude:
- 'lib/sanitize_ext/sanitize_config.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- 'config/initializers/content_security_policy.rb'
- 'config/initializers/doorkeeper.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/simple_form.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
Lint/UselessAssignment:
Exclude:
- 'app/services/activitypub/process_status_update_service.rb'
- 'config/initializers/3_omniauth.rb'
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
- 'spec/controllers/api/v1/favourites_controller_spec.rb'
- 'spec/controllers/concerns/account_controller_concern_spec.rb'
- 'spec/helpers/jsonld_helper_spec.rb'
- 'spec/models/account_spec.rb'
- 'spec/models/domain_block_spec.rb'
- 'spec/models/status_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/models/webauthn_credentials_spec.rb'
- 'spec/services/account_search_service_spec.rb'
- 'spec/services/post_status_service_spec.rb'
- 'spec/services/precompute_feed_service_spec.rb'
- 'spec/services/resolve_url_service_spec.rb'
- 'spec/views/statuses/show.html.haml_spec.rb'

# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 144
@@ -86,40 +41,11 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 27

# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
# SupportedStyles: snake_case, normalcase, non_integer
# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
Naming/VariableNumber:
Exclude:
- 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- 'db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- 'db/migrate/20190820003045_update_statuses_index.rb'
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
- 'spec/models/account_spec.rb'
- 'spec/models/domain_block_spec.rb'
- 'spec/models/user_spec.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SafeMultiline.
Performance/DeletePrefix:
Exclude:
- 'app/models/featured_tag.rb'

Performance/MapMethodChain:
Exclude:
- 'app/models/feed.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'spec/services/bulk_import_service_spec.rb'
- 'spec/services/import_service_spec.rb'

RSpec/AnyInstance:
Exclude:
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/resets_controller_spec.rb'
- 'spec/controllers/admin/settings/branding_controller_spec.rb'
- 'spec/controllers/api/v1/media_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
@@ -136,47 +62,8 @@ RSpec/AnyInstance:
RSpec/ExampleLength:
Max: 22

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: implicit, each, example
RSpec/HookArgument:
Exclude:
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/well_known/webfinger_controller_spec.rb'
- 'spec/helpers/instance_helper_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/rails_helper.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/services/import_service_spec.rb'

# Configuration parameters: AssignmentOnly.
RSpec/InstanceVariable:
Exclude:
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/auth/confirmations_controller_spec.rb'
- 'spec/controllers/auth/passwords_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
- 'spec/controllers/home_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
- 'spec/controllers/statuses_cleanup_controller_spec.rb'
- 'spec/models/concerns/account_finder_concern_spec.rb'
- 'spec/models/concerns/account_interactions_spec.rb'
- 'spec/models/public_feed_spec.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/services/remove_status_service_spec.rb'
- 'spec/services/search_service_spec.rb'
- 'spec/services/unblock_domain_service_spec.rb'

RSpec/LetSetup:
Exclude:
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/action_logs_controller_spec.rb'
- 'spec/controllers/admin/instances_controller_spec.rb'
- 'spec/controllers/admin/reports/actions_controller_spec.rb'
- 'spec/controllers/admin/statuses_controller_spec.rb'
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
- 'spec/controllers/api/v1/filters_controller_spec.rb'
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
@@ -217,37 +104,14 @@ RSpec/LetSetup:
- 'spec/services/unsuspend_account_service_spec.rb'
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'

RSpec/MessageChain:
Exclude:
- 'spec/controllers/api/v1/media_controller_spec.rb'
- 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'

# Configuration parameters: EnforcedStyle.
# SupportedStyles: have_received, receive
RSpec/MessageSpies:
Exclude:
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/helpers/admin/account_moderation_notes_helper_spec.rb'
- 'spec/lib/webfinger_resource_spec.rb'
- 'spec/models/admin/account_action_spec.rb'
- 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/follow_request_spec.rb'
- 'spec/models/identity_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'
- 'spec/services/activitypub/fetch_replies_service_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb'
- 'spec/spec_helper.rb'
- 'spec/validators/status_length_validator_spec.rb'

RSpec/MultipleExpectations:
Max: 8

# Configuration parameters: AllowSubject.
RSpec/MultipleMemoizedHelpers:
Max: 21
Exclude:
- 'spec/services/delete_account_service_spec.rb'

# Configuration parameters: AllowedGroups.
RSpec/NestedGroups:
@@ -258,26 +122,6 @@ Rails/ApplicationController:
Exclude:
- 'app/controllers/health_controller.rb'

# Configuration parameters: Include.
# Include: db/**/*.rb
Rails/CreateTableWithTimestamps:
Exclude:
- 'db/migrate/20170508230434_create_conversation_mutes.rb'
- 'db/migrate/20170823162448_create_status_pins.rb'
- 'db/migrate/20171116161857_create_list_accounts.rb'
- 'db/migrate/20180929222014_create_account_conversations.rb'
- 'db/migrate/20181007025445_create_pghero_space_stats.rb'
- 'db/migrate/20190103124649_create_scheduled_statuses.rb'
- 'db/migrate/20220824233535_create_status_trends.rb'
- 'db/migrate/20221006061337_create_preview_card_trends.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Severity.
Rails/DuplicateAssociation:
Exclude:
- 'app/serializers/activitypub/collection_serializer.rb'
- 'app/serializers/activitypub/note_serializer.rb'

# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasAndBelongsToMany:
@@ -301,18 +145,12 @@ Rails/HasManyOrHasOneDependent:
- 'app/models/user.rb'
- 'app/models/web/push_subscription.rb'

Rails/I18nLocaleTexts:
Exclude:
- 'lib/tasks/mastodon.rake'
- 'spec/helpers/flashes_helper_spec.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'
- 'app/controllers/auth/sessions_controller.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/NegateInclude:
@@ -328,7 +166,6 @@ Rails/NegateInclude:
- 'app/models/custom_filter.rb'
- 'app/services/activitypub/process_status_update_service.rb'
- 'app/services/fetch_link_card_service.rb'
- 'app/services/search_service.rb'
- 'app/workers/web/push_notification_worker.rb'
- 'lib/paperclip/color_extractor.rb'

@@ -348,24 +185,6 @@ Rails/RakeEnvironment:
- 'lib/tasks/repo.rake'
- 'lib/tasks/statistics.rake'

# Configuration parameters: Include.
# Include: db/**/*.rb
Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20160223164502_make_uris_nullable_in_statuses.rb'
- 'db/migrate/20161122163057_remove_unneeded_indexes.rb'
- 'db/migrate/20170205175257_remove_devices.rb'
- 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb'
- 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb'
- 'db/migrate/20170609145826_remove_default_language_from_statuses.rb'
- 'db/migrate/20170711225116_fix_null_booleans.rb'
- 'db/migrate/20171129172043_add_index_on_stream_entries.rb'
- 'db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb'
- 'db/migrate/20171226094803_more_faster_index_on_notifications.rb'
- 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- 'db/migrate/20180617162849_remove_unused_indexes.rb'
- 'db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb'

# 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:
@@ -412,42 +231,11 @@ Rails/SkipsModelValidations:
- 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb'
- 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb'
- 'lib/mastodon/cli/accounts.rb'
- 'lib/mastodon/cli/main.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'spec/lib/activitypub/activity/follow_spec.rb'
- 'spec/services/follow_service_spec.rb'
- 'spec/services/update_account_service_spec.rb'

# Configuration parameters: Include.
# Include: db/**/*.rb
Rails/ThreeStateBooleanColumn:
Exclude:
- 'db/migrate/20160325130944_add_admin_to_users.rb'
- 'db/migrate/20161123093447_add_sensitive_to_statuses.rb'
- 'db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb'
- 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb'
- 'db/migrate/20170209184350_add_reply_to_statuses.rb'
- 'db/migrate/20170330163835_create_imports.rb'
- 'db/migrate/20170905165803_add_local_to_statuses.rb'
- 'db/migrate/20181203021853_add_discoverable_to_accounts.rb'
- 'db/migrate/20190509164208_add_by_moderator_to_tombstone.rb'
- 'db/migrate/20190805123746_add_capabilities_to_tags.rb'
- 'db/migrate/20191212163405_add_hide_collections_to_accounts.rb'
- 'db/migrate/20200309150742_add_forwarded_to_reports.rb'
- 'db/migrate/20210609202149_create_login_activities.rb'
- 'db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb'
- 'db/migrate/20211031031021_create_preview_card_providers.rb'
- 'db/migrate/20211115032527_add_trendable_to_preview_cards.rb'
- 'db/migrate/20220202200743_add_trendable_to_accounts.rb'
- 'db/migrate/20220202200926_add_trendable_to_statuses.rb'
- 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb'
- 'db/migrate/20230314021909_add_group_message_following_only_to_accounts.rb'
- 'db/migrate/20230314081013_add_group_allow_private_message_to_accounts.rb'
- 'db/migrate/20230412005311_add_markdown_to_statuses.rb'
- 'db/migrate/20230412073021_add_markdown_to_status_edits.rb'
- 'db/migrate/20230428111230_add_emoji_reaction_streaming_to_accounts.rb'
- 'db/migrate/20230510004621_remove_stop_emoji_reaction_streaming_from_accounts.rb'

# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/UniqueValidationWithoutIndex:
@@ -511,7 +299,7 @@ Style/CaseEquality:
Exclude:
- 'config/initializers/trusted_proxies.rb'

# This cop supports safe autocorrection (--autocorrect).
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowedMethods, AllowedPatterns.
# AllowedMethods: ==, equal?, eql?
Style/ClassEqualityComparison:
@@ -535,7 +323,6 @@ Style/FetchEnvVar:
- 'config/initializers/3_omniauth.rb'
- 'config/initializers/blacklists.rb'
- 'config/initializers/cache_buster.rb'
- 'config/initializers/content_security_policy.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/vapid.rb'
@@ -719,7 +506,6 @@ Style/RedundantReturn:
Style/SafeNavigation:
Exclude:
- 'app/models/concerns/account_finder_concern.rb'
- 'app/models/status.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
@@ -734,14 +520,6 @@ Style/SingleArgumentDig:
Exclude:
- 'lib/webpacker/manifest_extensions.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_parentheses, require_no_parentheses
Style/StabbyLambdaParentheses:
Exclude:
- 'config/environments/production.rb'
- 'config/initializers/content_security_policy.rb'

# This cop supports safe autocorrection (--autocorrect).
Style/StderrPuts:
Exclude:
@@ -801,5 +579,3 @@ Style/TrailingCommaInHashLiteral:
Style/WordArray:
Exclude:
- 'app/helpers/languages_helper.rb'
- 'spec/controllers/settings/imports_controller_spec.rb'
- 'spec/models/form/import_spec.rb'
Empty file added .yarn/.gitkeep
Empty file.
13 changes: 13 additions & 0 deletions .yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/lib/index.js b/lib/index.js
index 16ed6be8be8f555cc99096c2ff60954b42dc313d..d009c069770d066ad0db7ad02de1ea473a29334e 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -99,7 +99,7 @@ function lodash(_ref) {

var node = _ref3;

- if ((0, _types.isModuleDeclaration)(node)) {
+ if ((0, _types.isImportDeclaration)(node) || (0, _types.isExportDeclaration)(node)) {
isModule = true;
break;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
diff --git a/dist/index.js b/dist/index.js
index 57e375592d984e9a429bcd9f800fa2d15cd662e4..0c47d96df3608e23adfd77d887a8f72abbd501c0 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
});
exports.default = void 0;

-var _crypto = _interopRequireDefault(require("crypto"));
+var _createHash = _interopRequireDefault(require("webpack/lib/util/createHash"));

var _path = _interopRequireDefault(require("path"));

@@ -227,7 +227,7 @@ class CompressionPlugin {
originalAlgorithm: this.options.algorithm,
compressionOptions: this.options.compressionOptions,
name,
- contentHash: _crypto.default.createHash("md4").update(input).digest("hex")
+ contentHash: _createHash.default("md4").update(input).digest("hex")
};
} else {
cacheData.name = (0, _serializeJavascript.default)({
49 changes: 0 additions & 49 deletions .yarnclean

This file was deleted.

1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
2,329 changes: 1 addition & 2,328 deletions CHANGELOG.md

Large diffs are not rendered by default.

18 changes: 12 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.4
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
ARG NODE_VERSION="20.8-bookworm-slim"
ARG NODE_VERSION="20.9-bookworm-slim"

FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
FROM node:${NODE_VERSION} as build
@@ -13,7 +13,6 @@ ENV DEBIAN_FRONTEND="noninteractive" \
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

WORKDIR /opt/mastodon
COPY Gemfile* package.json yarn.lock /opt/mastodon/

# hadolint ignore=DL3008
RUN apt-get update && \
@@ -28,16 +27,22 @@ RUN apt-get update && \
libgdbm-dev \
libgmp-dev \
libssl-dev \
libyaml-0-2 \
libyaml-dev \
ca-certificates \
libreadline8 \
python3 \
shared-mime-info && \
bundle config set --local deployment 'true' && \
bundle config set --local without 'development test' && \
bundle config set silence_root_warning true && \
bundle install -j"$(nproc)" && \
yarn install --pure-lockfile --production --network-timeout 600000 && \
corepack enable

COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY .yarn /opt/mastodon/.yarn

RUN bundle install -j"$(nproc)"

RUN yarn workspaces focus --all --production && \
yarn cache clean

FROM node:${NODE_VERSION}
@@ -78,7 +83,8 @@ RUN apt-get update && \
tzdata \
libreadline8 \
tini && \
ln -s /opt/mastodon /mastodon
ln -s /opt/mastodon /mastodon && \
corepack enable

# Note: no, cleaning here since Debian does this automatically
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
13 changes: 5 additions & 8 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ source 'https://rubygems.org'
ruby '>= 3.0.0'

gem 'puma', '~> 6.3'
gem 'rails', '~> 7.0'
gem 'rails', '~> 7.1.1'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.7'
@@ -16,14 +16,14 @@ gem 'dotenv-rails', '~> 2.8'

gem 'aws-sdk-s3', '~> 1.123', require: false
gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'fog-openstack', '~> 1.0', require: false
gem 'kt-paperclip', '~> 7.2'
gem 'md-paperclip-azure', '~> 2.2', require: false
gem 'blurhash', '~> 0.1'

gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.16.0', require: false
gem 'bootsnap', '~> 1.17.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.3'
@@ -88,7 +88,7 @@ gem 'simple-navigation', '~> 4.4'
gem 'simple_form', '~> 5.2'
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
gem 'stoplight', '~> 3.0.1'
gem 'strong_migrations', '~> 0.8'
gem 'strong_migrations', '1.6.4'
gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
gem 'tzinfo-data', '~> 1.2023'
@@ -103,9 +103,6 @@ gem 'rdf-normalize', '~> 0.5'
gem 'private_address_check', '~> 0.5'

group :test do
# Used to split testing into chunks in CI
gem 'rspec_chunked', '~> 0.6'

# Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
gem 'rspec-github', '~> 2.4', require: false

@@ -199,7 +196,7 @@ gem 'connection_pool', require: false
gem 'xorcist', '~> 1.1'
gem 'cocoon', '~> 1.2'

gem 'net-http', '~> 0.3.2'
gem 'net-http', '~> 0.4.0'
gem 'rubyzip', '~> 2.3'

gem 'hcaptcha', '~> 7.1'
268 changes: 147 additions & 121 deletions Gemfile.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -112,11 +112,11 @@ bundle install
# Install node modules
sudo corepack enable
yarn set version classic
corepack prepare
yarn install
# Build Mastodon
export RAILS_ENV=development
export RAILS_ENV=development
export $(cat ".env.vagrant" | xargs)
bundle exec rails db:setup
2 changes: 1 addition & 1 deletion app/controllers/admin/account_actions_controller.rb
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ def create
account_action.save!

if account_action.with_report?
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id])
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
else
redirect_to admin_account_path(@account.id)
end
4 changes: 2 additions & 2 deletions app/controllers/admin/custom_emojis_controller.rb
Original file line number Diff line number Diff line change
@@ -67,11 +67,11 @@ def set_custom_emoji
end

def resource_params
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker, :aliases_raw, :license)
params.require(:custom_emoji).permit(:shortcode, :image, :category_id, :visible_in_picker, :aliases_raw, :license)
end

def update_params
params.require(:custom_emoji).permit(:visible_in_picker, :aliases_raw, :license)
params.require(:custom_emoji).permit(:category_id, :visible_in_picker, :aliases_raw, :license)
end

def filtered_custom_emojis
2 changes: 1 addition & 1 deletion app/controllers/admin/disputes/appeals_controller.rb
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ def reject
authorize @appeal, :approve?
log_action :reject, @appeal
@appeal.reject!(current_account)
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
UserMailer.appeal_rejected(@appeal.account.user, @appeal).deliver_later
redirect_to disputes_strike_path(@appeal.strike)
end

2 changes: 1 addition & 1 deletion app/controllers/admin/domain_blocks_controller.rb
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ def create

# Disallow accidentally downgrading a domain block
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.save
@domain_block.validate
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe
@domain_block.errors.delete(:domain)
return render :new
2 changes: 1 addition & 1 deletion app/controllers/admin/instances_controller.rb
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ def stop_delivery
private

def set_instance
@instance = Instance.find(TagManager.instance.normalize_domain(params[:id]&.strip))
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(params[:id]&.strip))
end

def set_instances
5 changes: 5 additions & 0 deletions app/controllers/admin/statuses_controller.rb
Original file line number Diff line number Diff line change
@@ -86,6 +86,11 @@ def remove_status

private

def batched_ordered_status_edits
@status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc)
end
helper_method :batched_ordered_status_edits

def admin_status_batch_action_params
params.require(:admin_status_batch_action).permit(status_ids: [])
end
5 changes: 3 additions & 2 deletions app/controllers/api/v1/accounts/relationships_controller.rb
Original file line number Diff line number Diff line change
@@ -5,10 +5,11 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user!

def index
accounts = Account.without_suspended.where(id: account_ids).select('id')
scope = Account.where(id: account_ids).select('id')
scope.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
@accounts = scope.index_by(&:id).values_at(*account_ids).compact
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end

6 changes: 3 additions & 3 deletions app/controllers/api/v1/apps/credentials_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# frozen_string_literal: true

class Api::V1::Apps::CredentialsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }

def show
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
return doorkeeper_render_error unless valid_doorkeeper_token?

render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes)
end
end
6 changes: 1 addition & 5 deletions app/controllers/api/v1/instances/activity_controller.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# frozen_string_literal: true

class Api::V1::Instances::ActivityController < Api::BaseController
class Api::V1::Instances::ActivityController < Api::V1::Instances::BaseController
before_action :require_enabled_api!

skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?

vary_by ''

def show
cache_even_if_authenticated!
render_with_cache json: :activity, expires_in: 1.day
8 changes: 8 additions & 0 deletions app/controllers/api/v1/instances/base_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class Api::V1::Instances::BaseController < Api::BaseController
skip_before_action :require_authenticated_user!,
unless: :limited_federation_mode?

vary_by ''
end
4 changes: 1 addition & 3 deletions app/controllers/api/v1/instances/domain_blocks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# frozen_string_literal: true

class Api::V1::Instances::DomainBlocksController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?

class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseController
before_action :require_enabled_api!
before_action :set_domain_blocks

Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# frozen_string_literal: true

class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::BaseController
skip_around_action :set_locale

before_action :set_extended_description

vary_by ''

# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?
5 changes: 1 addition & 4 deletions app/controllers/api/v1/instances/languages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# frozen_string_literal: true

class Api::V1::Instances::LanguagesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::LanguagesController < Api::V1::Instances::BaseController
skip_around_action :set_locale

before_action :set_languages

vary_by ''

def show
cache_even_if_authenticated!
render json: @languages, each_serializer: REST::LanguageSerializer
5 changes: 1 addition & 4 deletions app/controllers/api/v1/instances/peers_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# frozen_string_literal: true

class Api::V1::Instances::PeersController < Api::BaseController
class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController
before_action :require_enabled_api!

skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
skip_around_action :set_locale

vary_by ''

# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# frozen_string_literal: true

class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?

class Api::V1::Instances::PrivacyPoliciesController < Api::V1::Instances::BaseController
before_action :set_privacy_policy

vary_by ''

def show
cache_even_if_authenticated!
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
5 changes: 1 addition & 4 deletions app/controllers/api/v1/instances/rules_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# frozen_string_literal: true

class Api::V1::Instances::RulesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
skip_around_action :set_locale

before_action :set_rules

vary_by ''

# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# frozen_string_literal: true

class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?

class Api::V1::Instances::TranslationLanguagesController < Api::V1::Instances::BaseController
before_action :set_languages

vary_by ''

def show
cache_even_if_authenticated!
render json: @languages
2 changes: 1 addition & 1 deletion app/controllers/api/v1/lists_controller.rb
Original file line number Diff line number Diff line change
@@ -45,6 +45,6 @@ def set_list
end

def list_params
params.permit(:title, :replies_policy, :exclusive)
params.permit(:title, :replies_policy, :exclusive, :notify)
end
end
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ def destroy

authorize @status, :show? if emoji_reaction.nil?

UnEmojiReactService.new.call(current_account.id, @status.id, emoji_reaction) if emoji_reaction.present?
UnEmojiReactService.new.call(current_account, @status, emoji_reaction) if emoji_reaction.present?
else
authorize @status, :show?
end
7 changes: 7 additions & 0 deletions app/controllers/api/v1/statuses_controller.rb
Original file line number Diff line number Diff line change
@@ -49,6 +49,13 @@ def context
loaded_descendants = cache_collection(descendants_results, Status)
loaded_references = cache_collection(references_results, Status)

if params[:with_reference]
loaded_references.reject! { |status| loaded_ancestors.any? { |ancestor| ancestor.id == status.id } }
else
loaded_ancestors = (loaded_ancestors + loaded_references).uniq(&:id)
loaded_references = []
end

@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants, references: loaded_references)
statuses = [@status] + @context.ancestors + @context.descendants + @context.references

12 changes: 12 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
include DomainControlHelper
include DatabaseHelper
include AuthorizedFetchHelper
include SelfDestructHelper

helper_method :current_account
helper_method :current_session
@@ -39,6 +40,8 @@ class ApplicationController < ActionController::Base
service_unavailable
end

before_action :check_self_destruct!

before_action :store_referrer, except: :raise_not_found, if: :devise_controller?
before_action :require_functional!, if: :user_signed_in?

@@ -170,6 +173,15 @@ def respond_with_error(code)
end
end

def check_self_destruct!
return unless self_destruct?

respond_to do |format|
format.any { render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html] }
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[410] }, status: code }
end
end

def set_cache_control_defaults
response.cache_control.replace(private: true, no_store: true)
end
1 change: 1 addition & 0 deletions app/controllers/auth/challenges_controller.rb
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ class Auth::ChallengesController < ApplicationController

before_action :authenticate_user!

skip_before_action :check_self_destruct!
skip_before_action :require_functional!

def create
9 changes: 8 additions & 1 deletion app/controllers/auth/confirmations_controller.rb
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
before_action :extend_csp_for_captcha!, only: [:show, :confirm_captcha]
before_action :require_captcha_if_needed!, only: [:show]

skip_before_action :check_self_destruct!
skip_before_action :require_functional!

def show
@@ -38,6 +39,12 @@ def confirm_captcha
show
end

def redirect_to_app?
truthy_param?(:redirect_to_app)
end

helper_method :redirect_to_app?

private

def require_captcha_if_needed!
@@ -81,7 +88,7 @@ def after_resending_confirmation_instructions_path_for(_resource_name)
end

def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && truthy_param?(:redirect_to_app)
if user.created_by_application && redirect_to_app?
user.created_by_application.confirmation_redirect_uri
elsif user_signed_in?
web_url('start')
1 change: 1 addition & 0 deletions app/controllers/auth/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :check_self_destruct!
skip_before_action :verify_authenticity_token

def self.provides_callback_for(provider)
1 change: 1 addition & 0 deletions app/controllers/auth/passwords_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

class Auth::PasswordsController < Devise::PasswordsController
skip_before_action :check_self_destruct!
before_action :check_validity_of_reset_password_token, only: :edit
before_action :set_body_classes

1 change: 1 addition & 0 deletions app/controllers/auth/registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :require_rules_acceptance!, only: :new
before_action :set_registration_form_time, only: :new

skip_before_action :check_self_destruct!, only: [:edit, :update]
skip_before_action :require_functional!, only: [:edit, :update]

def new
1 change: 1 addition & 0 deletions app/controllers/auth/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
class Auth::SessionsController < Devise::SessionsController
layout 'auth'

skip_before_action :check_self_destruct!
skip_before_action :require_no_authentication, only: [:create]
skip_before_action :require_functional!
skip_before_action :update_user_sign_in
1 change: 1 addition & 0 deletions app/controllers/backups_controller.rb
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
class BackupsController < ApplicationController
include RoutingHelper

skip_before_action :check_self_destruct!
skip_before_action :require_functional!

before_action :authenticate_user!
16 changes: 4 additions & 12 deletions app/controllers/concerns/cache_concern.rb
Original file line number Diff line number Diff line change
@@ -92,18 +92,10 @@ def serialize_record(record)
arguments
end

if Rails.gem_version >= Gem::Version.new('7.0')
def attributes_for_database(record)
attributes = record.attributes_for_database
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end
else
def attributes_for_database(record)
attributes = record.instance_variable_get(:@attributes).send(:attributes).transform_values(&:value_for_database)
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end
def attributes_for_database(record)
attributes = record.attributes_for_database
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end

def deserialize_record(class_name, attributes_from_database, new_record = false) # rubocop:disable Style/OptionalBooleanParameter
1 change: 1 addition & 0 deletions app/controllers/concerns/export_controller_concern.rb
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ module ExportControllerConcern
before_action :authenticate_user!
before_action :load_export

skip_before_action :check_self_destruct!
skip_before_action :require_functional!
end

1 change: 1 addition & 0 deletions app/controllers/settings/exports_controller.rb
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ class Settings::ExportsController < Settings::BaseController
include Redisable
include Lockable

skip_before_action :check_self_destruct!
skip_before_action :require_functional!

def show
3 changes: 3 additions & 0 deletions app/controllers/settings/login_activities_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# frozen_string_literal: true

class Settings::LoginActivitiesController < Settings::BaseController
skip_before_action :check_self_destruct!
skip_before_action :require_functional!

def index
@login_activities = LoginActivity.where(user: current_user).order(id: :desc).page(params[:page])
end
4 changes: 2 additions & 2 deletions app/controllers/settings/preferences/other_controller.rb
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ class Settings::Preferences::OtherController < Settings::Preferences::BaseContro
include DtlHelper

def show
@dtl_enabled = DTL_ENABLED
@dtl_tag = DTL_TAG
@dtl_enabled = dtl_enabled?
@dtl_tag = dtl_tag_name
end

private
2 changes: 1 addition & 1 deletion app/controllers/settings/privacy_extra_controller.rb
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ def update
private

def account_params
params.require(:account).permit(:dissubscribable, settings: UserSettings.keys)
params.require(:account).permit(:subscription_policy, settings: UserSettings.keys)
end

def set_account
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
module Settings
module TwoFactorAuthentication
class WebauthnCredentialsController < BaseController
skip_before_action :check_self_destruct!
skip_before_action :require_functional!

before_action :require_otp_enabled
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ module Settings
class TwoFactorAuthenticationMethodsController < BaseController
include ChallengableConcern

skip_before_action :check_self_destruct!
skip_before_action :require_functional!

before_action :require_challenge!, only: :disable
2 changes: 1 addition & 1 deletion app/helpers/admin/account_moderation_notes_helper.rb
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ def admin_account_link_to(account, path: nil)

link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
safe_join([
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'),
content_tag(:span, account.acct, class: 'username'),
], ' ')
end
19 changes: 19 additions & 0 deletions app/helpers/admin/disputes_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Admin
module DisputesHelper
def strike_action_label(appeal)
t(key_for_action(appeal),
scope: 'admin.strikes.actions',
name: content_tag(:span, appeal.strike.account.username, class: 'username'),
target: content_tag(:span, appeal.account.username, class: 'target'))
.html_safe
end

private

def key_for_action(appeal)
AccountWarning.actions.slice(appeal.strike.action).keys.first
end
end
end
8 changes: 8 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -91,6 +91,14 @@ def locale_direction
end
end

def html_title
safe_join(
[content_for(:page_title).to_s.chomp, title]
.select(&:present?),
' - '
)
end

def title
Rails.env.production? ? site_title : "#{site_title} (Dev)"
end
9 changes: 7 additions & 2 deletions app/helpers/dtl_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# frozen_string_literal: true

module DtlHelper
DTL_ENABLED = ENV.fetch('DTL_ENABLED', 'false') == 'true'
DTL_TAG = ENV.fetch('DTL_TAG', 'kmyblue')
def dtl_enabled?
ENV.fetch('DTL_ENABLED', 'false') == 'true'
end

def dtl_tag_name
ENV.fetch('DTL_TAG', 'kmyblue')
end
end
4 changes: 4 additions & 0 deletions app/helpers/formatting_helper.rb
Original file line number Diff line number Diff line change
@@ -9,6 +9,10 @@ def linkify(text, options = {})
TextFormatter.new(text, options).to_s
end

def url_for_preview_card(preview_card)
preview_card.url
end

def extract_status_plain_text(status)
PlainTextFormatter.new(status.text, status.local?).to_s
end
39 changes: 21 additions & 18 deletions app/helpers/kmyblue_capabilities_helper.rb
Original file line number Diff line number Diff line change
@@ -2,31 +2,33 @@

module KmyblueCapabilitiesHelper
def fedibird_capabilities
capabilities = [
:enable_wide_emoji,
:kmyblue_searchability,
:searchability,
:kmyblue_markdown,
:kmyblue_reaction_deck,
:kmyblue_visibility_login,
:status_reference,
:visibility_mutual,
:visibility_limited,
:kmyblue_limited_scope,
:kmyblue_antenna,
:kmyblue_bookmark_category,
:kmyblue_quote,
:kmyblue_searchability_limited,
:kmyblue_searchability_public_unlisted,
:kmyblue_circle_history,
]
capabilities = %i(
enable_wide_emoji
kmyblue_searchability
searchability
kmyblue_markdown
kmyblue_reaction_deck
kmyblue_visibility_login
status_reference
visibility_mutual
visibility_limited
kmyblue_limited_scope
kmyblue_antenna
kmyblue_bookmark_category
kmyblue_quote
kmyblue_searchability_limited
kmyblue_searchability_public_unlisted
kmyblue_circle_history
kmyblue_list_notification
)

capabilities << :profile_search unless Chewy.enabled?
if Setting.enable_emoji_reaction
capabilities << :emoji_reaction
capabilities << :enable_wide_emoji_reaction
end
capabilities << :kmyblue_visibility_public_unlisted if Setting.enable_public_unlisted_visibility
capabilities << :timeline_no_local unless Setting.enable_local_timeline

capabilities
end
@@ -58,6 +60,7 @@ def capabilities_for_nodeinfo
capabilities << :emoji_reaction
capabilities << :enable_wide_emoji_reaction
end
capabilities << :timeline_no_local unless Setting.enable_local_timeline

capabilities
end
2 changes: 0 additions & 2 deletions app/helpers/languages_helper.rb
Original file line number Diff line number Diff line change
@@ -298,5 +298,3 @@ def available_locale_or_nil(locale_name)
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
end
end

# rubocop:enable Metrics/ModuleLength
14 changes: 14 additions & 0 deletions app/helpers/self_destruct_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module SelfDestructHelper
def self.self_destruct?
value = ENV.fetch('SELF_DESTRUCT', nil)
value.present? && Rails.application.message_verifier('self-destruct').verify(value) == ENV['LOCAL_DOMAIN']
rescue ActiveSupport::MessageVerifier::InvalidSignature
false
end

def self_destruct?
SelfDestructHelper.self_destruct?
end
end
2 changes: 1 addition & 1 deletion app/helpers/settings_helper.rb
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ def compact_account_link_to(account)
return if account.nil?

link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
end
end
end
3 changes: 3 additions & 0 deletions app/javascript/__mocks__/svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// eslint-disable-next-line import/no-anonymous-default-export
export default 'SvgrURL';
export const ReactComponent = 'div';
4 changes: 2 additions & 2 deletions app/javascript/mastodon/actions/account_notes.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';

import api from '../api';

export const submitAccountNote = createAppAsyncThunk(
'account_note/submit',
async (args: { id: string; value: string }, { getState }) => {
// TODO: replace `unknown` with `ApiRelationshipJSON` when it is merged
const response = await api(getState).post<unknown>(
const response = await api(getState).post<ApiRelationshipJSON>(
`/api/v1/accounts/${args.id}/note`,
{
comment: args.value,
191 changes: 31 additions & 160 deletions app/javascript/mastodon/actions/accounts.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import api, { getLinks } from '../api';

import {
followAccountSuccess, unfollowAccountSuccess,
authorizeFollowRequestSuccess, rejectFollowRequestSuccess,
followAccountRequest, followAccountFail,
unfollowAccountRequest, unfollowAccountFail,
muteAccountSuccess, unmuteAccountSuccess,
blockAccountSuccess, unblockAccountSuccess,
pinAccountSuccess, unpinAccountSuccess,
fetchRelationshipsSuccess,
} from './accounts_typed';
import { importFetchedAccount, importFetchedAccounts } from './importer';

export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
@@ -10,36 +20,22 @@ export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST';
export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS';
export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL';

export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL';

export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST';
export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS';
export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL';

export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST';
export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS';
export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL';

export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST';
export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS';
export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL';

export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST';
export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS';
export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL';

export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';

export const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST';
export const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS';
export const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL';

export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST';
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';

export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
@@ -59,7 +55,6 @@ export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS';
export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL';

export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST';
export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS';
export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL';

export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST';
@@ -71,15 +66,15 @@ export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS';
export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL';

export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST';
export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS';
export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL';

export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST';
export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS';
export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL';

export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';

export * from './accounts_typed';

export function fetchAccount(id) {
return (dispatch, getState) => {
dispatch(fetchRelationships([id]));
@@ -149,12 +144,12 @@ export function followAccount(id, options = { reblogs: true }) {
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
const locked = getState().getIn(['accounts', id, 'locked'], false);

dispatch(followAccountRequest(id, locked));
dispatch(followAccountRequest({ id, locked }));

api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => {
dispatch(followAccountSuccess(response.data, alreadyFollowing));
dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing}));
}).catch(error => {
dispatch(followAccountFail(error, locked));
dispatch(followAccountFail({ id, error, locked }));
});
};
}
@@ -164,74 +159,22 @@ export function unfollowAccount(id) {
dispatch(unfollowAccountRequest(id));

api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => {
dispatch(unfollowAccountSuccess(response.data, getState().get('statuses')));
dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')}));
}).catch(error => {
dispatch(unfollowAccountFail(error));
dispatch(unfollowAccountFail({ id, error }));
});
};
}

export function followAccountRequest(id, locked) {
return {
type: ACCOUNT_FOLLOW_REQUEST,
id,
locked,
skipLoading: true,
};
}

export function followAccountSuccess(relationship, alreadyFollowing) {
return {
type: ACCOUNT_FOLLOW_SUCCESS,
relationship,
alreadyFollowing,
skipLoading: true,
};
}

export function followAccountFail(error, locked) {
return {
type: ACCOUNT_FOLLOW_FAIL,
error,
locked,
skipLoading: true,
};
}

export function unfollowAccountRequest(id) {
return {
type: ACCOUNT_UNFOLLOW_REQUEST,
id,
skipLoading: true,
};
}

export function unfollowAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_UNFOLLOW_SUCCESS,
relationship,
statuses,
skipLoading: true,
};
}

export function unfollowAccountFail(error) {
return {
type: ACCOUNT_UNFOLLOW_FAIL,
error,
skipLoading: true,
};
}

export function blockAccount(id) {
return (dispatch, getState) => {
dispatch(blockAccountRequest(id));

api(getState).post(`/api/v1/accounts/${id}/block`).then(response => {
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
dispatch(blockAccountSuccess(response.data, getState().get('statuses')));
dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
}).catch(error => {
dispatch(blockAccountFail(id, error));
dispatch(blockAccountFail({ id, error }));
});
};
}
@@ -241,9 +184,9 @@ export function unblockAccount(id) {
dispatch(unblockAccountRequest(id));

api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => {
dispatch(unblockAccountSuccess(response.data));
dispatch(unblockAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(unblockAccountFail(id, error));
dispatch(unblockAccountFail({ id, error }));
});
};
}
@@ -254,15 +197,6 @@ export function blockAccountRequest(id) {
id,
};
}

export function blockAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_BLOCK_SUCCESS,
relationship,
statuses,
};
}

export function blockAccountFail(error) {
return {
type: ACCOUNT_BLOCK_FAIL,
@@ -277,13 +211,6 @@ export function unblockAccountRequest(id) {
};
}

export function unblockAccountSuccess(relationship) {
return {
type: ACCOUNT_UNBLOCK_SUCCESS,
relationship,
};
}

export function unblockAccountFail(error) {
return {
type: ACCOUNT_UNBLOCK_FAIL,
@@ -298,9 +225,9 @@ export function muteAccount(id, notifications, duration=0) {

api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => {
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
dispatch(muteAccountSuccess(response.data, getState().get('statuses')));
dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
}).catch(error => {
dispatch(muteAccountFail(id, error));
dispatch(muteAccountFail({ id, error }));
});
};
}
@@ -310,9 +237,9 @@ export function unmuteAccount(id) {
dispatch(unmuteAccountRequest(id));

api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => {
dispatch(unmuteAccountSuccess(response.data));
dispatch(unmuteAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(unmuteAccountFail(id, error));
dispatch(unmuteAccountFail({ id, error }));
});
};
}
@@ -324,14 +251,6 @@ export function muteAccountRequest(id) {
};
}

export function muteAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_MUTE_SUCCESS,
relationship,
statuses,
};
}

export function muteAccountFail(error) {
return {
type: ACCOUNT_MUTE_FAIL,
@@ -346,13 +265,6 @@ export function unmuteAccountRequest(id) {
};
}

export function unmuteAccountSuccess(relationship) {
return {
type: ACCOUNT_UNMUTE_SUCCESS,
relationship,
};
}

export function unmuteAccountFail(error) {
return {
type: ACCOUNT_UNMUTE_FAIL,
@@ -548,8 +460,8 @@ export function fetchRelationships(accountIds) {

dispatch(fetchRelationshipsRequest(newAccountIds));

api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess(response.data));
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
}).catch(error => {
dispatch(fetchRelationshipsFail(error));
});
@@ -564,14 +476,6 @@ export function fetchRelationshipsRequest(ids) {
};
}

export function fetchRelationshipsSuccess(relationships) {
return {
type: RELATIONSHIPS_FETCH_SUCCESS,
relationships,
skipLoading: true,
};
}

export function fetchRelationshipsFail(error) {
return {
type: RELATIONSHIPS_FETCH_FAIL,
@@ -659,7 +563,7 @@ export function authorizeFollowRequest(id) {

api(getState)
.post(`/api/v1/follow_requests/${id}/authorize`)
.then(() => dispatch(authorizeFollowRequestSuccess(id)))
.then(() => dispatch(authorizeFollowRequestSuccess({ id })))
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
};
}
@@ -671,13 +575,6 @@ export function authorizeFollowRequestRequest(id) {
};
}

export function authorizeFollowRequestSuccess(id) {
return {
type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
id,
};
}

export function authorizeFollowRequestFail(id, error) {
return {
type: FOLLOW_REQUEST_AUTHORIZE_FAIL,
@@ -693,7 +590,7 @@ export function rejectFollowRequest(id) {

api(getState)
.post(`/api/v1/follow_requests/${id}/reject`)
.then(() => dispatch(rejectFollowRequestSuccess(id)))
.then(() => dispatch(rejectFollowRequestSuccess({ id })))
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
};
}
@@ -705,13 +602,6 @@ export function rejectFollowRequestRequest(id) {
};
}

export function rejectFollowRequestSuccess(id) {
return {
type: FOLLOW_REQUEST_REJECT_SUCCESS,
id,
};
}

export function rejectFollowRequestFail(id, error) {
return {
type: FOLLOW_REQUEST_REJECT_FAIL,
@@ -725,7 +615,7 @@ export function pinAccount(id) {
dispatch(pinAccountRequest(id));

api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => {
dispatch(pinAccountSuccess(response.data));
dispatch(pinAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(pinAccountFail(error));
});
@@ -737,7 +627,7 @@ export function unpinAccount(id) {
dispatch(unpinAccountRequest(id));

api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => {
dispatch(unpinAccountSuccess(response.data));
dispatch(unpinAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(unpinAccountFail(error));
});
@@ -751,13 +641,6 @@ export function pinAccountRequest(id) {
};
}

export function pinAccountSuccess(relationship) {
return {
type: ACCOUNT_PIN_SUCCESS,
relationship,
};
}

export function pinAccountFail(error) {
return {
type: ACCOUNT_PIN_FAIL,
@@ -772,21 +655,9 @@ export function unpinAccountRequest(id) {
};
}

export function unpinAccountSuccess(relationship) {
return {
type: ACCOUNT_UNPIN_SUCCESS,
relationship,
};
}

export function unpinAccountFail(error) {
return {
type: ACCOUNT_UNPIN_FAIL,
error,
};
}

export const revealAccount = id => ({
type: ACCOUNT_REVEAL,
id,
});
97 changes: 97 additions & 0 deletions app/javascript/mastodon/actions/accounts_typed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { createAction } from '@reduxjs/toolkit';

import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';

export const revealAccount = createAction<{
id: string;
}>('accounts/revealAccount');

export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
'accounts/importAccounts',
);

function actionWithSkipLoadingTrue<Args extends object>(args: Args) {
return {
payload: {
...args,
skipLoading: true,
},
};
}

export const followAccountSuccess = createAction(
'accounts/followAccountSuccess',
actionWithSkipLoadingTrue<{
relationship: ApiRelationshipJSON;
alreadyFollowing: boolean;
}>,
);

export const unfollowAccountSuccess = createAction(
'accounts/unfollowAccountSuccess',
actionWithSkipLoadingTrue<{
relationship: ApiRelationshipJSON;
statuses: unknown;
alreadyFollowing?: boolean;
}>,
);

export const authorizeFollowRequestSuccess = createAction<{ id: string }>(
'accounts/followRequestAuthorizeSuccess',
);

export const rejectFollowRequestSuccess = createAction<{ id: string }>(
'accounts/followRequestRejectSuccess',
);

export const followAccountRequest = createAction(
'accounts/followRequest',
actionWithSkipLoadingTrue<{ id: string; locked: boolean }>,
);

export const followAccountFail = createAction(
'accounts/followFail',
actionWithSkipLoadingTrue<{ id: string; error: string; locked: boolean }>,
);

export const unfollowAccountRequest = createAction(
'accounts/unfollowRequest',
actionWithSkipLoadingTrue<{ id: string }>,
);

export const unfollowAccountFail = createAction(
'accounts/unfollowFail',
actionWithSkipLoadingTrue<{ id: string; error: string }>,
);

export const blockAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
statuses: unknown;
}>('accounts/blockSuccess');

export const unblockAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/unblockSuccess');

export const muteAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
statuses: unknown;
}>('accounts/muteSuccess');

export const unmuteAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/unmuteSuccess');

export const pinAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/pinSuccess');

export const unpinAccountSuccess = createAction<{
relationship: ApiRelationshipJSON;
}>('accounts/unpinSuccess');

export const fetchRelationshipsSuccess = createAction(
'relationships/fetchSuccess',
actionWithSkipLoadingTrue<{ relationships: ApiRelationshipJSON[] }>,
);
2 changes: 2 additions & 0 deletions app/javascript/mastodon/actions/bookmarks.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Kmyblue tracking marker: copied bookmark_categories.js

import api, { getLinks } from '../api';

import { importFetchedStatuses } from './importer';
26 changes: 6 additions & 20 deletions app/javascript/mastodon/actions/domain_blocks.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import api, { getLinks } from '../api';

import { blockDomainSuccess, unblockDomainSuccess } from "./domain_blocks_typed";

export * from "./domain_blocks_typed";

export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';

export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL';

export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
@@ -24,7 +26,7 @@ export function blockDomain(domain) {
const at_domain = '@' + domain;
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));

dispatch(blockDomainSuccess(domain, accounts));
dispatch(blockDomainSuccess({ domain, accounts }));
}).catch(err => {
dispatch(blockDomainFail(domain, err));
});
@@ -38,14 +40,6 @@ export function blockDomainRequest(domain) {
};
}

export function blockDomainSuccess(domain, accounts) {
return {
type: DOMAIN_BLOCK_SUCCESS,
domain,
accounts,
};
}

export function blockDomainFail(domain, error) {
return {
type: DOMAIN_BLOCK_FAIL,
@@ -61,7 +55,7 @@ export function unblockDomain(domain) {
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
const at_domain = '@' + domain;
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
dispatch(unblockDomainSuccess(domain, accounts));
dispatch(unblockDomainSuccess({ domain, accounts }));
}).catch(err => {
dispatch(unblockDomainFail(domain, err));
});
@@ -75,14 +69,6 @@ export function unblockDomainRequest(domain) {
};
}

export function unblockDomainSuccess(domain, accounts) {
return {
type: DOMAIN_UNBLOCK_SUCCESS,
domain,
accounts,
};
}

export function unblockDomainFail(domain, error) {
return {
type: DOMAIN_UNBLOCK_FAIL,
13 changes: 13 additions & 0 deletions app/javascript/mastodon/actions/domain_blocks_typed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createAction } from '@reduxjs/toolkit';

import type { Account } from 'mastodon/models/account';

export const blockDomainSuccess = createAction<{
domain: string;
accounts: Account[];
}>('domain_blocks/blockSuccess');

export const unblockDomainSuccess = createAction<{
domain: string;
accounts: Account[];
}>('domain_blocks/unblockSuccess');
2 changes: 2 additions & 0 deletions app/javascript/mastodon/actions/favourites.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Kmyblue tracking marker: copied emoji_reactions.js

import api, { getLinks } from '../api';

import { importFetchedStatuses } from './importer';
18 changes: 5 additions & 13 deletions app/javascript/mastodon/actions/importer/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { normalizeAccount, normalizeStatus, normalizePoll } from './normalizer';
import { importAccounts } from '../accounts_typed';

import { normalizeStatus, normalizePoll } from './normalizer';

export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
export const STATUS_IMPORT = 'STATUS_IMPORT';
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
export const POLLS_IMPORT = 'POLLS_IMPORT';
@@ -13,14 +13,6 @@ function pushUnique(array, object) {
}
}

export function importAccount(account) {
return { type: ACCOUNT_IMPORT, account };
}

export function importAccounts(accounts) {
return { type: ACCOUNTS_IMPORT, accounts };
}

export function importStatus(status) {
return { type: STATUS_IMPORT, status };
}
@@ -45,7 +37,7 @@ export function importFetchedAccounts(accounts) {
const normalAccounts = [];

function processAccount(account) {
pushUnique(normalAccounts, normalizeAccount(account));
pushUnique(normalAccounts, account);

if (account.moved) {
processAccount(account.moved);
@@ -54,7 +46,7 @@ export function importFetchedAccounts(accounts) {

accounts.forEach(processAccount);

return importAccounts(normalAccounts);
return importAccounts({ accounts: normalAccounts });
}

export function importFetchedStatus(status) {
27 changes: 0 additions & 27 deletions app/javascript/mastodon/actions/importer/normalizer.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import escapeTextContentForBrowser from 'escape-html';

import emojify from '../../features/emoji/emoji';
import { expandSpoilers, me } from '../../initial_state';
import { unescapeHTML } from '../../utils/html';

const domParser = new DOMParser();

@@ -17,32 +16,6 @@ export function searchTextFromRawStatus (status) {
return domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
}

export function normalizeAccount(account) {
account = { ...account };

const emojiMap = makeEmojiMap(account.emojis);
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;

account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
account.note_emojified = emojify(account.note, emojiMap);
account.note_plain = unescapeHTML(account.note);

if (account.fields) {
account.fields = account.fields.map(pair => ({
...pair,
name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap),
value_emojified: emojify(pair.value, emojiMap),
value_plain: unescapeHTML(pair.value),
}));
}

if (account.moved) {
account.moved = account.moved.id;
}

return account;
}

export function normalizeFilterResult(result) {
const normalResult = { ...result };

11 changes: 9 additions & 2 deletions app/javascript/mastodon/actions/lists.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Kmyblue tracking marker: copied circles.js, antennas.js

import api from '../api';

import { showAlertForError } from './alerts';
@@ -151,10 +153,15 @@ export const createListFail = error => ({
error,
});

export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => {
export const updateList = (id, title, shouldReset, isExclusive, replies_policy, notify) => (dispatch, getState) => {
dispatch(updateListRequest(id));

api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
api(getState).put(`/api/v1/lists/${id}`, {
title,
replies_policy,
exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive,
notify: typeof notify === 'undefined' ? undefined : !!notify,
}).then(({ data }) => {
dispatch(updateListSuccess(data));

if (shouldReset) {
13 changes: 6 additions & 7 deletions app/javascript/mastodon/actions/notifications.js
Original file line number Diff line number Diff line change
@@ -18,11 +18,13 @@ import {
importFetchedStatuses,
} from './importer';
import { submitMarkers } from './markers';
import { notificationsUpdate } from "./notifications_typed";
import { register as registerPushNotifications } from './push_notifications';
import { saveSettings } from './settings';
import { STATUS_EMOJI_REACTION_UPDATE } from './statuses';

export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export * from "./notifications_typed";

export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';

export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
@@ -105,12 +107,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
dispatch(importFetchedAccount(notification.report.target_account));
}

dispatch({
type: NOTIFICATIONS_UPDATE,
notification,
usePendingItems: preferPendingItems,
meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
});

dispatch(notificationsUpdate({ notification, preferPendingItems, playSound: playSound && !filtered}));

fetchRelatedRelationships(dispatch, [notification]);
} else if (playSound && !filtered) {
@@ -148,6 +146,7 @@ const excludeTypesFromFilter = filter => {
'mention',
'poll',
'status',
'list_status',
'update',
'admin.sign_up',
'admin.report',
23 changes: 23 additions & 0 deletions app/javascript/mastodon/actions/notifications_typed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createAction } from '@reduxjs/toolkit';

import type { ApiAccountJSON } from '../api_types/accounts';
// To be replaced once ApiNotificationJSON type exists
interface FakeApiNotificationJSON {
type: string;
account: ApiAccountJSON;
}

export const notificationsUpdate = createAction(
'notifications/update',
({
playSound,
...args
}: {
notification: FakeApiNotificationJSON;
usePendingItems: boolean;
playSound: boolean;
}) => ({
payload: args,
meta: { playSound: playSound ? { sound: 'boop' } : undefined },
}),
);
2 changes: 1 addition & 1 deletion app/javascript/mastodon/actions/statuses.js
Original file line number Diff line number Diff line change
@@ -180,7 +180,7 @@ export function fetchContext(id) {
return (dispatch, getState) => {
dispatch(fetchContextRequest(id));

api(getState).get(`/api/v1/statuses/${id}/context`).then(response => {
api(getState).get(`/api/v1/statuses/${id}/context?with_reference=1`).then(response => {
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants).concat(response.data.references)));
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants, response.data.references));

1 change: 1 addition & 0 deletions app/javascript/mastodon/actions/store.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ const convertState = rawState =>
fromJS(rawState, (k, v) =>
Iterable.isIndexed(v) ? v.toList() : v.toMap());


export function hydrateStore(rawState) {
return dispatch => {
const state = convertState(rawState);
9 changes: 6 additions & 3 deletions app/javascript/mastodon/api_types/accounts.ts
Original file line number Diff line number Diff line change
@@ -21,13 +21,15 @@ export interface ApiAccountOtherSettingsJSON {
hide_followers_count: boolean;
translatable_private: boolean;
link_preview: boolean;
emoji_reaction_policy?:
allow_quote: boolean;
emoji_reaction_policy:
| 'allow'
| 'outside_only'
| 'following_only'
| 'followers_only'
| 'mutuals_only'
| 'block';
subscription_policy: 'allow' | 'followers_only' | 'block';
}

// See app/serializers/rest/account_serializer.rb
@@ -49,10 +51,10 @@ export interface ApiAccountJSON {
id: string;
last_status_at: string;
locked: boolean;
noindex: boolean;
noindex?: boolean;
note: string;
other_settings: ApiAccountOtherSettingsJSON;
roles: ApiAccountJSON[];
roles?: ApiAccountJSON[];
subscribable: boolean;
statuses_count: number;
uri: string;
@@ -62,4 +64,5 @@ export interface ApiAccountJSON {
suspended?: boolean;
limited?: boolean;
memorial?: boolean;
hide_collections: boolean;
}
1 change: 1 addition & 0 deletions app/javascript/mastodon/api_types/custom_emoji.ts
Original file line number Diff line number Diff line change
@@ -9,4 +9,5 @@ export interface ApiCustomEmojiJSON {
height?: number;
sensitive?: boolean;
aliases?: string[];
license?: string;
}
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
}
>
<img
alt="alice"
alt=""
src="/animated/alice.gif"
/>
</div>
@@ -32,7 +32,7 @@ exports[`<Avatar /> Still renders a still avatar 1`] = `
}
>
<img
alt="alice"
alt=""
src="/static/alice.jpg"
/>
</div>
5 changes: 3 additions & 2 deletions app/javascript/mastodon/components/__tests__/button-test.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { render, fireEvent, screen } from '@testing-library/react';
import renderer from 'react-test-renderer';

import Button from '../button';
import { render, fireEvent, screen } from 'mastodon/test_helpers';

import { Button } from '../button';

describe('<Button />', () => {
it('renders a button element', () => {
20 changes: 5 additions & 15 deletions app/javascript/mastodon/components/account.jsx
Original file line number Diff line number Diff line change
@@ -15,10 +15,9 @@ import { VerifiedBadge } from 'mastodon/components/verified_badge';
import { me } from '../initial_state';

import { Avatar } from './avatar';
import Button from './button';
import { Button } from './button';
import { FollowersCounter } from './counters';
import { DisplayName } from './display_name';
import { IconButton } from './icon_button';
import { RelativeTimestamp } from './relative_timestamp';

const messages = defineMessages({
@@ -37,7 +36,7 @@ class Account extends ImmutablePureComponent {

static propTypes = {
size: PropTypes.number,
account: ImmutablePropTypes.map,
account: ImmutablePropTypes.record,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
@@ -46,10 +45,7 @@ class Account extends ImmutablePureComponent {
hidden: PropTypes.bool,
hideButtons: PropTypes.bool,
minimal: PropTypes.bool,
actionIcon: PropTypes.string,
actionTitle: PropTypes.string,
defaultAction: PropTypes.string,
onActionClick: PropTypes.func,
children: PropTypes.object,
withBio: PropTypes.bool,
};
@@ -78,12 +74,8 @@ class Account extends ImmutablePureComponent {
this.props.onMuteNotifications(this.props.account, false);
};

handleAction = () => {
this.props.onActionClick(this.props.account);
};

render () {
const { account, intl, hidden, hideButtons, withBio, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal, children } = this.props;
const { account, intl, hidden, hideButtons, withBio, defaultAction, size, minimal, children } = this.props;

if (!account) {
return <EmptyAccount size={size} minimal={minimal} />;
@@ -100,9 +92,7 @@ class Account extends ImmutablePureComponent {

let buttons;

if (actionIcon && onActionClick) {
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
} else if (!hideButtons && !actionIcon && account.get('id') !== me && account.get('relationship', null) !== null) {
if (!hideButtons && account.get('id') !== me && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']);
@@ -131,7 +121,7 @@ class Account extends ImmutablePureComponent {
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
} else if (defaultAction === 'block') {
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
} else if (!account.get('moved') || following) {
} else if (!account.get('suspended') && !account.get('moved') || following) {
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
}
}
6 changes: 4 additions & 2 deletions app/javascript/mastodon/components/attachment_list.jsx
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';

import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';

import { Icon } from 'mastodon/components/icon';

const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
@@ -25,7 +27,7 @@ export default class AttachmentList extends ImmutablePureComponent {
<div className={classNames('attachment-list', { compact })}>
{!compact && (
<div className='attachment-list__icon'>
<Icon id='link' />
<Icon id='link' icon={LinkIcon} />
</div>
)}

@@ -36,7 +38,7 @@ export default class AttachmentList extends ImmutablePureComponent {
return (
<li key={attachment.get('id')}>
<a href={displayUrl} target='_blank' rel='noopener noreferrer'>
{compact && <Icon id='link' />}
{compact && <Icon id='link' icon={LinkIcon} />}
{compact && ' ' }
{displayUrl ? filename(displayUrl) : <FormattedMessage id='attachments_list.unprocessed' defaultMessage='(unprocessed)' />}
</a>
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/autosuggest_emoji.jsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { PureComponent } from 'react';

import { assetHost } from 'mastodon/utils/config';

import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
import { unicodeMapping } from '../features/emoji/emoji_unicode_mapping_light';

export default class AutosuggestEmoji extends PureComponent {

241 changes: 121 additions & 120 deletions app/javascript/mastodon/components/autosuggest_textarea.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { useCallback, useRef, useState, useEffect, forwardRef } from 'react';

import classNames from 'classnames';

import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';

import Textarea from 'react-textarea-autosize';

@@ -37,54 +37,46 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
}
};

export default class AutosuggestTextarea extends ImmutablePureComponent {

static propTypes = {
value: PropTypes.string,
suggestions: ImmutablePropTypes.list,
disabled: PropTypes.bool,
placeholder: PropTypes.string,
onSuggestionSelected: PropTypes.func.isRequired,
onSuggestionsClearRequested: PropTypes.func.isRequired,
onSuggestionsFetchRequested: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onKeyUp: PropTypes.func,
onKeyDown: PropTypes.func,
onPaste: PropTypes.func.isRequired,
autoFocus: PropTypes.bool,
lang: PropTypes.string,
};

static defaultProps = {
autoFocus: true,
};

state = {
suggestionsHidden: true,
focused: false,
selectedSuggestion: 0,
lastToken: null,
tokenStart: 0,
};

onChange = (e) => {
const AutosuggestTextarea = forwardRef(({
value,
suggestions,
disabled,
placeholder,
onSuggestionSelected,
onSuggestionsClearRequested,
onSuggestionsFetchRequested,
onChange,
onKeyUp,
onKeyDown,
onPaste,
onFocus,
autoFocus = true,
lang,
children,
}, textareaRef) => {

const [suggestionsHidden, setSuggestionsHidden] = useState(true);
const [selectedSuggestion, setSelectedSuggestion] = useState(0);
const lastTokenRef = useRef(null);
const tokenStartRef = useRef(0);

const handleChange = useCallback((e) => {
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);

if (token !== null && this.state.lastToken !== token) {
this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
this.props.onSuggestionsFetchRequested(token);
if (token !== null && lastTokenRef.current !== token) {
tokenStartRef.current = tokenStart;
lastTokenRef.current = token;
setSelectedSuggestion(0);
onSuggestionsFetchRequested(token);
} else if (token === null) {
this.setState({ lastToken: null });
this.props.onSuggestionsClearRequested();
lastTokenRef.current = null;
onSuggestionsClearRequested();
}

this.props.onChange(e);
};

onKeyDown = (e) => {
const { suggestions, disabled } = this.props;
const { selectedSuggestion, suggestionsHidden } = this.state;
onChange(e);
}, [onSuggestionsFetchRequested, onSuggestionsClearRequested, onChange, setSelectedSuggestion]);

const handleKeyDown = useCallback((e) => {
if (disabled) {
e.preventDefault();
return;
@@ -102,80 +94,75 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
document.querySelector('.ui').parentElement.focus();
} else {
e.preventDefault();
this.setState({ suggestionsHidden: true });
setSuggestionsHidden(true);
}

break;
case 'ArrowDown':
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
setSelectedSuggestion(Math.min(selectedSuggestion + 1, suggestions.size - 1));
}

break;
case 'ArrowUp':
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
setSelectedSuggestion(Math.max(selectedSuggestion - 1, 0));
}

break;
case 'Enter':
case 'Tab':
// Select suggestion
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
if (lastTokenRef.current !== null && suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
e.stopPropagation();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
onSuggestionSelected(tokenStartRef.current, lastTokenRef.current, suggestions.get(selectedSuggestion));
}

break;
}

if (e.defaultPrevented || !this.props.onKeyDown) {
if (e.defaultPrevented || !onKeyDown) {
return;
}

this.props.onKeyDown(e);
};
onKeyDown(e);
}, [disabled, suggestions, suggestionsHidden, selectedSuggestion, setSelectedSuggestion, setSuggestionsHidden, onSuggestionSelected, onKeyDown]);

onBlur = () => {
this.setState({ suggestionsHidden: true, focused: false });
};
const handleBlur = useCallback(() => {
setSuggestionsHidden(true);
}, [setSuggestionsHidden]);

onFocus = (e) => {
this.setState({ focused: true });
if (this.props.onFocus) {
this.props.onFocus(e);
const handleFocus = useCallback((e) => {
if (onFocus) {
onFocus(e);
}
};
}, [onFocus]);

onSuggestionClick = (e) => {
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
const handleSuggestionClick = useCallback((e) => {
const suggestion = suggestions.get(e.currentTarget.getAttribute('data-index'));
e.preventDefault();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
this.textarea.focus();
};
onSuggestionSelected(tokenStartRef.current, lastTokenRef.current, suggestion);
textareaRef.current?.focus();
}, [suggestions, onSuggestionSelected, textareaRef]);

UNSAFE_componentWillReceiveProps (nextProps) {
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
this.setState({ suggestionsHidden: false });
}
}

setTextarea = (c) => {
this.textarea = c;
};

onPaste = (e) => {
const handlePaste = useCallback((e) => {
if (e.clipboardData && e.clipboardData.files.length === 1) {
this.props.onPaste(e.clipboardData.files);
onPaste(e.clipboardData.files);
e.preventDefault();
}
};
}, [onPaste]);

// Show the suggestions again whenever they change and the textarea is focused
useEffect(() => {
if (suggestions.size > 0 && textareaRef.current === document.activeElement) {
setSuggestionsHidden(false);
}
}, [suggestions, textareaRef, setSuggestionsHidden]);

renderSuggestion = (suggestion, i) => {
const { selectedSuggestion } = this.state;
const renderSuggestion = (suggestion, i) => {
let inner, key;

if (suggestion.type === 'emoji') {
@@ -190,50 +177,64 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}

return (
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={handleSuggestionClick}>
{inner}
</div>
);
};

render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, lang, children } = this.props;
const { suggestionsHidden } = this.state;

return [
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
<div className='autosuggest-textarea'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>

<Textarea
ref={this.setTextarea}
className='autosuggest-textarea__textarea'
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
dir='auto'
aria-autocomplete='list'
lang={lang}
/>
</label>
</div>
{children}
</div>,

<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
{suggestions.map(this.renderSuggestion)}
</div>
</div>,
];
}
return [
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
<div className='autosuggest-textarea'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>

<Textarea
ref={textareaRef}
className='autosuggest-textarea__textarea'
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
onKeyUp={onKeyUp}
onFocus={handleFocus}
onBlur={handleBlur}
onPaste={handlePaste}
dir='auto'
aria-autocomplete='list'
lang={lang}
/>
</label>
</div>
{children}
</div>,

<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
{suggestions.map(renderSuggestion)}
</div>
</div>,
];
});

AutosuggestTextarea.propTypes = {
value: PropTypes.string,
suggestions: ImmutablePropTypes.list,
disabled: PropTypes.bool,
placeholder: PropTypes.string,
onSuggestionSelected: PropTypes.func.isRequired,
onSuggestionsClearRequested: PropTypes.func.isRequired,
onSuggestionsFetchRequested: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onKeyUp: PropTypes.func,
onKeyDown: PropTypes.func,
onPaste: PropTypes.func.isRequired,
onFocus:PropTypes.func,
children: PropTypes.node,
autoFocus: PropTypes.bool,
lang: PropTypes.string,
};

}
export default AutosuggestTextarea;
5 changes: 3 additions & 2 deletions app/javascript/mastodon/components/avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import classNames from 'classnames';

import type { Account } from 'mastodon/models/account';

import { useHovering } from '../../hooks/useHovering';
import type { Account } from '../../types/resources';
import { autoPlayGif } from '../initial_state';

interface Props {
@@ -41,7 +42,7 @@ export const Avatar: React.FC<Props> = ({
onMouseLeave={handleMouseLeave}
style={style}
>
{src && <img src={src} alt={account?.get('acct')} />}
{src && <img src={src} alt='' />}
</div>
);
};
3 changes: 2 additions & 1 deletion app/javascript/mastodon/components/avatar_overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Account } from 'mastodon/models/account';

import { useHovering } from '../../hooks/useHovering';
import type { Account } from '../../types/resources';
import { autoPlayGif } from '../initial_state';

interface Props {
6 changes: 3 additions & 3 deletions app/javascript/mastodon/components/badge.jsx
Original file line number Diff line number Diff line change
@@ -2,9 +2,9 @@ import PropTypes from 'prop-types';

import { FormattedMessage } from 'react-intl';

import { ReactComponent as GroupsIcon } from '@material-design-icons/svg/outlined/group.svg';
import { ReactComponent as PersonIcon } from '@material-design-icons/svg/outlined/person.svg';
import { ReactComponent as SmartToyIcon } from '@material-design-icons/svg/outlined/smart_toy.svg';
import { ReactComponent as GroupsIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg';
import { ReactComponent as SmartToyIcon } from '@material-symbols/svg-600/outlined/smart_toy.svg';


export const Badge = ({ icon, label, domain }) => (
58 changes: 0 additions & 58 deletions app/javascript/mastodon/components/button.jsx

This file was deleted.

58 changes: 58 additions & 0 deletions app/javascript/mastodon/components/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useCallback } from 'react';

import classNames from 'classnames';

interface BaseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
block?: boolean;
secondary?: boolean;
text?: JSX.Element;
}

interface PropsWithChildren extends BaseProps {
text?: never;
}

interface PropsWithText extends BaseProps {
text: JSX.Element;
children: never;
}

type Props = PropsWithText | PropsWithChildren;

export const Button: React.FC<Props> = ({
text,
type = 'button',
onClick,
disabled,
block,
secondary,
className,
title,
children,
...props
}) => {
const handleClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
(e) => {
if (!disabled && onClick) {
onClick(e);
}
},
[disabled, onClick],
);

return (
<button
className={classNames('button', className, {
'button-secondary': secondary,
'button--block': block,
})}
disabled={disabled}
onClick={handleClick}
title={title}
type={type}
{...props}
>
{text ?? children}
</button>
);
};
13 changes: 0 additions & 13 deletions app/javascript/mastodon/components/check.tsx

This file was deleted.

62 changes: 0 additions & 62 deletions app/javascript/mastodon/components/column_back_button.jsx

This file was deleted.

Loading

0 comments on commit c4410ac

Please sign in to comment.