Browse Source

Merge branch 'master' into live

master
Zac 1 month ago
parent
commit
423338d671
100 changed files with 1983 additions and 490 deletions
  1. 41
    15
      .env.nanobox
  2. 15
    1
      .env.production.sample
  3. 1
    1
      .ruby-version
  4. 255
    0
      CHANGELOG.md
  5. 2
    2
      Dockerfile
  6. 6
    5
      Gemfile
  7. 31
    24
      Gemfile.lock
  8. 0
    9
      app.json
  9. 4
    4
      app/chewy/statuses_index.rb
  10. 15
    28
      app/controllers/about_controller.rb
  11. 5
    4
      app/controllers/accounts_controller.rb
  12. 3
    3
      app/controllers/activitypub/collections_controller.rb
  13. 6
    1
      app/controllers/admin/relays_controller.rb
  14. 1
    0
      app/controllers/admin/two_factor_authentications_controller.rb
  15. 2
    0
      app/controllers/api/v1/accounts/statuses_controller.rb
  16. 2
    2
      app/controllers/api/v1/accounts_controller.rb
  17. 1
    1
      app/controllers/api/v2/search_controller.rb
  18. 29
    0
      app/controllers/auth/challenges_controller.rb
  19. 35
    26
      app/controllers/auth/sessions_controller.rb
  20. 5
    0
      app/controllers/auth/setup_controller.rb
  21. 65
    0
      app/controllers/concerns/challengable_concern.rb
  22. 7
    0
      app/controllers/concerns/export_controller_concern.rb
  23. 1
    0
      app/controllers/custom_css_controller.rb
  24. 2
    0
      app/controllers/directories_controller.rb
  25. 1
    0
      app/controllers/follower_accounts_controller.rb
  26. 1
    0
      app/controllers/following_accounts_controller.rb
  27. 1
    0
      app/controllers/manifests_controller.rb
  28. 1
    0
      app/controllers/media_controller.rb
  29. 1
    0
      app/controllers/media_proxy_controller.rb
  30. 2
    0
      app/controllers/remote_follow_controller.rb
  31. 2
    0
      app/controllers/remote_interaction_controller.rb
  32. 43
    0
      app/controllers/settings/aliases_controller.rb
  33. 7
    0
      app/controllers/settings/exports_controller.rb
  34. 45
    0
      app/controllers/settings/migration/redirects_controller.rb
  35. 28
    11
      app/controllers/settings/migrations_controller.rb
  36. 5
    0
      app/controllers/settings/two_factor_authentication/confirmations_controller.rb
  37. 6
    0
      app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
  38. 4
    0
      app/controllers/settings/two_factor_authentications_controller.rb
  39. 1
    0
      app/controllers/statuses_controller.rb
  40. 2
    0
      app/controllers/tags_controller.rb
  41. 19
    0
      app/controllers/well_known/nodeinfo_controller.rb
  42. 18
    4
      app/helpers/settings_helper.rb
  43. 14
    0
      app/javascript/flavours/glitch/actions/blocks.js
  44. 3
    2
      app/javascript/flavours/glitch/actions/compose.js
  45. 28
    0
      app/javascript/flavours/glitch/actions/conversations.js
  46. 2
    1
      app/javascript/flavours/glitch/actions/importer/normalizer.js
  47. 17
    11
      app/javascript/flavours/glitch/components/avatar_composite.js
  48. 1
    1
      app/javascript/flavours/glitch/components/column_back_button_slim.js
  49. 21
    6
      app/javascript/flavours/glitch/components/poll.js
  50. 3
    14
      app/javascript/flavours/glitch/containers/status_container.js
  51. 3
    1
      app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
  52. 3
    1
      app/javascript/flavours/glitch/features/account_gallery/index.js
  53. 2
    13
      app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
  54. 13
    3
      app/javascript/flavours/glitch/features/compose/components/search.js
  55. 185
    15
      app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
  56. 65
    10
      app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js
  57. 3
    1
      app/javascript/flavours/glitch/features/favourites/index.js
  58. 4
    2
      app/javascript/flavours/glitch/features/followers/index.js
  59. 4
    2
      app/javascript/flavours/glitch/features/following/index.js
  60. 3
    5
      app/javascript/flavours/glitch/features/getting_started/index.js
  61. 1
    1
      app/javascript/flavours/glitch/features/notifications/components/filter_bar.js
  62. 3
    1
      app/javascript/flavours/glitch/features/reblogs/index.js
  63. 3
    15
      app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js
  64. 4
    16
      app/javascript/flavours/glitch/features/status/index.js
  65. 53
    0
      app/javascript/flavours/glitch/features/ui/components/audio_modal.js
  66. 103
    0
      app/javascript/flavours/glitch/features/ui/components/block_modal.js
  67. 13
    4
      app/javascript/flavours/glitch/features/ui/components/embed_modal.js
  68. 11
    1
      app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js
  69. 1
    1
      app/javascript/flavours/glitch/features/ui/components/media_modal.js
  70. 4
    0
      app/javascript/flavours/glitch/features/ui/components/modal_root.js
  71. 9
    6
      app/javascript/flavours/glitch/features/ui/components/mute_modal.js
  72. 9
    4
      app/javascript/flavours/glitch/features/ui/components/video_modal.js
  73. 1
    1
      app/javascript/flavours/glitch/features/ui/index.js
  74. 10
    0
      app/javascript/flavours/glitch/packs/public.js
  75. 20
    0
      app/javascript/flavours/glitch/packs/settings.js
  76. 22
    0
      app/javascript/flavours/glitch/reducers/blocks.js
  77. 5
    2
      app/javascript/flavours/glitch/reducers/compose.js
  78. 3
    0
      app/javascript/flavours/glitch/reducers/conversations.js
  79. 2
    0
      app/javascript/flavours/glitch/reducers/index.js
  80. 0
    2
      app/javascript/flavours/glitch/reducers/mutes.js
  81. 2
    2
      app/javascript/flavours/glitch/reducers/notifications.js
  82. 3
    2
      app/javascript/flavours/glitch/reducers/timelines.js
  83. 0
    18
      app/javascript/flavours/glitch/styles/_mixins.scss
  84. 113
    77
      app/javascript/flavours/glitch/styles/about.scss
  85. 154
    45
      app/javascript/flavours/glitch/styles/admin.scss
  86. 0
    3
      app/javascript/flavours/glitch/styles/basics.scss
  87. 36
    0
      app/javascript/flavours/glitch/styles/components/accounts.scss
  88. 8
    0
      app/javascript/flavours/glitch/styles/components/composer.scss
  89. 54
    32
      app/javascript/flavours/glitch/styles/components/index.scss
  90. 8
    2
      app/javascript/flavours/glitch/styles/components/media.scss
  91. 56
    21
      app/javascript/flavours/glitch/styles/components/modal.scss
  92. 22
    0
      app/javascript/flavours/glitch/styles/components/search.scss
  93. 5
    0
      app/javascript/flavours/glitch/styles/components/status.scss
  94. 76
    0
      app/javascript/flavours/glitch/styles/containers.scss
  95. 15
    0
      app/javascript/flavours/glitch/styles/forms.scss
  96. 15
    2
      app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
  97. 2
    0
      app/javascript/flavours/glitch/styles/mastodon-light/variables.scss
  98. 8
    2
      app/javascript/flavours/glitch/styles/polls.scss
  99. 34
    1
      app/javascript/flavours/glitch/styles/rtl.scss
  100. 0
    0
      app/javascript/flavours/glitch/styles/tables.scss

+ 41
- 15
.env.nanobox View File

@@ -11,24 +11,14 @@ DB_NAME=gonano
DB_PASS=$DATA_DB_PASS
DB_PORT=5432

DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
# DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano

# Optional ElasticSearch configuration
ES_ENABLED=true
ES_HOST=$DATA_ELASTIC_HOST
ES_PORT=9200

# Optimizations
LD_PRELOAD=/data/lib/libjemalloc.so

# ImageMagick optimizations
MAGICK_TEMPORARY_PATH=/app/tmp
MAGICK_MEMORY_LIMIT=128MiB
MAGICK_MAP_LIMIT=64MiB
MAGICK_TIME_LIMIT=15
MAGICK_AREA_LIMIT=16MP
MAGICK_WIDTH_LIMIT=8KP
MAGICK_HEIGHT_LIMIT=8KP
BIND=0.0.0.0

# Federation
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
@@ -84,6 +74,7 @@ SMTP_PORT=587
SMTP_LOGIN=$SMTP_LOGIN
SMTP_PASSWORD=$SMTP_PASSWORD
SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
#SMTP_REPLY_TO=
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
@@ -97,9 +88,17 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# PAPERCLIP_ROOT_URL=/system

# Optional asset host for multi-server setups
# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN
# if WEB_DOMAIN is not set. For example, the server may have the
# following header field:
# Access-Control-Allow-Origin: https://example.com/
# CDN_HOST=https://assets.example.com

# S3 (optional)
# The attachment host must allow cross origin request from WEB_DOMAIN or
# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the
# following header field:
# Access-Control-Allow-Origin: https://192.168.1.123:9000/
# S3_ENABLED=true
# S3_BUCKET=
# AWS_ACCESS_KEY_ID=
@@ -109,6 +108,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# S3_HOSTNAME=192.168.1.123:9000

# S3 (Minio Config (optional) Please check Minio instance for details)
# The attachment host must allow cross origin request - see the description
# above.
# S3_ENABLED=true
# S3_BUCKET=
# AWS_ACCESS_KEY_ID=
@@ -119,12 +120,30 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# S3_ENDPOINT=
# S3_SIGNATURE_VERSION=

# Google Cloud Storage (optional)
# Use S3 compatible API. Since GCS does not support Multipart Upload,
# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
# The attachment host must allow cross origin request - see the description
# above.
# S3_ENABLED=true
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=https
# S3_HOSTNAME=storage.googleapis.com
# S3_ENDPOINT=https://storage.googleapis.com
# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes

# Swift (optional)
# The attachment host must allow cross origin request - see the description
# above.
# SWIFT_ENABLED=true
# SWIFT_USERNAME=
# For Keystone V3, the value for SWIFT_TENANT should be the project name
# SWIFT_TENANT=
# SWIFT_PASSWORD=
# Some OpenStack V3 providers require PROJECT_ID (optional)
# SWIFT_PROJECT_ID=
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
# issues with token rate-limiting during high load.
# SWIFT_AUTH_URL=
@@ -171,8 +190,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# The pam environment variable "email" is provided by:
# https://github.com/devkral/pam_email_extractor
# PAM_ENABLED=true
# Fallback Suffix for email address generation (nil by default)
# PAM_DEFAULT_SUFFIX=pam
# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
# PAM_EMAIL_DOMAIN=example.com
# Name of the pam service (pam "auth" section is evaluated)
# PAM_DEFAULT_SERVICE=rpam
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
@@ -220,7 +239,14 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=

# Use HTTP proxy for outgoing request (optional)
# http_proxy=http://gateway.local:8118
# Access control for hidden service.
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true

+ 15
- 1
.env.production.sample View File

@@ -115,6 +115,20 @@ SMTP_FROM_ADDRESS=notifications@example.com
# S3_ENDPOINT=
# S3_SIGNATURE_VERSION=

# Google Cloud Storage (optional)
# Use S3 compatible API. Since GCS does not support Multipart Upload,
# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
# The attachment host must allow cross origin request - see the description
# above.
# S3_ENABLED=true
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=https
# S3_HOSTNAME=storage.googleapis.com
# S3_ENDPOINT=https://storage.googleapis.com
# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes

# Swift (optional)
# The attachment host must allow cross origin request - see the description
# above.
@@ -193,7 +207,7 @@ STREAMING_CLUSTER_NUM=1
# LDAP_BIND_DN=
# LDAP_PASSWORD=
# LDAP_UID=cn
# LDAP_SEARCH_FILTER="%{uid}=%{email}"
# LDAP_SEARCH_FILTER=%{uid}=%{email}

# PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable

+ 1
- 1
.ruby-version View File

@@ -1 +1 @@
2.6.3
2.6.5

+ 255
- 0
CHANGELOG.md View File

@@ -3,6 +3,261 @@ Changelog

All notable changes to this project will be documented in this file.

## [3.0.0] - 2019-10-03
### Added

- Add "not available" label to unloaded media attachments in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11715), [Gargron](https://github.com/tootsuite/mastodon/pull/11745))
- **Add profile directory to web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11688), [mayaeh](https://github.com/tootsuite/mastodon/pull/11872))
- Add profile directory opt-in federation
- Add profile directory REST API
- Add special alert for throttled requests in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11677))
- Add confirmation modal when logging out from the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11671))
- **Add audio player in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11644), [Gargron](https://github.com/tootsuite/mastodon/pull/11652), [Gargron](https://github.com/tootsuite/mastodon/pull/11654), [ThibG](https://github.com/tootsuite/mastodon/pull/11629), [Gargron](https://github.com/tootsuite/mastodon/pull/12056))
- **Add autosuggestions for hashtags in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11422), [ThibG](https://github.com/tootsuite/mastodon/pull/11632), [Gargron](https://github.com/tootsuite/mastodon/pull/11764), [Gargron](https://github.com/tootsuite/mastodon/pull/11588), [Gargron](https://github.com/tootsuite/mastodon/pull/11442))
- **Add media editing modal with OCR tool in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11563), [Gargron](https://github.com/tootsuite/mastodon/pull/11566), [ThibG](https://github.com/tootsuite/mastodon/pull/11575), [ThibG](https://github.com/tootsuite/mastodon/pull/11576), [Gargron](https://github.com/tootsuite/mastodon/pull/11577), [Gargron](https://github.com/tootsuite/mastodon/pull/11573), [Gargron](https://github.com/tootsuite/mastodon/pull/11571))
- Add indicator of unread notifications to window title when web UI is out of focus ([Gargron](https://github.com/tootsuite/mastodon/pull/11560), [Gargron](https://github.com/tootsuite/mastodon/pull/11572))
- Add indicator for which options you voted for in a poll in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11195))
- **Add search results pagination to web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11409), [ThibG](https://github.com/tootsuite/mastodon/pull/11447))
- **Add option to disable real-time updates in web UI ("slow mode")** ([Gargron](https://github.com/tootsuite/mastodon/pull/9984), [ykzts](https://github.com/tootsuite/mastodon/pull/11880), [ThibG](https://github.com/tootsuite/mastodon/pull/11883), [Gargron](https://github.com/tootsuite/mastodon/pull/11898), [ThibG](https://github.com/tootsuite/mastodon/pull/11859))
- Add option to disable blurhash previews in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11188))
- Add native smooth scrolling when supported in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11207))
- Add scrolling to the search bar on focus in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/12032))
- Add refresh button to list of rebloggers/favouriters in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12031))
- Add error description and button to copy stack trace to web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12033))
- Add search and sort functions to hashtag admin UI ([mayaeh](https://github.com/tootsuite/mastodon/pull/11829), [Gargron](https://github.com/tootsuite/mastodon/pull/11897), [mayaeh](https://github.com/tootsuite/mastodon/pull/11875))
- Add setting for default search engine indexing in admin UI ([brortao](https://github.com/tootsuite/mastodon/pull/11804))
- Add account bio to account view in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11473))
- **Add option to include reported statuses in warning e-mail from admin UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11639), [Gargron](https://github.com/tootsuite/mastodon/pull/11812), [Gargron](https://github.com/tootsuite/mastodon/pull/11741), [Gargron](https://github.com/tootsuite/mastodon/pull/11698), [mayaeh](https://github.com/tootsuite/mastodon/pull/11765))
- Add number of pending accounts and pending hashtags to dashboard in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11514))
- **Add account migration UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11846), [noellabo](https://github.com/tootsuite/mastodon/pull/11905), [noellabo](https://github.com/tootsuite/mastodon/pull/11907), [noellabo](https://github.com/tootsuite/mastodon/pull/11906), [noellabo](https://github.com/tootsuite/mastodon/pull/11902))
- **Add table of contents to about page** ([Gargron](https://github.com/tootsuite/mastodon/pull/11885), [ykzts](https://github.com/tootsuite/mastodon/pull/11941), [ykzts](https://github.com/tootsuite/mastodon/pull/11895), [Kjwon15](https://github.com/tootsuite/mastodon/pull/11916))
- **Add password challenge to 2FA settings, e-mail notifications** ([Gargron](https://github.com/tootsuite/mastodon/pull/11878))
- **Add optional public list of domain blocks with comments** ([ThibG](https://github.com/tootsuite/mastodon/pull/11298), [ThibG](https://github.com/tootsuite/mastodon/pull/11515), [Gargron](https://github.com/tootsuite/mastodon/pull/11908))
- Add an RSS feed for featured hashtags ([noellabo](https://github.com/tootsuite/mastodon/pull/10502))
- Add explanations to featured hashtags UI and profile ([Gargron](https://github.com/tootsuite/mastodon/pull/11586))
- **Add hashtag trends with admin and user settings** ([Gargron](https://github.com/tootsuite/mastodon/pull/11490), [Gargron](https://github.com/tootsuite/mastodon/pull/11502), [Gargron](https://github.com/tootsuite/mastodon/pull/11641), [Gargron](https://github.com/tootsuite/mastodon/pull/11594), [Gargron](https://github.com/tootsuite/mastodon/pull/11517), [mayaeh](https://github.com/tootsuite/mastodon/pull/11845), [Gargron](https://github.com/tootsuite/mastodon/pull/11774), [Gargron](https://github.com/tootsuite/mastodon/pull/11712), [Gargron](https://github.com/tootsuite/mastodon/pull/11791), [Gargron](https://github.com/tootsuite/mastodon/pull/11743), [Gargron](https://github.com/tootsuite/mastodon/pull/11740), [Gargron](https://github.com/tootsuite/mastodon/pull/11714), [ThibG](https://github.com/tootsuite/mastodon/pull/11631), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/11569), [Gargron](https://github.com/tootsuite/mastodon/pull/11524), [Gargron](https://github.com/tootsuite/mastodon/pull/11513))
- Add hashtag usage breakdown to admin UI
- Add batch actions for hashtags to admin UI
- Add trends to web UI
- Add trends to public pages
- Add user preference to hide trends
- Add admin setting to disable trends
- **Add categories for custom emojis** ([Gargron](https://github.com/tootsuite/mastodon/pull/11196), [Gargron](https://github.com/tootsuite/mastodon/pull/11793), [Gargron](https://github.com/tootsuite/mastodon/pull/11920), [highemerly](https://github.com/tootsuite/mastodon/pull/11876))
- Add custom emoji categories to emoji picker in web UI
- Add `category` to custom emojis in REST API
- Add batch actions for custom emojis in admin UI
- Add max image dimensions to error message ([raboof](https://github.com/tootsuite/mastodon/pull/11552))
- Add aac, m4a, 3gp, amr, wma to allowed audio formats ([Gargron](https://github.com/tootsuite/mastodon/pull/11342), [umonaca](https://github.com/tootsuite/mastodon/pull/11687))
- **Add search syntax for operators and phrases** ([Gargron](https://github.com/tootsuite/mastodon/pull/11411))
- **Add REST API for managing featured hashtags** ([noellabo](https://github.com/tootsuite/mastodon/pull/11778))
- **Add REST API for managing timeline read markers** ([Gargron](https://github.com/tootsuite/mastodon/pull/11762))
- Add `exclude_unreviewed` param to `GET /api/v2/search` REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/11977))
- Add `reason` param to `POST /api/v1/accounts` REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/12064))
- **Add ActivityPub secure mode** ([Gargron](https://github.com/tootsuite/mastodon/pull/11269), [ThibG](https://github.com/tootsuite/mastodon/pull/11332), [ThibG](https://github.com/tootsuite/mastodon/pull/11295))
- Add HTTP signatures to all outgoing ActivityPub GET requests ([Gargron](https://github.com/tootsuite/mastodon/pull/11284), [ThibG](https://github.com/tootsuite/mastodon/pull/11300))
- Add support for ActivityPub Audio activities ([ThibG](https://github.com/tootsuite/mastodon/pull/11189))
- Add ActivityPub actor representing the entire server ([ThibG](https://github.com/tootsuite/mastodon/pull/11321), [rtucker](https://github.com/tootsuite/mastodon/pull/11400), [ThibG](https://github.com/tootsuite/mastodon/pull/11561), [Gargron](https://github.com/tootsuite/mastodon/pull/11798))
- **Add whitelist mode** ([Gargron](https://github.com/tootsuite/mastodon/pull/11291), [mayaeh](https://github.com/tootsuite/mastodon/pull/11634))
- Add config of multipart threshold for S3 ([ykzts](https://github.com/tootsuite/mastodon/pull/11924), [ykzts](https://github.com/tootsuite/mastodon/pull/11944))
- Add health check endpoint for web ([ykzts](https://github.com/tootsuite/mastodon/pull/11770), [ykzts](https://github.com/tootsuite/mastodon/pull/11947))
- Add HTTP signature keyId to request log ([Gargron](https://github.com/tootsuite/mastodon/pull/11591))
- Add `SMTP_REPLY_TO` environment variable ([hugogameiro](https://github.com/tootsuite/mastodon/pull/11718))
- Add `tootctl preview_cards remove` command ([mayaeh](https://github.com/tootsuite/mastodon/pull/11320))
- Add `tootctl media refresh` command ([Gargron](https://github.com/tootsuite/mastodon/pull/11775))
- Add `tootctl cache recount` command ([Gargron](https://github.com/tootsuite/mastodon/pull/11597))
- Add option to exclude suspended domains from `tootctl domains crawl` ([dariusk](https://github.com/tootsuite/mastodon/pull/11454))
- Add parallelization to `tootctl search deploy` ([noellabo](https://github.com/tootsuite/mastodon/pull/12051))
- Add soft delete for statuses for instant deletes through API ([Gargron](https://github.com/tootsuite/mastodon/pull/11623), [Gargron](https://github.com/tootsuite/mastodon/pull/11648))
- Add rails-level JSON caching ([Gargron](https://github.com/tootsuite/mastodon/pull/11333), [Gargron](https://github.com/tootsuite/mastodon/pull/11271))
- **Add request pool to improve delivery performance** ([Gargron](https://github.com/tootsuite/mastodon/pull/10353), [ykzts](https://github.com/tootsuite/mastodon/pull/11756))
- Add concurrent connection attempts to resolved IP addresses ([ThibG](https://github.com/tootsuite/mastodon/pull/11757))
- Add index for remember_token to improve login performance ([abcang](https://github.com/tootsuite/mastodon/pull/11881))
- **Add more accurate hashtag search** ([Gargron](https://github.com/tootsuite/mastodon/pull/11579), [Gargron](https://github.com/tootsuite/mastodon/pull/11427), [Gargron](https://github.com/tootsuite/mastodon/pull/11448))
- **Add more accurate account search** ([Gargron](https://github.com/tootsuite/mastodon/pull/11537), [Gargron](https://github.com/tootsuite/mastodon/pull/11580))
- **Add a spam check** ([Gargron](https://github.com/tootsuite/mastodon/pull/11217), [Gargron](https://github.com/tootsuite/mastodon/pull/11806), [ThibG](https://github.com/tootsuite/mastodon/pull/11296))
- Add new languages ([Gargron](https://github.com/tootsuite/mastodon/pull/12062))
- Breton
- Spanish (Argentina)
- Estonian
- Macedonian
- New Norwegian
- Add NodeInfo endpoint ([Gargron](https://github.com/tootsuite/mastodon/pull/12002), [Gargron](https://github.com/tootsuite/mastodon/pull/12058))

### Changed

- **Change conversations UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11896))
- Change dashboard to short number notation ([noellabo](https://github.com/tootsuite/mastodon/pull/11847), [noellabo](https://github.com/tootsuite/mastodon/pull/11911))
- Change REST API `GET /api/v1/timelines/public` to require authentication when public preview is off ([ThibG](https://github.com/tootsuite/mastodon/pull/11802))
- Change REST API `POST /api/v1/follow_requests/:id/(approve|reject)` to return relationship ([ThibG](https://github.com/tootsuite/mastodon/pull/11800))
- Change rate limit for media proxy ([ykzts](https://github.com/tootsuite/mastodon/pull/11814))
- Change unlisted custom emoji to not appear in autosuggestions ([Gargron](https://github.com/tootsuite/mastodon/pull/11818))
- Change max length of media descriptions from 420 to 1500 characters ([Gargron](https://github.com/tootsuite/mastodon/pull/11819), [ThibG](https://github.com/tootsuite/mastodon/pull/11836))
- **Change deletes to preserve soft-deleted statuses in unresolved reports** ([Gargron](https://github.com/tootsuite/mastodon/pull/11805))
- **Change tootctl to use inline parallelization instead of Sidekiq** ([Gargron](https://github.com/tootsuite/mastodon/pull/11776))
- **Change account deletion page to have better explanations** ([Gargron](https://github.com/tootsuite/mastodon/pull/11753), [Gargron](https://github.com/tootsuite/mastodon/pull/11763))
- Change hashtag component in web UI to show numbers for 2 last days ([Gargron](https://github.com/tootsuite/mastodon/pull/11742), [Gargron](https://github.com/tootsuite/mastodon/pull/11755), [Gargron](https://github.com/tootsuite/mastodon/pull/11754))
- Change OpenGraph description on sign-up page to reflect invite ([Gargron](https://github.com/tootsuite/mastodon/pull/11744))
- Change layout of public profile directory to be the same as in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11705))
- Change detailed status child ordering to sort self-replies on top ([ThibG](https://github.com/tootsuite/mastodon/pull/11686))
- Change window resize handler to switch to/from mobile layout as soon as needed ([ThibG](https://github.com/tootsuite/mastodon/pull/11656))
- Change icon button styles to make hover/focus states more obvious ([ThibG](https://github.com/tootsuite/mastodon/pull/11474))
- Change contrast of status links that are not mentions or hashtags ([ThibG](https://github.com/tootsuite/mastodon/pull/11406))
- **Change hashtags to preserve first-used casing** ([Gargron](https://github.com/tootsuite/mastodon/pull/11416), [Gargron](https://github.com/tootsuite/mastodon/pull/11508), [Gargron](https://github.com/tootsuite/mastodon/pull/11504), [Gargron](https://github.com/tootsuite/mastodon/pull/11507), [Gargron](https://github.com/tootsuite/mastodon/pull/11441))
- **Change unconfirmed user login behaviour** ([Gargron](https://github.com/tootsuite/mastodon/pull/11375), [ThibG](https://github.com/tootsuite/mastodon/pull/11394), [Gargron](https://github.com/tootsuite/mastodon/pull/11860))
- **Change single-column mode to scroll the whole page** ([Gargron](https://github.com/tootsuite/mastodon/pull/11359), [Gargron](https://github.com/tootsuite/mastodon/pull/11894), [Gargron](https://github.com/tootsuite/mastodon/pull/11891), [ThibG](https://github.com/tootsuite/mastodon/pull/11655), [Gargron](https://github.com/tootsuite/mastodon/pull/11463), [Gargron](https://github.com/tootsuite/mastodon/pull/11458), [ThibG](https://github.com/tootsuite/mastodon/pull/11395), [Gargron](https://github.com/tootsuite/mastodon/pull/11418))
- Change `tootctl accounts follow` to only work with local accounts ([angristan](https://github.com/tootsuite/mastodon/pull/11592))
- Change Dockerfile ([Shleeble](https://github.com/tootsuite/mastodon/pull/11710), [ykzts](https://github.com/tootsuite/mastodon/pull/11768), [Shleeble](https://github.com/tootsuite/mastodon/pull/11707))
- Change supported Node versions to include v12 ([abcang](https://github.com/tootsuite/mastodon/pull/11706))
- Change Portuguese language from `pt` to `pt-PT` ([Gargron](https://github.com/tootsuite/mastodon/pull/11820))
- Change domain block silence to always require approval on follow ([ThibG](https://github.com/tootsuite/mastodon/pull/11975))
- Change link preview fetcher to not perform a HEAD request first ([Gargron](https://github.com/tootsuite/mastodon/pull/12028))
- Change `tootctl domains purge` to accept multiple domains at once ([Gargron](https://github.com/tootsuite/mastodon/pull/12046))

### Removed

- **Remove OStatus support** ([Gargron](https://github.com/tootsuite/mastodon/pull/11205), [Gargron](https://github.com/tootsuite/mastodon/pull/11303), [Gargron](https://github.com/tootsuite/mastodon/pull/11460), [ThibG](https://github.com/tootsuite/mastodon/pull/11280), [ThibG](https://github.com/tootsuite/mastodon/pull/11278))
- Remove Atom feeds and old URLs in the form of `GET /:username/updates/:id` ([Gargron](https://github.com/tootsuite/mastodon/pull/11247))
- Remove WebP support ([angristan](https://github.com/tootsuite/mastodon/pull/11589))
- Remove deprecated config options from Heroku and Scalingo ([ykzts](https://github.com/tootsuite/mastodon/pull/11925))
- Remove deprecated REST API `GET /api/v1/search` API ([Gargron](https://github.com/tootsuite/mastodon/pull/11823))
- Remove deprecated REST API `GET /api/v1/statuses/:id/card` ([Gargron](https://github.com/tootsuite/mastodon/pull/11213))
- Remove deprecated REST API `POST /api/v1/notifications/dismiss?id=:id` ([Gargron](https://github.com/tootsuite/mastodon/pull/11214))
- Remove deprecated REST API `GET /api/v1/timelines/direct` ([Gargron](https://github.com/tootsuite/mastodon/pull/11212))

### Fixed

- Fix manifest warning ([ykzts](https://github.com/tootsuite/mastodon/pull/11767))
- Fix admin UI for custom emoji not respecting GIF autoplay preference ([ThibG](https://github.com/tootsuite/mastodon/pull/11801))
- Fix page body not being scrollable in admin/settings layout ([Gargron](https://github.com/tootsuite/mastodon/pull/11893))
- Fix placeholder colors for inputs not being explicitly defined ([Gargron](https://github.com/tootsuite/mastodon/pull/11890))
- Fix incorrect enclosure length in RSS ([tsia](https://github.com/tootsuite/mastodon/pull/11889))
- Fix TOTP codes not being filtered from logs during enabling/disabling ([Gargron](https://github.com/tootsuite/mastodon/pull/11877))
- Fix webfinger response not returning 410 when account is suspended ([Gargron](https://github.com/tootsuite/mastodon/pull/11869))
- Fix ActivityPub Move handler queuing jobs that will fail if account is suspended ([Gargron](https://github.com/tootsuite/mastodon/pull/11864))
- Fix SSO login not using existing account when e-mail is verified ([Gargron](https://github.com/tootsuite/mastodon/pull/11862))
- Fix web UI allowing uploads past status limit via drag & drop ([Gargron](https://github.com/tootsuite/mastodon/pull/11863))
- Fix expiring polls not being displayed as such in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11835))
- Fix 2FA challenge and password challenge for non-database users ([Gargron](https://github.com/tootsuite/mastodon/pull/11831), [Gargron](https://github.com/tootsuite/mastodon/pull/11943))
- Fix profile fields overflowing page width in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11828))
- Fix web push subscriptions being deleted on rate limit or timeout ([Gargron](https://github.com/tootsuite/mastodon/pull/11826))
- Fix display of long poll options in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11717), [ThibG](https://github.com/tootsuite/mastodon/pull/11833))
- Fix search API not resolving URL when `type` is given ([Gargron](https://github.com/tootsuite/mastodon/pull/11822))
- Fix hashtags being split by ZWNJ character ([Gargron](https://github.com/tootsuite/mastodon/pull/11821))
- Fix scroll position resetting when opening media modals in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11815))
- Fix duplicate HTML IDs on about page ([ThibG](https://github.com/tootsuite/mastodon/pull/11803))
- Fix admin UI showing superfluous reject media/reports on suspended domain blocks ([ThibG](https://github.com/tootsuite/mastodon/pull/11749))
- Fix ActivityPub context not being dynamically computed ([ThibG](https://github.com/tootsuite/mastodon/pull/11746))
- Fix Mastodon logo style on hover on public pages' footer ([ThibG](https://github.com/tootsuite/mastodon/pull/11735))
- Fix height of dashboard counters ([ThibG](https://github.com/tootsuite/mastodon/pull/11736))
- Fix custom emoji animation on hover in web UI directory bios ([ThibG](https://github.com/tootsuite/mastodon/pull/11716))
- Fix non-numbers being passed to Redis and causing an error ([Gargron](https://github.com/tootsuite/mastodon/pull/11697))
- Fix error in REST API for an account's statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/11700))
- Fix uncaught error when resource param is missing in Webfinger request ([Gargron](https://github.com/tootsuite/mastodon/pull/11701))
- Fix uncaught domain normalization error in remote follow ([Gargron](https://github.com/tootsuite/mastodon/pull/11703))
- Fix uncaught 422 and 500 errors ([Gargron](https://github.com/tootsuite/mastodon/pull/11590), [Gargron](https://github.com/tootsuite/mastodon/pull/11811))
- Fix uncaught parameter missing exceptions and missing error templates ([Gargron](https://github.com/tootsuite/mastodon/pull/11702))
- Fix encoding error when checking e-mail MX records ([Gargron](https://github.com/tootsuite/mastodon/pull/11696))
- Fix items in StatusContent render list not all having a key ([ThibG](https://github.com/tootsuite/mastodon/pull/11645))
- Fix remote and staff-removed statuses leaving media behind for a day ([Gargron](https://github.com/tootsuite/mastodon/pull/11638))
- Fix CSP needlessly allowing blob URLs in script-src ([ThibG](https://github.com/tootsuite/mastodon/pull/11620))
- Fix ignoring whole status because of one invalid hashtag ([Gargron](https://github.com/tootsuite/mastodon/pull/11621))
- Fix hidden statuses losing focus ([ThibG](https://github.com/tootsuite/mastodon/pull/11208))
- Fix loading bar being obscured by other elements in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11598))
- Fix multiple issues with replies collection for pages further than self-replies ([ThibG](https://github.com/tootsuite/mastodon/pull/11582))
- Fix blurhash and autoplay not working on public pages ([Gargron](https://github.com/tootsuite/mastodon/pull/11585))
- Fix 422 being returned instead of 404 when POSTing to unmatched routes ([Gargron](https://github.com/tootsuite/mastodon/pull/11574), [Gargron](https://github.com/tootsuite/mastodon/pull/11704))
- Fix client-side resizing of image uploads ([ThibG](https://github.com/tootsuite/mastodon/pull/11570))
- Fix short number formatting for numbers above million in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11559))
- Fix ActivityPub and REST API queries setting cookies and preventing caching ([ThibG](https://github.com/tootsuite/mastodon/pull/11539), [ThibG](https://github.com/tootsuite/mastodon/pull/11557), [ThibG](https://github.com/tootsuite/mastodon/pull/11336), [ThibG](https://github.com/tootsuite/mastodon/pull/11331))
- Fix some emojis in profile metadata labels are not emojified. ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/11534))
- Fix account search always returning exact match on paginated results ([Gargron](https://github.com/tootsuite/mastodon/pull/11525))
- Fix acct URIs with IDN domains not being resolved ([Gargron](https://github.com/tootsuite/mastodon/pull/11520))
- Fix admin dashboard missing latest features ([Gargron](https://github.com/tootsuite/mastodon/pull/11505))
- Fix jumping of toot date when clicking spoiler button ([ariasuni](https://github.com/tootsuite/mastodon/pull/11449))
- Fix boost to original audience not working on mobile in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11371))
- Fix handling of webfinger redirects in ResolveAccountService ([ThibG](https://github.com/tootsuite/mastodon/pull/11279))
- Fix URLs appearing twice in errors of ActivityPub::DeliveryWorker ([Gargron](https://github.com/tootsuite/mastodon/pull/11231))
- Fix support for HTTP proxies ([ThibG](https://github.com/tootsuite/mastodon/pull/11245))
- Fix HTTP requests to IPv6 hosts ([ThibG](https://github.com/tootsuite/mastodon/pull/11240))
- Fix error in ElasticSearch index import ([mayaeh](https://github.com/tootsuite/mastodon/pull/11192))
- Fix duplicate account error when seeding development database ([ysksn](https://github.com/tootsuite/mastodon/pull/11366))
- Fix performance of session clean-up scheduler ([abcang](https://github.com/tootsuite/mastodon/pull/11871))
- Fix older migrations not running ([zunda](https://github.com/tootsuite/mastodon/pull/11377))
- Fix URLs counting towards RTL detection ([ahangarha](https://github.com/tootsuite/mastodon/pull/11759))
- Fix unnecessary status re-rendering in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11211))
- Fix http_parser.rb gem not being compiled when no network available ([petabyteboy](https://github.com/tootsuite/mastodon/pull/11444))
- Fix muted text color not applying to all text ([trwnh](https://github.com/tootsuite/mastodon/pull/11996))
- Fix follower/following lists resetting on back-navigation in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11986))
- Fix n+1 query when approving multiple follow requests ([abcang](https://github.com/tootsuite/mastodon/pull/12004))
- Fix records not being indexed into ElasticSearch sometimes ([Gargron](https://github.com/tootsuite/mastodon/pull/12024))
- Fix needlessly indexing unsearchable statuses into ElasticSearch ([Gargron](https://github.com/tootsuite/mastodon/pull/12041))
- Fix new user bootstrapping crashing when to-be-followed accounts are invalid ([ThibG](https://github.com/tootsuite/mastodon/pull/12037))
- Fix featured hashtag URL being interpreted as media or replies tab ([Gargron](https://github.com/tootsuite/mastodon/pull/12048))
- Fix account counters being overwritten by parallel writes ([Gargron](https://github.com/tootsuite/mastodon/pull/12045))

### Security

- Fix performance of GIF re-encoding and always strip EXIF data from videos ([Gargron](https://github.com/tootsuite/mastodon/pull/12057))

## [2.9.3] - 2019-08-10
### Added

- Add GIF and WebP support for custom emojis ([Gargron](https://github.com/tootsuite/mastodon/pull/11519))
- Add logout link to dropdown menu in web UI ([koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/11353))
- Add indication that text search is unavailable in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11112), [ThibG](https://github.com/tootsuite/mastodon/pull/11202))
- Add `suffix` to `Mastodon::Version` to help forks ([clarfon](https://github.com/tootsuite/mastodon/pull/11407))
- Add on-hover animation to animated custom emoji in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11348), [ThibG](https://github.com/tootsuite/mastodon/pull/11404), [ThibG](https://github.com/tootsuite/mastodon/pull/11522))
- Add custom emoji support in profile metadata labels ([ThibG](https://github.com/tootsuite/mastodon/pull/11350))

### Changed

- Change default interface of web and streaming from 0.0.0.0 to 127.0.0.1 ([Gargron](https://github.com/tootsuite/mastodon/pull/11302), [zunda](https://github.com/tootsuite/mastodon/pull/11378), [Gargron](https://github.com/tootsuite/mastodon/pull/11351), [zunda](https://github.com/tootsuite/mastodon/pull/11326))
- Change the retry limit of web push notifications ([highemerly](https://github.com/tootsuite/mastodon/pull/11292))
- Change ActivityPub deliveries to not retry HTTP 501 errors ([Gargron](https://github.com/tootsuite/mastodon/pull/11233))
- Change language detection to include hashtags as words ([Gargron](https://github.com/tootsuite/mastodon/pull/11341))
- Change terms and privacy policy pages to always be accessible ([Gargron](https://github.com/tootsuite/mastodon/pull/11334))
- Change robots tag to include `noarchive` when user opts out of indexing ([Kjwon15](https://github.com/tootsuite/mastodon/pull/11421))

### Fixed

- Fix account domain block not clearing out notifications ([Gargron](https://github.com/tootsuite/mastodon/pull/11393))
- Fix incorrect locale sometimes being detected for browser ([Gargron](https://github.com/tootsuite/mastodon/pull/8657))
- Fix crash when saving invalid domain name ([Gargron](https://github.com/tootsuite/mastodon/pull/11528))
- Fix pinned statuses REST API returning pagination headers ([Gargron](https://github.com/tootsuite/mastodon/pull/11526))
- Fix "cancel follow request" button having unreadable text in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11521))
- Fix image uploads being blank when canvas read access is blocked ([ThibG](https://github.com/tootsuite/mastodon/pull/11499))
- Fix avatars not being animated on hover when not logged in ([ThibG](https://github.com/tootsuite/mastodon/pull/11349))
- Fix overzealous sanitization of HTML lists ([ThibG](https://github.com/tootsuite/mastodon/pull/11354))
- Fix block crashing when a follow request exists ([ThibG](https://github.com/tootsuite/mastodon/pull/11288))
- Fix backup service crashing when an attachment is missing ([ThibG](https://github.com/tootsuite/mastodon/pull/11241))
- Fix account moderation action always sending e-mail notification ([Gargron](https://github.com/tootsuite/mastodon/pull/11242))
- Fix swiping columns on mobile sometimes failing in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11200))
- Fix wrong actor URI being serialized into poll updates ([ThibG](https://github.com/tootsuite/mastodon/pull/11194))
- Fix statsd UDP sockets not being cleaned up in Sidekiq ([Gargron](https://github.com/tootsuite/mastodon/pull/11230))
- Fix expiration date of filters being set to "never" when editing them ([ThibG](https://github.com/tootsuite/mastodon/pull/11204))
- Fix support for MP4 files that are actually M4V files ([Gargron](https://github.com/tootsuite/mastodon/pull/11210))
- Fix `alerts` not being typecast correctly in push subscription in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/11343))
- Fix some notices staying on unrelated pages ([ThibG](https://github.com/tootsuite/mastodon/pull/11364))
- Fix unboosting sometimes preventing a boost from reappearing on feed ([ThibG](https://github.com/tootsuite/mastodon/pull/11405), [Gargron](https://github.com/tootsuite/mastodon/pull/11450))
- Fix only one middle dot being recognized in hashtags ([Gargron](https://github.com/tootsuite/mastodon/pull/11345), [ThibG](https://github.com/tootsuite/mastodon/pull/11363))
- Fix unnecessary SQL query performed on unauthenticated requests ([Gargron](https://github.com/tootsuite/mastodon/pull/11179))
- Fix incorrect timestamp displayed on featured tags ([Kjwon15](https://github.com/tootsuite/mastodon/pull/11477))
- Fix privacy dropdown active state when dropdown is placed on top of it ([ThibG](https://github.com/tootsuite/mastodon/pull/11495))
- Fix filters not being applied to poll options ([ThibG](https://github.com/tootsuite/mastodon/pull/11174))
- Fix keyboard navigation on various dropdowns ([ThibG](https://github.com/tootsuite/mastodon/pull/11511), [ThibG](https://github.com/tootsuite/mastodon/pull/11492), [ThibG](https://github.com/tootsuite/mastodon/pull/11491))
- Fix keyboard navigation in modals ([ThibG](https://github.com/tootsuite/mastodon/pull/11493))
- Fix image conversation being non-deterministic due to timestamps ([Gargron](https://github.com/tootsuite/mastodon/pull/11408))
- Fix web UI performance ([ThibG](https://github.com/tootsuite/mastodon/pull/11211), [ThibG](https://github.com/tootsuite/mastodon/pull/11234))
- Fix scrolling to compose form when not necessary in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11246), [ThibG](https://github.com/tootsuite/mastodon/pull/11182))
- Fix save button being enabled when list title is empty in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11475))
- Fix poll expiration not being pre-filled on delete & redraft in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11203))
- Fix content warning sometimes being set when not requested in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11206))

### Security

- Fix invites not being disabled upon account suspension ([ThibG](https://github.com/tootsuite/mastodon/pull/11412))
- Fix blocked domains still being able to fill database with account records ([Gargron](https://github.com/tootsuite/mastodon/pull/11219))

## [2.9.2] - 2019-06-22
### Added


+ 2
- 2
Dockerfile View File

@@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep
SHELL ["bash", "-c"]

# Install Node
ENV NODE_VER="12.9.1"
ENV NODE_VER="12.11.1"
RUN echo "Etc/UTC" > /etc/localtime && \
apt update && \
apt -y install wget python && \
@@ -28,7 +28,7 @@ RUN apt update && \
make install_bin install_include install_lib

# Install ruby
ENV RUBY_VER="2.6.4"
ENV RUBY_VER="2.6.5"
ENV CPPFLAGS="-I/opt/jemalloc/include"
ENV LDFLAGS="-L/opt/jemalloc/lib/"
RUN apt update && \

+ 6
- 5
Gemfile View File

@@ -5,7 +5,7 @@ ruby '>= 2.4.0', '< 2.7.0'

gem 'pkg-config', '~> 1.3'

gem 'puma', '~> 4.1'
gem 'puma', '~> 4.2'
gem 'rails', '~> 5.2.3'
gem 'thor', '~> 0.20'

@@ -29,7 +29,7 @@ gem 'bootsnap', '~> 1.4', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.6'
gem 'iso-639'
gem 'chewy', '~> 5.0'
gem 'chewy', '~> 5.1'
gem 'cld3', '~> 3.2.4'
gem 'devise', '~> 4.7'
gem 'devise-two-factor', '~> 3.1'
@@ -44,13 +44,13 @@ gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.9'

gem 'discard', '~> 1.1'
gem 'doorkeeper', '~> 5.1'
gem 'doorkeeper', '~> 5.2'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'goldfinger', '~> 2.1'
gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.5'
gem 'health_check', '~> 3.0'
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
gem 'html2text'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 3.3'
@@ -68,6 +68,7 @@ gem 'oj', '~> 3.9'
gem 'ostatus2', '~> 2.0'
gem 'ox', '~> 2.11'
gem 'parslet'
gem 'parallel', '~> 1.17'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
gem 'pundit', '~> 2.1'
gem 'premailer-rails'
@@ -118,7 +119,7 @@ end
group :test do
gem 'capybara', '~> 3.29'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.3'
gem 'faker', '~> 2.4'
gem 'microformats', '~> 4.1'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0'

+ 31
- 24
Gemfile.lock View File

@@ -1,4 +1,12 @@
GIT
remote: https://github.com/ianheggie/health_check
revision: 0b799ead604f900ed50685e9b2d469cd2befba5b
ref: 0b799ead604f900ed50685e9b2d469cd2befba5b
specs:
health_check (4.0.0.pre)
rails (>= 4.0)

GIT
remote: https://github.com/rtomayko/posix-spawn
revision: 58465d2e213991f8afb13b984854a49fcdcc980c
ref: 58465d2e213991f8afb13b984854a49fcdcc980c
@@ -161,7 +169,7 @@ GEM
case_transform (0.2)
activesupport
charlock_holmes (0.7.6)
chewy (5.0.0)
chewy (5.1.0)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
@@ -209,19 +217,19 @@ GEM
docile (1.3.2)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.1.0)
doorkeeper (5.2.1)
railties (>= 5)
dotenv (2.7.5)
dotenv-rails (2.7.5)
dotenv (= 2.7.5)
railties (>= 3.2, < 6.1)
elasticsearch (6.0.2)
elasticsearch-api (= 6.0.2)
elasticsearch-transport (= 6.0.2)
elasticsearch-api (6.0.2)
elasticsearch (7.3.0)
elasticsearch-api (= 7.3.0)
elasticsearch-transport (= 7.3.0)
elasticsearch-api (7.3.0)
multi_json
elasticsearch-dsl (0.1.5)
elasticsearch-transport (6.0.2)
elasticsearch-dsl (0.1.8)
elasticsearch-transport (7.3.0)
faraday
multi_json
encryptor (3.0.0)
@@ -231,9 +239,9 @@ GEM
tzinfo
excon (0.62.0)
fabrication (2.20.2)
faker (2.3.0)
faker (2.4.0)
i18n (~> 1.6.0)
faraday (0.15.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
fast_blank (1.0.0)
fastimage (2.1.7)
@@ -278,8 +286,6 @@ GEM
concurrent-ruby (~> 1.0)
hashdiff (1.0.0)
hashie (3.6.0)
health_check (3.0.0)
railties (>= 5.0)
heapy (0.1.4)
highline (2.0.1)
hiredis (0.6.3)
@@ -372,10 +378,10 @@ GEM
mimemagic (0.3.3)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.11.3)
minitest (5.12.0)
msgpack (1.3.1)
multi_json (1.13.1)
multipart-post (2.0.0)
multipart-post (2.1.1)
necromancer (0.5.0)
net-ldap (0.16.1)
net-scp (2.0.0)
@@ -447,7 +453,7 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.1)
puma (4.1.1)
puma (4.2.0)
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)
@@ -503,7 +509,7 @@ GEM
rdf-normalize (0.3.3)
rdf (>= 2.2, < 4.0)
redcarpet (3.4.0)
redis (4.1.2)
redis (4.1.3)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
@@ -593,7 +599,7 @@ GEM
simple_form (4.1.0)
actionpack (>= 5.0)
activemodel (>= 5.0)
simplecov (0.17.0)
simplecov (0.17.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
@@ -649,7 +655,7 @@ GEM
uniform_notifier (1.12.1)
warden (1.2.8)
rack (>= 2.0.6)
webmock (3.7.3)
webmock (3.7.5)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -690,7 +696,7 @@ DEPENDENCIES
capistrano-yarn (~> 2.0)
capybara (~> 3.29)
charlock_holmes (~> 0.7.6)
chewy (~> 5.0)
chewy (~> 5.1)
cld3 (~> 3.2.4)
climate_control (~> 0.2)
concurrent-ruby
@@ -700,10 +706,10 @@ DEPENDENCIES
devise-two-factor (~> 3.1)
devise_pam_authenticatable2 (~> 9.2)
discard (~> 1.1)
doorkeeper (~> 5.1)
doorkeeper (~> 5.2)
dotenv-rails (~> 2.7)
fabrication (~> 2.20)
faker (~> 2.3)
faker (~> 2.4)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.1.0)
@@ -711,7 +717,7 @@ DEPENDENCIES
fuubar (~> 2.4)
goldfinger (~> 2.1)
hamlit-rails (~> 0.2)
health_check (~> 3.0)
health_check!
hiredis (~> 0.6)
html2text
htmlentities (~> 4.3)
@@ -746,6 +752,7 @@ DEPENDENCIES
ox (~> 2.11)
paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6)
parallel (~> 1.17)
parallel_tests (~> 2.29)
parslet
pg (~> 1.1)
@@ -756,7 +763,7 @@ DEPENDENCIES
private_address_check (~> 0.5)
pry-byebug (~> 3.7)
pry-rails (~> 0.3)
puma (~> 4.1)
puma (~> 4.2)
pundit (~> 2.1)
rack-attack (~> 6.1)
rack-cors (~> 1.0)
@@ -798,7 +805,7 @@ DEPENDENCIES
webpush

RUBY VERSION
ruby 2.6.1p33
ruby 2.6.5p114

BUNDLED WITH
1.17.3

+ 0
- 9
app.json View File

@@ -13,15 +13,6 @@
"description": "The domain that your Mastodon instance will run on (this can be appname.herokuapp.com or a custom domain)",
"required": true
},
"LOCAL_HTTPS": {
"description": "Will your domain support HTTPS? (Automatic for herokuapp, requires manual configuration for custom domains)",
"value": "false",
"required": true
},
"PAPERCLIP_SECRET": {
"description": "The secret key for storing media files",
"generator": "secret"
},
"SECRET_KEY_BASE": {
"description": "The secret key base",
"generator": "secret"

+ 4
- 4
app/chewy/statuses_index.rb View File

@@ -31,19 +31,19 @@ class StatusesIndex < Chewy::Index
},
}

define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do
define_type ::Status.unscoped.kept.without_reblogs.includes(:media_attachments), delete_if: ->(status) { status.searchable_by.empty? } do
crutch :mentions do |collection|
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end

crutch :favourites do |collection|
data = ::Favourite.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
data = ::Favourite.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end

crutch :reblogs do |collection|
data = ::Status.where(reblog_of_id: collection.map(&:id)).pluck(:reblog_of_id, :account_id)
data = ::Status.where(reblog_of_id: collection.map(&:id)).where(account: Account.local).pluck(:reblog_of_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end


+ 15
- 28
app/controllers/about_controller.rb View File

@@ -4,9 +4,7 @@ class AboutController < ApplicationController
before_action :set_pack
layout 'public'

before_action :require_open_federation!, only: [:show, :more, :blocks]
before_action :check_blocklist_enabled, only: [:blocks]
before_action :authenticate_user!, only: [:blocks], if: :blocklist_account_required?
before_action :require_open_federation!, only: [:show, :more]
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
before_action :set_expires_in, only: [:show, :more, :terms]
@@ -17,15 +15,20 @@ class AboutController < ApplicationController

def more
flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]

toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)

@contents = toc_generator.html
@table_of_contents = toc_generator.toc
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
end

def terms; end

def blocks
@show_rationale = Setting.show_domain_blocks_rationale == 'all'
@show_rationale |= Setting.show_domain_blocks_rationale == 'users' && !current_user.nil? && current_user.functional?
@blocks = DomainBlock.with_user_facing_limitations.order('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain').to_a
end
helper_method :display_blocks?
helper_method :display_blocks_rationale?
helper_method :public_fetch_mode?
helper_method :new_user

private

@@ -33,28 +36,14 @@ class AboutController < ApplicationController
not_found if whitelist_mode?
end

def check_blocklist_enabled
not_found if Setting.show_domain_blocks == 'disabled'
end

def blocklist_account_required?
Setting.show_domain_blocks == 'users'
def display_blocks?
Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
end

def block_severity_text(block)
if block.severity == 'suspend'
I18n.t('domain_blocks.suspension')
else
limitations = []
limitations << I18n.t('domain_blocks.media_block') if block.reject_media?
limitations << I18n.t('domain_blocks.silence') if block.severity == 'silence'
limitations.join(', ')
end
def display_blocks_rationale?
Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
end

helper_method :block_severity_text
helper_method :public_fetch_mode?

def new_user
User.new.tap do |user|
user.build_account
@@ -62,8 +51,6 @@ class AboutController < ApplicationController
end
end

helper_method :new_user

def set_pack
use_pack 'public'
end

+ 5
- 4
app/controllers/accounts_controller.rb View File

@@ -9,7 +9,8 @@ class AccountsController < ApplicationController
before_action :set_cache_headers
before_action :set_body_classes

skip_around_action :set_locale, if: -> { request.format == :json }
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format) }
skip_before_action :require_functional!

def show
respond_to do |format|
@@ -38,7 +39,7 @@ class AccountsController < ApplicationController
end

format.rss do
expires_in 0, public: true
expires_in 1.minute, public: true

@statuses = filtered_statuses.without_reblogs.without_replies.limit(PAGE_SIZE)
@statuses = cache_collection(@statuses, Status)
@@ -129,11 +130,11 @@ class AccountsController < ApplicationController
end

def media_requested?
request.path.ends_with?('/media')
request.path.ends_with?('/media') && !tag_requested?
end

def replies_requested?
request.path.ends_with?('/with_replies')
request.path.ends_with?('/with_replies') && !tag_requested?
end

def tag_requested?

+ 3
- 3
app/controllers/activitypub/collections_controller.rb View File

@@ -33,9 +33,9 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def scope_for_collection
case params[:id]
when 'featured'
@account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
scope.merge!(@account.pinned_statuses)
end
return Status.none if @account.blocking?(signed_request_account)
@account.pinned_statuses
else
raise ActiveRecord::RecordNotFound
end

+ 6
- 1
app/controllers/admin/relays_controller.rb View File

@@ -3,6 +3,7 @@
module Admin
class RelaysController < BaseController
before_action :set_relay, except: [:index, :new, :create]
before_action :require_signatures_enabled!, only: [:new, :create, :enable]

def index
authorize :relay, :update?
@@ -11,7 +12,7 @@ module Admin

def new
authorize :relay, :update?
@relay = Relay.new(inbox_url: Relay::PRESET_RELAY)
@relay = Relay.new
end

def create
@@ -54,5 +55,9 @@ module Admin
def resource_params
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?
end
end
end

+ 1
- 0
app/controllers/admin/two_factor_authentications_controller.rb View File

@@ -8,6 +8,7 @@ module Admin
authorize @user, :disable_2fa?
@user.disable_two_factor!
log_action :disable_2fa, @user
UserMailer.two_factor_disabled(@user).deliver_later!
redirect_to admin_accounts_path
end


+ 2
- 0
app/controllers/api/v1/accounts/statuses_controller.rb View File

@@ -57,6 +57,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end

def pinned_scope
return Status.none if @account.blocking?(current_account)

@account.pinned_statuses
end


+ 2
- 2
app/controllers/api/v1/accounts_controller.rb View File

@@ -33,7 +33,7 @@ class Api::V1::AccountsController < Api::BaseController
def follow
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))

options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }

render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end
@@ -78,7 +78,7 @@ class Api::V1::AccountsController < Api::BaseController
end

def account_params
params.permit(:username, :email, :password, :agreement, :locale)
params.permit(:username, :email, :password, :agreement, :locale, :reason)
end

def check_enabled_registrations

+ 1
- 1
app/controllers/api/v2/search_controller.rb View File

@@ -22,7 +22,7 @@ class Api::V2::SearchController < Api::BaseController
params[:q],
current_account,
limit_param(RESULTS_LIMIT),
search_params.merge(resolve: truthy_param?(:resolve))
search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed))
)
end


+ 29
- 0
app/controllers/auth/challenges_controller.rb View File

@@ -0,0 +1,29 @@
# frozen_string_literal: true

class Auth::ChallengesController < ApplicationController
include ChallengableConcern

layout 'auth'

before_action :set_pack
before_action :authenticate_user!

skip_before_action :require_functional!

def create
if challenge_passed?
session[:challenge_passed_at] = Time.now.utc
redirect_to challenge_params[:return_to]
else
@challenge = Form::Challenge.new(return_to: challenge_params[:return_to])
flash.now[:alert] = I18n.t('challenge.invalid_password')
render_challenge
end
end

private

def set_pack
use_pack 'auth'
end
end

+ 35
- 26
app/controllers/auth/sessions_controller.rb View File

@@ -9,6 +9,7 @@ class Auth::SessionsController < Devise::SessionsController
skip_before_action :require_functional!

prepend_before_action :set_pack
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]

before_action :set_instance_presenter, only: [:new]
before_action :set_body_classes
@@ -22,34 +23,32 @@ class Auth::SessionsController < Devise::SessionsController
end

def create
self.resource = begin
if user_params[:email].blank? && session[:otp_user_id].present?
User.find(session[:otp_user_id])
else
warden.authenticate!(auth_options)
end
end

if resource.otp_required_for_login?
if user_params[:otp_attempt].present? && session[:otp_user_id].present?
authenticate_with_two_factor_via_otp(resource)
else
prompt_for_two_factor(resource)
end
else
authenticate_and_respond(resource)
super do |resource|
remember_me(resource)
flash.delete(:notice)
end
end

def destroy
tmp_stored_location = stored_location_for(:user)
super
session.delete(:challenge_passed_at)
flash.delete(:notice)
store_location_for(:user, tmp_stored_location) if continue_after?
end

protected

def find_user
if session[:otp_user_id]
User.find(session[:otp_user_id])
else
user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
user ||= User.find_for_authentication(email: user_params[:email])
end
end

def user_params
params.require(:user).permit(:email, :password, :otp_attempt)
end
@@ -72,6 +71,10 @@ class Auth::SessionsController < Devise::SessionsController
super
end

def two_factor_enabled?
find_user&.otp_required_for_login?
end

def valid_otp_attempt?(user)
user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
@@ -79,10 +82,24 @@ class Auth::SessionsController < Devise::SessionsController
false
end

def authenticate_with_two_factor
user = self.resource = find_user

if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user.present? && (user.encrypted_password.blank? || user.valid_password?(user_params[:password]))
# If encrypted_password is blank, we got the user from LDAP or PAM,
# so credentials are already valid

prompt_for_two_factor(user)
end
end

def authenticate_with_two_factor_via_otp(user)
if valid_otp_attempt?(user)
session.delete(:otp_user_id)
authenticate_and_respond(user)
remember_me(user)
sign_in(user)
else
flash.now[:alert] = I18n.t('users.invalid_otp_token')
prompt_for_two_factor(user)
@@ -91,16 +108,10 @@ class Auth::SessionsController < Devise::SessionsController

def prompt_for_two_factor(user)
session[:otp_user_id] = user.id
@body_classes = 'lighter'
render :two_factor
end

def authenticate_and_respond(user)
sign_in(user)
remember_me(user)

respond_with user, location: after_sign_in_path_for(user)
end

private

def set_pack
@@ -117,11 +128,9 @@ class Auth::SessionsController < Devise::SessionsController

def home_paths(resource)
paths = [about_path]

if single_user_mode? && resource.is_a?(User)
paths << short_account_path(username: resource.account)
end

paths
end


+ 5
- 0
app/controllers/auth/setup_controller.rb View File

@@ -3,6 +3,7 @@
class Auth::SetupController < ApplicationController
layout 'auth'

before_action :set_pack
before_action :authenticate_user!
before_action :require_unconfirmed_or_pending!
before_action :set_body_classes
@@ -55,4 +56,8 @@ class Auth::SetupController < ApplicationController
def missing_email?
truthy_param?(:missing_email)
end

def set_pack
use_pack 'auth'
end
end

+ 65
- 0
app/controllers/concerns/challengable_concern.rb View File

@@ -0,0 +1,65 @@
# frozen_string_literal: true

# This concern is inspired by "sudo mode" on GitHub. It
# is a way to re-authenticate a user before allowing them
# to see or perform an action.
#
# Add `before_action :require_challenge!` to actions you
# want to protect.
#
# The user will be shown a page to enter the challenge (which
# is either the password, or just the username when no
# password exists). Upon passing, there is a grace period
# during which no challenge will be asked from the user.
#
# Accessing challenge-protected resources during the grace
# period will refresh the grace period.
module ChallengableConcern
extend ActiveSupport::Concern

CHALLENGE_TIMEOUT = 1.hour.freeze

def require_challenge!
return if skip_challenge?

if challenge_passed_recently?
session[:challenge_passed_at] = Time.now.utc
return
end

@challenge = Form::Challenge.new(return_to: request.url)

if params.key?(:form_challenge)
if challenge_passed?
session[:challenge_passed_at] = Time.now.utc
return
else
flash.now[:alert] = I18n.t('challenge.invalid_password')
render_challenge
end
else
render_challenge
end
end

def render_challenge
@body_classes = 'lighter'
render template: 'auth/challenges/new', layout: 'auth'
end

def challenge_passed?
current_user.valid_password?(challenge_params[:current_password])
end

def skip_challenge?
current_user.encrypted_password.blank?
end

def challenge_passed_recently?
session[:challenge_passed_at].present? && session[:challenge_passed_at] >= CHALLENGE_TIMEOUT.ago
end

def challenge_params
params.require(:form_challenge).permit(:current_password, :return_to)
end
end

+ 7
- 0
app/controllers/concerns/export_controller_concern.rb View File

@@ -5,7 +5,10 @@ module ExportControllerConcern

included do
before_action :authenticate_user!
before_action :require_not_suspended!
before_action :load_export

skip_before_action :require_functional!
end

private
@@ -27,4 +30,8 @@ module ExportControllerConcern
def export_filename
"#{controller_name}.csv"
end

def require_not_suspended!
forbidden if current_account.suspended?
end
end

+ 1
- 0
app/controllers/custom_css_controller.rb View File

@@ -2,6 +2,7 @@

class CustomCssController < ApplicationController
skip_before_action :store_current_location
skip_before_action :require_functional!

before_action :set_cache_headers


+ 2
- 0
app/controllers/directories_controller.rb View File

@@ -10,6 +10,8 @@ class DirectoriesController < ApplicationController
before_action :set_accounts
before_action :set_pack

skip_before_action :require_functional!

def index
render :index
end

+ 1
- 0
app/controllers/follower_accounts_controller.rb View File

@@ -8,6 +8,7 @@ class FollowerAccountsController < ApplicationController
before_action :set_cache_headers

skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!

def index
respond_to do |format|

+ 1
- 0
app/controllers/following_accounts_controller.rb View File

@@ -8,6 +8,7 @@ class FollowingAccountsController < ApplicationController
before_action :set_cache_headers

skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!

def index
respond_to do |format|

+ 1
- 0
app/controllers/manifests_controller.rb View File

@@ -2,6 +2,7 @@

class ManifestsController < ApplicationController
skip_before_action :store_current_location
skip_before_action :require_functional!

def show
expires_in 3.minutes, public: true

+ 1
- 0
app/controllers/media_controller.rb View File

@@ -4,6 +4,7 @@ class MediaController < ApplicationController
include Authorization

skip_before_action :store_current_location
skip_before_action :require_functional!

before_action :authenticate_user!, if: :whitelist_mode?
before_action :set_media_attachment

+ 1
- 0
app/controllers/media_proxy_controller.rb View File

@@ -4,6 +4,7 @@ class MediaProxyController < ApplicationController
include RoutingHelper

skip_before_action :store_current_location
skip_before_action :require_functional!

before_action :authenticate_user!, if: :whitelist_mode?


+ 2
- 0
app/controllers/remote_follow_controller.rb View File

@@ -8,6 +8,8 @@ class RemoteFollowController < ApplicationController
before_action :set_pack
before_action :set_body_classes

skip_before_action :require_functional!

def new
@remote_follow = RemoteFollow.new(session_params)
end

+ 2
- 0
app/controllers/remote_interaction_controller.rb View File

@@ -11,6 +11,8 @@ class RemoteInteractionController < ApplicationController
before_action :set_body_classes
before_action :set_pack

skip_before_action :require_functional!

def new
@remote_follow = RemoteFollow.new(session_params)
end

+ 43
- 0
app/controllers/settings/aliases_controller.rb View File

@@ -0,0 +1,43 @@
# frozen_string_literal: true

class Settings::AliasesController < Settings::BaseController
layout 'admin'

before_action :authenticate_user!
before_action :set_aliases, except: :destroy
before_action :set_alias, only: :destroy

def index
@alias = current_account.aliases.build
end

def create
@alias = current_account.aliases.build(resource_params)

if @alias.save
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
redirect_to settings_aliases_path, notice: I18n.t('aliases.created_msg')
else
render :index
end
end

def destroy
@alias.destroy!
redirect_to settings_aliases_path, notice: I18n.t('aliases.deleted_msg')
end

private

def resource_params
params.require(:account_alias).permit(:acct)
end

def set_alias
@alias = current_account.aliases.find(params[:id])
end

def set_aliases
@aliases = current_account.aliases.order(id: :desc).reject(&:new_record?)
end
end

+ 7
- 0
app/controllers/settings/exports_controller.rb View File

@@ -6,6 +6,9 @@ class Settings::ExportsController < Settings::BaseController
layout 'admin'

before_action :authenticate_user!
before_action :require_not_suspended!

skip_before_action :require_functional!

def show
@export = Export.new(current_account)
@@ -34,4 +37,8 @@ class Settings::ExportsController < Settings::BaseController
def lock_options
{ redis: Redis.current, key: "backup:#{current_user.id}" }
end

def require_not_suspended!
forbidden if current_account.suspended?
end
end

+ 45
- 0
app/controllers/settings/migration/redirects_controller.rb View File

@@ -0,0 +1,45 @@
# frozen_string_literal: true

class Settings::Migration::RedirectsController < Settings::BaseController
layout 'admin'

before_action :authenticate_user!
before_action :require_not_suspended!

skip_before_action :require_functional!

def new
@redirect = Form::Redirect.new
end

def create
@redirect = Form::Redirect.new(resource_params.merge(account: current_account))

if @redirect.valid_with_challenge?(current_user)
current_account.update!(moved_to_account: @redirect.target_account)
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct)
else
render :new
end
end

def destroy
if current_account.moved_to_account_id.present?
current_account.update!(moved_to_account: nil)
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
end

redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg')
end

private

def resource_params
params.require(:form_redirect).permit(:acct, :current_password, :current_username)
end

def require_not_suspended!
forbidden if current_account.suspended?
end
end

+ 28
- 11
app/controllers/settings/migrations_controller.rb View File

@@ -4,31 +4,48 @@ class Settings::MigrationsController < Settings::BaseController
layout 'admin'

before_action :authenticate_user!
before_action :require_not_suspended!
before_action :set_migrations
before_action :set_cooldown

skip_before_action :require_functional!

def show
@migration = Form::Migration.new(account: current_account.moved_to_account)
@migration = current_account.migrations.build
end

def update
@migration = Form::Migration.new(resource_params)
def create
@migration = current_account.migrations.build(resource_params)

if @migration.valid? && migration_account_changed?
current_account.update!(moved_to_account: @migration.account)
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
redirect_to settings_migration_path, notice: I18n.t('migrations.updated_msg')
if @migration.save_with_challenge(current_user)
MoveService.new.call(@migration)
redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct)
else
render :show
end
end

helper_method :on_cooldown?

private

def resource_params
params.require(:migration).permit(:acct)
params.require(:account_migration).permit(:acct, :current_password, :current_username)
end

def set_migrations
@migrations = current_account.migrations.includes(:target_account).order(id: :desc).reject(&:new_record?)
end

def set_cooldown
@cooldown = current_account.migrations.within_cooldown.first
end

def on_cooldown?
@cooldown.present?
end

def migration_account_changed?
current_account.moved_to_account_id != @migration.account&.id &&
current_account.id != @migration.account&.id
def require_not_suspended!
forbidden if current_account.suspended?
end
end

+ 5
- 0
app/controllers/settings/two_factor_authentication/confirmations_controller.rb View File

@@ -3,9 +3,12 @@
module Settings
module TwoFactorAuthentication
class ConfirmationsController < BaseController
include ChallengableConcern

layout 'admin'

before_action :authenticate_user!
before_action :require_challenge!
before_action :ensure_otp_secret

skip_before_action :require_functional!
@@ -22,6 +25,8 @@ module Settings
@recovery_codes = current_user.generate_otp_backup_codes!
current_user.save!

UserMailer.two_factor_enabled(current_user).deliver_later!

render 'settings/two_factor_authentication/recovery_codes/index'
else
flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')

+ 6
- 0
app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb View File

@@ -3,16 +3,22 @@
module Settings
module TwoFactorAuthentication
class RecoveryCodesController < BaseController
include ChallengableConcern

layout 'admin'

before_action :authenticate_user!
before_action :require_challenge!, on: :create

skip_before_action :require_functional!

def create
@recovery_codes = current_user.generate_otp_backup_codes!
current_user.save!

UserMailer.two_factor_recovery_codes_changed(current_user).deliver_later!
flash.now[:notice] = I18n.t('two_factor_authentication.recovery_codes_regenerated')

render :index
end
end

+ 4
- 0
app/controllers/settings/two_factor_authentications_controller.rb View File

@@ -2,10 +2,13 @@

module Settings
class TwoFactorAuthenticationsController < BaseController
include ChallengableConcern

layout 'admin'

before_action :authenticate_user!
before_action :verify_otp_required, only: [:create]
before_action :require_challenge!, only: [:create]

skip_before_action :require_functional!

@@ -23,6 +26,7 @@ module Settings
if acceptable_code?
current_user.otp_required_for_login = false
current_user.save!
UserMailer.two_factor_disabled(current_user).deliver_later!
redirect_to settings_two_factor_authentication_path
else
flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')

+ 1
- 0
app/controllers/statuses_controller.rb View File

@@ -19,6 +19,7 @@ class StatusesController < ApplicationController
before_action :set_autoplay, only: :embed

skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, only: [:show, :embed]

content_security_policy only: :embed do |p|
p.frame_ancestors(false)

+ 2
- 0
app/controllers/tags_controller.rb View File

@@ -13,6 +13,8 @@ class TagsController < ApplicationController
before_action :set_body_classes
before_action :set_instance_presenter

skip_before_action :require_functional!

def show
respond_to do |format|
format.html do

+ 19
- 0
app/controllers/well_known/nodeinfo_controller.rb View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true

module WellKnown
class NodeInfoController < ActionController::Base
include CacheConcern

before_action { response.headers['Vary'] = 'Accept' }

def index
expires_in 3.days, public: true
render_with_cache json: {}, serializer: NodeInfo::DiscoverySerializer, adapter: NodeInfo::Adapter, expires_in: 3.days, root: 'nodeinfo'
end

def show
expires_in 30.minutes, public: true
render_with_cache json: {}, serializer: NodeInfo::Serializer, adapter: NodeInfo::Adapter, expires_in: 30.minutes, root: 'nodeinfo'
end
end
end

+ 18
- 4
app/helpers/settings_helper.rb View File

@@ -8,6 +8,7 @@ module SettingsHelper
ast: 'Asturianu',
bg: 'Български',
bn: 'বাংলা',
br: 'Breton',
ca: 'Català',
co: 'Corsu',
cs: 'Čeština',
@@ -15,8 +16,11 @@ module SettingsHelper
da: 'Dansk',
de: 'Deutsch',
el: 'Ελληνικά',
en: 'English',
eo: 'Esperanto',
'es-AR': 'Español (Argentina)',
es: 'Español',
et: 'Eesti',
eu: 'Euskara',
fa: 'فارسی',
fi: 'Suomi',
@@ -37,32 +41,34 @@ module SettingsHelper
ko: '한국어',
lt: 'Lietuvių',
lv: 'Latviešu',
mk: 'Македонски',
ml: 'മലയാളം',
ms: 'Bahasa Melayu',
nl: 'Nederlands',
nn: 'Nynorsk',
no: 'Norsk',
oc: 'Occitan',
pl: 'Polski',
pt: 'Português',
'pt-PT': 'Português (Portugal)',
'pt-BR': 'Português (Brasil)',
'pt-PT': 'Português (Portugal)',
pt: 'Português',
ro: 'Română',
ru: 'Русский',
sk: 'Slovenčina',
sl: 'Slovenščina',
sq: 'Shqip',
sr: 'Српски',
'sr-Latn': 'Srpski (latinica)',
sr: 'Српски',
sv: 'Svenska',
ta: 'தமிழ்',
te: 'తెలుగు',
th: 'ไทย',
tr: 'Türkçe',
uk: 'Українська',
zh: '中文',
'zh-CN': '简体中文',
'zh-HK': '繁體中文(香港)',
'zh-TW': '繁體中文(臺灣)',
zh: '中文',
}.freeze

def human_locale(locale)
@@ -88,4 +94,12 @@ module SettingsHelper
'desktop'
end
end

def compact_account_link_to(account)
return if account.nil?

link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
end
end
end

+ 14
- 0
app/javascript/flavours/glitch/actions/blocks.js View File

@@ -1,6 +1,7 @@
import api, { getLinks } from 'flavours/glitch/util/api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { openModal } from './modal';

export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS';
@@ -10,6 +11,8 @@ export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST';
export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS';
export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL';

export const BLOCKS_INIT_MODAL = 'BLOCKS_INIT_MODAL';

export function fetchBlocks() {
return (dispatch, getState) => {
dispatch(fetchBlocksRequest());
@@ -83,3 +86,14 @@ export function expandBlocksFail(error) {
error,
};
};

export function initBlockModal(account) {
return dispatch => {
dispatch({
type: BLOCKS_INIT_MODAL,
account,
});

dispatch(openModal('BLOCK'));
};
}

+ 3
- 2
app/javascript/flavours/glitch/actions/compose.js View File

@@ -261,7 +261,7 @@ export function uploadCompose(files) {
progress[i] = loaded;
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
},
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
}).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
}).catch(error => dispatch(uploadComposeFail(error)));
};
};
@@ -316,10 +316,11 @@ export function uploadComposeProgress(loaded, total) {
};
};

export function uploadComposeSuccess(media) {
export function uploadComposeSuccess(media, file) {
return {
type: COMPOSE_UPLOAD_SUCCESS,
media: media,
file: file,
skipLoading: true,
};
};

+ 28
- 0
app/javascript/flavours/glitch/actions/conversations.js View File

@@ -15,6 +15,10 @@ export const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE';

export const CONVERSATIONS_READ = 'CONVERSATIONS_READ';

export const CONVERSATIONS_DELETE_REQUEST = 'CONVERSATIONS_DELETE_REQUEST';
export const CONVERSATIONS_DELETE_SUCCESS = 'CONVERSATIONS_DELETE_SUCCESS';
export const CONVERSATIONS_DELETE_FAIL = 'CONVERSATIONS_DELETE_FAIL';

export const mountConversations = () => ({
type: CONVERSATIONS_MOUNT,
});
@@ -82,3 +86,27 @@ export const updateConversations = conversation => dispatch => {
conversation,
});
};

export const deleteConversation = conversationId => (dispatch, getState) => {
dispatch(deleteConversationRequest(conversationId));

api(getState).delete(`/api/v1/conversations/${conversationId}`)
.then(() => dispatch(deleteConversationSuccess(conversationId)))
.catch(error => dispatch(deleteConversationFail(conversationId, error)));
};

export const deleteConversationRequest = id => ({
type: CONVERSATIONS_DELETE_REQUEST,
id,
});

export const deleteConversationSuccess = id => ({
type: CONVERSATIONS_DELETE_SUCCESS,
id,
});

export const deleteConversationFail = (id, error) => ({
type: CONVERSATIONS_DELETE_FAIL,
id,
error,
});

+ 2
- 1
app/javascript/flavours/glitch/actions/importer/normalizer.js View File

@@ -71,8 +71,9 @@ export function normalizePoll(poll) {

const emojiMap = makeEmojiMap(normalPoll);

normalPoll.options = poll.options.map(option => ({
normalPoll.options = poll.options.map((option, index) => ({
...option,
voted: poll.own_votes && poll.own_votes.includes(index),
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
}));


+ 17
- 11
app/javascript/flavours/glitch/components/avatar_composite.js View File

@@ -35,35 +35,35 @@ export default class AvatarComposite extends React.PureComponent {

if (size === 2) {
if (index === 0) {
right = '2px';
right = '1px';
} else {
left = '2px';
left = '1px';
}
} else if (size === 3) {
if (index === 0) {
right = '2px';
right = '1px';
} else if (index > 0) {
left = '2px';
left = '1px';
}

if (index === 1) {
bottom = '2px';
bottom = '1px';
} else if (index > 1) {
top = '2px';
top = '1px';
}
} else if (size === 4) {
if (index === 0 || index === 2) {
right = '2px';
right = '1px';
}

if (index === 1 || index === 3) {
left = '2px';
left = '1px';
}

if (index < 2) {
bottom = '2px';
bottom = '1px';
} else {
top = '2px';
top = '1px';
}
}

@@ -96,7 +96,13 @@ export default class AvatarComposite extends React.PureComponent {

return (
<div className='account__avatar-composite' style={{ width: `${size}px`, height: `${size}px` }}>
{accounts.take(4).map((account, i) => this.renderItem(account, accounts.size, i))}
{accounts.take(4).map((account, i) => this.renderItem(account, Math.min(accounts.size, 4), i))}

{accounts.size > 4 && (
<span className='account__avatar-composite__label'>
+{accounts.size - 4}
</span>
)}
</div>
);
}

+ 1
- 1
app/javascript/flavours/glitch/components/column_back_button_slim.js View File

@@ -1,7 +1,7 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import Icon from 'mastodon/components/icon';
import Icon from 'flavours/glitch/components/icon';

export default class ColumnBackButtonSlim extends React.PureComponent {


+ 21
- 6
app/javascript/flavours/glitch/components/poll.js View File

@@ -10,9 +10,11 @@ import spring from 'react-motion/lib/spring';
import escapeTextContentForBrowser from 'escape-html';
import emojify from 'flavours/glitch/util/emoji';
import RelativeTimestamp from './relative_timestamp';
import Icon from 'flavours/glitch/components/icon';

const messages = defineMessages({
closed: { id: 'poll.closed', defaultMessage: 'Closed' },
voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer', description: 'Tooltip of the "voted" checkmark in polls' },
});

const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
@@ -99,10 +101,12 @@ class Poll extends ImmutablePureComponent {
};

renderOption (option, optionIndex, showResults) {
const { poll, disabled } = this.props;
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
const active = !!this.state.selected[`${optionIndex}`];
const { poll, disabled, intl } = this.props;
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
const active = !!this.state.selected[`${optionIndex}`];
const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex));

let titleEmojified = option.get('title_emojified');
if (!titleEmojified) {
@@ -131,7 +135,10 @@ class Poll extends ImmutablePureComponent {
/>

{!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />}
{showResults && <span className='poll__number'>{Math.round(percent)}%</span>}
{showResults && <span className='poll__number'>
{!!voted && <Icon id='check' className='poll__vote__mark' title={intl.formatMessage(messages.voted)} />}
{Math.round(percent)}%
</span>}

<span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
</label>
@@ -151,6 +158,14 @@ class Poll extends ImmutablePureComponent {
const showResults = poll.get('voted') || expired;
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);

let votesCount = null;

if (poll.get('voters_count') !== null && poll.get('voters_count') !== undefined) {
votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get('voters_count') }} />;
} else {
votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />;
}

return (
<div className='poll'>
<ul>
@@ -160,7 +175,7 @@ class Poll extends ImmutablePureComponent {
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
<FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />
{votesCount}
{poll.get('expires_at') && <span> · {timeRemaining}</span>}
</div>
</div>

+ 3
- 14
app/javascript/flavours/glitch/containers/status_container.js View File

@@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux';
import Status from 'flavours/glitch/components/status';
import { List as ImmutableList } from 'immutable';
@@ -18,9 +17,9 @@ import {
pin,
unpin,
} from 'flavours/glitch/actions/interactions';
import { blockAccount } from 'flavours/glitch/actions/accounts';
import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses';
import { initMuteModal } from 'flavours/glitch/actions/mutes';
import { initBlockModal } from 'flavours/glitch/actions/blocks';
import { initReport } from 'flavours/glitch/actions/reports';
import { openModal } from 'flavours/glitch/actions/modal';
import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
@@ -37,10 +36,8 @@ const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
unfilterConfirm: { id: 'confirmations.unfilter.confirm', defaultMessage: 'Show' },
author: { id: 'confirmations.unfilter.author', defaultMessage: 'Author' },
matchingFilters: { id: 'confirmations.unfilter.filters', defaultMessage: 'Matching {count, plural, one {filter} other {filters}}' },
@@ -83,6 +80,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
onReply (status, router) {
dispatch((_, getState) => {
let state = getState();

if (state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.replyMessage),
@@ -186,16 +184,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({

onBlock (status) {
const account = status.get('account');
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account, status));
},
}));
dispatch(initBlockModal(account));
},

onUnfilter (status, onConfirm) {

+ 3
- 1
app/javascript/flavours/glitch/features/account_gallery/components/media_item.js View File

@@ -118,6 +118,7 @@ export default class MediaItem extends ImmutablePureComponent {
);
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
const autoPlay = !isIOS() && autoPlayGif;
const label = attachment.get('type') === 'video' ? <Icon id='play' /> : 'GIF';