Browse Source

Merge tag 'v4.1.0rc1' into lets-bump-hometown-to-mastodon-4.2

There were quite a couple of conflicts, they were resolved in the
following manner:

- Translations: Moved to "publish" as translation, aligns with other
  languages
- Options: `trends_as_landing_page` is kept false
- UI: clicking the display name opens the original profile

Potential problems:

1. Not all translations for mails and stuff are prefixed with
   `%{title}`, some are, some are hardcoded to `Mastodon`.
lets-bump-hometown-to-mastodon-4.2
nachtjasmin 2 years ago
parent
commit
dbf4adb848
No known key found for this signature in database
  1. 2
      .devcontainer/Dockerfile
  2. 10
      .devcontainer/devcontainer.json
  3. 4
      .devcontainer/docker-compose.yml
  4. 21
      .devcontainer/post-create.sh
  5. 2
      .env.production.sample
  6. 16
      .eslintrc.js
  7. 4
      .github/ISSUE_TEMPLATE/1.bug_report.yml
  8. 2
      .github/ISSUE_TEMPLATE/2.feature_request.yml
  9. 2
      .github/ISSUE_TEMPLATE/config.yml
  10. 2
      .github/workflows/check-i18n.yml
  11. 62
      .github/workflows/codeql.yml
  12. 38
      .github/workflows/lint-json.yml
  13. 40
      .github/workflows/lint-yml.yml
  14. 4
      .github/workflows/linter.yml
  15. 17
      .github/workflows/rebase-needed.yml
  16. 138
      .github/workflows/test-chart.yml
  17. 6
      .gitignore
  18. 2
      .nvmrc
  19. 6
      .prettierignore
  20. 198
      .rubocop.yml
  21. 22
      Aptfile
  22. 320
      CHANGELOG.md
  23. 6
      CODE_OF_CONDUCT.md
  24. 183
      Dockerfile
  25. 45
      Gemfile
  26. 344
      Gemfile.lock
  27. 71
      Vagrantfile
  28. 15
      app/controllers/accounts_controller.rb
  29. 2
      app/controllers/admin/account_actions_controller.rb
  30. 2
      app/controllers/admin/accounts_controller.rb
  31. 8
      app/controllers/admin/domain_blocks_controller.rb
  32. 4
      app/controllers/admin/export_domain_allows_controller.rb
  33. 24
      app/controllers/admin/export_domain_blocks_controller.rb
  34. 4
      app/controllers/admin/instances_controller.rb
  35. 6
      app/controllers/admin/relays_controller.rb
  36. 17
      app/controllers/admin/reports/actions_controller.rb
  37. 22
      app/controllers/api/base_controller.rb
  38. 12
      app/controllers/api/v1/accounts/credentials_controller.rb
  39. 2
      app/controllers/api/v1/admin/accounts_controller.rb
  40. 6
      app/controllers/api/v1/admin/domain_blocks_controller.rb
  41. 8
      app/controllers/api/v1/admin/trends/tags_controller.rb
  42. 6
      app/controllers/api/v1/filters_controller.rb
  43. 6
      app/controllers/api/v1/followed_tags_controller.rb
  44. 2
      app/controllers/api/v1/notifications_controller.rb
  45. 7
      app/controllers/api/v1/statuses_controller.rb
  46. 2
      app/controllers/api/v1/tags_controller.rb
  47. 2
      app/controllers/auth/passwords_controller.rb
  48. 6
      app/controllers/auth/registrations_controller.rb
  49. 4
      app/controllers/auth/sessions_controller.rb
  50. 10
      app/controllers/concerns/admin_export_controller_concern.rb
  51. 2
      app/controllers/concerns/rate_limit_headers.rb
  52. 20
      app/controllers/concerns/signature_verification.rb
  53. 9
      app/controllers/concerns/web_app_controller_concern.rb
  54. 2
      app/controllers/follower_accounts_controller.rb
  55. 2
      app/controllers/following_accounts_controller.rb
  56. 4
      app/controllers/media_controller.rb
  57. 6
      app/controllers/oauth/authorizations_controller.rb
  58. 2
      app/controllers/settings/base_controller.rb
  59. 4
      app/controllers/statuses_cleanup_controller.rb
  60. 4
      app/controllers/statuses_controller.rb
  61. 2
      app/controllers/tags_controller.rb
  62. 25
      app/helpers/formatting_helper.rb
  63. 3
      app/helpers/languages_helper.rb
  64. 2
      app/helpers/statuses_helper.rb
  65. 2
      app/javascript/images/logo-symbol-icon.svg
  66. 2
      app/javascript/images/logo-symbol-wordmark.svg
  67. 8
      app/javascript/mastodon/actions/account_notes.js
  68. 144
      app/javascript/mastodon/actions/accounts.js
  69. 6
      app/javascript/mastodon/actions/alerts.js
  70. 4
      app/javascript/mastodon/actions/announcements.js
  71. 16
      app/javascript/mastodon/actions/blocks.js
  72. 16
      app/javascript/mastodon/actions/bookmarks.js
  73. 6
      app/javascript/mastodon/actions/columns.js
  74. 134
      app/javascript/mastodon/actions/compose.js
  75. 8
      app/javascript/mastodon/actions/custom_emojis.js
  76. 32
      app/javascript/mastodon/actions/domain_blocks.js
  77. 4
      app/javascript/mastodon/actions/dropdown_menu.js
  78. 2
      app/javascript/mastodon/actions/emojis.js
  79. 16
      app/javascript/mastodon/actions/favourites.js
  80. 4
      app/javascript/mastodon/actions/height_cache.js
  81. 80
      app/javascript/mastodon/actions/interactions.js
  82. 10
      app/javascript/mastodon/actions/markers.js
  83. 4
      app/javascript/mastodon/actions/modal.js
  84. 16
      app/javascript/mastodon/actions/mutes.js
  85. 18
      app/javascript/mastodon/actions/notifications.js
  86. 8
      app/javascript/mastodon/actions/pin_statuses.js
  87. 12
      app/javascript/mastodon/actions/search.js
  88. 4
      app/javascript/mastodon/actions/settings.js
  89. 48
      app/javascript/mastodon/actions/statuses.js
  90. 2
      app/javascript/mastodon/actions/store.js
  91. 8
      app/javascript/mastodon/actions/suggestions.js
  92. 82
      app/javascript/mastodon/actions/tags.js
  93. 18
      app/javascript/mastodon/actions/timelines.js
  94. 2
      app/javascript/mastodon/common.js
  95. 2
      app/javascript/mastodon/compare_id.js
  96. 2
      app/javascript/mastodon/components/admin/Retention.js
  97. 2
      app/javascript/mastodon/components/admin/Trends.js
  98. 102
      app/javascript/mastodon/components/dropdown_menu.js
  99. 5
      app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js
  100. 4
      app/javascript/mastodon/components/icon_button.js
  101. Some files were not shown because too many files have changed in this diff Show More

2
.devcontainer/Dockerfile

@ -9,7 +9,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
# The value is a comma-separated list of allowed domains
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev"
# [Choice] Node.js version: lts/*, 16, 14, 12, 10
# [Choice] Node.js version: lts/*, 18, 16, 14
ARG NODE_VERSION="lts/*"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"

10
.devcontainer/devcontainer.json

@ -2,7 +2,7 @@
"name": "Mastodon",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/mastodon",
"workspaceFolder": "/mastodon",
// Set *default* container specific settings.json values on container create.
"settings": {},
@ -15,12 +15,18 @@
"webben.browserslist"
],
"features": {
"ghcr.io/devcontainers/features/sshd:1": {
"version": "latest"
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// This can be used to network with other containers or the host.
"forwardPorts": [3000, 4000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup",
"postCreateCommand": ".devcontainer/post-create.sh",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"

4
.devcontainer/docker-compose.yml

@ -11,9 +11,9 @@ services:
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: '3.0-bullseye'
# Optional Node.js version to install
NODE_VERSION: '14'
NODE_VERSION: '16'
volumes:
- ..:/workspaces/mastodon:cached
- ..:/mastodon:cached
environment:
RAILS_ENV: development
NODE_ENV: development

21
.devcontainer/post-create.sh

@ -0,0 +1,21 @@
#!/bin/bash
set -e # Fail the whole script on first error
# Fetch Ruby gem dependencies
bundle install --path vendor/bundle --with='development test'
# Fetch Javascript dependencies
yarn install
# Make Gemfile.lock pristine again
git checkout -- Gemfile.lock
# [re]create, migrate, and seed the test database
RAILS_ENV=test ./bin/rails db:setup
# Precompile assets for development
RAILS_ENV=development ./bin/rails assets:precompile
# Precompile assets for test
RAILS_ENV=test NODE_ENV=tests ./bin/rails assets:precompile

2
.env.production.sample

@ -54,7 +54,7 @@ VAPID_PUBLIC_KEY=
# Sending mail
# ------------
SMTP_SERVER=smtp.mailgun.org
SMTP_SERVER=
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=

16
.eslintrc.js

@ -1,6 +1,10 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
],
env: {
browser: true,
node: true,
@ -64,8 +68,8 @@ module.exports = {
eqeqeq: 'error',
indent: ['warn', 2],
'jsx-quotes': ['error', 'prefer-single'],
'no-case-declarations': 'off',
'no-catch-shadow': 'error',
'no-cond-assign': 'error',
'no-console': [
'warn',
{
@ -75,18 +79,16 @@ module.exports = {
],
},
],
'no-fallthrough': 'error',
'no-irregular-whitespace': 'error',
'no-mixed-spaces-and-tabs': 'warn',
'no-empty': 'off',
'no-nested-ternary': 'warn',
'no-prototype-builtins': 'off',
'no-restricted-properties': [
'error',
{ property: 'substring', message: 'Use .slice instead of .substring.' },
{ property: 'substr', message: 'Use .slice instead of .substr.' },
],
'no-self-assign': 'off',
'no-trailing-spaces': 'warn',
'no-undef': 'error',
'no-unreachable': 'error',
'no-unused-expressions': 'error',
'no-unused-vars': [
'error',
@ -96,6 +98,7 @@ module.exports = {
ignoreRestSiblings: true,
},
],
'no-useless-escape': 'off',
'object-curly-spacing': ['error', 'always'],
'padded-blocks': [
'error',
@ -105,7 +108,6 @@ module.exports = {
],
quotes: ['error', 'single'],
semi: 'error',
strict: 'off',
'valid-typeof': 'error',
'react/jsx-boolean-value': 'error',

4
.github/ISSUE_TEMPLATE/1.bug_report.yml

@ -1,6 +1,6 @@
name: Bug Report
description: If something isn't working as expected
labels: bug
labels: [bug]
body:
- type: markdown
attributes:
@ -50,7 +50,7 @@ body:
Google Chrome 106.0.5249.119
Firefox 105.0.3
etc...
validations:
required: true

2
.github/ISSUE_TEMPLATE/2.feature_request.yml

@ -1,6 +1,6 @@
name: Feature Request
description: I have a suggestion
labels: suggestion
labels: [suggestion]
body:
- type: markdown
attributes:

2
.github/ISSUE_TEMPLATE/config.yml

@ -2,4 +2,4 @@ blank_issues_enabled: true
contact_links:
- name: GitHub Discussions
url: https://github.com/mastodon/mastodon/discussions
about: Please ask and answer questions here.
about: Please ask and answer questions here.

2
.github/workflows/check-i18n.yml

@ -25,7 +25,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
ruby-version: .ruby-version
bundler-cache: true
- name: Check locale file normalization
run: bundle exec i18n-tasks check-normalized

62
.github/workflows/codeql.yml

@ -0,0 +1,62 @@
name: 'CodeQL'
on:
push:
branches: ['main']
pull_request:
# The branches below must be a subset of the branches above
branches: ['main']
schedule:
- cron: '22 6 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['javascript', 'ruby']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: '/language:${{matrix.language}}'

38
.github/workflows/lint-json.yml

@ -0,0 +1,38 @@
name: JSON Linting
on:
push:
branches-ignore:
- 'dependabot/**'
paths:
- 'package.json'
- 'yarn.lock'
- '.prettier*'
- '**/*.json'
- '.github/workflows/lint-json.yml'
pull_request:
paths:
- 'package.json'
- 'yarn.lock'
- '.prettier*'
- '**/*.json'
- '.github/workflows/lint-json.yml'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Prettier
run: yarn prettier --check "**/*.json"

40
.github/workflows/lint-yml.yml

@ -0,0 +1,40 @@
name: YML Linting
on:
push:
branches-ignore:
- 'dependabot/**'
paths:
- 'package.json'
- 'yarn.lock'
- '.prettier*'
- '**/*.yaml'
- '**/*.yml'
- '.github/workflows/lint-yml.yml'
pull_request:
paths:
- 'package.json'
- 'yarn.lock'
- '.prettier*'
- '**/*.yaml'
- '**/*.yml'
- '.github/workflows/lint-yml.yml'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Prettier
run: yarn prettier --check "**/*.{yml,yaml}"

4
.github/workflows/linter.yml

@ -53,7 +53,7 @@ jobs:
- name: Set-up Node.js
uses: actions/setup-node@v3
with:
node-version: 16.x
node-version-file: .nvmrc
cache: yarn
- name: Install dependencies
run: yarn install --frozen-lockfile
@ -62,7 +62,7 @@ jobs:
- name: Set-up Stylelint Problem Matcher
uses: xt0rted/stylelint-problem-matcher@v1
# https://github.com/xt0rted/stylelint-problem-matcher/issues/360
- run: echo "::add-matcher::.github/stylelint-matcher.json"
- run: echo "::add-matcher::.github/stylelint-matcher.json"
################################
# Run Linter against code base #

17
.github/workflows/rebase-needed.yml

@ -0,0 +1,17 @@
name: PR Needs Rebase
on:
push:
pull_request_target:
types: [synchronize]
jobs:
label-rebase-needed:
runs-on: ubuntu-latest
steps:
- name: Check for merge conflicts
uses: eps1lon/actions-label-merge-conflict@releases/2.x
with:
dirtyLabel: 'rebase needed :construction:'
repoToken: '${{ secrets.GITHUB_TOKEN }}'
commentOnDirty: This pull request has merge conflicts that must be resolved before it can be merged.

138
.github/workflows/test-chart.yml

@ -1,138 +0,0 @@
# This is a GitHub workflow defining a set of jobs with a set of steps.
# ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
#
name: Test chart
on:
pull_request:
paths:
- "chart/**"
- "!**.md"
- ".github/workflows/test-chart.yml"
push:
paths:
- "chart/**"
- "!**.md"
- ".github/workflows/test-chart.yml"
branches-ignore:
- "dependabot/**"
workflow_dispatch:
permissions:
contents: read
defaults:
run:
working-directory: chart
jobs:
lint-templates:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install dependencies (yamllint)
run: pip install yamllint
- run: helm dependency update
- name: helm lint
run: |
helm lint . \
--values dev-values.yaml
- name: helm template
run: |
helm template . \
--values dev-values.yaml \
--output-dir rendered-templates
- name: yamllint (only on templates we manage)
run: |
rm -rf rendered-templates/mastodon/charts
yamllint rendered-templates \
--config-data "{rules: {indentation: {spaces: 2}, line-length: disable}}"
# This job helps us validate that rendered templates are valid k8s resources
# against a k8s api-server, via "helm template --validate", but also that a
# basic configuration can be used to successfully startup mastodon.
#
test-install:
runs-on: ubuntu-22.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
# k3s-channel reference: https://update.k3s.io/v1-release/channels
- k3s-channel: latest
- k3s-channel: stable
# This represents the oldest configuration we test against.
#
# The k8s version chosen is based on the oldest still supported k8s
# version among two managed k8s services, GKE, EKS.
# - GKE: https://endoflife.date/google-kubernetes-engine
# - EKS: https://endoflife.date/amazon-eks
#
# The helm client's version can influence what helper functions is
# available for use in the templates, currently we need v3.6.0 or
# higher.
#
- k3s-channel: v1.21
helm-version: v3.6.0
steps:
- uses: actions/checkout@v3
# This action starts a k8s cluster with NetworkPolicy enforcement and
# installs both kubectl and helm.
#
# ref: https://github.com/jupyterhub/action-k3s-helm#readme
#
- uses: jupyterhub/action-k3s-helm@v3
with:
k3s-channel: ${{ matrix.k3s-channel }}
helm-version: ${{ matrix.helm-version }}
metrics-enabled: false
traefik-enabled: false
docker-enabled: false
- run: helm dependency update
# Validate rendered helm templates against the k8s api-server
- name: helm template --validate
run: |
helm template --validate mastodon . \
--values dev-values.yaml
- name: helm install
run: |
helm install mastodon . \
--values dev-values.yaml \
--timeout 10m
# This actions provides a report about the state of the k8s cluster,
# providing logs etc on anything that has failed and workloads marked as
# important.
#
# ref: https://github.com/jupyterhub/action-k8s-namespace-report#readme
#
- name: Kubernetes namespace report
uses: jupyterhub/action-k8s-namespace-report@v1
if: always()
with:
important-workloads: >-
deploy/mastodon-sidekiq
deploy/mastodon-streaming
deploy/mastodon-web
job/mastodon-assets-precompile
job/mastodon-chewy-upgrade
job/mastodon-create-admin
job/mastodon-db-migrate

6
.gitignore vendored

@ -44,12 +44,6 @@
/redis
/elasticsearch
# ignore Helm charts
/chart/*.tgz
# ignore Helm dependency charts
/chart/charts/*.tgz
# Ignore Apple files
.DS_Store

2
.nvmrc

@ -1 +1 @@
14
16

6
.prettierignore

@ -44,9 +44,6 @@
/redis
/elasticsearch
# ignore Helm dependency charts
/chart/charts/*.tgz
# Ignore Apple files
.DS_Store
@ -67,9 +64,6 @@ yarn-debug.log
# Ignore Docker option files
docker-compose.override.yml
# Ignore Helm files
/chart
# Ignore emoji map file
/app/javascript/mastodon/features/emoji/emoji_map.json

198
.rubocop.yml

@ -1,12 +1,18 @@
require:
- rubocop-rails
- rubocop-rspec
- rubocop-performance
AllCops:
TargetRubyVersion: 2.7
NewCops: disable
DisplayCopNames: true
DisplayStyleGuide: true
ExtraDetails: true
UseCache: true
CacheRootDirectory: tmp
NewCops: enable
Exclude:
- 'spec/**/*'
- 'db/**/*'
- db/schema.rb
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
@ -67,17 +73,57 @@ Lint/UselessAccessModifier:
- class_methods
Metrics/AbcSize:
Max: 115
Max: 34 # RuboCop default 17
Exclude:
- 'lib/mastodon/*_cli.rb'
- 'lib/**/*cli*.rb'
- db/*migrate/**/*
- lib/paperclip/color_extractor.rb
- app/workers/scheduler/follow_recommendations_scheduler.rb
- app/services/activitypub/fetch*_service.rb
- lib/paperclip/**/*
CountRepeatedAttributes: false
AllowedMethods:
- update_media_attachments!
- account_link_to
- attempt_oembed
- build_crutches
- calculate_scores
- cc
- dump_actor!
- filter_from_home?
- hydrate
- import_bookmarks!
- import_relationships!
- initialize
- link_to_mention
- log_target
- matches_time_window?
- parse_metadata
- perform_statuses_search!
- privatize_media_attachments!
- process_update
- publish_media_attachments!
- remotable_attachment
- render_initial_state
- render_with_cache
- searchable_by
- self.cached_filters_for
- set_fetchable_attributes!
- signed_request_actor
- statuses_to_delete
- update_poll!
Metrics/BlockLength:
Max: 55
Exclude:
- 'lib/tasks/**/*'
- 'lib/mastodon/*_cli.rb'
CountComments: false
CountAsOne: [array, heredoc]
AllowedMethods:
- task
- namespace
- class_methods
- included
Metrics/BlockNesting:
Max: 3
@ -87,38 +133,144 @@ Metrics/BlockNesting:
Metrics/ClassLength:
CountComments: false
Max: 500
CountAsOne: [array, heredoc]
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/CyclomaticComplexity:
Max: 25
Max: 12
Exclude:
- 'lib/mastodon/*_cli.rb'
- lib/mastodon/*cli*.rb
- db/*migrate/**/*
AllowedMethods:
- attempt_oembed
- blocked?
- build_crutches
- calculate_scores
- cc
- discover_endpoint!
- filter_from_home?
- hydrate
- klass
- link_to_mention
- log_target
- matches_time_window?
- patch_for_forwarding!
- preprocess_attributes!
- process_update
- remotable_attachment
- scan_text!
- self.cached_filters_for
- set_fetchable_attributes!
- setup_redis_env_url
- update_media_attachments!
Layout/LineLength:
Max: 140 # RuboCop default 120
AllowHeredoc: true
AllowURI: true
Enabled: false
IgnoreCopDirectives: true
AllowedPatterns:
# Allow comments to be long lines
- !ruby/regexp / \# .*$/
- !ruby/regexp /^\# .*$/
Exclude:
- lib/**/*cli*.rb
- db/*migrate/**/*
- db/seeds/**/*
Metrics/MethodLength:
CountComments: false
Max: 65
CountAsOne: [array, heredoc]
Max: 25 # RuboCop default 10
Exclude:
- 'lib/mastodon/*_cli.rb'
AllowedMethods:
- account_link_to
- attempt_oembed
- body_with_limit
- build_crutches
- cached_filters_for
- calculate_scores
- check_webfinger!
- clean_feeds!
- collection_items
- collection_presenter
- copy_account_notes!
- deduplicate_accounts!
- deduplicate_conversations!
- deduplicate_local_accounts!
- deduplicate_statuses!
- deduplicate_tags!
- deduplicate_users!
- discover_endpoint!
- extract_extra_uris_with_indices
- extract_hashtags_with_indices
- extract_mentions_or_lists_with_indices
- filter_from_home?
- from_elasticsearch
- handle_explicit_update!
- handle_mark_as_sensitive!
- hsl_to_rgb
- import_bookmarks!
- import_domain_blocks!
- import_relationships!
- ldap_options
- matches_time_window?
- outbox_presenter
- pam_get_user
- parallelize_with_progress
- parse_and_transform
- patch_for_forwarding!
- populate_home
- post_process_style
- preload_cache_collection_target_statuses
- privatize_media_attachments!
- provides_callback_for
- publish_media_attachments!
- relevant_account_timestamp
- remotable_attachment
- rgb_to_hsl
- rss_status_content_format
- set_fetchable_attributes!
- setup_redis_env_url
- signed_request_actor
- to_preview_card_attributes
- upgrade_storage_filesystem
- upgrade_storage_s3
- user_settings_params
- hydrate
- cc
- self_destruct
Metrics/ModuleLength:
CountComments: false
Max: 200
CountAsOne: [array, heredoc]
Metrics/ParameterLists:
Max: 5
CountKeywordArgs: true
Max: 5 # RuboCop default 5
CountKeywordArgs: true # RuboCop default true
MaxOptionalParameters: 3 # RuboCop default 3
Exclude:
- app/models/concerns/account_interactions.rb
- app/services/activitypub/fetch_remote_account_service.rb
- app/services/activitypub/fetch_remote_actor_service.rb
Metrics/PerceivedComplexity:
Max: 25
Max: 16 # RuboCop default 8
AllowedMethods:
- attempt_oembed
- build_crutches
- calculate_scores
- deduplicate_users!
- discover_endpoint!
- filter_from_home?
- hydrate
- patch_for_forwarding!
- process_update
- remove_orphans
- update_media_attachments!
Naming/MemoizedInstanceVariableName:
Enabled: false
@ -249,6 +401,10 @@ Style/HashTransformKeys:
Style/HashTransformValues:
Enabled: false
Style/HashSyntax:
Enabled: true
EnforcedStyle: ruby19_no_mixed_keys
Style/IfUnlessModifier:
Enabled: false
@ -269,9 +425,6 @@ Style/PercentLiteralDelimiters:
Style/PerlBackrefs:
AutoCorrect: false
Style/RedundantAssignment:
Enabled: false
Style/RedundantFetchBlock:
Enabled: true
@ -294,7 +447,7 @@ Style/RegexpLiteral:
Enabled: false
Style/RescueStandardError:
Enabled: false
Enabled: true
Style/SignalException:
Enabled: false
@ -313,3 +466,14 @@ Style/TrailingCommaInHashLiteral:
Style/UnpackFirst:
Enabled: false
RSpec/ScatteredSetup:
Enabled: false
RSpec/ImplicitExpect:
Enabled: false
RSpec/NamedSubject:
Enabled: false
RSpec/DescribeClass:
Enabled: false
RSpec/LetSetup:
Enabled: false

22
Aptfile

@ -1,26 +1,4 @@
ffmpeg
libicu[0-9][0-9]
libicu-dev
libidn12
libidn-dev
libpq-dev
libxdamage1
libxfixes3
zlib1g-dev
libcairo2
libcroco3
libdatrie1
libgdk-pixbuf2.0-0
libgraphite2-3
libharfbuzz0b
libpango-1.0-0
libpangocairo-1.0-0
libpangoft2-1.0-0
libpixman-1-0
librsvg2-2
libthai-data
libthai0
libvpx[5-9]
libxcb-render0
libxcb-shm0
libxrender1

320
CHANGELOG.md

@ -3,181 +3,181 @@ Changelog
All notable changes to this project will be documented in this file.
## End of life notice
**The 4.0.x branch will not receive any update after 2023-10-31.**
This means that no security fix will be made available for this branch after this date, and you will need to update to a more recent version (such as the 4.2.x branch) to receive security fixes.
## [4.0.12] - 2023-10-10
### Changed
- Change some worker lock TTLs to be shorter-lived ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27246))
- Change user archive export allowed period from 7 days to 6 days ([suddjian](https://github.com/mastodon/mastodon/pull/27200))
### Fixed
- Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656))
- Fix multiple instances of the trend refresh scheduler sometimes running at once ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27253))
- Fix importer returning negative row estimates ([jgillich](https://github.com/mastodon/mastodon/pull/27258))
- Fix filtering audit log for entries about disabling 2FA ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27186))
- Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111))
- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306))
## [4.0.11] - 2023-09-20
### Fixed
- Fix post translation erroring out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26990))
## [4.0.10] - 2023-09-19
### Fixed
- Fix moderator rights inconsistencies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26729))
- Fix crash when encountering invalid URL ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26814))
- Fix cached posts including stale stats ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26409))
- Fix uploading of video files for which `ffprobe` reports `0/0` average framerate ([NicolaiSoeborg](https://github.com/mastodon/mastodon/pull/26500))
- Fix unexpected audio stream transcoding when uploaded video is eligible to passthrough ([yufushiro](https://github.com/mastodon/mastodon/pull/26608))
### Security
- Fix missing HTML sanitization in translation API (CVE-2023-42452)
- Fix incorrect domain name normalization (CVE-2023-42451)
## [4.0.9] - 2023-09-05
### Changed
- Change remote report processing to accept reports with long comments, but truncate them ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25028))
### Fixed
- **Fix blocking subdomains of an already-blocked domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26392))
- Fix `/api/v1/timelines/tag/:hashtag` allowing for unauthenticated access when public preview is disabled ([danielmbrasil](https://github.com/mastodon/mastodon/pull/26237))
- Fix inefficiencies in `PlainTextFormatter` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26727))
## [4.0.8] - 2023-07-31
### Fixed
- Fix memory leak in streaming server ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26228))
- Fix wrong filters sometimes applying in streaming ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26159), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26213), [renchap](https://github.com/mastodon/mastodon/pull/26233))
- Fix incorrect connect timeout in outgoing requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26116))
## [4.0.7] - 2023-07-21
## [4.1.0] - UNRELEASED
### Added
- Add check preventing Sidekiq workers from running with Makara configured ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25850))
- **Add support for importing/exporting server-wide domain blocks** ([enbylenore](https://github.com/mastodon/mastodon/pull/20597), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21471), [dariusk](https://github.com/mastodon/mastodon/pull/22803), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21470))
- Add listing of followed hashtags ([connorshea](https://github.com/mastodon/mastodon/pull/21773))
- Add support for editing media description and focus point of already-sent posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20878))
- Add follow request banner on account header ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20785))
- Add confirmation screen when handling reports ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22375), [Gargron](https://github.com/mastodon/mastodon/pull/23156))
- Add option to make the landing page be `/about` even when trends are enabled ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20808))
- Add `noindex` setting back to the admin interface ([prplecake](https://github.com/mastodon/mastodon/pull/22205))
- Add instance peers API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22810))
- Add instance activity API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22833))
- Add `account.approved` webhook ([Saiv46](https://github.com/mastodon/mastodon/pull/22938))
- Add 12 hours option to polls ([Pleclown](https://github.com/mastodon/mastodon/pull/21131))
- Add dropdown menu item to open admin interface for remote domains ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21895))
- Add `--remove-headers`, `--prune-profiles` and `--include-follows` flags to `tootctl media remove` ([evanphilip](https://github.com/mastodon/mastodon/pull/22149))
- Add `--email` and `--dry-run` options to `tootctl accounts delete` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22328))
- Add `tootctl accounts migrate` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22330))
- Add `tootctl accounts prune` ([tribela](https://github.com/mastodon/mastodon/pull/18397))
- Add `tootctl domains purge` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22063))
- Add `SIDEKIQ_CONCURRENCY` environment variable ([muffinista](https://github.com/mastodon/mastodon/pull/19589))
- Add `MIN_THREADS` environment variable to set minimum Puma threads ([jimeh](https://github.com/mastodon/mastodon/pull/21048))
- Add explanation text to log-in page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20946))
- Add user profile OpenGraph tag on post pages ([bramus](https://github.com/mastodon/mastodon/pull/21423))
- Add maskable icon support for Android ([workeffortwaste](https://github.com/mastodon/mastodon/pull/20904))
- Add Belarusian to supported languages ([Mixaill](https://github.com/mastodon/mastodon/pull/22022))
- Add Western Frisian to supported languages ([ykzts](https://github.com/mastodon/mastodon/pull/18602))
- Add Montenegrin to the language picker ([ayefries](https://github.com/mastodon/mastodon/pull/21013))
- Add Southern Sami and Lule Sami to the language picker ([Jullan-M](https://github.com/mastodon/mastodon/pull/21262))
- Add logging for Rails cache timeouts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21667))
- Add color highlight for active hashtag “follow” button ([MFTabriz](https://github.com/mastodon/mastodon/pull/21629))
- Add brotli compression to `assets:precompile` ([Izorkin](https://github.com/mastodon/mastodon/pull/19025))
- Add “disabled” account filter to the `/admin/accounts` UI ([tribela](https://github.com/mastodon/mastodon/pull/21282))
- Add transparency to modal background for accessibility ([edent](https://github.com/mastodon/mastodon/pull/18081))
- Add `title` attribute to video elements in media attachments ([bramus](https://github.com/mastodon/mastodon/pull/21420))
- Add left and right margins to emojis ([dsblank](https://github.com/mastodon/mastodon/pull/20464))
- Add `reading:autoplay:gifs` to `/api/v1/preferences` ([j-f1](https://github.com/mastodon/mastodon/pull/22706))
- Add `hide_collections` parameter to `/api/v1/accounts/credentials` ([CarlSchwan](https://github.com/mastodon/mastodon/pull/22790))
- Add more specific error messages to HTTP signature verification ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21617))
- Add Storj DCS to cloud object storage options in the `mastodon:setup` rake task ([jtolio](https://github.com/mastodon/mastodon/pull/21929))
- Add checkmark symbol in the checkbox for sensitive media ([sidp](https://github.com/mastodon/mastodon/pull/22795))
- Add missing accessibility attributes to logout link in modals ([kytta](https://github.com/mastodon/mastodon/pull/22549))
- Add missing accessibility attributes to “Hide image” button in `MediaGallery` ([hs4man21](https://github.com/mastodon/mastodon/pull/22513))
- Add missing accessibility attributes to hide content warning field when disabled ([hs4man21](https://github.com/mastodon/mastodon/pull/22568))
- Add `aria-hidden` to footer circle dividers to improve accessibility ([hs4man21](https://github.com/mastodon/mastodon/pull/22576))
### Changed
- Change request timeout handling to use a longer deadline ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26055))
### Fixed
- Fix moderation interface for remote instances with a .zip TLD ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25886))
- Fix remote accounts being possibly persisted to database with incomplete protocol values ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25886))
- Fix trending publishers table not rendering correctly on narrow screens ([vmstan](https://github.com/mastodon/mastodon/pull/25945))
### Security
- Fix CSP headers being unintentionally wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26105))
## [4.0.6] - 2023-07-07
### Fixed
- Fix branding:generate_app_icons failing because of disallowed ICO coder ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25794))
- Fix crash in admin interface when viewing a remote user with verified links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25796))
- Fix processing of media files with unusual names ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25788))
## [4.0.5] - 2023-07-06
### Changed
- Change OpenGraph-based embeds to allow fullscreen ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25058))
- Change profile updates to be sent to recently-mentioned servers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24852))
- Change `/api/v1/statuses/:id/history` to always return at least one item ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25510))
- Change auto-linking to allow carets in URL query params ([renchap](https://github.com/mastodon/mastodon/pull/25216))
- **Ensure exact match is the first result in hashtag searches** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21315))
- Change account search to return followed accounts first ([dariusk](https://github.com/mastodon/mastodon/pull/22956))
- Change batch account suspension to create a strike ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20897))
- Change default reply language to match the default language when replying to a translated post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22272))
- Change misleading wording about waitlists ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20850))
- Increase width of the unread notification border ([connorshea](https://github.com/mastodon/mastodon/pull/21692))
- Change new post notification button on profiles to make it more apparent when it is enabled ([tribela](https://github.com/mastodon/mastodon/pull/22541))
- Change trending tags admin interface to always show batch action controls ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23013))
- Change wording of some OAuth scope descriptions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22491))
- Change wording of admin report handling actions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18388))
- Change confirm prompts for relationships management ([tribela](https://github.com/mastodon/mastodon/pull/19411))
- Change language surrounding disability in prompts for media descriptions ([hs4man21](https://github.com/mastodon/mastodon/pull/20923))
- Change confusing wording in the sign in banner ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22490))
- Change account moderation notes to make links clickable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22553))
- Save avatar or header correctly even if the other one fails ([tribela](https://github.com/mastodon/mastodon/pull/18465))
- Change `referrer-policy` to `same-origin` application-wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23014), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23037))
- Add 'private' to `Cache-Control`, match Rails expectations ([daxtens](https://github.com/mastodon/mastodon/pull/20608))
- Make the button that expands the compose form differentiable from the button that publishes a post ([Tak](https://github.com/mastodon/mastodon/pull/20864))
- Change automatic post deletion configuration to be accessible to moved users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20774))
- Make tag following idempotent ([trwnh](https://github.com/mastodon/mastodon/pull/20860), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21285))
- Use buildx functions for faster builds ([inductor](https://github.com/mastodon/mastodon/pull/20692))
- Split off Dockerfile components for faster builds ([moritzheiber](https://github.com/mastodon/mastodon/pull/20933), [ineffyble](https://github.com/mastodon/mastodon/pull/20948), [BtbN](https://github.com/mastodon/mastodon/pull/21028))
- Change last occurrence of “silence” to “limit” in UI text ([cincodenada](https://github.com/mastodon/mastodon/pull/20637))
- Change “hide toot” to “hide post” ([seanthegeek](https://github.com/mastodon/mastodon/pull/22385))
- Don't allow URLs that contain non-normalized paths to be verified ([dgl](https://github.com/mastodon/mastodon/pull/20999))
- Change the “Trending now” header to be a link to the Explore page ([connorshea](https://github.com/mastodon/mastodon/pull/21759))
- Change PostgreSQL connection timeout from 2 minutes to 15 seconds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21790))
- Make handle more easily selectable on profile page ([cadars](https://github.com/mastodon/mastodon/pull/21479))
- Allow admins to refresh remotely-suspended accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22327))
- Change dropdown menu to contain “Copy link to post” even for non-public posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21316))
- Allow adding relays in secure mode and limited federation mode ([ineffyble](https://github.com/mastodon/mastodon/pull/22324))
- Change timestamps to be displayed using the user's timezone throughout the moderation interface ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/21878), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22555))
- Change CSP directives on API to be tight and concise ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20960))
- Change web UI to not autofocus the compose form ([raboof](https://github.com/mastodon/mastodon/pull/16517))
- Change idempotency key handling for posting when database access is slow ([lambda](https://github.com/mastodon/mastodon/pull/21840))
- Change remote media files to be downloaded outside of transactions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21796))
- Improve contrast of charts in “poll has ended” notifications ([j-f1](https://github.com/mastodon/mastodon/pull/22575))
- Change OEmbed detection and validation to be somewhat more lenient ([ineffyble](https://github.com/mastodon/mastodon/pull/22533))
- Widen ElasticSearch version detection to not display a warning for OpenSearch ([VyrCossont](https://github.com/mastodon/mastodon/pull/22422), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23064))
- Change link verification to allow pages larger than 1MB as long as the link is in the first 1MB ([untitaker](https://github.com/mastodon/mastodon/pull/22879))
- Update default Node.js version to Node.js 16 ([ineffyble](https://github.com/mastodon/mastodon/pull/22223), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22342))
### Removed
- Remove invalid `X-Frame-Options: ALLOWALL` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25070))
### Fixed
- Fix wrong view being displayed when a webhook fails validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25464))
- Fix soft-deleted post cleanup scheduler overwhelming the streaming server ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25519))
- Fix incorrect pagination headers in `/api/v2/admin/accounts` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25477))
- Fix performance of streaming by parsing message JSON once ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25278), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25361))
- Fix CSP headers when `S3_ALIAS_HOST` includes a path component ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25273))
- Fix `tootctl accounts approve --number N` not aproving N earliest registrations ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24605))
- Fix being able to vote on your own polls ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25015))
- Fix race condition when reblogging a status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25016))
- Fix “Authorized applications” inefficiently and incorrectly getting last use date ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25060))
- Fix multiple N+1s in ConversationsController ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25134), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25399), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25499))
- Fix user archive takeouts when using OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24431))
- Fix searching for remote content by URL not working under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25637))
- Fix inefficiencies in indexing content for search ([VyrCossont](https://github.com/mastodon/mastodon/pull/24285), [VyrCossont](https://github.com/mastodon/mastodon/pull/24342))
### Security
- Add finer permission requirements for managing webhooks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25463))
- Update dependencies
- Add hardening headers for user-uploaded files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25756))
- Fix verified links possibly hiding important parts of the URL (CVE-2023-36462)
- Fix timeout handling of outbound HTTP requests (CVE-2023-36461)
- Fix arbitrary file creation through media processing (CVE-2023-36460)
- Fix possible XSS in preview cards (CVE-2023-36459)
## [4.0.4] - 2023-04-04
### Fixed
- Fix crash in `tootctl` commands making use of parallelization when Elasticsearch is enabled ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24182), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24377))
- Fix crash in `db:setup` when Elasticsearch is enabled ([rrgeorge](https://github.com/mastodon/mastodon/pull/24302))
- Fix user archive takeout when using OpenStack Swift or S3 providers with no ACL support ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24200))
- Fix invalid/expired invites being processed on sign-up ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24337))
### Security
- Update Ruby to 3.0.6 due to ReDoS vulnerabilities ([saizai](https://github.com/mastodon/mastodon/pull/24333))
- Fix unescaped user input in LDAP query ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24379))
# [4.0.3] - 2023-03-16
### Added
- Add redirection from paths with url-encoded `@` to their decoded form ([thijskh](https://github.com/mastodon/mastodon/pull/23593))
- Add `lang` attribute to native language names in language picker in Web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23749))
- Add headers to outgoing mails to avoid auto-replies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23597))
- Officially remove support for Ruby 2.6 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21477))
- Remove LDSignature on actor Delete activities ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21466))
- Remove `object-fit` polyfill used for old versions of Microsoft Edge ([shuuji3](https://github.com/mastodon/mastodon/pull/22693))
- Remove empty `title` tag from mailer layout ([nametoolong](https://github.com/mastodon/mastodon/pull/23078))
### Fixed
- Fix “Remove all followers from the selected domains” being more destructive than it claims ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23805))
- Fix case-sensitive check for previously used hashtags in hashtag autocompletion ([deanveloper](https://github.com/mastodon/mastodon/pull/23526))
- Fix sidebar behavior in settings/admin UI on mobile ([wxt2005](https://github.com/mastodon/mastodon/pull/23764))
- Fix inefficiency when searching accounts per username in admin interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23801))
- Fix server error when failing to follow back followers from `/relationships` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23787))
- Fix server error when attempting to display the edit history of a trendable post in the admin interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23574))
- Fix original account being unfollowed on migration before the follow request to the new account could be sent ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21957))
- Fix pgBouncer resetting application name on every transaction ([Gargron](https://github.com/mastodon/mastodon/pull/23958))
- Fix unconfirmed accounts being counted as active users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23803))
- Fix `/api/v1/streaming` sub-paths not being redirected ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23988))
- Fix drag'n'drop upload area text that spans multiple lines not being centered ([vintprox](https://github.com/mastodon/mastodon/pull/24029))
- Fix sidekiq jobs not triggering Elasticsearch index updates ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24046))
- Fix tags being unnecessarily stripped from plain-text short site description ([c960657](https://github.com/mastodon/mastodon/pull/23975))
- Fix HTML entities not being un-escaped in extracted plain-text from remote posts ([c960657](https://github.com/mastodon/mastodon/pull/24019))
- Fix dashboard crash on ElasticSearch server error ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23751))
- Fix incorrect post links in strikes when the account is remote ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23611))
- Fix misleading error code when receiving invalid WebAuthn credentials ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23568))
- **Fix changing domain block severity not undoing individual account effects** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22135))
- Fix suspension worker crashing on S3-compatible setups without ACL support ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22487))
- Fix possible race conditions when suspending/unsuspending accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22363))
- Fix being stuck in edit mode when deleting the edited status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22126))
- Fix some performance issues with `/admin/instances` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21907))
- Fix some pre-4.0 admin audit logs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22091))
- Fix missing OAuth scopes for admin APIs ([trwnh](https://github.com/mastodon/mastodon/pull/20918), [trwnh](https://github.com/mastodon/mastodon/pull/20979))
- Fix voter count not being cleared when a poll is reset ([afontenot](https://github.com/mastodon/mastodon/pull/21700))
- Fix attachments of edited statuses not being fetched ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21565))
- Fix irreversible and whole_word parameters handling in `/api/v1/filters` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21988))
- Fix 500 error when marking posts as sensitive while some of them are deleted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22134))
- Fix expanded statuses not always being scrolled into view ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21797))
- Fix not being able to scroll the remote interaction modal on small screens ([xendke](https://github.com/mastodon/mastodon/pull/21763))
- Fix disappearing “Explore” tabs on Safari ([nyura](https://github.com/mastodon/mastodon/pull/20917), [ykzts](https://github.com/mastodon/mastodon/pull/20982))
- Fix wrong padding in RTL layout ([Gargron](https://github.com/mastodon/mastodon/pull/23157))
- Fix being unable to get a single EmailDomainBlock from the admin API ([trwnh](https://github.com/mastodon/mastodon/pull/20846))
- Fix pagination of followed tags ([trwnh](https://github.com/mastodon/mastodon/pull/20861))
- Fix dropdown menu positions when scrolling ([sidp](https://github.com/mastodon/mastodon/pull/22916), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23062))
- Fix mysterious registration failure when “Require a reason to join” is set with open registrations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22127))
- Fix attachment rendering of edited posts in OpenGraph ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22270))
- Fix invalid/empty RSS feed link on account pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20772))
- Fix error in `VerifyLinkService` when processing links with no href ([joshuap](https://github.com/mastodon/mastodon/pull/20741))
- Fix media uploads with FFmpeg 5 ([dead10ck](https://github.com/mastodon/mastodon/pull/21191))
- Fix sensitive flag not being set when replying to a post with a content warning under certain conditions ([kedamaDQ](https://github.com/mastodon/mastodon/pull/21724))
- Fix “Share @user's profile” profile menu item not working ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21490))
- Fix crash and incorrect behavior in `tootctl domains crawl` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19004))
- Fix autoplay on iOS ([jamesadney](https://github.com/mastodon/mastodon/pull/21422))
- Fix spaces not being stripped in admin account search ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21324))
- Fix spaces not being stripped when adding relays ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22655))
- Fix infinite loading spinner instead of soft 404 for non-existing remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21303))
- Fix minor visual issue with the top border of verified account fields ([j-f1](https://github.com/mastodon/mastodon/pull/22006))
- Fix pending account approval and rejection not being recorded in the admin audit log ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/22088))
- Fix “Sign up” button with closed registrations not opening modal on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22060))
- Fix UI header overflowing on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21783))
- Fix 500 error when trying to migrate to an invalid address ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21462))
- Fix crash when trying to fetch unobtainable avatar of user using external authentication ([lochiiconnectivity](https://github.com/mastodon/mastodon/pull/22462))
- Fix potential duplicate statuses in Explore tab ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22121))
- Fix deprecation warning in `tootctl accounts rotate` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22120))
- Fix missing style in warning and strike cards ([AtelierSnek](https://github.com/mastodon/mastodon/pull/22177), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22302))
- Fix wasteful request to `/api/v1/custom_emojis` when not logged in ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22326))
- Fix replies sometimes being delivered to user-blocked domains ([tribela](https://github.com/mastodon/mastodon/pull/22117))
- Fix admin dashboard crash when using some ElasticSearch replacements ([cortices](https://github.com/mastodon/mastodon/pull/21006))
- Fix profile avatar being slightly offset into left border ([RiedleroD](https://github.com/mastodon/mastodon/pull/20994))
- Fix N+1 queries in `NotificationsController` ([nametoolong](https://github.com/mastodon/mastodon/pull/21202))
- Fix being unable to react to announcements with the keycap number sign emoji ([kescherCode](https://github.com/mastodon/mastodon/pull/22231))
- Fix height computation of post embeds ([hodgesmr](https://github.com/mastodon/mastodon/pull/22141))
- Fix accessibility issue of the search bar due to hidden placeholder ([alexstine](https://github.com/mastodon/mastodon/pull/21275))
- Fix layout change handler not being removed due to a typo ([nschonni](https://github.com/mastodon/mastodon/pull/21829))
- Fix typo in the default `S3_HOSTNAME` used in the `mastodon:setup` rake task ([danp](https://github.com/mastodon/mastodon/pull/19932))
- Fix the top action bar appearing in the multi-column layout ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20943))
- Fix inability to use local LibreTranslate without setting `ALLOWED_PRIVATE_ADDRESSES` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21926))
- Fix punycoded local domains not being prettified in initial state ([Tritlo](https://github.com/mastodon/mastodon/pull/21440))
- Fix CSP violation warning by removing inline CSS from SVG logo ([luxiaba](https://github.com/mastodon/mastodon/pull/20814))
- Fix margin for search field on medium window size ([minacle](https://github.com/mastodon/mastodon/pull/21606))
- Fix search popout scrolling with the page in single-column mode ([rgroothuijsen](https://github.com/mastodon/mastodon/pull/16463))
- Fix minor status cache hydration discrepancy ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19879))
- Fix `・` detection in hashtags ([parthoghosh24](https://github.com/mastodon/mastodon/pull/22888))
- Fix hashtag follows bypassing user blocks ([tribela](https://github.com/mastodon/mastodon/pull/22849))
- Fix moved accounts being incorrectly redirected to account settings when trying to view a remote profile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22497))
- Fix site upload validations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22479))
- Fix “Add new domain block” button using last submitted search value instead of the current one ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22485))
- Fix misleading hashtag warning when posting with “Followers only” or “Mentioned people only” visibility ([n0toose](https://github.com/mastodon/mastodon/pull/22827))
- Fix embedded posts with videos grabbing focus ([Akkiesoft](https://github.com/mastodon/mastodon/pull/22778))
- Fix `$` not being escaped in `.env.production` files generated by the `mastodon:setup` rake task ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23012), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23072))
- Fix sanitizer parsing link text as HTML when stripping unsupported links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22558))
- Fix `scheduled_at` input not using `datetime-local` when editing announcements ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21896))
- Fix REST API serializer for `Account` not including `moved` when the moved account has itself moved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22483))
- Fix `/api/v1/admin/trends/tags` using wrong serializer ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18943))
- Fix situations in which instance actor can be set to a Mastodon-incompatible name ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22307))
### Security
- Change user backups to use expiring URLs for download when possible ([Gargron](https://github.com/mastodon/mastodon/pull/24136))
- Add warning for object storage misconfiguration ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24137))
- Add `form-action` CSP directive ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20781), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20958), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20962))
- Fix unbounded recursion in account discovery ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22025))
- Revoke all authorized applications on password reset ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/21325))
## [4.0.2] - 2022-11-15
### Fixed

6
CODE_OF_CONDUCT.md

@ -40,7 +40,7 @@ Project maintainers who do not follow or enforce the Code of Conduct in good fai
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/

183
Dockerfile

@ -1,122 +1,99 @@
FROM ubuntu:20.04 as build-dep
# Use bash for the shell
SHELL ["/bin/bash", "-c"]
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
# Install Node v16 (LTS)
ENV NODE_VER="16.17.1"
RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \
amd64) ARCH='x64';; \
ppc64el) ARCH='ppc64le';; \
s390x) ARCH='s390x';; \
arm64) ARCH='arm64';; \
armhf) ARCH='armv7l';; \
i386) ARCH='x86';; \
*) echo "unsupported architecture"; exit 1 ;; \
esac && \
echo "Etc/UTC" > /etc/localtime && \
apt-get update && \
apt-get -yq dist-upgrade && \
apt-get install -y --no-install-recommends ca-certificates wget python3 apt-utils && \
cd ~ && \
wget -q https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
rm node-v$NODE_VER-linux-$ARCH.tar.gz && \
mv node-v$NODE_VER-linux-$ARCH /opt/node
# Install Ruby 3.0
ENV RUBY_VER="3.0.6"
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential \
bison libyaml-dev libgdbm-dev libreadline-dev libjemalloc-dev \
libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \
cd ~ && \
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
tar xf ruby-$RUBY_VER.tar.gz && \
cd ruby-$RUBY_VER && \
./configure --prefix=/opt/ruby \
--with-jemalloc \
--with-shared \
--disable-install-doc && \
make -j"$(nproc)" > /dev/null && \
make install && \
rm -rf ../ruby-$RUBY_VER.tar.gz ../ruby-$RUBY_VER
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
RUN npm install -g npm@9 && \
npm install -g yarn && \
gem install bundler && \
apt-get update && \
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
libpq-dev shared-mime-info
# syntax=docker/dockerfile:1.4
# This needs to be bullseye-slim because the Ruby image is built on bullseye-slim
ARG NODE_VERSION="16.18.1-bullseye-slim"
COPY Gemfile* package.json yarn.lock /opt/mastodon/
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.0.4-slim as ruby
FROM node:${NODE_VERSION} as build
RUN cd /opt/mastodon && \
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
COPY --link --from=ruby /opt/ruby /opt/ruby
FROM ubuntu:20.04
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin"
# Copy over all the langs needed for runtime
COPY --from=build-dep /opt/node /opt/node
COPY --from=build-dep /opt/ruby /opt/ruby
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Add more PATHs to the PATH
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
WORKDIR /opt/mastodon
COPY Gemfile* package.json yarn.lock /opt/mastodon/
# Create the mastodon user
ARG UID=991
ARG GID=991
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# hadolint ignore=DL3008
RUN apt-get update && \
echo "Etc/UTC" > /etc/localtime && \
apt-get install -y --no-install-recommends whois wget && \
addgroup --gid $GID mastodon && \
useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \
echo "mastodon:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256)" | chpasswd && \
rm -rf /var/lib/apt/lists/*
# Install mastodon runtime deps
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
apt-get install -y --no-install-recommends build-essential \
ca-certificates \
git \
libicu-dev \
libidn11-dev \
libpq-dev \
libjemalloc-dev \
zlib1g-dev \
libgdbm-dev \
libgmp-dev \
libssl-dev \
libyaml-0-2 \
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 --network-timeout 600000
FROM node:${NODE_VERSION}
ARG UID="991"
ARG GID="991"
COPY --link --from=ruby /opt/ruby /opt/ruby
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
# Ignoreing these here since we don't want to pin any versions and the Debian image removes apt-get content after use
# hadolint ignore=DL3008,DL3009
RUN apt-get update && \
apt-get -y --no-install-recommends install \
libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \
libicu66 libidn11 libyaml-0-2 \
file ca-certificates tzdata libreadline8 gcc tini apt-utils && \
ln -s /opt/mastodon /mastodon && \
gem install bundler && \
rm -rf /var/cache && \
rm -rf /var/lib/apt/lists/*
# Copy over mastodon source, and dependencies from building, and set permissions
COPY --chown=mastodon:mastodon . /opt/mastodon
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
echo "Etc/UTC" > /etc/localtime && \
groupadd -g "${GID}" mastodon && \
useradd -l -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
apt-get -y --no-install-recommends install whois \
wget \
procps \
libssl1.1 \
libpq5 \
imagemagick \
ffmpeg \
libjemalloc2 \
libicu67 \
libidn11 \
libyaml-0-2 \
file \
ca-certificates \
tzdata \
libreadline8 \
tini && \
ln -s /opt/mastodon /mastodon
# 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
# Run mastodon services in prod mode
ENV RAILS_ENV="production"
ENV NODE_ENV="production"
COPY --chown=mastodon:mastodon . /opt/mastodon
COPY --chown=mastodon:mastodon --from=build /opt/mastodon /opt/mastodon
# Tell rails to serve static files
ENV RAILS_SERVE_STATIC_FILES="true"
ENV BIND="0.0.0.0"
ENV RAILS_ENV="production" \
NODE_ENV="production" \
RAILS_SERVE_STATIC_FILES="true" \
BIND="0.0.0.0"
# Set the run user
USER mastodon
WORKDIR /opt/mastodon
# Precompile assets
RUN cd ~ && \
OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
yarn cache clean
RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
yarn cache clean
# Set the work dir and the container entry point
WORKDIR /opt/mastodon
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 3000 4000

45
Gemfile

@ -1,16 +1,16 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 2.6.0', '< 3.1.0'
ruby '>= 2.7.0', '< 3.1.0'
gem 'pkg-config', '~> 1.4'
gem 'pkg-config', '~> 1.5'
gem 'rexml', '~> 3.2'
gem 'puma', '~> 5.6'
gem 'rails', '~> 6.1.7'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.4'
gem 'rack', '~> 2.2.6'
gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.4'
@ -18,15 +18,15 @@ gem 'makara', '~> 0.5'
gem 'pghero', '~> 2.8'
gem 'dotenv-rails', '~> 2.8'
gem 'aws-sdk-s3', '~> 1.114', require: false
gem 'fog-core', '<= 2.1.0'
gem 'aws-sdk-s3', '~> 1.117', require: false
gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'kt-paperclip', '~> 7.1'
gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.13.0', require: false
gem 'bootsnap', '~> 1.15.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.2'
@ -51,22 +51,23 @@ gem 'ed25519', '~> 1.3'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.9'
gem 'redis-namespace', '~> 1.10'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 5.1'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.6.0'
gem 'httplog', '~> 1.6.2'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.13'
gem 'nokogiri', '~> 1.14'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.13'
gem 'ox', '~> 2.14'
gem 'parslet'
gem 'posix-spawn'
gem 'pundit', '~> 2.2'
gem 'public_suffix', '~> 5.0'
gem 'pundit', '~> 2.3'
gem 'premailer-rails'
gem 'rack-attack', '~> 6.6'
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
@ -78,7 +79,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 2.1'
gem 'ruby-progressbar', '~> 1.11'
gem 'sanitize', '~> 6.0'
gem 'scenic', '~> 1.6'
gem 'scenic', '~> 1.7'
gem 'sidekiq', '~> 6.5'
gem 'sidekiq-scheduler', '~> 4.0'
gem 'sidekiq-unique-jobs', '~> 7.1'
@ -86,7 +87,7 @@ gem 'sidekiq-bulk', '~> 0.2.0'
gem 'simple-navigation', '~> 4.4'
gem 'simple_form', '~> 5.1'
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
gem 'stoplight', '~> 3.0.0'
gem 'stoplight', '~> 3.0.1'
gem 'strong_migrations', '~> 0.7'
gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
@ -106,6 +107,10 @@ group :development, :test do
gem 'pry-byebug', '~> 3.10'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 5.1'
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
gem 'rubocop', require: false
end
group :production, :test do
@ -113,16 +118,16 @@ group :production, :test do
end
group :test do
gem 'capybara', '~> 3.37'
gem 'capybara', '~> 3.38'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.23'
gem 'microformats', '~> 4.4'
gem 'faker', '~> 3.1'
gem 'json-schema', '~> 3.0'
gem 'rack-test', '~> 2.0'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec_junit_formatter', '~> 0.6'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.21', require: false
gem 'simplecov', '~> 0.22', require: false
gem 'webmock', '~> 3.18'
gem 'rspec_junit_formatter', '~> 0.6'
gem 'rack-test', '~> 2.0'
end
group :development do
@ -134,9 +139,7 @@ group :development do
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0'
gem 'memory_profiler'
gem 'rubocop', '~> 1.30', require: false
gem 'rubocop-rails', '~> 2.15', require: false
gem 'brakeman', '~> 5.3', require: false
gem 'brakeman', '~> 5.4', require: false
gem 'bundler-audit', '~> 0.9', require: false
gem 'capistrano', '~> 3.17'

344
Gemfile.lock

@ -10,40 +10,40 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.7.6)
actionpack (= 6.1.7.6)
activesupport (= 6.1.7.6)
actioncable (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.7.6)
actionpack (= 6.1.7.6)
activejob (= 6.1.7.6)
activerecord (= 6.1.7.6)
activestorage (= 6.1.7.6)
activesupport (= 6.1.7.6)
actionmailbox (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
mail (>= 2.7.1)
actionmailer (6.1.7.6)
actionpack (= 6.1.7.6)
actionview (= 6.1.7.6)
activejob (= 6.1.7.6)
activesupport (= 6.1.7.6)
actionmailer (6.1.7.1)
actionpack (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activesupport (= 6.1.7.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.7.6)
actionview (= 6.1.7.6)
activesupport (= 6.1.7.6)
actionpack (6.1.7.1)
actionview (= 6.1.7.1)
activesupport (= 6.1.7.1)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.7.6)
actionpack (= 6.1.7.6)
activerecord (= 6.1.7.6)
activestorage (= 6.1.7.6)
activesupport (= 6.1.7.6)
actiontext (6.1.7.1)
actionpack (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
nokogiri (>= 1.8.5)
actionview (6.1.7.6)
activesupport (= 6.1.7.6)
actionview (6.1.7.1)
activesupport (= 6.1.7.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -54,22 +54,22 @@ GEM
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.8)
activejob (6.1.7.6)
activesupport (= 6.1.7.6)
activejob (6.1.7.1)
activesupport (= 6.1.7.1)
globalid (>= 0.3.6)
activemodel (6.1.7.6)
activesupport (= 6.1.7.6)
activerecord (6.1.7.6)
activemodel (= 6.1.7.6)
activesupport (= 6.1.7.6)
activestorage (6.1.7.6)
actionpack (= 6.1.7.6)
activejob (= 6.1.7.6)
activerecord (= 6.1.7.6)
activesupport (= 6.1.7.6)
activemodel (6.1.7.1)
activesupport (= 6.1.7.1)
activerecord (6.1.7.1)
activemodel (= 6.1.7.1)
activesupport (= 6.1.7.1)
activestorage (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activesupport (= 6.1.7.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (6.1.7.6)
activesupport (6.1.7.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -90,20 +90,20 @@ GEM
attr_required (1.0.1)
awrence (1.2.1)
aws-eventstream (1.2.0)
aws-partitions (1.587.0)
aws-sdk-core (3.130.2)
aws-partitions (1.670.0)
aws-sdk-core (3.168.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.60.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.56.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-s3 (1.117.2)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.0)
aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.17)
better_errors (2.9.1)
@ -122,15 +122,15 @@ GEM
debug_inspector (>= 0.0.1)
blurhash (0.1.6)
ffi (~> 1.14)
bootsnap (1.13.0)
bootsnap (1.15.0)
msgpack (~> 1.2)
brakeman (5.3.1)
brakeman (5.4.0)
browser (4.2.0)
brpoplpush-redis_script (0.1.2)
brpoplpush-redis_script (0.1.3)
concurrent-ruby (~> 1.0, >= 1.0.5)
redis (>= 1.0, <= 5.0)
redis (>= 1.0, < 6)
builder (3.2.4)
bullet (7.0.3)
bullet (7.0.7)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
bundler-audit (0.9.1)
@ -152,7 +152,7 @@ GEM
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
capybara (3.37.1)
capybara (3.38.0)
addressable
matrix
mini_mime (>= 0.1.3)
@ -174,7 +174,7 @@ GEM
cocoon (1.2.15)
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.2.2)
concurrent-ruby (1.1.10)
connection_pool (2.3.0)
cose (1.2.1)
cbor (~> 0.5.9)
@ -182,8 +182,9 @@ GEM
crack (0.4.5)
rexml
crass (1.0.6)
css_parser (1.7.1)
css_parser (1.12.0)
addressable
date (3.3.3)
debug_inspector (1.0.0)
devise (4.8.1)
bcrypt (~> 3.0)
@ -203,10 +204,10 @@ GEM
diff-lcs (1.5.0)
discard (1.2.1)
activerecord (>= 4.2, < 8)
docile (1.3.4)
docile (1.4.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.6.6)
doorkeeper (5.6.2)
railties (>= 5)
dotenv (2.8.1)
dotenv-rails (2.8.1)
@ -223,12 +224,12 @@ GEM
faraday (~> 1)
multi_json
encryptor (3.0.0)
erubi (1.11.0)
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
excon (0.76.0)
excon (0.95.0)
fabrication (2.30.0)
faker (2.23.0)
faker (3.1.0)
i18n (>= 1.8.11, < 2)
faraday (1.9.3)
faraday-em_http (~> 1.0)
@ -271,7 +272,7 @@ GEM
fog-core (>= 1.45, <= 2.1.0)
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.2.5)
formatador (0.3.0)
fugit (1.7.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
@ -299,7 +300,7 @@ GEM
hiredis (0.6.3)
hkdf (0.3.0)
htmlentities (4.3.4)
http (5.1.0)
http (5.1.1)
addressable (~> 2.8)
http-cookie (~> 1.0)
http-form_data (~> 2.2)
@ -309,10 +310,10 @@ GEM
http-form_data (2.3.0)
http_accept_language (2.1.1)
httpclient (2.8.3)
httplog (1.6.0)
httplog (1.6.2)
rack (>= 2.0)
rainbow (>= 2.0.0)
i18n (1.14.1)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
i18n-tasks (1.0.12)
activesupport (>= 4.0.2)
@ -325,12 +326,12 @@ GEM
rails-i18n
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
idn-ruby (0.1.4)
idn-ruby (0.1.5)
ipaddress (0.8.3)
jmespath (1.6.1)
json (2.6.2)
jmespath (1.6.2)
json (2.6.3)
json-canonicalization (0.3.0)
json-jwt (1.13.0)
json-jwt (1.14.0)
activesupport (>= 4.2)
aes_key_wrap
bindata
@ -341,11 +342,13 @@ GEM
multi_json (~> 1.15)
rack (~> 2.2)
rdf (~> 3.2, >= 3.2.9)
json-ld-preloaded (3.2.0)
json-ld-preloaded (3.2.2)
json-ld (~> 3.2)
rdf (~> 3.2)
json-schema (3.0.0)
addressable (>= 2.8)
jsonapi-renderer (0.2.2)
jwt (2.4.1)
jwt (2.5.0)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
@ -385,42 +388,51 @@ GEM
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mail (2.8.0.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
makara (0.5.1)
activerecord (>= 5.2.0)
marcel (1.0.2)
mario-redis-lock (1.2.1)
redis (>= 3.0.5)
matrix (0.4.2)
memory_profiler (1.0.0)
memory_profiler (1.0.1)
method_source (1.0.0)
microformats (4.4.1)
json (~> 2.2)
nokogiri (~> 1.10)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mini_mime (1.1.5)
mini_portile2 (2.8.4)
minitest (5.20.0)
msgpack (1.5.4)
mini_mime (1.1.2)
mini_portile2 (2.8.1)
minitest (5.17.0)
msgpack (1.6.0)
multi_json (1.15.0)
multipart-post (2.1.1)
net-imap (0.3.4)
date
net-protocol
net-ldap (0.17.1)
net-pop (0.1.2)
net-protocol
net-protocol (0.1.3)
timeout
net-scp (4.0.0.rc1)
net-ssh (>= 2.6.5, < 8.0.0)
net-smtp (0.3.3)
net-protocol
net-ssh (7.0.1)
nio4r (2.5.9)
nokogiri (1.15.4)
mini_portile2 (~> 2.8.2)
nio4r (2.5.8)
nokogiri (1.14.0)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nsa (0.2.8)
activesupport (>= 4.2, < 7)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.13.21)
oj (3.13.23)
omniauth (1.9.2)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
@ -448,24 +460,25 @@ GEM
openssl-signature_algorithm (1.2.1)
openssl (> 2.0, < 3.1)
orm_adapter (0.5.0)
ox (2.14.11)
ox (2.14.13)
parallel (1.22.1)
parser (3.1.2.1)
parser (3.2.0.0)
ast (~> 2.4.1)
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.4.3)
pg (1.4.5)
pghero (2.8.3)
activerecord (>= 5)
pkg-config (1.4.9)
pkg-config (1.5.1)
posix-spawn (0.3.15)
premailer (1.14.2)
premailer (1.18.0)
addressable
css_parser (>= 1.6.0)
css_parser (>= 1.12.0)
htmlentities (>= 4.0.0)
premailer-rails (1.11.1)
premailer-rails (1.12.0)
actionmailer (>= 3)
net-smtp
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0)
pry (0.14.1)
@ -476,14 +489,14 @@ GEM
pry (>= 0.13, < 0.15)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.0)
puma (5.6.7)
public_suffix (5.0.1)
puma (5.6.5)
nio4r (~> 2.0)
pundit (2.2.0)
pundit (2.3.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.1)
rack (2.2.8)
racc (1.6.2)
rack (2.2.6.2)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack-cors (1.1.1)
@ -498,20 +511,20 @@ GEM
rack
rack-test (2.0.2)
rack (>= 1.3)
rails (6.1.7.6)
actioncable (= 6.1.7.6)
actionmailbox (= 6.1.7.6)
actionmailer (= 6.1.7.6)
actionpack (= 6.1.7.6)
actiontext (= 6.1.7.6)
actionview (= 6.1.7.6)
activejob (= 6.1.7.6)
activemodel (= 6.1.7.6)
activerecord (= 6.1.7.6)
activestorage (= 6.1.7.6)
activesupport (= 6.1.7.6)
rails (6.1.7.1)
actioncable (= 6.1.7.1)
actionmailbox (= 6.1.7.1)
actionmailer (= 6.1.7.1)
actionpack (= 6.1.7.1)
actiontext (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activemodel (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
bundler (>= 1.15.0)
railties (= 6.1.7.6)
railties (= 6.1.7.1)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -527,9 +540,9 @@ GEM
railties (>= 6.0.0, < 7)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
railties (6.1.7.6)
actionpack (= 6.1.7.6)
activesupport (= 6.1.7.6)
railties (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -537,13 +550,15 @@ GEM
rake (13.0.6)
rdf (3.2.9)
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.5.0)
rdf-normalize (0.5.1)
rdf (~> 3.2)
redcarpet (3.5.1)
redis (4.5.1)
redis-namespace (1.9.0)
redis-namespace (1.10.0)
redis (>= 4)
regexp_parser (2.5.0)
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.6.1)
request_store (1.5.1)
rack (>= 1.4)
responders (3.0.1)
@ -578,21 +593,30 @@ GEM
rspec-support (3.11.1)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.30.1)
rubocop (1.43.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.0.0)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
rubocop-ast (>= 1.24.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.18.0)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-rails (2.15.0)
rubocop-capybara (2.17.0)
rubocop (~> 1.41)
rubocop-performance (1.15.2)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.17.4)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.18.0)
rubocop (~> 1.33)
rubocop-capybara
ruby-progressbar (1.11.0)
ruby-saml (1.13.0)
nokogiri (>= 1.10.5)
@ -602,14 +626,14 @@ GEM
fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
sanitize (6.0.2)
sanitize (6.0.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
scenic (1.6.0)
scenic (1.7.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
semantic_range (3.0.0)
sidekiq (6.5.11)
sidekiq (6.5.8)
connection_pool (>= 2.2.5, < 3)
rack (~> 2.0)
redis (>= 4.5.0, < 5)
@ -620,22 +644,23 @@ GEM
rufus-scheduler (~> 3.2)
sidekiq (>= 4, < 7)
tilt (>= 1.4.0)
sidekiq-unique-jobs (7.1.27)
sidekiq-unique-jobs (7.1.29)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 5.0, < 8.0)
redis (< 5.0)
sidekiq (>= 5.0, < 7.0)
thor (>= 0.20, < 3.0)
simple-navigation (4.4.0)
activesupport (>= 2.3.2)
simple_form (5.1.0)
actionpack (>= 5.2)
activemodel (>= 5.2)
simplecov (0.21.2)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.2)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
@ -647,9 +672,10 @@ GEM
sshkit (1.21.2)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
stackprof (0.2.22)
stackprof (0.2.23)
statsd-ruby (1.5.0)
stoplight (3.0.0)
stoplight (3.0.1)
redlock (~> 1.0)
strong_migrations (0.7.9)
activerecord (>= 5)
swd (1.3.0)
@ -661,8 +687,9 @@ GEM
unicode-display_width (>= 1.1.1, < 3)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
thor (1.2.2)
thor (1.2.1)
tilt (2.0.11)
timeout (0.3.0)
tpm-key_attestation (0.11.0)
bindata (~> 2.4)
openssl (> 2.0, < 3.1)
@ -680,14 +707,14 @@ GEM
twitter-text (3.1.0)
idn-ruby
unf (~> 0.1.0)
tzinfo (2.0.6)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2022.4)
tzinfo-data (1.2022.7)
tzinfo (>= 1.0.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.3.0)
unicode-display_width (2.4.2)
uniform_notifier (1.16.0)
validate_email (0.1.6)
activemodel (>= 3.0)
@ -718,14 +745,14 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
websocket-driver (0.7.6)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
wisper (2.0.1)
xorcist (1.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.12)
zeitwerk (2.6.6)
PLATFORMS
ruby
@ -735,12 +762,12 @@ DEPENDENCIES
active_record_query_trace (~> 1.8)
addressable (~> 2.8)
annotate (~> 3.2)
aws-sdk-s3 (~> 1.114)
aws-sdk-s3 (~> 1.117)
better_errors (~> 2.9)
binding_of_caller (~> 1.0)
blurhash (~> 0.1)
bootsnap (~> 1.13.0)
brakeman (~> 5.3)
bootsnap (~> 1.15.0)
brakeman (~> 5.4)
browser
bullet (~> 7.0)
bundler-audit (~> 0.9)
@ -748,7 +775,7 @@ DEPENDENCIES
capistrano-rails (~> 1.6)
capistrano-rbenv (~> 2.2)
capistrano-yarn (~> 2.0)
capybara (~> 3.37)
capybara (~> 3.38)
charlock_holmes (~> 0.7.7)
chewy (~> 7.2)
climate_control (~> 0.2)
@ -764,10 +791,10 @@ DEPENDENCIES
dotenv-rails (~> 2.8)
ed25519 (~> 1.3)
fabrication (~> 2.30)
faker (~> 2.23)
faker (~> 3.1)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.1.0)
fog-core (<= 2.4.0)
fog-openstack (~> 0.3)
fuubar (~> 2.5)
gitlab-omniauth-openid-connect (~> 0.10.0)
@ -776,11 +803,12 @@ DEPENDENCIES
htmlentities (~> 4.3)
http (~> 5.1)
http_accept_language (~> 2.1)
httplog (~> 1.6.0)
httplog (~> 1.6.2)
i18n-tasks (~> 1.0)
idn-ruby
json-ld
json-ld-preloaded (~> 3.2)
json-schema (~> 3.0)
kaminari (~> 1.2)
kt-paperclip (~> 7.1)
letter_opener (~> 1.8)
@ -790,10 +818,9 @@ DEPENDENCIES
makara (~> 0.5)
mario-redis-lock (~> 1.2)
memory_profiler
microformats (~> 4.4)
mime-types (~> 3.4.1)
net-ldap (~> 0.17)
nokogiri (~> 1.13)
nokogiri (~> 1.14)
nsa (~> 0.2)
oj (~> 3.13)
omniauth (~> 1.9)
@ -804,15 +831,16 @@ DEPENDENCIES
parslet
pg (~> 1.4)
pghero (~> 2.8)
pkg-config (~> 1.4)
pkg-config (~> 1.5)
posix-spawn
premailer-rails
private_address_check (~> 0.5)
pry-byebug (~> 3.10)
pry-rails (~> 0.3)
public_suffix (~> 5.0)
puma (~> 5.6)
pundit (~> 2.2)
rack (~> 2.2.4)
pundit (~> 2.3)
rack (~> 2.2.6)
rack-attack (~> 6.6)
rack-cors (~> 1.1)
rack-test (~> 2.0)
@ -823,28 +851,30 @@ DEPENDENCIES
rdf-normalize (~> 0.5)
redcarpet (~> 3.5)
redis (~> 4.5)
redis-namespace (~> 1.9)
redis-namespace (~> 1.10)
rexml (~> 3.2)
rqrcode (~> 2.1)
rspec-rails (~> 5.1)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.6)
rubocop (~> 1.30)
rubocop-rails (~> 2.15)
rubocop
rubocop-performance
rubocop-rails
rubocop-rspec
ruby-progressbar (~> 1.11)
sanitize (~> 6.0)
scenic (~> 1.6)
scenic (~> 1.7)
sidekiq (~> 6.5)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 4.0)
sidekiq-unique-jobs (~> 7.1)
simple-navigation (~> 4.4)
simple_form (~> 5.1)
simplecov (~> 0.21)
simplecov (~> 0.22)
sprockets (~> 3.7.2)
sprockets-rails (~> 3.4)
stackprof
stoplight (~> 3.0.0)
stoplight (~> 3.0.1)
strong_migrations (~> 0.7)
thor (~> 1.2)
tty-prompt (~> 0.23)
@ -855,9 +885,3 @@ DEPENDENCIES
webpacker (~> 5.4)
webpush!
xorcist (~> 1.1)
RUBY VERSION
ruby 3.0.3p157
BUNDLED WITH
2.3.25

71
Vagrantfile vendored

@ -3,16 +3,14 @@
ENV["PORT"] ||= "3000"
$provision = <<SCRIPT
cd /vagrant # This is where the host folder/repo is mounted
$provisionA = <<SCRIPT
# Add the yarn repo + yarn repo keys
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash -
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
# Add firewall rule to redirect 80 to PORT and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
@ -33,32 +31,56 @@ sudo apt-get install \
redis-tools \
postgresql \
postgresql-contrib \
yarn \
libicu-dev \
libidn11-dev \
libreadline-dev \
libpam0g-dev \
libreadline6-dev \
autoconf \
bison \
build-essential \
ffmpeg \
file \
gcc \
libffi-dev \
libgdbm-dev \
libjemalloc-dev \
libncurses5-dev \
libprotobuf-dev \
libssl-dev \
libyaml-dev \
pkg-config \
protobuf-compiler \
zlib1g-dev \
-y
# Install rvm
read RUBY_VERSION < .ruby-version
sudo apt-add-repository -y ppa:rael-gc/rvm
sudo apt-get install rvm -y
curl -sSL https://rvm.io/mpapis.asc | gpg --import
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import
sudo usermod -a -G rvm $USER
SCRIPT
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
source /home/vagrant/.rvm/scripts/rvm
$provisionB = <<SCRIPT
source "/etc/profile.d/rvm.sh"
# Install Ruby
rvm reinstall ruby-$RUBY_VERSION --disable-binary
read RUBY_VERSION < /vagrant/.ruby-version
rvm install ruby-$RUBY_VERSION --disable-binary
# Configure database
sudo -u postgres createuser -U postgres vagrant -s
sudo -u postgres createdb -U postgres mastodon_development
# Install gems and node modules
cd /vagrant # This is where the host folder/repo is mounted
# Install gems
gem install bundler foreman
bundle install
# Install node modules
sudo corepack enable
yarn set version classic
yarn install
# Build Mastodon
@ -72,18 +94,11 @@ echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
SCRIPT
$start = <<SCRIPT
echo 'To start server'
echo ' $ vagrant ssh -c "cd /vagrant && foreman start"'
SCRIPT
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.box = "ubuntu/focal64"
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"
@ -100,7 +115,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# Use "virtio" network interfaces for better performance.
vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
end
# This uses the vagrant-hostsupdater plugin, and lets you
@ -118,7 +132,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
end
if config.vm.networks.any? { |type, options| type == :private_network }
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp', 'actimeo=1']
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'actimeo=1']
else
config.vm.synced_folder ".", "/vagrant"
end
@ -129,9 +143,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.network :forwarded_port, guest: 8080, host: 8080
# Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision'
config.vm.provision :shell, inline: $provision, privileged: false
config.vm.provision :shell, inline: $provisionA, privileged: false, reset: true
config.vm.provision :shell, inline: $provisionB, privileged: false
# Start up script, runs on every 'vagrant up'
config.vm.provision :shell, inline: $start, run: 'always', privileged: false
config.vm.post_up_message = <<MESSAGE
To start server
$ vagrant ssh -c "cd /vagrant && foreman start"
MESSAGE
end

15
app/controllers/accounts_controller.rb

@ -18,19 +18,14 @@ class AccountsController < ApplicationController
format.html do
expires_in 0, public: true unless user_signed_in?
@rss_url = rss_url
@rss_url = rss_url
end
format.rss do
expires_in 1.minute, public: true
if @account&.user&.setting_norss == true
@statuses = []
next
end
limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
@statuses = filtered_statuses.without_reblogs.without_local_only.limit(limit)
@statuses = filtered_statuses.without_reblogs.limit(limit)
@statuses = cache_collection(@statuses, Status)
end
@ -52,11 +47,7 @@ class AccountsController < ApplicationController
end
def default_statuses
if current_user.nil?
@account.statuses.without_local_only.where(visibility: [:public, :unlisted])
else
@account.statuses.where(visibility: [:public, :unlisted])
end
@account.statuses.where(visibility: [:public, :unlisted])
end
def only_media_scope

2
app/controllers/admin/account_actions_controller.rb

@ -21,7 +21,7 @@ module Admin
account_action.save!
if account_action.with_report?
redirect_to admin_reports_path
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id])
else
redirect_to admin_account_path(@account.id)
end

2
app/controllers/admin/accounts_controller.rb

@ -55,12 +55,14 @@ module Admin
def approve
authorize @account.user, :approve?
@account.user.approve!
log_action :approve, @account.user
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
end
def reject
authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
log_action :reject, @account.user
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
end

8
app/controllers/admin/domain_blocks_controller.rb

@ -55,12 +55,8 @@ module Admin
def update
authorize :domain_block, :update?
@domain_block.update(update_params)
severity_changed = @domain_block.severity_changed?
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
if @domain_block.update(update_params)
DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?)
log_action :update, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else

4
app/controllers/admin/export_domain_allows_controller.rb

@ -23,9 +23,7 @@ module Admin
@import = Admin::Import.new(import_params)
return render :new unless @import.validate
parse_import_data!(export_headers)
@data.take(Admin::Import::ROWS_PROCESSING_LIMIT).each do |row|
@import.csv_rows.each do |row|
domain = row['#domain'].strip
next if DomainAllow.allowed?(domain)

24
app/controllers/admin/export_domain_blocks_controller.rb

@ -23,24 +23,30 @@ module Admin
@import = Admin::Import.new(import_params)
return render :new unless @import.validate
parse_import_data!(export_headers)
@global_private_comment = I18n.t('admin.export_domain_blocks.import.private_comment_template', source: @import.data_file_name, date: I18n.l(Time.now.utc))
@form = Form::DomainBlockBatch.new
@domain_blocks = @data.take(Admin::Import::ROWS_PROCESSING_LIMIT).filter_map do |row|
@domain_blocks = @import.csv_rows.filter_map do |row|
domain = row['#domain'].strip
next if DomainBlock.rule_for(domain).present?
domain_block = DomainBlock.new(domain: domain,
severity: row['#severity'].strip,
reject_media: row['#reject_media'].strip,
reject_reports: row['#reject_reports'].strip,
severity: row.fetch('#severity', :suspend),
reject_media: row.fetch('#reject_media', false),
reject_reports: row.fetch('#reject_reports', false),
private_comment: @global_private_comment,
public_comment: row['#public_comment']&.strip,
obfuscate: row['#obfuscate'].strip)
public_comment: row['#public_comment'],
obfuscate: row.fetch('#obfuscate', false))
if domain_block.invalid?
flash.now[:alert] = I18n.t('admin.export_domain_blocks.invalid_domain_block', error: domain_block.errors.full_messages.join(', '))
next
end
domain_block if domain_block.valid?
domain_block
rescue ArgumentError => e
flash.now[:alert] = I18n.t('admin.export_domain_blocks.invalid_domain_block', error: e.message)
next
end
@warning_domains = Instance.where(domain: @domain_blocks.map(&:domain)).where('EXISTS (SELECT 1 FROM follows JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id WHERE accounts.domain = instances.domain)').pluck(:domain)

4
app/controllers/admin/instances_controller.rb

@ -49,7 +49,7 @@ module Admin
private
def set_instance
@instance = Instance.find(params[:id])
@instance = Instance.find(TagManager.instance.normalize_domain(params[:id]&.strip))
end
def set_instances
@ -57,7 +57,7 @@ module Admin
end
def preload_delivery_failures!
warning_domains_map = DeliveryFailureTracker.warning_domains_map
warning_domains_map = DeliveryFailureTracker.warning_domains_map(@instances.map(&:domain))
@instances.each do |instance|
instance.failure_days = warning_domains_map[instance.domain]

6
app/controllers/admin/relays_controller.rb

@ -3,7 +3,7 @@
module Admin
class RelaysController < BaseController
before_action :set_relay, except: [:index, :new, :create]
before_action :require_signatures_enabled!, only: [:new, :create, :enable]
before_action :warn_signatures_not_enabled!, only: [:new, :create, :enable]
def index
authorize :relay, :update?
@ -56,8 +56,8 @@ module Admin
params.require(:relay).permit(:inbox_url)
end
def require_signatures_enabled!
redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
def warn_signatures_not_enabled!
flash.now[:error] = I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
end
end
end

17
app/controllers/admin/reports/actions_controller.rb

@ -3,6 +3,11 @@
class Admin::Reports::ActionsController < Admin::BaseController
before_action :set_report
def preview
authorize @report, :show?
@moderation_action = action_from_button
end
def create
authorize @report, :show?
@ -13,7 +18,8 @@ class Admin::Reports::ActionsController < Admin::BaseController
status_ids: @report.status_ids,
current_account: current_account,
report_id: @report.id,
send_email_notification: !@report.spam?
send_email_notification: !@report.spam?,
text: params[:text]
)
status_batch_action.save!
@ -23,13 +29,16 @@ class Admin::Reports::ActionsController < Admin::BaseController
report_id: @report.id,
target_account: @report.target_account,
current_account: current_account,
send_email_notification: !@report.spam?
send_email_notification: !@report.spam?,
text: params[:text]
)
account_action.save!
else
return redirect_to admin_report_path(@report), alert: I18n.t('admin.reports.unknown_action_msg', action: action_from_button)
end
redirect_to admin_reports_path
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: @report.id)
end
private
@ -47,6 +56,8 @@ class Admin::Reports::ActionsController < Admin::BaseController
'silence'
elsif params[:suspend]
'suspend'
elsif params[:moderation_action]
params[:moderation_action]
end
end
end

22
app/controllers/api/base_controller.rb

@ -16,6 +16,26 @@ class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session
content_security_policy do |p|
# Set every directive that does not have a fallback
p.default_src :none
p.frame_ancestors :none
p.form_action :none
# Disable every directive with a fallback to cut on response size
p.base_uri false
p.font_src false
p.img_src false
p.style_src false
p.media_src false
p.frame_src false
p.manifest_src false
p.connect_src false
p.script_src false
p.child_src false
p.worker_src false
end
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
render json: { error: e.to_s }, status: 422
end
@ -129,7 +149,7 @@ class Api::BaseController < ApplicationController
end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
response.headers['Cache-Control'] = 'private, no-store'
end
def disallow_unauthenticated_api_access?

12
app/controllers/api/v1/accounts/credentials_controller.rb

@ -21,7 +21,17 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
private
def account_params
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
params.permit(
:display_name,
:note,
:avatar,
:header,
:locked,
:bot,
:discoverable,
:hide_collections,
fields_attributes: [:name, :value]
)
end
def user_settings_params

2
app/controllers/api/v1/admin/accounts_controller.rb

@ -54,12 +54,14 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def approve
authorize @account.user, :approve?
@account.user.approve!
log_action :approve, @account.user
render json: @account, serializer: REST::Admin::AccountSerializer
end
def reject
authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
log_action :reject, @account.user
render_empty
end

6
app/controllers/api/v1/admin/domain_blocks_controller.rb

@ -40,10 +40,8 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
def update
authorize @domain_block, :update?
@domain_block.update(domain_block_params)
severity_changed = @domain_block.severity_changed?
@domain_block.save!
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
@domain_block.update!(domain_block_params)
DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?)
log_action :update, @domain_block
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
end

8
app/controllers/api/v1/admin/trends/tags_controller.rb

@ -3,6 +3,14 @@
class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
before_action -> { authorize_if_got_token! :'admin:read' }
def index
if current_user&.can?(:manage_taxonomies)
render json: @tags, each_serializer: REST::Admin::TagSerializer
else
super
end
end
private
def enabled?

6
app/controllers/api/v1/filters_controller.rb

@ -13,7 +13,7 @@ class Api::V1::FiltersController < Api::BaseController
def create
ApplicationRecord.transaction do
filter_category = current_account.custom_filters.create!(resource_params)
filter_category = current_account.custom_filters.create!(filter_params)
@filter = filter_category.keywords.create!(keyword_params)
end
@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController
end
def resource_params
params.permit(:phrase, :expires_in, :irreversible, context: [])
params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
end
def filter_params
resource_params.slice(:expires_in, :irreversible, :context)
resource_params.slice(:phrase, :expires_in, :irreversible, :context)
end
def keyword_params

6
app/controllers/api/v1/followed_tags_controller.rb

@ -3,11 +3,11 @@
class Api::V1::FollowedTagsController < Api::BaseController
TAGS_LIMIT = 100
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }, except: :show
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }
before_action :require_user!
before_action :set_results
after_action :insert_pagination_headers, only: :show
after_action :insert_pagination_headers
def index
render json: @results.map(&:tag), each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@results.map(&:tag), current_user&.account_id)
@ -43,7 +43,7 @@ class Api::V1::FollowedTagsController < Api::BaseController
end
def records_continue?
@results.size == limit_param(TAG_LIMIT)
@results.size == limit_param(TAGS_LIMIT)
end
def pagination_params(core_params)

2
app/controllers/api/v1/notifications_controller.rb

@ -31,7 +31,7 @@ class Api::V1::NotificationsController < Api::BaseController
private
def load_notifications
notifications = browserable_account_notifications.includes(from_account: :account_stat).to_a_paginated_by_id(
notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_paginated_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)

7
app/controllers/api/v1/statuses_controller.rb

@ -80,6 +80,7 @@ class Api::V1::StatusesController < Api::BaseController
current_account.id,
text: status_params[:status],
media_ids: status_params[:media_ids],
media_attributes: status_params[:media_attributes],
sensitive: status_params[:sensitive],
language: status_params[:language],
spoiler_text: status_params[:spoiler_text],
@ -130,6 +131,12 @@ class Api::V1::StatusesController < Api::BaseController
:scheduled_at,
:local_only,
media_ids: [],
media_attributes: [
:id,
:thumbnail,
:description,
:focus,
],
poll: [
:multiple,
:hide_totals,

2
app/controllers/api/v1/tags_controller.rb

@ -12,7 +12,7 @@ class Api::V1::TagsController < Api::BaseController
end
def follow
TagFollow.create!(tag: @tag, account: current_account, rate_limit: true)
TagFollow.create_with(rate_limit: true).find_or_create_by!(tag: @tag, account: current_account)
render json: @tag, serializer: REST::TagSerializer
end

2
app/controllers/auth/passwords_controller.rb

@ -10,6 +10,8 @@ class Auth::PasswordsController < Devise::PasswordsController
super do |resource|
if resource.errors.empty?
resource.session_activations.destroy_all
resource.revoke_access!
end
end
end

6
app/controllers/auth/registrations_controller.rb

@ -56,8 +56,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
end
end
@ -154,6 +154,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
response.headers['Cache-Control'] = 'private, no-store'
end
end

4
app/controllers/auth/sessions_controller.rb

@ -14,6 +14,10 @@ class Auth::SessionsController < Devise::SessionsController
before_action :set_instance_presenter, only: [:new]
before_action :set_body_classes
content_security_policy only: :new do |p|
p.form_action(false)
end
def check_suspicious!
user = find_user
@login_is_suspicious = suspicious_sign_in?(user) unless user.nil?

10
app/controllers/concerns/admin_export_controller_concern.rb

@ -26,14 +26,4 @@ module AdminExportControllerConcern
def import_params
params.require(:admin_import).permit(:data)
end
def import_data_path
params[:admin_import][:data].path
end
def parse_import_data!(default_headers)
data = CSV.read(import_data_path, headers: true, encoding: 'UTF-8')
data = CSV.read(import_data_path, headers: default_headers, encoding: 'UTF-8') unless data.headers&.first&.strip&.include?(default_headers[0])
@data = data.reject(&:blank?)
end
end

2
app/controllers/concerns/rate_limit_headers.rb

@ -58,7 +58,7 @@ module RateLimitHeaders
end
def api_throttle_data
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] - v[:count] }
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_key, value| value[:limit] - value[:count] }
request.env['rack.attack.throttle_data'][most_limited_type]
end

20
app/controllers/concerns/signature_verification.rb

@ -28,8 +28,8 @@ module SignatureVerification
end
class SignatureParamsTransformer < Parslet::Transform
rule(params: subtree(:p)) do
(p.is_a?(Array) ? p : [p]).each_with_object({}) { |(key, val), h| h[key] = val }
rule(params: subtree(:param)) do
(param.is_a?(Array) ? param : [param]).each_with_object({}) { |(key, value), hash| hash[key] = value }
end
rule(param: { key: simple(:key), value: simple(:val) }) do
@ -46,11 +46,11 @@ module SignatureVerification
end
def require_account_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
end
def require_actor_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor
render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor
end
def signed_request?
@ -97,11 +97,11 @@ module SignatureVerification
actor = stoplight_wrap_request { actor_refresh_key!(actor) }
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)"
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
rescue SignatureVerificationError => e
fail_with! e.message
rescue HTTP::Error, OpenSSL::SSL::SSLError => e
@ -118,8 +118,8 @@ module SignatureVerification
private
def fail_with!(message)
@signature_verification_failure_reason = message
def fail_with!(message, **options)
@signature_verification_failure_reason = { error: message }.merge(options)
@signed_request_actor = nil
end
@ -209,8 +209,8 @@ module SignatureVerification
end
expires_time = Time.at(signature_params['expires'].to_i).utc if signature_params['expires'].present?
rescue ArgumentError
return false
rescue ArgumentError => e
raise SignatureVerificationError, "Invalid Date header: #{e.message}"
end
expires_time ||= created_time + 5.minutes unless created_time.nil?

9
app/controllers/concerns/web_app_controller_concern.rb

@ -4,21 +4,16 @@ module WebAppControllerConcern
extend ActiveSupport::Concern
included do
before_action :redirect_unauthenticated_to_permalinks!
prepend_before_action :redirect_unauthenticated_to_permalinks!
before_action :set_app_body_class
before_action :set_referrer_policy_header
end
def set_app_body_class
@body_classes = 'app-body'
end
def set_referrer_policy_header
response.headers['Referrer-Policy'] = 'origin'
end
def redirect_unauthenticated_to_permalinks!
return if user_signed_in?
return if user_signed_in? && current_account.moved_to_account_id.nil?
redirect_path = PermalinkRedirector.new(request.path).redirect_path

2
app/controllers/follower_accounts_controller.rb

@ -63,7 +63,7 @@ class FollowerAccountsController < ApplicationController
id: account_followers_url(@account, page: params.fetch(:page, 1)),
type: :ordered,
size: @account.followers_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.account) },
part_of: account_followers_url(@account),
next: next_page_url,
prev: prev_page_url

2
app/controllers/following_accounts_controller.rb

@ -66,7 +66,7 @@ class FollowingAccountsController < ApplicationController
id: account_following_index_url(@account, page: params.fetch(:page, 1)),
type: :ordered,
size: @account.following_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.target_account) },
part_of: account_following_index_url(@account),
next: next_page_url,
prev: prev_page_url

4
app/controllers/media_controller.rb

@ -12,8 +12,8 @@ class MediaController < ApplicationController
before_action :check_playable, only: :player
before_action :allow_iframing, only: :player
content_security_policy only: :player do |p|
p.frame_ancestors(false)
content_security_policy only: :player do |policy|
policy.frame_ancestors(false)
end
def show

6
app/controllers/oauth/authorizations_controller.rb

@ -7,6 +7,10 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_action :authenticate_resource_owner!
before_action :set_cache_headers
content_security_policy do |p|
p.form_action(false)
end
include Localized
private
@ -30,6 +34,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
response.headers['Cache-Control'] = 'private, no-store'
end
end

2
app/controllers/settings/base_controller.rb

@ -14,7 +14,7 @@ class Settings::BaseController < ApplicationController
end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
response.headers['Cache-Control'] = 'private, no-store'
end
def require_not_suspended!

4
app/controllers/statuses_cleanup_controller.rb

@ -19,6 +19,10 @@ class StatusesCleanupController < ApplicationController
# Do nothing
end
def require_functional!
redirect_to edit_user_registration_path unless current_user.functional_or_moved?
end
private
def set_policy

4
app/controllers/statuses_controller.rb

@ -17,8 +17,8 @@ class StatusesController < ApplicationController
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
content_security_policy only: :embed do |p|
p.frame_ancestors(false)
content_security_policy only: :embed do |policy|
policy.frame_ancestors(false)
end
def show

2
app/controllers/tags_controller.rb

@ -65,7 +65,7 @@ class TagsController < ApplicationController
id: tag_url(@tag),
type: :ordered,
size: @tag.statuses.count,
items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
items: @statuses.map { |status| ActivityPub::TagManager.instance.uri_for(status) }
)
end
end

25
app/helpers/formatting_helper.rb

@ -23,19 +23,28 @@ module FormattingHelper
before_html = begin
if status.spoiler_text?
"<p><strong>#{I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)}</strong> #{h(status.spoiler_text)}</p><hr />"
else
''
tag.p do
tag.strong do
I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)
end
status.spoiler_text
end + tag.hr
end
end.html_safe # rubocop:disable Rails/OutputSafety
end
after_html = begin
if status.preloadable_poll
"<p>#{status.preloadable_poll.options.map { |o| "<input type=#{status.preloadable_poll.multiple? ? 'checkbox' : 'radio'} disabled /> #{h(o)}" }.join('<br />')}</p>"
else
''
tag.p do
safe_join(
status.preloadable_poll.options.map do |o|
tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', o, disabled: true)
end,
tag.br
)
end
end
end.html_safe # rubocop:disable Rails/OutputSafety
end
prerender_custom_emojis(
safe_join([before_html, html, after_html]),

3
app/helpers/languages_helper.rb

@ -190,12 +190,15 @@ module LanguagesHelper
ISO_639_3 = {
ast: ['Asturian', 'Asturianu'].freeze,
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
cnr: ['Montenegrin', 'crnogorski'].freeze,
jbo: ['Lojban', 'la .lojban.'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
ldn: ['Láadan', 'Láadan'].freeze,
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
sco: ['Scots', 'Scots'].freeze,
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
tok: ['Toki Pona', 'toki pona'].freeze,
zba: ['Balaibalan', 'باليبلن'].freeze,
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,

2
app/helpers/statuses_helper.rb

@ -21,7 +21,7 @@ module StatusesHelper
def media_summary(status)
attachments = { image: 0, video: 0, audio: 0 }
status.media_attachments.each do |media|
status.ordered_media_attachments.each do |media|
if media.video?
attachments[:video] += 1
elsif media.audio?

2
app/javascript/images/logo-symbol-icon.svg

@ -1,2 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="79" height="79" viewBox="0 0 79 75"><symbol id="logo-symbol-icon"><path d="M74.7135 16.6043C73.6199 8.54587 66.5351 2.19527 58.1366 0.964691C56.7196 0.756754 51.351 0 38.9148 0H38.822C26.3824 0 23.7135 0.756754 22.2966 0.964691C14.1319 2.16118 6.67571 7.86752 4.86669 16.0214C3.99657 20.0369 3.90371 24.4888 4.06535 28.5726C4.29578 34.4289 4.34049 40.275 4.877 46.1075C5.24791 49.9817 5.89495 53.8251 6.81328 57.6088C8.53288 64.5968 15.4938 70.4122 22.3138 72.7848C29.6155 75.259 37.468 75.6697 44.9919 73.971C45.8196 73.7801 46.6381 73.5586 47.4475 73.3063C49.2737 72.7302 51.4164 72.086 52.9915 70.9542C53.0131 70.9384 53.0308 70.9178 53.0433 70.8942C53.0558 70.8706 53.0628 70.8445 53.0637 70.8179V65.1661C53.0634 65.1412 53.0574 65.1167 53.0462 65.0944C53.035 65.0721 53.0189 65.0525 52.9992 65.0371C52.9794 65.0218 52.9564 65.011 52.9318 65.0056C52.9073 65.0002 52.8819 65.0003 52.8574 65.0059C48.0369 66.1472 43.0971 66.7193 38.141 66.7103C29.6118 66.7103 27.3178 62.6981 26.6609 61.0278C26.1329 59.5842 25.7976 58.0784 25.6636 56.5486C25.6622 56.5229 25.667 56.4973 25.6775 56.4738C25.688 56.4502 25.7039 56.4295 25.724 56.4132C25.7441 56.397 25.7678 56.3856 25.7931 56.3801C25.8185 56.3746 25.8448 56.3751 25.8699 56.3816C30.6101 57.5151 35.4693 58.0873 40.3455 58.086C41.5183 58.086 42.6876 58.086 43.8604 58.0553C48.7647 57.919 53.9339 57.6701 58.7591 56.7361C58.8794 56.7123 58.9998 56.6918 59.103 56.6611C66.7139 55.2124 73.9569 50.665 74.6929 39.1501C74.7204 38.6967 74.7892 34.4016 74.7892 33.9312C74.7926 32.3325 75.3085 22.5901 74.7135 16.6043ZM62.9996 45.3371H54.9966V25.9069C54.9966 21.8163 53.277 19.7302 49.7793 19.7302C45.9343 19.7302 44.0083 22.1981 44.0083 27.0727V37.7082H36.0534V27.0727C36.0534 22.1981 34.124 19.7302 30.279 19.7302C26.8019 19.7302 25.0651 21.8163 25.0617 25.9069V45.3371H17.0656V25.3172C17.0656 21.2266 18.1191 17.9769 20.2262 15.568C22.3998 13.1648 25.2509 11.9308 28.7898 11.9308C32.8859 11.9308 35.9812 13.492 38.0447 16.6111L40.036 19.9245L42.0308 16.6111C44.0943 13.492 47.1896 11.9308 51.2788 11.9308C54.8143 11.9308 57.6654 13.1648 59.8459 15.568C61.9529 17.9746 63.0065 21.2243 63.0065 25.3172L62.9996 45.3371Z" fill="currentColor"/></symbol><use xlink:href="#logo-symbol-icon" style="color:#fff" /></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="79" height="79" viewBox="0 0 79 75"><symbol id="logo-symbol-icon"><path d="M74.7135 16.6043C73.6199 8.54587 66.5351 2.19527 58.1366 0.964691C56.7196 0.756754 51.351 0 38.9148 0H38.822C26.3824 0 23.7135 0.756754 22.2966 0.964691C14.1319 2.16118 6.67571 7.86752 4.86669 16.0214C3.99657 20.0369 3.90371 24.4888 4.06535 28.5726C4.29578 34.4289 4.34049 40.275 4.877 46.1075C5.24791 49.9817 5.89495 53.8251 6.81328 57.6088C8.53288 64.5968 15.4938 70.4122 22.3138 72.7848C29.6155 75.259 37.468 75.6697 44.9919 73.971C45.8196 73.7801 46.6381 73.5586 47.4475 73.3063C49.2737 72.7302 51.4164 72.086 52.9915 70.9542C53.0131 70.9384 53.0308 70.9178 53.0433 70.8942C53.0558 70.8706 53.0628 70.8445 53.0637 70.8179V65.1661C53.0634 65.1412 53.0574 65.1167 53.0462 65.0944C53.035 65.0721 53.0189 65.0525 52.9992 65.0371C52.9794 65.0218 52.9564 65.011 52.9318 65.0056C52.9073 65.0002 52.8819 65.0003 52.8574 65.0059C48.0369 66.1472 43.0971 66.7193 38.141 66.7103C29.6118 66.7103 27.3178 62.6981 26.6609 61.0278C26.1329 59.5842 25.7976 58.0784 25.6636 56.5486C25.6622 56.5229 25.667 56.4973 25.6775 56.4738C25.688 56.4502 25.7039 56.4295 25.724 56.4132C25.7441 56.397 25.7678 56.3856 25.7931 56.3801C25.8185 56.3746 25.8448 56.3751 25.8699 56.3816C30.6101 57.5151 35.4693 58.0873 40.3455 58.086C41.5183 58.086 42.6876 58.086 43.8604 58.0553C48.7647 57.919 53.9339 57.6701 58.7591 56.7361C58.8794 56.7123 58.9998 56.6918 59.103 56.6611C66.7139 55.2124 73.9569 50.665 74.6929 39.1501C74.7204 38.6967 74.7892 34.4016 74.7892 33.9312C74.7926 32.3325 75.3085 22.5901 74.7135 16.6043ZM62.9996 45.3371H54.9966V25.9069C54.9966 21.8163 53.277 19.7302 49.7793 19.7302C45.9343 19.7302 44.0083 22.1981 44.0083 27.0727V37.7082H36.0534V27.0727C36.0534 22.1981 34.124 19.7302 30.279 19.7302C26.8019 19.7302 25.0651 21.8163 25.0617 25.9069V45.3371H17.0656V25.3172C17.0656 21.2266 18.1191 17.9769 20.2262 15.568C22.3998 13.1648 25.2509 11.9308 28.7898 11.9308C32.8859 11.9308 35.9812 13.492 38.0447 16.6111L40.036 19.9245L42.0308 16.6111C44.0943 13.492 47.1896 11.9308 51.2788 11.9308C54.8143 11.9308 57.6654 13.1648 59.8459 15.568C61.9529 17.9746 63.0065 21.2243 63.0065 25.3172L62.9996 45.3371Z" fill="currentColor"/></symbol><use xlink:href="#logo-symbol-icon"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

2
app/javascript/images/logo-symbol-wordmark.svg

@ -7,5 +7,5 @@
<stop stop-color="#6364FF"/>
<stop offset="1" stop-color="#563ACC"/>
</linearGradient>
</defs></symbol><use xlink:href="#logo-symbol-wordmark" style="color:#fff"/>
</defs></symbol><use xlink:href="#logo-symbol-wordmark"/>
</svg>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

8
app/javascript/mastodon/actions/account_notes.js

@ -14,24 +14,24 @@ export function submitAccountNote(id, value) {
dispatch(submitAccountNoteSuccess(response.data));
}).catch(error => dispatch(submitAccountNoteFail(error)));
};
};
}
export function submitAccountNoteRequest() {
return {
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
};
};
}
export function submitAccountNoteSuccess(relationship) {
return {
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
relationship,
};
};
}
export function submitAccountNoteFail(error) {
return {
type: ACCOUNT_NOTE_SUBMIT_FAIL,
error,
};
};
}

144
app/javascript/mastodon/actions/accounts.js

@ -91,7 +91,7 @@ export function fetchAccount(id) {
dispatch(fetchAccountFail(id, error));
});
};
};
}
export const lookupAccount = acct => (dispatch, getState) => {
dispatch(lookupAccountRequest(acct));
@ -126,13 +126,13 @@ export function fetchAccountRequest(id) {
type: ACCOUNT_FETCH_REQUEST,
id,
};
};
}
export function fetchAccountSuccess() {
return {
type: ACCOUNT_FETCH_SUCCESS,
};
};
}
export function fetchAccountFail(id, error) {
return {
@ -141,7 +141,7 @@ export function fetchAccountFail(id, error) {
error,
skipAlert: true,
};
};
}
export function followAccount(id, options = { reblogs: true }) {
return (dispatch, getState) => {
@ -156,7 +156,7 @@ export function followAccount(id, options = { reblogs: true }) {
dispatch(followAccountFail(error, locked));
});
};
};
}
export function unfollowAccount(id) {
return (dispatch, getState) => {
@ -168,7 +168,7 @@ export function unfollowAccount(id) {
dispatch(unfollowAccountFail(error));
});
};
};
}
export function followAccountRequest(id, locked) {
return {
@ -177,7 +177,7 @@ export function followAccountRequest(id, locked) {
locked,
skipLoading: true,
};
};
}
export function followAccountSuccess(relationship, alreadyFollowing) {
return {
@ -186,7 +186,7 @@ export function followAccountSuccess(relationship, alreadyFollowing) {
alreadyFollowing,
skipLoading: true,
};
};
}
export function followAccountFail(error, locked) {
return {
@ -195,7 +195,7 @@ export function followAccountFail(error, locked) {
locked,
skipLoading: true,
};
};
}
export function unfollowAccountRequest(id) {
return {
@ -203,7 +203,7 @@ export function unfollowAccountRequest(id) {
id,
skipLoading: true,
};
};
}
export function unfollowAccountSuccess(relationship, statuses) {
return {
@ -212,7 +212,7 @@ export function unfollowAccountSuccess(relationship, statuses) {
statuses,
skipLoading: true,
};
};
}
export function unfollowAccountFail(error) {
return {
@ -220,7 +220,7 @@ export function unfollowAccountFail(error) {
error,
skipLoading: true,
};
};
}
export function blockAccount(id) {
return (dispatch, getState) => {
@ -233,7 +233,7 @@ export function blockAccount(id) {
dispatch(blockAccountFail(id, error));
});
};
};
}
export function unblockAccount(id) {
return (dispatch, getState) => {
@ -245,14 +245,14 @@ export function unblockAccount(id) {
dispatch(unblockAccountFail(id, error));
});
};
};
}
export function blockAccountRequest(id) {
return {
type: ACCOUNT_BLOCK_REQUEST,
id,
};
};
}
export function blockAccountSuccess(relationship, statuses) {
return {
@ -260,35 +260,35 @@ export function blockAccountSuccess(relationship, statuses) {
relationship,
statuses,
};
};
}
export function blockAccountFail(error) {
return {
type: ACCOUNT_BLOCK_FAIL,
error,
};
};
}
export function unblockAccountRequest(id) {
return {
type: ACCOUNT_UNBLOCK_REQUEST,
id,
};
};
}
export function unblockAccountSuccess(relationship) {
return {
type: ACCOUNT_UNBLOCK_SUCCESS,
relationship,
};
};
}
export function unblockAccountFail(error) {
return {
type: ACCOUNT_UNBLOCK_FAIL,
error,
};
};
}
export function muteAccount(id, notifications, duration=0) {
@ -302,7 +302,7 @@ export function muteAccount(id, notifications, duration=0) {
dispatch(muteAccountFail(id, error));
});
};
};
}
export function unmuteAccount(id) {
return (dispatch, getState) => {
@ -314,14 +314,14 @@ export function unmuteAccount(id) {
dispatch(unmuteAccountFail(id, error));
});
};
};
}
export function muteAccountRequest(id) {
return {
type: ACCOUNT_MUTE_REQUEST,
id,
};
};
}
export function muteAccountSuccess(relationship, statuses) {
return {
@ -329,35 +329,35 @@ export function muteAccountSuccess(relationship, statuses) {
relationship,
statuses,
};
};
}
export function muteAccountFail(error) {
return {
type: ACCOUNT_MUTE_FAIL,
error,
};
};
}
export function unmuteAccountRequest(id) {
return {
type: ACCOUNT_UNMUTE_REQUEST,
id,
};
};
}
export function unmuteAccountSuccess(relationship) {
return {
type: ACCOUNT_UNMUTE_SUCCESS,
relationship,
};
};
}
export function unmuteAccountFail(error) {
return {
type: ACCOUNT_UNMUTE_FAIL,
error,
};
};
}
export function fetchFollowers(id) {
@ -374,14 +374,14 @@ export function fetchFollowers(id) {
dispatch(fetchFollowersFail(id, error));
});
};
};
}
export function fetchFollowersRequest(id) {
return {
type: FOLLOWERS_FETCH_REQUEST,
id,
};
};
}
export function fetchFollowersSuccess(id, accounts, next) {
return {
@ -390,7 +390,7 @@ export function fetchFollowersSuccess(id, accounts, next) {
accounts,
next,
};
};
}
export function fetchFollowersFail(id, error) {
return {
@ -399,7 +399,7 @@ export function fetchFollowersFail(id, error) {
error,
skipNotFound: true,
};
};
}
export function expandFollowers(id) {
return (dispatch, getState) => {
@ -421,14 +421,14 @@ export function expandFollowers(id) {
dispatch(expandFollowersFail(id, error));
});
};
};
}
export function expandFollowersRequest(id) {
return {
type: FOLLOWERS_EXPAND_REQUEST,
id,
};
};
}
export function expandFollowersSuccess(id, accounts, next) {
return {
@ -437,7 +437,7 @@ export function expandFollowersSuccess(id, accounts, next) {
accounts,
next,
};
};
}
export function expandFollowersFail(id, error) {
return {
@ -445,7 +445,7 @@ export function expandFollowersFail(id, error) {
id,
error,
};
};
}
export function fetchFollowing(id) {
return (dispatch, getState) => {
@ -461,14 +461,14 @@ export function fetchFollowing(id) {
dispatch(fetchFollowingFail(id, error));
});
};
};
}
export function fetchFollowingRequest(id) {
return {
type: FOLLOWING_FETCH_REQUEST,
id,
};
};
}
export function fetchFollowingSuccess(id, accounts, next) {
return {
@ -477,7 +477,7 @@ export function fetchFollowingSuccess(id, accounts, next) {
accounts,
next,
};
};
}
export function fetchFollowingFail(id, error) {
return {
@ -486,7 +486,7 @@ export function fetchFollowingFail(id, error) {
error,
skipNotFound: true,
};
};
}
export function expandFollowing(id) {
return (dispatch, getState) => {
@ -508,14 +508,14 @@ export function expandFollowing(id) {
dispatch(expandFollowingFail(id, error));
});
};
};
}
export function expandFollowingRequest(id) {
return {
type: FOLLOWING_EXPAND_REQUEST,
id,
};
};
}
export function expandFollowingSuccess(id, accounts, next) {
return {
@ -524,7 +524,7 @@ export function expandFollowingSuccess(id, accounts, next) {
accounts,
next,
};
};
}
export function expandFollowingFail(id, error) {
return {
@ -532,7 +532,7 @@ export function expandFollowingFail(id, error) {
id,
error,
};
};
}
export function fetchRelationships(accountIds) {
return (dispatch, getState) => {
@ -553,7 +553,7 @@ export function fetchRelationships(accountIds) {
dispatch(fetchRelationshipsFail(error));
});
};
};
}
export function fetchRelationshipsRequest(ids) {
return {
@ -561,7 +561,7 @@ export function fetchRelationshipsRequest(ids) {
ids,
skipLoading: true,
};
};
}
export function fetchRelationshipsSuccess(relationships) {
return {
@ -569,7 +569,7 @@ export function fetchRelationshipsSuccess(relationships) {
relationships,
skipLoading: true,
};
};
}
export function fetchRelationshipsFail(error) {
return {
@ -578,7 +578,7 @@ export function fetchRelationshipsFail(error) {
skipLoading: true,
skipNotFound: true,
};
};
}
export function fetchFollowRequests() {
return (dispatch, getState) => {
@ -590,13 +590,13 @@ export function fetchFollowRequests() {
dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null));
}).catch(error => dispatch(fetchFollowRequestsFail(error)));
};
};
}
export function fetchFollowRequestsRequest() {
return {
type: FOLLOW_REQUESTS_FETCH_REQUEST,
};
};
}
export function fetchFollowRequestsSuccess(accounts, next) {
return {
@ -604,14 +604,14 @@ export function fetchFollowRequestsSuccess(accounts, next) {
accounts,
next,
};
};
}
export function fetchFollowRequestsFail(error) {
return {
type: FOLLOW_REQUESTS_FETCH_FAIL,
error,
};
};
}
export function expandFollowRequests() {
return (dispatch, getState) => {
@ -629,13 +629,13 @@ export function expandFollowRequests() {
dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null));
}).catch(error => dispatch(expandFollowRequestsFail(error)));
};
};
}
export function expandFollowRequestsRequest() {
return {
type: FOLLOW_REQUESTS_EXPAND_REQUEST,
};
};
}
export function expandFollowRequestsSuccess(accounts, next) {
return {
@ -643,14 +643,14 @@ export function expandFollowRequestsSuccess(accounts, next) {
accounts,
next,
};
};
}
export function expandFollowRequestsFail(error) {
return {
type: FOLLOW_REQUESTS_EXPAND_FAIL,
error,
};
};
}
export function authorizeFollowRequest(id) {
return (dispatch, getState) => {
@ -661,21 +661,21 @@ export function authorizeFollowRequest(id) {
.then(() => dispatch(authorizeFollowRequestSuccess(id)))
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
};
};
}
export function authorizeFollowRequestRequest(id) {
return {
type: FOLLOW_REQUEST_AUTHORIZE_REQUEST,
id,
};
};
}
export function authorizeFollowRequestSuccess(id) {
return {
type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
id,
};
};
}
export function authorizeFollowRequestFail(id, error) {
return {
@ -683,7 +683,7 @@ export function authorizeFollowRequestFail(id, error) {
id,
error,
};
};
}
export function rejectFollowRequest(id) {
@ -695,21 +695,21 @@ export function rejectFollowRequest(id) {
.then(() => dispatch(rejectFollowRequestSuccess(id)))
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
};
};
}
export function rejectFollowRequestRequest(id) {
return {
type: FOLLOW_REQUEST_REJECT_REQUEST,
id,
};
};
}
export function rejectFollowRequestSuccess(id) {
return {
type: FOLLOW_REQUEST_REJECT_SUCCESS,
id,
};
};
}
export function rejectFollowRequestFail(id, error) {
return {
@ -717,7 +717,7 @@ export function rejectFollowRequestFail(id, error) {
id,
error,
};
};
}
export function pinAccount(id) {
return (dispatch, getState) => {
@ -729,7 +729,7 @@ export function pinAccount(id) {
dispatch(pinAccountFail(error));
});
};
};
}
export function unpinAccount(id) {
return (dispatch, getState) => {
@ -741,49 +741,49 @@ export function unpinAccount(id) {
dispatch(unpinAccountFail(error));
});
};
};
}
export function pinAccountRequest(id) {
return {
type: ACCOUNT_PIN_REQUEST,
id,
};
};
}
export function pinAccountSuccess(relationship) {
return {
type: ACCOUNT_PIN_SUCCESS,
relationship,
};
};
}
export function pinAccountFail(error) {
return {
type: ACCOUNT_PIN_FAIL,
error,
};
};
}
export function unpinAccountRequest(id) {
return {
type: ACCOUNT_UNPIN_REQUEST,
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,

6
app/javascript/mastodon/actions/alerts.js

@ -17,13 +17,13 @@ export function dismissAlert(alert) {
type: ALERT_DISMISS,
alert,
};
};
}
export function clearAlert() {
return {
type: ALERT_CLEAR,
};
};
}
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, message_values = undefined) {
return {
@ -32,7 +32,7 @@ export function showAlert(title = messages.unexpectedTitle, message = messages.u
message,
message_values,
};
};
}
export function showAlertForError(error, skipNotFound = false) {
if (error.response) {

4
app/javascript/mastodon/actions/announcements.js

@ -102,7 +102,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name, alreadyAdded));
}
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(addReactionSuccess(announcementId, name, alreadyAdded));
}).catch(err => {
if (!alreadyAdded) {
@ -136,7 +136,7 @@ export const addReactionFail = (announcementId, name, error) => ({
export const removeReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(removeReactionRequest(announcementId, name));
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(removeReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(removeReactionFail(announcementId, name, err));

16
app/javascript/mastodon/actions/blocks.js

@ -24,13 +24,13 @@ export function fetchBlocks() {
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(fetchBlocksFail(error)));
};
};
}
export function fetchBlocksRequest() {
return {
type: BLOCKS_FETCH_REQUEST,
};
};
}
export function fetchBlocksSuccess(accounts, next) {
return {
@ -38,14 +38,14 @@ export function fetchBlocksSuccess(accounts, next) {
accounts,
next,
};
};
}
export function fetchBlocksFail(error) {
return {
type: BLOCKS_FETCH_FAIL,
error,
};
};
}
export function expandBlocks() {
return (dispatch, getState) => {
@ -64,13 +64,13 @@ export function expandBlocks() {
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(expandBlocksFail(error)));
};
};
}
export function expandBlocksRequest() {
return {
type: BLOCKS_EXPAND_REQUEST,
};
};
}
export function expandBlocksSuccess(accounts, next) {
return {
@ -78,14 +78,14 @@ export function expandBlocksSuccess(accounts, next) {
accounts,
next,
};
};
}
export function expandBlocksFail(error) {
return {
type: BLOCKS_EXPAND_FAIL,
error,
};
};
}
export function initBlockModal(account) {
return dispatch => {

16
app/javascript/mastodon/actions/bookmarks.js

@ -25,13 +25,13 @@ export function fetchBookmarkedStatuses() {
dispatch(fetchBookmarkedStatusesFail(error));
});
};
};
}
export function fetchBookmarkedStatusesRequest() {
return {
type: BOOKMARKED_STATUSES_FETCH_REQUEST,
};
};
}
export function fetchBookmarkedStatusesSuccess(statuses, next) {
return {
@ -39,14 +39,14 @@ export function fetchBookmarkedStatusesSuccess(statuses, next) {
statuses,
next,
};
};
}
export function fetchBookmarkedStatusesFail(error) {
return {
type: BOOKMARKED_STATUSES_FETCH_FAIL,
error,
};
};
}
export function expandBookmarkedStatuses() {
return (dispatch, getState) => {
@ -66,13 +66,13 @@ export function expandBookmarkedStatuses() {
dispatch(expandBookmarkedStatusesFail(error));
});
};
};
}
export function expandBookmarkedStatusesRequest() {
return {
type: BOOKMARKED_STATUSES_EXPAND_REQUEST,
};
};
}
export function expandBookmarkedStatusesSuccess(statuses, next) {
return {
@ -80,11 +80,11 @@ export function expandBookmarkedStatusesSuccess(statuses, next) {
statuses,
next,
};
};
}
export function expandBookmarkedStatusesFail(error) {
return {
type: BOOKMARKED_STATUSES_EXPAND_FAIL,
error,
};
};
}

6
app/javascript/mastodon/actions/columns.js

@ -15,7 +15,7 @@ export function addColumn(id, params) {
dispatch(saveSettings());
};
};
}
export function removeColumn(uuid) {
return dispatch => {
@ -26,7 +26,7 @@ export function removeColumn(uuid) {
dispatch(saveSettings());
};
};
}
export function moveColumn(uuid, direction) {
return dispatch => {
@ -38,7 +38,7 @@ export function moveColumn(uuid, direction) {
dispatch(saveSettings());
};
};
}
export function changeColumnParams(uuid, path, value) {
return dispatch => {

134
app/javascript/mastodon/actions/compose.js

@ -95,14 +95,14 @@ export function setComposeToStatus(status, text, spoiler_text) {
text,
spoiler_text,
};
};
}
export function changeCompose(text) {
return {
type: COMPOSE_CHANGE,
text: text,
};
};
}
export function replyCompose(status, routerHistory) {
return (dispatch, getState) => {
@ -113,19 +113,19 @@ export function replyCompose(status, routerHistory) {
ensureComposeIsVisible(getState, routerHistory);
};
};
}
export function cancelReplyCompose() {
return {
type: COMPOSE_REPLY_CANCEL,
};
};
}
export function resetCompose() {
return {
type: COMPOSE_RESET,
};
};
}
export function mentionCompose(account, routerHistory) {
return (dispatch, getState) => {
@ -136,7 +136,7 @@ export function mentionCompose(account, routerHistory) {
ensureComposeIsVisible(getState, routerHistory);
};
};
}
export function directCompose(account, routerHistory) {
return (dispatch, getState) => {
@ -147,7 +147,7 @@ export function directCompose(account, routerHistory) {
ensureComposeIsVisible(getState, routerHistory);
};
};
}
export function submitCompose(routerHistory) {
return function (dispatch, getState) {
@ -161,6 +161,18 @@ export function submitCompose(routerHistory) {
dispatch(submitComposeRequest());
// If we're editing a post with media attachments, those have not
// necessarily been changed on the server. Do it now in the same
// API call.
let media_attributes;
if (statusId !== null) {
media_attributes = media.map(item => ({
id: item.get('id'),
description: item.get('description'),
focus: item.get('focus'),
}));
}
api(getState).request({
url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`,
method: statusId === null ? 'post' : 'put',
@ -168,6 +180,7 @@ export function submitCompose(routerHistory) {
status,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: media.map(item => item.get('id')),
media_attributes,
sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '',
visibility: getState().getIn(['compose', 'privacy']),
@ -213,27 +226,27 @@ export function submitCompose(routerHistory) {
dispatch(submitComposeFail(error));
});
};
};
}
export function submitComposeRequest() {
return {
type: COMPOSE_SUBMIT_REQUEST,
};
};
}
export function submitComposeSuccess(status) {
return {
type: COMPOSE_SUBMIT_SUCCESS,
status: status,
};
};
}
export function submitComposeFail(error) {
return {
type: COMPOSE_SUBMIT_FAIL,
error: error,
};
};
}
export function uploadCompose(files) {
return function (dispatch, getState) {
@ -296,9 +309,9 @@ export function uploadCompose(files) {
}
});
}).catch(error => dispatch(uploadComposeFail(error)));
};
}
};
};
}
export const uploadComposeProcessing = () => ({
type: COMPOSE_UPLOAD_PROCESSING,
@ -356,14 +369,14 @@ export function initMediaEditModal(id) {
dispatch(openModal('FOCAL_POINT', { id }));
};
};
}
export function onChangeMediaDescription(description) {
return {
type: COMPOSE_CHANGE_MEDIA_DESCRIPTION,
description,
};
};
}
export function onChangeMediaFocus(focusX, focusY) {
return {
@ -371,34 +384,55 @@ export function onChangeMediaFocus(focusX, focusY) {
focusX,
focusY,
};
};
}
export function changeUploadCompose(id, params) {
return (dispatch, getState) => {
dispatch(changeUploadComposeRequest());
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
});
let media = getState().getIn(['compose', 'media_attachments']).find((item) => item.get('id') === id);
// Editing already-attached media is deferred to editing the post itself.
// For simplicity's sake, fake an API reply.
if (media && !media.get('unattached')) {
let { description, focus } = params;
const data = media.toJS();
if (description) {
data.description = description;
}
if (focus) {
focus = focus.split(',');
data.meta = { focus: { x: parseFloat(focus[0]), y: parseFloat(focus[1]) } };
}
dispatch(changeUploadComposeSuccess(data, true));
} else {
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data, false));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
});
}
};
};
}
export function changeUploadComposeRequest() {
return {
type: COMPOSE_UPLOAD_CHANGE_REQUEST,
skipLoading: true,
};
};
}
export function changeUploadComposeSuccess(media) {
export function changeUploadComposeSuccess(media, attached) {
return {
type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
media: media,
attached: attached,
skipLoading: true,
};
};
}
export function changeUploadComposeFail(error) {
return {
@ -406,14 +440,14 @@ export function changeUploadComposeFail(error) {
error: error,
skipLoading: true,
};
};
}
export function uploadComposeRequest() {
return {
type: COMPOSE_UPLOAD_REQUEST,
skipLoading: true,
};
};
}
export function uploadComposeProgress(loaded, total) {
return {
@ -421,7 +455,7 @@ export function uploadComposeProgress(loaded, total) {
loaded: loaded,
total: total,
};
};
}
export function uploadComposeSuccess(media, file) {
return {
@ -430,7 +464,7 @@ export function uploadComposeSuccess(media, file) {
file: file,
skipLoading: true,
};
};
}
export function uploadComposeFail(error) {
return {
@ -438,14 +472,14 @@ export function uploadComposeFail(error) {
error: error,
skipLoading: true,
};
};
}
export function undoUploadCompose(media_id) {
return {
type: COMPOSE_UPLOAD_UNDO,
media_id: media_id,
};
};
}
export function clearComposeSuggestions() {
if (fetchComposeSuggestionsAccountsController) {
@ -454,7 +488,7 @@ export function clearComposeSuggestions() {
return {
type: COMPOSE_SUGGESTIONS_CLEAR,
};
};
}
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
if (fetchComposeSuggestionsAccountsController) {
@ -532,7 +566,7 @@ export function fetchComposeSuggestions(token) {
break;
}
};
};
}
export function readyComposeSuggestionsEmojis(token, emojis) {
return {
@ -540,7 +574,7 @@ export function readyComposeSuggestionsEmojis(token, emojis) {
token,
emojis,
};
};
}
export function readyComposeSuggestionsAccounts(token, accounts) {
return {
@ -548,7 +582,7 @@ export function readyComposeSuggestionsAccounts(token, accounts) {
token,
accounts,
};
};
}
export const readyComposeSuggestionsTags = (token, tags) => ({
type: COMPOSE_SUGGESTIONS_READY,
@ -593,7 +627,7 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
});
}
};
};
}
export function updateSuggestionTags(token) {
return {
@ -654,19 +688,19 @@ export function mountCompose() {
return {
type: COMPOSE_MOUNT,
};
};
}
export function unmountCompose() {
return {
type: COMPOSE_UNMOUNT,
};
};
}
export function changeComposeSensitivity() {
return {
type: COMPOSE_SENSITIVITY_CHANGE,
};
};
}
export const changeComposeLanguage = language => ({
type: COMPOSE_LANGUAGE_CHANGE,
@ -677,21 +711,21 @@ export function changeComposeSpoilerness() {
return {
type: COMPOSE_SPOILERNESS_CHANGE,
};
};
}
export function changeComposeSpoilerText(text) {
return {
type: COMPOSE_SPOILER_TEXT_CHANGE,
text,
};
};
}
export function changeComposeVisibility(value) {
return {
type: COMPOSE_VISIBILITY_CHANGE,
value,
};
};
}
export function changeComposeFederation(value) {
return {
@ -707,33 +741,33 @@ export function insertEmojiCompose(position, emoji, needsSpace) {
emoji,
needsSpace,
};
};
}
export function changeComposing(value) {
return {
type: COMPOSE_COMPOSING_CHANGE,
value,
};
};
}
export function addPoll() {
return {
type: COMPOSE_POLL_ADD,
};
};
}
export function removePoll() {
return {
type: COMPOSE_POLL_REMOVE,
};
};
}
export function addPollOption(title) {
return {
type: COMPOSE_POLL_OPTION_ADD,
title,
};
};
}
export function changePollOption(index, title) {
return {
@ -741,14 +775,14 @@ export function changePollOption(index, title) {
index,
title,
};
};
}
export function removePollOption(index) {
return {
type: COMPOSE_POLL_OPTION_REMOVE,
index,
};
};
}
export function changePollSettings(expiresIn, isMultiple) {
return {
@ -756,4 +790,4 @@ export function changePollSettings(expiresIn, isMultiple) {
expiresIn,
isMultiple,
};
};
}

8
app/javascript/mastodon/actions/custom_emojis.js

@ -14,14 +14,14 @@ export function fetchCustomEmojis() {
dispatch(fetchCustomEmojisFail(error));
});
};
};
}
export function fetchCustomEmojisRequest() {
return {
type: CUSTOM_EMOJIS_FETCH_REQUEST,
skipLoading: true,
};
};
}
export function fetchCustomEmojisSuccess(custom_emojis) {
return {
@ -29,7 +29,7 @@ export function fetchCustomEmojisSuccess(custom_emojis) {
custom_emojis,
skipLoading: true,
};
};
}
export function fetchCustomEmojisFail(error) {
return {
@ -37,4 +37,4 @@ export function fetchCustomEmojisFail(error) {
error,
skipLoading: true,
};
};
}

32
app/javascript/mastodon/actions/domain_blocks.js

@ -29,14 +29,14 @@ export function blockDomain(domain) {
dispatch(blockDomainFail(domain, err));
});
};
};
}
export function blockDomainRequest(domain) {
return {
type: DOMAIN_BLOCK_REQUEST,
domain,
};
};
}
export function blockDomainSuccess(domain, accounts) {
return {
@ -44,7 +44,7 @@ export function blockDomainSuccess(domain, accounts) {
domain,
accounts,
};
};
}
export function blockDomainFail(domain, error) {
return {
@ -52,7 +52,7 @@ export function blockDomainFail(domain, error) {
domain,
error,
};
};
}
export function unblockDomain(domain) {
return (dispatch, getState) => {
@ -66,14 +66,14 @@ export function unblockDomain(domain) {
dispatch(unblockDomainFail(domain, err));
});
};
};
}
export function unblockDomainRequest(domain) {
return {
type: DOMAIN_UNBLOCK_REQUEST,
domain,
};
};
}
export function unblockDomainSuccess(domain, accounts) {
return {
@ -81,7 +81,7 @@ export function unblockDomainSuccess(domain, accounts) {
domain,
accounts,
};
};
}
export function unblockDomainFail(domain, error) {
return {
@ -89,7 +89,7 @@ export function unblockDomainFail(domain, error) {
domain,
error,
};
};
}
export function fetchDomainBlocks() {
return (dispatch, getState) => {
@ -102,13 +102,13 @@ export function fetchDomainBlocks() {
dispatch(fetchDomainBlocksFail(err));
});
};
};
}
export function fetchDomainBlocksRequest() {
return {
type: DOMAIN_BLOCKS_FETCH_REQUEST,
};
};
}
export function fetchDomainBlocksSuccess(domains, next) {
return {
@ -116,14 +116,14 @@ export function fetchDomainBlocksSuccess(domains, next) {
domains,
next,
};
};
}
export function fetchDomainBlocksFail(error) {
return {
type: DOMAIN_BLOCKS_FETCH_FAIL,
error,
};
};
}
export function expandDomainBlocks() {
return (dispatch, getState) => {
@ -142,13 +142,13 @@ export function expandDomainBlocks() {
dispatch(expandDomainBlocksFail(err));
});
};
};
}
export function expandDomainBlocksRequest() {
return {
type: DOMAIN_BLOCKS_EXPAND_REQUEST,
};
};
}
export function expandDomainBlocksSuccess(domains, next) {
return {
@ -156,11 +156,11 @@ export function expandDomainBlocksSuccess(domains, next) {
domains,
next,
};
};
}
export function expandDomainBlocksFail(error) {
return {
type: DOMAIN_BLOCKS_EXPAND_FAIL,
error,
};
};
}

4
app/javascript/mastodon/actions/dropdown_menu.js

@ -1,8 +1,8 @@
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
export function openDropdownMenu(id, placement, keyboard, scroll_key) {
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard, scroll_key };
export function openDropdownMenu(id, keyboard, scroll_key) {
return { type: DROPDOWN_MENU_OPEN, id, keyboard, scroll_key };
}
export function closeDropdownMenu(id) {

2
app/javascript/mastodon/actions/emojis.js

@ -11,4 +11,4 @@ export function useEmoji(emoji) {
dispatch(saveSettings());
};
};
}

16
app/javascript/mastodon/actions/favourites.js

@ -25,14 +25,14 @@ export function fetchFavouritedStatuses() {
dispatch(fetchFavouritedStatusesFail(error));
});
};
};
}
export function fetchFavouritedStatusesRequest() {
return {
type: FAVOURITED_STATUSES_FETCH_REQUEST,
skipLoading: true,
};
};
}
export function fetchFavouritedStatusesSuccess(statuses, next) {
return {
@ -41,7 +41,7 @@ export function fetchFavouritedStatusesSuccess(statuses, next) {
next,
skipLoading: true,
};
};
}
export function fetchFavouritedStatusesFail(error) {
return {
@ -49,7 +49,7 @@ export function fetchFavouritedStatusesFail(error) {
error,
skipLoading: true,
};
};
}
export function expandFavouritedStatuses() {
return (dispatch, getState) => {
@ -69,13 +69,13 @@ export function expandFavouritedStatuses() {
dispatch(expandFavouritedStatusesFail(error));
});
};
};
}
export function expandFavouritedStatusesRequest() {
return {
type: FAVOURITED_STATUSES_EXPAND_REQUEST,
};
};
}
export function expandFavouritedStatusesSuccess(statuses, next) {
return {
@ -83,11 +83,11 @@ export function expandFavouritedStatusesSuccess(statuses, next) {
statuses,
next,
};
};
}
export function expandFavouritedStatusesFail(error) {
return {
type: FAVOURITED_STATUSES_EXPAND_FAIL,
error,
};
};
}

4
app/javascript/mastodon/actions/height_cache.js

@ -8,10 +8,10 @@ export function setHeight (key, id, height) {
id,
height,
};
};
}
export function clearHeight () {
return {
type: HEIGHT_CACHE_CLEAR,
};
};
}

80
app/javascript/mastodon/actions/interactions.js

@ -54,7 +54,7 @@ export function reblog(status, visibility) {
dispatch(reblogFail(status, error));
});
};
};
}
export function unreblog(status) {
return (dispatch, getState) => {
@ -67,7 +67,7 @@ export function unreblog(status) {
dispatch(unreblogFail(status, error));
});
};
};
}
export function reblogRequest(status) {
return {
@ -75,7 +75,7 @@ export function reblogRequest(status) {
status: status,
skipLoading: true,
};
};
}
export function reblogSuccess(status) {
return {
@ -83,7 +83,7 @@ export function reblogSuccess(status) {
status: status,
skipLoading: true,
};
};
}
export function reblogFail(status, error) {
return {
@ -92,7 +92,7 @@ export function reblogFail(status, error) {
error: error,
skipLoading: true,
};
};
}
export function unreblogRequest(status) {
return {
@ -100,7 +100,7 @@ export function unreblogRequest(status) {
status: status,
skipLoading: true,
};
};
}
export function unreblogSuccess(status) {
return {
@ -108,7 +108,7 @@ export function unreblogSuccess(status) {
status: status,
skipLoading: true,
};
};
}
export function unreblogFail(status, error) {
return {
@ -117,7 +117,7 @@ export function unreblogFail(status, error) {
error: error,
skipLoading: true,
};
};
}
export function favourite(status) {
return function (dispatch, getState) {
@ -130,7 +130,7 @@ export function favourite(status) {
dispatch(favouriteFail(status, error));
});
};
};
}
export function unfavourite(status) {
return (dispatch, getState) => {
@ -143,7 +143,7 @@ export function unfavourite(status) {
dispatch(unfavouriteFail(status, error));
});
};
};
}
export function favouriteRequest(status) {
return {
@ -151,7 +151,7 @@ export function favouriteRequest(status) {
status: status,
skipLoading: true,
};
};
}
export function favouriteSuccess(status) {
return {
@ -159,7 +159,7 @@ export function favouriteSuccess(status) {
status: status,
skipLoading: true,
};
};
}
export function favouriteFail(status, error) {
return {
@ -168,7 +168,7 @@ export function favouriteFail(status, error) {
error: error,
skipLoading: true,
};
};
}
export function unfavouriteRequest(status) {
return {
@ -176,7 +176,7 @@ export function unfavouriteRequest(status) {
status: status,
skipLoading: true,
};
};
}
export function unfavouriteSuccess(status) {
return {
@ -184,7 +184,7 @@ export function unfavouriteSuccess(status) {
status: status,
skipLoading: true,
};
};
}
export function unfavouriteFail(status, error) {
return {
@ -193,7 +193,7 @@ export function unfavouriteFail(status, error) {
error: error,
skipLoading: true,
};
};
}
export function bookmark(status) {
return function (dispatch, getState) {
@ -206,7 +206,7 @@ export function bookmark(status) {
dispatch(bookmarkFail(status, error));
});
};
};
}
export function unbookmark(status) {
return (dispatch, getState) => {
@ -219,14 +219,14 @@ export function unbookmark(status) {
dispatch(unbookmarkFail(status, error));
});
};
};
}
export function bookmarkRequest(status) {
return {
type: BOOKMARK_REQUEST,
status: status,
};
};
}
export function bookmarkSuccess(status, response) {
return {
@ -234,7 +234,7 @@ export function bookmarkSuccess(status, response) {
status: status,
response: response,
};
};
}
export function bookmarkFail(status, error) {
return {
@ -242,14 +242,14 @@ export function bookmarkFail(status, error) {
status: status,
error: error,
};
};
}
export function unbookmarkRequest(status) {
return {
type: UNBOOKMARK_REQUEST,
status: status,
};
};
}
export function unbookmarkSuccess(status, response) {
return {
@ -257,7 +257,7 @@ export function unbookmarkSuccess(status, response) {
status: status,
response: response,
};
};
}
export function unbookmarkFail(status, error) {
return {
@ -265,7 +265,7 @@ export function unbookmarkFail(status, error) {
status: status,
error: error,
};
};
}
export function fetchReblogs(id) {
return (dispatch, getState) => {
@ -278,14 +278,14 @@ export function fetchReblogs(id) {
dispatch(fetchReblogsFail(id, error));
});
};
};
}
export function fetchReblogsRequest(id) {
return {
type: REBLOGS_FETCH_REQUEST,
id,
};
};
}
export function fetchReblogsSuccess(id, accounts) {
return {
@ -293,14 +293,14 @@ export function fetchReblogsSuccess(id, accounts) {
id,
accounts,
};
};
}
export function fetchReblogsFail(id, error) {
return {
type: REBLOGS_FETCH_FAIL,
error,
};
};
}
export function fetchFavourites(id) {
return (dispatch, getState) => {
@ -313,14 +313,14 @@ export function fetchFavourites(id) {
dispatch(fetchFavouritesFail(id, error));
});
};
};
}
export function fetchFavouritesRequest(id) {
return {
type: FAVOURITES_FETCH_REQUEST,
id,
};
};
}
export function fetchFavouritesSuccess(id, accounts) {
return {
@ -328,14 +328,14 @@ export function fetchFavouritesSuccess(id, accounts) {
id,
accounts,
};
};
}
export function fetchFavouritesFail(id, error) {
return {
type: FAVOURITES_FETCH_FAIL,
error,
};
};
}
export function pin(status) {
return (dispatch, getState) => {
@ -348,7 +348,7 @@ export function pin(status) {
dispatch(pinFail(status, error));
});
};
};
}
export function pinRequest(status) {
return {
@ -356,7 +356,7 @@ export function pinRequest(status) {
status,
skipLoading: true,
};
};
}
export function pinSuccess(status) {
return {
@ -364,7 +364,7 @@ export function pinSuccess(status) {
status,
skipLoading: true,
};
};
}
export function pinFail(status, error) {
return {
@ -373,7 +373,7 @@ export function pinFail(status, error) {
error,
skipLoading: true,
};
};
}
export function unpin (status) {
return (dispatch, getState) => {
@ -386,7 +386,7 @@ export function unpin (status) {
dispatch(unpinFail(status, error));
});
};
};
}
export function unpinRequest(status) {
return {
@ -394,7 +394,7 @@ export function unpinRequest(status) {
status,
skipLoading: true,
};
};
}
export function unpinSuccess(status) {
return {
@ -402,7 +402,7 @@ export function unpinSuccess(status) {
status,
skipLoading: true,
};
};
}
export function unpinFail(status, error) {
return {
@ -411,4 +411,4 @@ export function unpinFail(status, error) {
error,
skipLoading: true,
};
};
}

10
app/javascript/mastodon/actions/markers.js

@ -101,7 +101,7 @@ export function submitMarkersSuccess({ home, notifications }) {
home: (home || {}).last_read_id,
notifications: (notifications || {}).last_read_id,
};
};
}
export function submitMarkers(params = {}) {
const result = (dispatch, getState) => debouncedSubmitMarkers(dispatch, getState);
@ -111,7 +111,7 @@ export function submitMarkers(params = {}) {
}
return result;
};
}
export const fetchMarkers = () => (dispatch, getState) => {
const params = { timeline: ['notifications'] };
@ -130,7 +130,7 @@ export function fetchMarkersRequest() {
type: MARKERS_FETCH_REQUEST,
skipLoading: true,
};
};
}
export function fetchMarkersSuccess(markers) {
return {
@ -138,7 +138,7 @@ export function fetchMarkersSuccess(markers) {
markers,
skipLoading: true,
};
};
}
export function fetchMarkersFail(error) {
return {
@ -147,4 +147,4 @@ export function fetchMarkersFail(error) {
skipLoading: true,
skipAlert: true,
};
};
}

4
app/javascript/mastodon/actions/modal.js

@ -7,7 +7,7 @@ export function openModal(type, props) {
modalType: type,
modalProps: props,
};
};
}
export function closeModal(type, options = { ignoreFocus: false }) {
return {
@ -15,4 +15,4 @@ export function closeModal(type, options = { ignoreFocus: false }) {
modalType: type,
ignoreFocus: options.ignoreFocus,
};
};
}

16
app/javascript/mastodon/actions/mutes.js

@ -26,13 +26,13 @@ export function fetchMutes() {
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(fetchMutesFail(error)));
};
};
}
export function fetchMutesRequest() {
return {
type: MUTES_FETCH_REQUEST,
};
};
}
export function fetchMutesSuccess(accounts, next) {
return {
@ -40,14 +40,14 @@ export function fetchMutesSuccess(accounts, next) {
accounts,
next,
};
};
}
export function fetchMutesFail(error) {
return {
type: MUTES_FETCH_FAIL,
error,
};
};
}
export function expandMutes() {
return (dispatch, getState) => {
@ -66,13 +66,13 @@ export function expandMutes() {
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(expandMutesFail(error)));
};
};
}
export function expandMutesRequest() {
return {
type: MUTES_EXPAND_REQUEST,
};
};
}
export function expandMutesSuccess(accounts, next) {
return {
@ -80,14 +80,14 @@ export function expandMutesSuccess(accounts, next) {
accounts,
next,
};
};
}
export function expandMutesFail(error) {
return {
type: MUTES_EXPAND_FAIL,
error,
};
};
}
export function initMuteModal(account) {
return dispatch => {

18
app/javascript/mastodon/actions/notifications.js

@ -118,7 +118,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
});
}
};
};
}
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
@ -197,14 +197,14 @@ export function expandNotifications({ maxId, forceLoad } = {}, done = noOp) {
done();
});
};
};
}
export function expandNotificationsRequest(isLoadingMore) {
return {
type: NOTIFICATIONS_EXPAND_REQUEST,
skipLoading: !isLoadingMore,
};
};
}
export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) {
return {
@ -215,7 +215,7 @@ export function expandNotificationsSuccess(notifications, next, isLoadingMore, i
usePendingItems,
skipLoading: !isLoadingMore,
};
};
}
export function expandNotificationsFail(error, isLoadingMore) {
return {
@ -224,7 +224,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
skipLoading: !isLoadingMore,
skipAlert: !isLoadingMore || error.name === 'AbortError',
};
};
}
export function clearNotifications() {
return (dispatch, getState) => {
@ -234,14 +234,14 @@ export function clearNotifications() {
api(getState).post('/api/v1/notifications/clear');
};
};
}
export function scrollTopNotifications(top) {
return {
type: NOTIFICATIONS_SCROLL_TOP,
top,
};
};
}
export function setFilter (filterType) {
return dispatch => {
@ -253,7 +253,7 @@ export function setFilter (filterType) {
dispatch(expandNotifications({ forceLoad: true }));
dispatch(saveSettings());
};
};
}
export const mountNotifications = () => ({
type: NOTIFICATIONS_MOUNT,
@ -291,7 +291,7 @@ export function requestBrowserPermission(callback = noOp) {
callback(permission);
});
};
};
}
export function setBrowserSupport (value) {
return {

8
app/javascript/mastodon/actions/pin_statuses.js

@ -18,13 +18,13 @@ export function fetchPinnedStatuses() {
dispatch(fetchPinnedStatusesFail(error));
});
};
};
}
export function fetchPinnedStatusesRequest() {
return {
type: PINNED_STATUSES_FETCH_REQUEST,
};
};
}
export function fetchPinnedStatusesSuccess(statuses, next) {
return {
@ -32,11 +32,11 @@ export function fetchPinnedStatusesSuccess(statuses, next) {
statuses,
next,
};
};
}
export function fetchPinnedStatusesFail(error) {
return {
type: PINNED_STATUSES_FETCH_FAIL,
error,
};
};
}

12
app/javascript/mastodon/actions/search.js

@ -19,13 +19,13 @@ export function changeSearch(value) {
type: SEARCH_CHANGE,
value,
};
};
}
export function clearSearch() {
return {
type: SEARCH_CLEAR,
};
};
}
export function submitSearch() {
return (dispatch, getState) => {
@ -60,13 +60,13 @@ export function submitSearch() {
dispatch(fetchSearchFail(error));
});
};
};
}
export function fetchSearchRequest() {
return {
type: SEARCH_FETCH_REQUEST,
};
};
}
export function fetchSearchSuccess(results, searchTerm) {
return {
@ -74,14 +74,14 @@ export function fetchSearchSuccess(results, searchTerm) {
results,
searchTerm,
};
};
}
export function fetchSearchFail(error) {
return {
type: SEARCH_FETCH_FAIL,
error,
};
};
}
export const expandSearch = type => (dispatch, getState) => {
const value = getState().getIn(['search', 'value']);

4
app/javascript/mastodon/actions/settings.js

@ -15,7 +15,7 @@ export function changeSetting(path, value) {
dispatch(saveSettings());
};
};
}
const debouncedSave = debounce((dispatch, getState) => {
if (getState().getIn(['settings', 'saved'])) {
@ -31,4 +31,4 @@ const debouncedSave = debounce((dispatch, getState) => {
export function saveSettings() {
return (dispatch, getState) => debouncedSave(dispatch, getState);
};
}

48
app/javascript/mastodon/actions/statuses.js

@ -45,7 +45,7 @@ export function fetchStatusRequest(id, skipLoading) {
id,
skipLoading,
};
};
}
export function fetchStatus(id, forceFetch = false) {
return (dispatch, getState) => {
@ -66,14 +66,14 @@ export function fetchStatus(id, forceFetch = false) {
dispatch(fetchStatusFail(id, error, skipLoading));
});
};
};
}
export function fetchStatusSuccess(skipLoading) {
return {
type: STATUS_FETCH_SUCCESS,
skipLoading,
};
};
}
export function fetchStatusFail(id, error, skipLoading) {
return {
@ -83,7 +83,7 @@ export function fetchStatusFail(id, error, skipLoading) {
skipLoading,
skipAlert: true,
};
};
}
export function redraft(status, raw_text) {
return {
@ -91,7 +91,7 @@ export function redraft(status, raw_text) {
status,
raw_text,
};
};
}
export const editStatus = (id, routerHistory) => (dispatch, getState) => {
let status = getState().getIn(['statuses', id]);
@ -147,21 +147,21 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
dispatch(deleteStatusFail(id, error));
});
};
};
}
export function deleteStatusRequest(id) {
return {
type: STATUS_DELETE_REQUEST,
id: id,
};
};
}
export function deleteStatusSuccess(id) {
return {
type: STATUS_DELETE_SUCCESS,
id: id,
};
};
}
export function deleteStatusFail(id, error) {
return {
@ -169,7 +169,7 @@ export function deleteStatusFail(id, error) {
id: id,
error: error,
};
};
}
export const updateStatus = status => dispatch =>
dispatch(importFetchedStatus(status));
@ -190,14 +190,14 @@ export function fetchContext(id) {
dispatch(fetchContextFail(id, error));
});
};
};
}
export function fetchContextRequest(id) {
return {
type: CONTEXT_FETCH_REQUEST,
id,
};
};
}
export function fetchContextSuccess(id, ancestors, descendants) {
return {
@ -207,7 +207,7 @@ export function fetchContextSuccess(id, ancestors, descendants) {
descendants,
statuses: ancestors.concat(descendants),
};
};
}
export function fetchContextFail(id, error) {
return {
@ -216,7 +216,7 @@ export function fetchContextFail(id, error) {
error,
skipAlert: true,
};
};
}
export function muteStatus(id) {
return (dispatch, getState) => {
@ -228,21 +228,21 @@ export function muteStatus(id) {
dispatch(muteStatusFail(id, error));
});
};
};
}
export function muteStatusRequest(id) {
return {
type: STATUS_MUTE_REQUEST,
id,
};
};
}
export function muteStatusSuccess(id) {
return {
type: STATUS_MUTE_SUCCESS,
id,
};
};
}
export function muteStatusFail(id, error) {
return {
@ -250,7 +250,7 @@ export function muteStatusFail(id, error) {
id,
error,
};
};
}
export function unmuteStatus(id) {
return (dispatch, getState) => {
@ -262,21 +262,21 @@ export function unmuteStatus(id) {
dispatch(unmuteStatusFail(id, error));
});
};
};
}
export function unmuteStatusRequest(id) {
return {
type: STATUS_UNMUTE_REQUEST,
id,
};
};
}
export function unmuteStatusSuccess(id) {
return {
type: STATUS_UNMUTE_SUCCESS,
id,
};
};
}
export function unmuteStatusFail(id, error) {
return {
@ -284,7 +284,7 @@ export function unmuteStatusFail(id, error) {
id,
error,
};
};
}
export function hideStatus(ids) {
if (!Array.isArray(ids)) {
@ -295,7 +295,7 @@ export function hideStatus(ids) {
type: STATUS_HIDE,
ids,
};
};
}
export function revealStatus(ids) {
if (!Array.isArray(ids)) {
@ -306,7 +306,7 @@ export function revealStatus(ids) {
type: STATUS_REVEAL,
ids,
};
};
}
export function toggleStatusCollapse(id, isCollapsed) {
return {
@ -314,7 +314,7 @@ export function toggleStatusCollapse(id, isCollapsed) {
id,
isCollapsed,
};
};
}
export const translateStatus = id => (dispatch, getState) => {
dispatch(translateStatusRequest(id));

2
app/javascript/mastodon/actions/store.js

@ -21,4 +21,4 @@ export function hydrateStore(rawState) {
dispatch(hydrateCompose());
dispatch(importFetchedAccounts(Object.values(rawState.accounts)));
};
};
}

8
app/javascript/mastodon/actions/suggestions.js

@ -21,14 +21,14 @@ export function fetchSuggestions(withRelationships = false) {
}
}).catch(error => dispatch(fetchSuggestionsFail(error)));
};
};
}
export function fetchSuggestionsRequest() {
return {
type: SUGGESTIONS_FETCH_REQUEST,
skipLoading: true,
};
};
}
export function fetchSuggestionsSuccess(suggestions) {
return {
@ -36,7 +36,7 @@ export function fetchSuggestionsSuccess(suggestions) {
suggestions,
skipLoading: true,
};
};
}
export function fetchSuggestionsFail(error) {
return {
@ -45,7 +45,7 @@ export function fetchSuggestionsFail(error) {
skipLoading: true,
skipAlert: true,
};
};
}
export const dismissSuggestion = accountId => (dispatch, getState) => {
dispatch({

82
app/javascript/mastodon/actions/tags.js

@ -1,9 +1,17 @@
import api from '../api';
import api, { getLinks } from '../api';
export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST';
export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS';
export const HASHTAG_FETCH_FAIL = 'HASHTAG_FETCH_FAIL';
export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST';
export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS';
export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL';
export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST';
export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS';
export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL';
export const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST';
export const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS';
export const HASHTAG_FOLLOW_FAIL = 'HASHTAG_FOLLOW_FAIL';
@ -37,6 +45,78 @@ export const fetchHashtagFail = error => ({
error,
});
export const fetchFollowedHashtags = () => (dispatch, getState) => {
dispatch(fetchFollowedHashtagsRequest());
api(getState).get('/api/v1/followed_tags').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(err => {
dispatch(fetchFollowedHashtagsFail(err));
});
};
export function fetchFollowedHashtagsRequest() {
return {
type: FOLLOWED_HASHTAGS_FETCH_REQUEST,
};
};
export function fetchFollowedHashtagsSuccess(followed_tags, next) {
return {
type: FOLLOWED_HASHTAGS_FETCH_SUCCESS,
followed_tags,
next,
};
};
export function fetchFollowedHashtagsFail(error) {
return {
type: FOLLOWED_HASHTAGS_FETCH_FAIL,
error,
};
};
export function expandFollowedHashtags() {
return (dispatch, getState) => {
const url = getState().getIn(['followed_tags', 'next']);
if (url === null) {
return;
}
dispatch(expandFollowedHashtagsRequest());
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(error => {
dispatch(expandFollowedHashtagsFail(error));
});
};
};
export function expandFollowedHashtagsRequest() {
return {
type: FOLLOWED_HASHTAGS_EXPAND_REQUEST,
};
};
export function expandFollowedHashtagsSuccess(followed_tags, next) {
return {
type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS,
followed_tags,
next,
};
};
export function expandFollowedHashtagsFail(error) {
return {
type: FOLLOWED_HASHTAGS_EXPAND_FAIL,
error,
};
};
export const followHashtag = name => (dispatch, getState) => {
dispatch(followHashtagRequest(name));

18
app/javascript/mastodon/actions/timelines.js

@ -51,7 +51,7 @@ export function updateTimeline(timeline, status, accept) {
dispatch(submitMarkers());
}
};
};
}
export function deleteFromTimelines(id) {
return (dispatch, getState) => {
@ -67,13 +67,13 @@ export function deleteFromTimelines(id) {
reblogOf,
});
};
};
}
export function clearTimeline(timeline) {
return (dispatch) => {
dispatch({ type: TIMELINE_CLEAR, timeline });
};
};
}
const noOp = () => {};
@ -122,7 +122,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
done();
});
};
};
}
export function fillTimelineGaps(timelineId, path, params = {}, done = noOp) {
return (dispatch, getState) => {
@ -168,7 +168,7 @@ export function expandTimelineRequest(timeline, isLoadingMore) {
timeline,
skipLoading: !isLoadingMore,
};
};
}
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore, usePendingItems) {
return {
@ -181,7 +181,7 @@ export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadi
usePendingItems,
skipLoading: !isLoadingMore,
};
};
}
export function expandTimelineFail(timeline, error, isLoadingMore) {
return {
@ -191,7 +191,7 @@ export function expandTimelineFail(timeline, error, isLoadingMore) {
skipLoading: !isLoadingMore,
skipNotFound: timeline.startsWith('account:'),
};
};
}
export function scrollTopTimeline(timeline, top) {
return {
@ -199,7 +199,7 @@ export function scrollTopTimeline(timeline, top) {
timeline,
top,
};
};
}
export function connectTimeline(timeline) {
return {
@ -207,7 +207,7 @@ export function connectTimeline(timeline) {
timeline,
usePendingItems: preferPendingItems,
};
};
}
export const disconnectTimeline = timeline => ({
type: TIMELINE_DISCONNECT,

2
app/javascript/mastodon/common.js

@ -9,4 +9,4 @@ export function start() {
} catch (e) {
// If called twice
}
};
}

2
app/javascript/mastodon/compare_id.js

@ -8,4 +8,4 @@ export default function compareId (id1, id2) {
} else {
return id1.length > id2.length ? 1 : -1;
}
};
}

2
app/javascript/mastodon/components/admin/Retention.js

@ -137,7 +137,7 @@ export default class Retention extends React.PureComponent {
break;
default:
title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
};
}
return (
<div className='retention'>

2
app/javascript/mastodon/components/admin/Trends.js

@ -50,7 +50,7 @@ export default class Trends extends React.PureComponent {
<Hashtag
key={hashtag.name}
name={hashtag.name}
href={`/admin/tags/${hashtag.id}`}
to={hashtag.id === undefined ? undefined : `/admin/tags/${hashtag.id}`}
people={hashtag.history[0].accounts * 1 + hashtag.history[1].accounts * 1}
uses={hashtag.history[0].uses * 1 + hashtag.history[1].uses * 1}
history={hashtag.history.reverse().map(day => day.uses)}

102
app/javascript/mastodon/components/dropdown_menu.js

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import IconButton from './icon_button';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames';
import { CircularProgress } from 'mastodon/components/loading_indicator';
@ -24,9 +22,6 @@ class DropdownMenu extends React.PureComponent {
scrollable: PropTypes.bool,
onClose: PropTypes.func.isRequired,
style: PropTypes.object,
placement: PropTypes.string,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
openedViaKeyboard: PropTypes.bool,
renderItem: PropTypes.func,
renderHeader: PropTypes.func,
@ -35,11 +30,6 @@ class DropdownMenu extends React.PureComponent {
static defaultProps = {
style: {},
placement: 'bottom',
};
state = {
mounted: false,
};
handleDocumentClick = e => {
@ -56,8 +46,6 @@ class DropdownMenu extends React.PureComponent {
if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}
componentWillUnmount () {
@ -139,40 +127,28 @@ class DropdownMenu extends React.PureComponent {
}
render () {
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop, scrollable, renderHeader, loading } = this.props;
const { mounted } = this.state;
const { items, scrollable, renderHeader, loading } = this.props;
let renderItem = this.props.renderItem || this.renderItem;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })}>
{loading && (
<CircularProgress size={30} strokeWidth={3.5} />
)}
{!loading && renderHeader && (
<div className='dropdown-menu__container__header'>
{renderHeader(items)}
</div>
)}
{!loading && (
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
</ul>
)}
</div>
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })} ref={this.setRef}>
{loading && (
<CircularProgress size={30} strokeWidth={3.5} />
)}
{!loading && renderHeader && (
<div className='dropdown-menu__container__header'>
{renderHeader(items)}
</div>
)}
</Motion>
{!loading && (
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
</ul>
)}
</div>
);
}
@ -197,7 +173,6 @@ export default class Dropdown extends React.PureComponent {
isUserTouching: PropTypes.func,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
dropdownPlacement: PropTypes.string,
openDropdownId: PropTypes.number,
openedViaKeyboard: PropTypes.bool,
renderItem: PropTypes.func,
@ -213,13 +188,11 @@ export default class Dropdown extends React.PureComponent {
id: id++,
};
handleClick = ({ target, type }) => {
handleClick = ({ type }) => {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
} else {
const { top } = target.getBoundingClientRect();
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click');
this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
}
}
@ -303,7 +276,6 @@ export default class Dropdown extends React.PureComponent {
disabled,
loading,
scrollable,
dropdownPlacement,
openDropdownId,
openedViaKeyboard,
children,
@ -314,7 +286,6 @@ export default class Dropdown extends React.PureComponent {
const open = this.state.id === openDropdownId;
const button = children ? React.cloneElement(React.Children.only(children), {
ref: this.setTargetRef,
onClick: this.handleClick,
onMouseDown: this.handleMouseDown,
onKeyDown: this.handleButtonKeyDown,
@ -326,7 +297,6 @@ export default class Dropdown extends React.PureComponent {
active={open}
disabled={disabled}
size={size}
ref={this.setTargetRef}
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
onKeyDown={this.handleButtonKeyDown}
@ -336,19 +306,27 @@ export default class Dropdown extends React.PureComponent {
return (
<React.Fragment>
{button}
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
<DropdownMenu
items={items}
loading={loading}
scrollable={scrollable}
onClose={this.handleClose}
openedViaKeyboard={openedViaKeyboard}
renderItem={renderItem}
renderHeader={renderHeader}
onItemClick={this.handleItemClick}
/>
<span ref={this.setTargetRef}>
{button}
</span>
<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, arrowProps, placement }) => (
<div {...props}>
<div className={`dropdown-animation dropdown-menu ${placement}`}>
<div className={`dropdown-menu__arrow ${placement}`} {...arrowProps} />
<DropdownMenu
items={items}
loading={loading}
scrollable={scrollable}
onClose={this.handleClose}
openedViaKeyboard={openedViaKeyboard}
renderItem={renderItem}
renderHeader={renderHeader}
onItemClick={this.handleItemClick}
/>
</div>
</div>
)}
</Overlay>
</React.Fragment>
);

5
app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js

@ -4,7 +4,6 @@ import { fetchHistory } from 'mastodon/actions/history';
import DropdownMenu from 'mastodon/components/dropdown_menu';
const mapStateToProps = (state, { statusId }) => ({
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
items: state.getIn(['history', statusId, 'items']),
@ -13,9 +12,9 @@ const mapStateToProps = (state, { statusId }) => ({
const mapDispatchToProps = (dispatch, { statusId }) => ({
onOpen (id, onItemClick, dropdownPlacement, keyboard) {
onOpen (id, onItemClick, keyboard) {
dispatch(fetchHistory(statusId));
dispatch(openDropdownMenu(id, dropdownPlacement, keyboard));
dispatch(openDropdownMenu(id, keyboard));
},
onClose (id) {

4
app/javascript/mastodon/components/icon_button.js

@ -27,6 +27,7 @@ export default class IconButton extends React.PureComponent {
counter: PropTypes.number,
obfuscateCount: PropTypes.bool,
href: PropTypes.string,
ariaHidden: PropTypes.bool,
};
static defaultProps = {
@ -36,6 +37,7 @@ export default class IconButton extends React.PureComponent {
animate: false,
overlay: false,
tabIndex: '0',
ariaHidden: false,
};
state = {
@ -102,6 +104,7 @@ export default class IconButton extends React.PureComponent {
counter,
obfuscateCount,
href,
ariaHidden,
} = this.props;
const {
@ -142,6 +145,7 @@ export default class IconButton extends React.PureComponent {
type='button'
aria-label={title}
aria-expanded={expanded}
aria-hidden={ariaHidden}
title={title}
className={classes}
onClick={this.handleClick}

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

Loading…
Cancel
Save