Browse Source

Merge branch 'main' of https://github.com/mastodon/mastodon

Conflicts:
	Gemfile.lock
	app/controllers/api/v1/statuses_controller.rb
	app/helpers/languages_helper.rb
	app/javascript/mastodon/actions/compose.js
	app/javascript/mastodon/features/compose/components/compose_form.js
	app/javascript/mastodon/locales/da.json
	app/javascript/mastodon/reducers/compose.js
	chart/values.yaml
	config/locales/fi.yml
	config/locales/lv.yml
	config/locales/simple_form.de.yml
	db/schema.rb
main
Lerk 3 months ago
parent
commit
d0e6c472bb
  1. 2
      .circleci/config.yml
  2. 8
      .github/workflows/build-image.yml
  3. 2
      .github/workflows/check-i18n.yml
  4. 2
      Aptfile
  5. 45
      CHANGELOG.md
  6. 4
      Dockerfile
  7. 18
      Gemfile
  8. 21
      Gemfile.lock
  9. 2
      Vagrantfile
  10. 32
      app/controllers/activitypub/replies_controller.rb
  11. 4
      app/controllers/admin/accounts_controller.rb
  12. 1
      app/controllers/admin/dashboard_controller.rb
  13. 40
      app/controllers/admin/disputes/appeals_controller.rb
  14. 3
      app/controllers/admin/relationships_controller.rb
  15. 50
      app/controllers/admin/reports/actions_controller.rb
  16. 9
      app/controllers/admin/statuses_controller.rb
  17. 6
      app/controllers/api/v1/media_controller.rb
  18. 2
      app/controllers/api/v1/reports_controller.rb
  19. 2
      app/controllers/api/v1/statuses/histories_controller.rb
  20. 53
      app/controllers/api/v1/statuses_controller.rb
  21. 12
      app/controllers/api/web/push_subscriptions_controller.rb
  22. 11
      app/controllers/auth/registrations_controller.rb
  23. 29
      app/controllers/concerns/localized.rb
  24. 26
      app/controllers/disputes/appeals_controller.rb
  25. 18
      app/controllers/disputes/base_controller.rb
  26. 17
      app/controllers/disputes/strikes_controller.rb
  27. 1
      app/controllers/instance_actors_controller.rb
  28. 4
      app/helpers/admin/account_moderation_notes_helper.rb
  29. 2
      app/helpers/admin/action_logs_helper.rb
  30. 20
      app/helpers/admin/trends/statuses_helper.rb
  31. 54
      app/helpers/context_helper.rb
  32. 80
      app/helpers/jsonld_helper.rb
  33. 305
      app/helpers/languages_helper.rb
  34. 2
      app/helpers/settings_helper.rb
  35. 43
      app/javascript/mastodon/actions/compose.js
  36. 37
      app/javascript/mastodon/actions/history.js
  37. 13
      app/javascript/mastodon/actions/notifications.js
  38. 37
      app/javascript/mastodon/actions/statuses.js
  39. 149
      app/javascript/mastodon/components/dropdown_menu.js
  40. 27
      app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js
  41. 70
      app/javascript/mastodon/components/edited_timestamp/index.js
  42. 34
      app/javascript/mastodon/components/inline_account.js
  43. 27
      app/javascript/mastodon/components/loading_indicator.js
  44. 23
      app/javascript/mastodon/components/relative_timestamp.js
  45. 6
      app/javascript/mastodon/components/status_action_bar.js
  46. 5
      app/javascript/mastodon/containers/status_container.js
  47. 8
      app/javascript/mastodon/features/compose/components/compose_form.js
  48. 15
      app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
  49. 4
      app/javascript/mastodon/features/compose/components/privacy_dropdown.js
  50. 5
      app/javascript/mastodon/features/compose/components/upload.js
  51. 1
      app/javascript/mastodon/features/compose/containers/compose_form_container.js
  52. 17
      app/javascript/mastodon/features/compose/containers/reply_indicator_container.js
  53. 1
      app/javascript/mastodon/features/compose/containers/upload_container.js
  54. 11
      app/javascript/mastodon/features/notifications/components/column_settings.js
  55. 35
      app/javascript/mastodon/features/notifications/components/notification.js
  56. 7
      app/javascript/mastodon/features/status/components/action_bar.js
  57. 5
      app/javascript/mastodon/features/status/components/detailed_status.js
  58. 6
      app/javascript/mastodon/features/status/index.js
  59. 79
      app/javascript/mastodon/features/ui/components/compare_history_modal.js
  60. 6
      app/javascript/mastodon/features/ui/components/modal_root.js
  61. 4
      app/javascript/mastodon/features/ui/util/async-components.js
  62. 22
      app/javascript/mastodon/locales/af.json
  63. 90
      app/javascript/mastodon/locales/ar.json
  64. 48
      app/javascript/mastodon/locales/ast.json
  65. 22
      app/javascript/mastodon/locales/bg.json
  66. 22
      app/javascript/mastodon/locales/bn.json
  67. 248
      app/javascript/mastodon/locales/br.json
  68. 54
      app/javascript/mastodon/locales/ca.json
  69. 30
      app/javascript/mastodon/locales/co.json
  70. 42
      app/javascript/mastodon/locales/cs.json
  71. 22
      app/javascript/mastodon/locales/cy.json
  72. 298
      app/javascript/mastodon/locales/da.json
  73. 38
      app/javascript/mastodon/locales/de.json
  74. 95
      app/javascript/mastodon/locales/defaultMessages.json
  75. 36
      app/javascript/mastodon/locales/el.json
  76. 17
      app/javascript/mastodon/locales/en.json
  77. 36
      app/javascript/mastodon/locales/eo.json
  78. 40
      app/javascript/mastodon/locales/es-AR.json
  79. 40
      app/javascript/mastodon/locales/es-MX.json
  80. 38
      app/javascript/mastodon/locales/es.json
  81. 22
      app/javascript/mastodon/locales/et.json
  82. 38
      app/javascript/mastodon/locales/eu.json
  83. 110
      app/javascript/mastodon/locales/fa.json
  84. 90
      app/javascript/mastodon/locales/fi.json
  85. 78
      app/javascript/mastodon/locales/fr.json
  86. 22
      app/javascript/mastodon/locales/ga.json
  87. 38
      app/javascript/mastodon/locales/gd.json
  88. 42
      app/javascript/mastodon/locales/gl.json
  89. 84
      app/javascript/mastodon/locales/he.json
  90. 22
      app/javascript/mastodon/locales/hi.json
  91. 60
      app/javascript/mastodon/locales/hr.json
  92. 40
      app/javascript/mastodon/locales/hu.json
  93. 36
      app/javascript/mastodon/locales/hy.json
  94. 56
      app/javascript/mastodon/locales/id.json
  95. 22
      app/javascript/mastodon/locales/io.json
  96. 38
      app/javascript/mastodon/locales/is.json
  97. 116
      app/javascript/mastodon/locales/it.json
  98. 40
      app/javascript/mastodon/locales/ja.json
  99. 22
      app/javascript/mastodon/locales/ka.json
  100. 26
      app/javascript/mastodon/locales/kab.json

2
.circleci/config.yml

@ -32,7 +32,7 @@ commands:
name: Install system dependencies
command: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
sudo apt-get install -y libicu-dev libidn11-dev
install-ruby-dependencies:
parameters:
ruby-version:

8
.github/workflows/build-image.yml

@ -6,11 +6,16 @@ on:
- "main"
tags:
- "*"
pull_request:
paths:
- .github/workflows/build-image.yml
- Dockerfile
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1
with:
@ -28,7 +33,8 @@ jobs:
- uses: docker/build-push-action@v2
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=tootsuite/mastodon:latest
cache-to: type=inline

2
.github/workflows/check-i18n.yml

@ -18,7 +18,7 @@ jobs:
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:

2
Aptfile

@ -4,10 +4,8 @@ libicu-dev
libidn11
libidn11-dev
libpq-dev
libprotobuf-dev
libxdamage1
libxfixes3
protobuf-compiler
zlib1g-dev
libcairo2
libcroco3

45
CHANGELOG.md

@ -3,6 +3,51 @@ Changelog
All notable changes to this project will be documented in this file.
## [3.4.6] - 2022-02-03
### Fixed
- Fix `mastodon:webpush:generate_vapid_key` task requiring a functional environment ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17338))
- Fix spurious errors when receiving an Add activity for a private post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17425))
### Security
- Fix error-prone SQL queries ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15828))
- Fix not compacting incoming signed JSON-LD activities ([puckipedia](https://github.com/mastodon/mastodon/pull/17426), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17428)) (CVE-2022-24307)
- Fix insufficient sanitization of report comments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17430))
- Fix stop condition of a Common Table Expression ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17427))
- Disable legacy XSS filtering ([Wonderfall](https://github.com/mastodon/mastodon/pull/17289))
## [3.4.5] - 2022-01-31
### Added
- Add more advanced migration tests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17393))
- Add github workflow to build Docker images ([unasuke](https://github.com/mastodon/mastodon/pull/16973), [Gargron](https://github.com/mastodon/mastodon/pull/16980), [Gargron](https://github.com/mastodon/mastodon/pull/17000))
### Fixed
- Fix some old migrations failing when skipping releases ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17394))
- Fix migrations script failing in certain edge cases ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17398))
- Fix Docker build ([tribela](https://github.com/mastodon/mastodon/pull/17188))
- Fix Ruby 3.0 dependencies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16723))
- Fix followers synchronization mechanism ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16510))
## [3.4.4] - 2021-11-26
### Fixed
- Fix error when suspending user with an already blocked canonical email ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17036))
- Fix overflow of long profile fields in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17010))
- Fix confusing error when WebFinger request returns empty document ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16986))
- Fix upload of remote media with OpenStack Swift sometimes failing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16998))
- Fix logout link not working in Safari ([noellabo](https://github.com/mastodon/mastodon/pull/16574))
- Fix “open” link of media modal not closing modal in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16524))
- Fix replying from modal in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16516))
- Fix `mastodon:setup` command crashing in some circumstances ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16976))
### Security
- Fix filtering DMs from non-followed users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17042))
- Fix handling of recursive toots in WebUI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17041))
## [3.4.3] - 2021-11-06
### Fixed

4
Dockerfile

@ -51,7 +51,7 @@ RUN npm install -g npm@latest && \
gem install bundler && \
apt-get update && \
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
libpq-dev libprotobuf-dev protobuf-compiler shared-mime-info
libpq-dev shared-mime-info
COPY Gemfile* package.json yarn.lock /opt/mastodon/
@ -88,7 +88,7 @@ RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selectio
RUN apt-get update && \
apt-get -y --no-install-recommends install \
libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \
libicu66 libprotobuf17 libidn11 libyaml-0-2 \
libicu66 libidn11 libyaml-0-2 \
file ca-certificates tzdata libreadline8 gcc tini apt-utils && \
ln -s /opt/mastodon /mastodon && \
gem install bundler && \

18
Gemfile

@ -6,7 +6,7 @@ ruby '>= 2.5.0', '< 3.1.0'
gem 'pkg-config', '~> 1.4'
gem "rexml", "~> 3.2"
gem 'puma', '~> 5.5'
gem 'puma', '~> 5.6'
gem 'rails', '~> 6.1.4'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
@ -18,20 +18,18 @@ gem 'makara', '~> 0.5'
gem 'pghero', '~> 2.8'
gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.111', require: false
gem 'aws-sdk-s3', '~> 1.112', require: false
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'kt-paperclip', '~> 7.0'
gem 'kt-paperclip', '~> 7.1'
gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.10.2', require: false
gem 'bootsnap', '~> 1.10.3', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639'
gem 'chewy', '~> 7.2'
gem 'cld3', '~> 3.4.4'
gem 'devise', '~> 4.8'
gem 'devise-two-factor', '~> 4.0'
@ -78,7 +76,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.5'
gem 'scenic', '~> 1.6'
gem 'sidekiq', '~> 6.4'
gem 'sidekiq-scheduler', '~> 3.1'
gem 'sidekiq-unique-jobs', '~> 7.1'
@ -100,12 +98,12 @@ gem 'json-ld-preloaded', '~> 3.2'
gem 'rdf-normalize', '~> 0.5'
group :development, :test do
gem 'fabrication', '~> 2.24'
gem 'fabrication', '~> 2.27'
gem 'fuubar', '~> 2.5'
gem 'i18n-tasks', '~> 0.9', require: false
gem 'pry-byebug', '~> 3.9'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 5.0'
gem 'rspec-rails', '~> 5.1'
end
group :production, :test do
@ -126,7 +124,7 @@ end
group :development do
gem 'active_record_query_trace', '~> 1.8'
gem 'annotate', '~> 3.1'
gem 'annotate', '~> 3.2'
gem 'better_errors', '~> 2.9'
gem 'binding_of_caller', '~> 1.0'
gem 'bullet', '~> 7.0'

21
Gemfile.lock

@ -152,8 +152,6 @@ GEM
elasticsearch (>= 7.12.0, < 7.14.0)
elasticsearch-dsl
chunky_png (1.4.0)
cld3 (3.4.4)
ffi (>= 1.1.0, < 1.16.0)
climate_control (0.2.0)
coderay (1.1.3)
color_diff (0.1)
@ -306,7 +304,6 @@ GEM
idn-ruby (0.1.4)
ipaddr (1.2.4)
ipaddress (0.8.3)
iso-639 (0.3.5)
jmespath (1.6.0)
json (2.6.1)
json-canonicalization (0.3.0)
@ -695,12 +692,12 @@ DEPENDENCIES
active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.8)
addressable (~> 2.8)
annotate (~> 3.1)
aws-sdk-s3 (~> 1.111)
annotate (~> 3.2)
aws-sdk-s3 (~> 1.112)
better_errors (~> 2.9)
binding_of_caller (~> 1.0)
blurhash (~> 0.1)
bootsnap (~> 1.10.2)
bootsnap (~> 1.10.3)
brakeman (~> 5.2)
browser
bullet (~> 7.0)
@ -712,7 +709,6 @@ DEPENDENCIES
capybara (~> 3.36)
charlock_holmes (~> 0.7.7)
chewy (~> 7.2)
cld3 (~> 3.4.4)
climate_control (~> 0.2)
color_diff (~> 0.1)
concurrent-ruby
@ -724,7 +720,7 @@ DEPENDENCIES
doorkeeper (~> 5.5)
dotenv-rails (~> 2.7)
ed25519 (~> 1.3)
fabrication (~> 2.24)
fabrication (~> 2.27)
faker (~> 2.19)
fast_blank (~> 1.0)
fastimage
@ -739,11 +735,10 @@ DEPENDENCIES
httplog (~> 1.5.0)
i18n-tasks (~> 0.9)
idn-ruby
iso-639
json-ld
json-ld-preloaded (~> 3.2)
kaminari (~> 1.2)
kt-paperclip (~> 7.0)
kt-paperclip (~> 7.1)
letter_opener (~> 1.7)
letter_opener_web (~> 2.0)
link_header (~> 0.0)
@ -773,7 +768,7 @@ DEPENDENCIES
prometheus_exporter (~> 0.8.0)
pry-byebug (~> 3.9)
pry-rails (~> 0.3)
puma (~> 5.5)
puma (~> 5.6)
pundit (~> 2.1)
rack (~> 2.2.3)
rack-attack (~> 6.5)
@ -789,14 +784,14 @@ DEPENDENCIES
resolv (~> 0.1.0)
rexml (~> 3.2)
rqrcode (~> 2.1)
rspec-rails (~> 5.0)
rspec-rails (~> 5.1)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.5)
rubocop (~> 1.25)
rubocop-rails (~> 2.13)
ruby-progressbar (~> 1.11)
sanitize (~> 6.0)
scenic (~> 1.5)
scenic (~> 1.6)
sidekiq (~> 6.4)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.1)

2
Vagrantfile

@ -33,11 +33,9 @@ sudo apt-get install \
redis-tools \
postgresql \
postgresql-contrib \
protobuf-compiler \
yarn \
libicu-dev \
libidn11-dev \
libprotobuf-dev \
libreadline-dev \
libpam0g-dev \
-y

32
app/controllers/activitypub/replies_controller.rb

@ -63,15 +63,29 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end
def next_page
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
account_status_replies_url(
@account,
@status,
page: true,
min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
only_other_accounts: only_other_accounts
)
if only_other_accounts?
# Only consider remote accounts
return nil if @replies.size < DESCENDANTS_LIMIT
account_status_replies_url(
@account,
@status,
page: true,
min_id: @replies&.last&.id,
only_other_accounts: true
)
else
# For now, we're serving only self-replies, but next page might be other accounts
next_only_other_accounts = @replies&.last&.account_id != @account.id || @replies.size < DESCENDANTS_LIMIT
account_status_replies_url(
@account,
@status,
page: true,
min_id: next_only_other_accounts ? nil : @replies&.last&.id,
only_other_accounts: next_only_other_accounts
)
end
end
def page_params

4
app/controllers/admin/accounts_controller.rb

@ -28,7 +28,7 @@ module Admin
@deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.strikes.custom.latest
@warnings = @account.strikes.includes(:target_account, :account, :appeal).latest
@domain_block = DomainBlock.rule_for(@account.domain)
end
@ -146,7 +146,7 @@ module Admin
end
def filter_params
params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
params.slice(:page, *AccountFilter::KEYS).permit(:page, *AccountFilter::KEYS)
end
def form_account_batch_params

1
app/controllers/admin/dashboard_controller.rb

@ -8,6 +8,7 @@ module Admin
@pending_users_count = User.pending.count
@pending_reports_count = Report.unresolved.count
@pending_tags_count = Tag.pending_review.count
@pending_appeals_count = Appeal.pending.count
end
private

40
app/controllers/admin/disputes/appeals_controller.rb

@ -0,0 +1,40 @@
# frozen_string_literal: true
class Admin::Disputes::AppealsController < Admin::BaseController
before_action :set_appeal, except: :index
def index
authorize :appeal, :index?
@appeals = filtered_appeals.page(params[:page])
end
def approve
authorize @appeal, :approve?
log_action :approve, @appeal
ApproveAppealService.new.call(@appeal, current_account)
redirect_to disputes_strike_path(@appeal.strike)
end
def reject
authorize @appeal, :approve?
log_action :reject, @appeal
@appeal.reject!(current_account)
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
redirect_to disputes_strike_path(@appeal.strike)
end
private
def filtered_appeals
Admin::AppealFilter.new(filter_params.with_defaults(status: 'pending')).results.includes(strike: :account)
end
def filter_params
params.slice(:page, *Admin::AppealFilter::KEYS).permit(:page, *Admin::AppealFilter::KEYS)
end
def set_appeal
@appeal = Appeal.find(params[:id])
end
end

3
app/controllers/admin/relationships_controller.rb

@ -9,7 +9,8 @@ module Admin
def index
authorize :account, :index?
@accounts = RelationshipFilter.new(@account, filter_params).results.page(params[:page]).per(PER_PAGE)
@accounts = RelationshipFilter.new(@account, filter_params).results.includes(:account_stat, user: [:ips, :invite_request]).page(params[:page]).per(PER_PAGE)
@form = Form::AccountBatch.new
end
private

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

@ -0,0 +1,50 @@
# frozen_string_literal: true
class Admin::Reports::ActionsController < Admin::BaseController
before_action :set_report
def create
authorize @report, :show?
case action_from_button
when 'delete'
status_batch_action = Admin::StatusBatchAction.new(
type: action_from_button,
status_ids: @report.status_ids,
current_account: current_account,
report_id: @report.id,
send_email_notification: !@report.spam?
)
status_batch_action.save!
when 'silence', 'suspend'
account_action = Admin::AccountAction.new(
type: action_from_button,
report_id: @report.id,
target_account: @report.target_account,
current_account: current_account,
send_email_notification: !@report.spam?
)
account_action.save!
end
redirect_to admin_reports_path
end
private
def set_report
@report = Report.find(params[:report_id])
end
def action_from_button
if params[:delete]
'delete'
elsif params[:silence]
'silence'
elsif params[:suspend]
'suspend'
end
end
end

9
app/controllers/admin/statuses_controller.rb

@ -29,8 +29,9 @@ module Admin
end
def after_create_redirect_path
if @status_batch_action.report_id.present?
admin_report_path(@status_batch_action.report_id)
report_id = @status_batch_action&.report_id || params[:report_id]
if report_id.present?
admin_report_path(report_id)
else
admin_account_statuses_path(params[:account_id], current_params)
end
@ -48,6 +49,10 @@ module Admin
params.slice(*Admin::StatusFilter::KEYS).permit(*Admin::StatusFilter::KEYS)
end
def current_params
params.slice(:media, :page).permit(:media, :page)
end
def action_from_button
if params[:report]
'report'

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

@ -20,7 +20,7 @@ class Api::V1::MediaController < Api::BaseController
end
def update
@media_attachment.update!(media_attachment_params)
@media_attachment.update!(updateable_media_attachment_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
@ -42,6 +42,10 @@ class Api::V1::MediaController < Api::BaseController
params.permit(:file, :thumbnail, :description, :focus)
end
def updateable_media_attachment_params
params.permit(:thumbnail, :description, :focus)
end
def file_type_error
{ error: 'File type of uploaded media could not be verified' }
end

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

@ -33,6 +33,6 @@ class Api::V1::ReportsController < Api::BaseController
end
def report_params
params.permit(:account_id, :comment, :forward, status_ids: [])
params.permit(:account_id, :comment, :category, :forward, status_ids: [], rule_ids: [])
end
end

2
app/controllers/api/v1/statuses/histories_controller.rb

@ -7,7 +7,7 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
before_action :set_status
def show
render json: @status.edits, each_serializer: REST::StatusEditSerializer
render json: @status.edits.includes(:account, status: [:account]), each_serializer: REST::StatusEditSerializer
end
private

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

@ -3,8 +3,8 @@
class Api::V1::StatusesController < Api::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy]
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
before_action :require_user!, except: [:show, :context]
before_action :set_status, only: [:show, :context]
before_action :set_thread, only: [:create]
@ -35,25 +35,45 @@ class Api::V1::StatusesController < Api::BaseController
end
def create
@status = PostStatusService.new.call(current_user.account,
text: status_params[:status],
thread: @thread,
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
visibility: status_params[:visibility],
scheduled_at: status_params[:scheduled_at],
application: doorkeeper_token.application,
poll: status_params[:poll],
idempotency: request.headers['Idempotency-Key'],
content_type: status_params[:content_type],
with_rate_limit: true)
@status = PostStatusService.new.call(
current_user.account,
text: status_params[:status],
thread: @thread,
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
visibility: status_params[:visibility],
language: status_params[:language],
scheduled_at: status_params[:scheduled_at],
application: doorkeeper_token.application,
poll: status_params[:poll],
content_type: status_params[:content_type],
idempotency: request.headers['Idempotency-Key'],
with_rate_limit: true
)
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
end
def update
@status = Status.where(account: current_account).find(params[:id])
authorize @status, :update?
UpdateStatusService.new.call(
@status,
current_account.id,
text: status_params[:status],
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
poll: status_params[:poll]
)
render json: @status, serializer: REST::StatusSerializer
end
def destroy
@status = Status.where(account_id: current_user.account).find(params[:id])
@status = Status.where(account: current_account).find(params[:id])
authorize @status, :destroy?
@status.discard
@ -86,6 +106,7 @@ class Api::V1::StatusesController < Api::BaseController
:spoiler_text,
:content_type,
:visibility,
:language,
:scheduled_at,
media_ids: [],
poll: [

12
app/controllers/api/web/push_subscriptions_controller.rb

@ -26,6 +26,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
mention: alerts_enabled,
poll: alerts_enabled,
status: alerts_enabled,
update: alerts_enabled,
},
}
@ -61,6 +62,15 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def data_params
@data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
@data_params ||= params.require(:data).permit(:policy, alerts: [
:follow,
:follow_request,
:favourite,
:reblog,
:mention,
:poll,
:status,
:update,
])
end
end

11
app/controllers/auth/registrations_controller.rb

@ -9,6 +9,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :check_enabled_registrations, only: [:new, :create]
before_action :configure_sign_up_params, only: [:create]
before_action :set_sessions, only: [:edit, :update]
before_action :set_strikes, only: [:edit, :update]
before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update]
@ -111,8 +112,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def set_invite
invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
@invite = invite&.valid_for_use? ? invite : nil
@invite = begin
invite = Invite.find_by(code: invite_code) if invite_code.present?
invite if invite&.valid_for_use?
end
end
def determine_layout
@ -123,6 +126,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
@sessions = current_user.session_activations
end
def set_strikes
@strikes = current_account.strikes.active.latest
end
def require_not_suspended!
forbidden if current_account.suspended?
end

29
app/controllers/concerns/localized.rb

@ -7,27 +7,24 @@ module Localized
around_action :set_locale
end
def set_locale
locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in?
locale ||= session[:locale] ||= default_locale
locale = default_locale unless I18n.available_locales.include?(locale.to_sym)
I18n.with_locale(locale) do
yield
end
def set_locale(&block)
I18n.with_locale(requested_locale || I18n.default_locale, &block)
end
private
def default_locale
if ENV['DEFAULT_LOCALE'].present?
I18n.default_locale
else
request_locale || I18n.default_locale
end
def requested_locale
requested_locale_name = available_locale_or_nil(params[:locale])
requested_locale_name ||= available_locale_or_nil(current_user.locale) if respond_to?(:user_signed_in?) && user_signed_in?
requested_locale_name ||= http_accept_language if ENV['DEFAULT_LOCALE'].blank?
requested_locale_name
end
def http_accept_language
HttpAcceptLanguage::Parser.new(request.headers.fetch('Accept-Language')).language_region_compatible_from(I18n.available_locales) if request.headers.key?('Accept-Language')
end
def request_locale
http_accept_language.language_region_compatible_from(I18n.available_locales)
def available_locale_or_nil(locale_name)
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
end
end

26
app/controllers/disputes/appeals_controller.rb

@ -0,0 +1,26 @@
# frozen_string_literal: true
class Disputes::AppealsController < Disputes::BaseController
before_action :set_strike
def create
authorize @strike, :appeal?
@appeal = AppealService.new.call(@strike, appeal_params[:text])
redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg')
rescue ActiveRecord::RecordInvalid => e
@appeal = e.record
render template: 'disputes/strikes/show'
end
private
def set_strike
@strike = current_account.strikes.find(params[:strike_id])
end
def appeal_params
params.require(:appeal).permit(:text)
end
end

18
app/controllers/disputes/base_controller.rb

@ -0,0 +1,18 @@
# frozen_string_literal: true
class Disputes::BaseController < ApplicationController
include Authorization
layout 'admin'
skip_before_action :require_functional!
before_action :set_body_classes
before_action :authenticate_user!
private
def set_body_classes
@body_classes = 'admin'
end
end

17
app/controllers/disputes/strikes_controller.rb

@ -0,0 +1,17 @@
# frozen_string_literal: true
class Disputes::StrikesController < Disputes::BaseController
before_action :set_strike
def show
authorize @strike, :show?
@appeal = @strike.appeal || @strike.build_appeal
end
private
def set_strike
@strike = AccountWarning.find(params[:id])
end
end

1
app/controllers/instance_actors_controller.rb

@ -3,6 +3,7 @@
class InstanceActorsController < ApplicationController
include AccountControllerConcern
skip_before_action :check_account_confirmation
skip_around_action :set_locale
def show

4
app/helpers/admin/account_moderation_notes_helper.rb

@ -1,10 +1,10 @@
# frozen_string_literal: true
module Admin::AccountModerationNotesHelper
def admin_account_link_to(account)
def admin_account_link_to(account, path: nil)
return if account.nil?
link_to admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
safe_join([
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
content_tag(:span, account.acct, class: 'username'),

2
app/helpers/admin/action_logs_helper.rb

@ -33,6 +33,8 @@ module Admin::ActionLogsHelper
"#{record.ip}/#{record.ip.prefix} (#{I18n.t("simple_form.labels.ip_block.severities.#{record.severity}")})"
when 'Instance'
record.domain
when 'Appeal'
link_to record.account.acct, disputes_strike_path(record.strike)
end
end

20
app/helpers/admin/trends/statuses_helper.rb

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Admin::Trends::StatusesHelper
def one_line_preview(status)
text = begin
if status.local?
status.text.split("\n").first
else
Nokogiri::HTML(status.text).css('html > body > *').first&.text
end
end
return '' if text.blank?
html = Formatter.instance.send(:encode, text)
html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
html.html_safe # rubocop:disable Rails/OutputSafety
end
end

54
app/helpers/context_helper.rb

@ -0,0 +1,54 @@
# frozen_string_literal: true
module ContextHelper
NAMED_CONTEXT_MAP = {
activitystreams: 'https://www.w3.org/ns/activitystreams',
security: 'https://w3id.org/security/v1',
}.freeze
CONTEXT_EXTENSION_MAP = {
manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' },
sensitive: { 'sensitive' => 'as:sensitive' },
hashtag: { 'Hashtag' => 'as:Hashtag' },
moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } },
also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } },
emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' },
featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' }, 'featuredTags' => { '@id' => 'toot:featuredTags', '@type' => '@id' } },
property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' },
atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' },
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' },
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' },
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
}.freeze
def full_context
serialized_context(NAMED_CONTEXT_MAP, CONTEXT_EXTENSION_MAP)
end
def serialized_context(named_contexts_map, context_extensions_map)
context_array = []
named_contexts = named_contexts_map.keys
context_extensions = context_extensions_map.keys
named_contexts.each do |key|
context_array << NAMED_CONTEXT_MAP[key]
end
extensions = context_extensions.each_with_object({}) do |key, h|
h.merge!(CONTEXT_EXTENSION_MAP[key])
end
context_array << extensions unless extensions.empty?
if context_array.size == 1
context_array.first
else
context_array
end
end
end

80
app/helpers/jsonld_helper.rb

@ -1,6 +1,8 @@
# frozen_string_literal: true
module JsonLdHelper
include ContextHelper
def equals_or_includes?(haystack, needle)
haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle
end
@ -69,6 +71,84 @@ module JsonLdHelper
graph.dump(:normalize)
end
def compact(json)
compacted = JSON::LD::API.compact(json.without('signature'), full_context, documentLoader: method(:load_jsonld_context))
compacted['signature'] = json['signature']
compacted
end
# Patches a JSON-LD document to avoid compatibility issues on redistribution
#
# Since compacting a JSON-LD document against Mastodon's built-in vocabulary
# means other extension namespaces will be expanded, malformed JSON-LD
# attributes lost, and some values “unexpectedly” compacted this method
# patches the following likely sources of incompatibility:
# - 'https://www.w3.org/ns/activitystreams#Public' being compacted to
# 'as:Public' (for instance, pre-3.4.0 Mastodon does not understand
# 'as:Public')
# - single-item arrays being compacted to the item itself (`[foo]` being
# compacted to `foo`)
#
# It is not always possible for `patch_for_forwarding!` to produce a document
# deemed safe for forwarding. Use `safe_for_forwarding?` to check the status
# of the output document.
#
# @param original [Hash] The original JSON-LD document used as reference
# @param compacted [Hash] The compacted JSON-LD document to be patched
# @return [void]
def patch_for_forwarding!(original, compacted)
original.without('@context', 'signature').each do |key, value|
next if value.nil? || !compacted.key?(key)
compacted_value = compacted[key]
if value.is_a?(Hash) && compacted_value.is_a?(Hash)
patch_for_forwarding!(value, compacted_value)
elsif value.is_a?(Array)
compacted_value = [compacted_value] unless compacted_value.is_a?(Array)
return if value.size != compacted_value.size
compacted[key] = value.zip(compacted_value).map do |v, vc|
if v.is_a?(Hash) && vc.is_a?(Hash)
patch_for_forwarding!(v, vc)
vc
elsif v == 'https://www.w3.org/ns/activitystreams#Public' && vc == 'as:Public'
v
else
vc
end
end
elsif value == 'https://www.w3.org/ns/activitystreams#Public' && compacted_value == 'as:Public'
compacted[key] = value
end
end
end
# Tests whether a JSON-LD compaction is deemed safe for redistribution,
# that is, if it doesn't change its meaning to consumers that do not actually
# handle JSON-LD, but rely on values being serialized in a certain way.
#
# See `patch_for_forwarding!` for details.
#
# @param original [Hash] The original JSON-LD document used as reference
# @param compacted [Hash] The compacted JSON-LD document to be patched
# @return [Boolean] Whether the patched document is deemed safe
def safe_for_forwarding?(original, compacted)
original.without('@context', 'signature').all? do |key, value|
compacted_value = compacted[key]
return false unless value.class == compacted_value.class
if value.is_a?(Hash)
safe_for_forwarding?(value, compacted_value)
elsif value.is_a?(Array)
value.zip(compacted_value).all? do |v, vc|
v.is_a?(Hash) ? (vc.is_a?(Hash) && safe_for_forwarding?(v, vc)) : v == vc
end
else
value == compacted_value
end
end
end
def fetch_resource(uri, id, on_behalf_of = nil)
unless id
json = fetch_resource_without_id_validation(uri, on_behalf_of)

305
app/helpers/languages_helper.rb

@ -1,97 +1,250 @@
# frozen_string_literal: true
module LanguagesHelper
HUMAN_LOCALES = {
af: 'Afrikaans',
ar: 'العربية',
ast: 'Asturianu',
bg: 'Български',
bn: 'বাংলা',
br: 'Breton',
ca: 'Català',
co: 'Corsu',
cs: 'Čeština',
cy: 'Cymraeg',
da: 'Dansk',
de: 'Deutsch',
el: 'Ελληνικά',
en: 'English',
eo: 'Esperanto',
ISO_639_1 = {
aa: ['Afar', 'Afaraf'].freeze,
ab: ['Abkhaz', 'аҧсуа бызшәа'].freeze,
ae: ['Avestan', 'avesta'].freeze,
af: ['Afrikaans', 'Afrikaans'].freeze,
ak: ['Akan', 'Akan'].freeze,
am: ['Amharic', 'አማርኛ'].freeze,
an: ['Aragonese', 'aragonés'].freeze,
ar: ['Arabic', 'اللغة العربية'].freeze,
as: ['Assamese', 'অসমীয়া'].freeze,
av: ['Avaric', 'авар мацӀ'].freeze,
ay: ['Aymara', 'aymar aru'].freeze,
az: ['Azerbaijani', 'azərbaycan dili'].freeze,
ba: ['Bashkir', 'башҡорт теле'].freeze,
be: ['Belarusian', 'беларуская мова'].freeze,
bg: ['Bulgarian', 'български език'].freeze,
bh: ['Bihari', 'भोजपुरी'].freeze,
bi: ['Bislama', 'Bislama'].freeze,
bm: ['Bambara', 'bamanankan'].freeze,
bn: ['Bengali', 'বাংলা'].freeze,
bo: ['Tibetan', 'བོད་ཡིག'].freeze,
br: ['Breton', 'brezhoneg'].freeze,
bs: ['Bosnian', 'bosanski jezik'].freeze,
ca: ['Catalan', 'Català'].freeze,
ce: ['Chechen', 'нохчийн мотт'].freeze,
ch: ['Chamorro', 'Chamoru'].freeze,
co: ['Corsican', 'corsu'].freeze,
cr: ['Cree', 'ᓀᐦᐃᔭᐍᐏᐣ'].freeze,
cs: ['Czech', 'čeština'].freeze,
cu: ['Old Church Slavonic', 'ѩзыкъ словѣньскъ'].freeze,
cv: ['Chuvash', 'чӑваш чӗлхи'].freeze,
cy: ['Welsh', 'Cymraeg'].freeze,
da: ['Danish', 'dansk'].freeze,
de: ['German', 'Deutsch'].freeze,
dv: ['Divehi', 'Dhivehi'].freeze,
dz: ['Dzongkha', 'རྫོང་ཁ'].freeze,
ee: ['Ewe', 'Eʋegbe'].freeze,
el: ['Greek', 'Ελληνικά'].freeze,
en: ['English', 'English'].freeze,
eo: ['Esperanto', 'Esperanto'].freeze,
es: ['Spanish', 'Español'].freeze,
et: ['Estonian', 'eesti'].freeze,
eu: ['Basque', 'euskara'].freeze,
fa: ['Persian', 'فارسی'].freeze,
ff: ['Fula', 'Fulfulde'].freeze,
fi: ['Finnish', 'suomi'].freeze,
fj: ['Fijian', 'Vakaviti'].freeze,
fo: ['Faroese', 'føroyskt'].freeze,
fr: ['French', 'Français'].freeze,
fy: ['Western Frisian', 'Frysk'].freeze,
ga: ['Irish', 'Gaeilge'].freeze,
gd: ['Scottish Gaelic', 'Gàidhlig'].freeze,
gl: ['Galician', 'galego'].freeze,
gu: ['Gujarati', 'ગુજરાતી'].freeze,
gv: ['Manx', 'Gaelg'].freeze,
ha: ['Hausa', 'هَوُسَ'].freeze,
he: ['Hebrew', 'עברית'].freeze,
hi: ['Hindi', 'हिन्दी'].freeze,
ho: ['Hiri Motu', 'Hiri Motu'].freeze,
hr: ['Croatian', 'Hrvatski'].freeze,
ht: ['Haitian', 'Kreyòl ayisyen'].freeze,
hu: ['Hungarian', 'magyar'].freeze,
hy: ['Armenian', 'Հայերեն'].freeze,
hz: ['Herero', 'Otjiherero'].freeze,
ia: ['Interlingua', 'Interlingua'].freeze,
id: ['Indonesian', 'Bahasa Indonesia'].freeze,
ie: ['Interlingue', 'Interlingue'].freeze,
ig: ['Igbo', 'Asụsụ Igbo'].freeze,
ii: ['Nuosu', 'ꆈꌠ꒿ Nuosuhxop'].freeze,
ik: ['Inupiaq', 'Iñupiaq'].freeze,
io: ['Ido', 'Ido'].freeze,
is: ['Icelandic', 'Íslenska'].freeze,
it: ['Italian', 'Italiano'].freeze,
iu: ['Inuktitut', 'ᐃᓄᒃᑎᑐᑦ'].freeze,
ja: ['Japanese', '日本語'].freeze,
jv: ['Javanese', 'basa Jawa'].freeze,
ka: ['Georgian', 'ქართული'].freeze,
kg: ['Kongo', 'Kikongo'].freeze,
ki: ['Kikuyu', 'Gĩkũyũ'].freeze,
kj: ['Kwanyama', 'Kuanyama'].freeze,
kk: ['Kazakh', 'қазақ тілі'].freeze,
kl: ['Kalaallisut', 'kalaallisut'].freeze,
km: ['Khmer', 'ខេមរភាសា'].freeze,
kn: ['Kannada', 'ಕನ್ನಡ'].freeze,
ko: ['Korean', '한국어'].freeze,
kr: ['Kanuri', 'Kanuri'].freeze,
ks: ['Kashmiri', 'कश्मीरी'].freeze,
ku: ['Kurdish', 'Kurdî'].freeze,
kv: ['Komi', 'коми кыв'].freeze,
kw: ['Cornish', 'Kernewek'].freeze,
ky: ['Kyrgyz', 'Кыргызча'].freeze,
la: ['Latin', 'latine'].freeze,
lb: ['Luxembourgish', 'Lëtzebuergesch'].freeze,
lg: ['Ganda', 'Luganda'].freeze,
li: ['Limburgish', 'Limburgs'].freeze,
ln: ['Lingala', 'Lingála'].freeze,
lo: ['Lao', 'ພາສາ'].freeze,
lt: ['Lithuanian', 'lietuvių kalba'].freeze,
lu: ['Luba-Katanga', 'Tshiluba'].freeze,
lv: ['Latvian', 'latviešu valoda'].freeze,
mg: ['Malagasy', 'fiteny malagasy'].freeze,
mh: ['Marshallese', 'Kajin M̧ajeļ'].freeze,
mi: ['Māori', 'te reo Māori'].freeze,
mk: ['Macedonian', 'македонски јазик'].freeze,
ml: ['Malayalam', 'മലയാളം'].freeze,
mn: ['Mongolian', 'Монгол хэл'].freeze,
mr: ['Marathi', 'मराठी'].freeze,
ms: ['Malay', 'Bahasa Malaysia'].freeze,
mt: ['Maltese', 'Malti'].freeze,
my: ['Burmese', 'ဗမာစာ'].freeze,
na: ['Nauru', 'Ekakairũ Naoero'].freeze,
nb: ['Norwegian Bokmål', 'Norsk bokmål'].freeze,
nd: ['Northern Ndebele', 'isiNdebele'].freeze,
ne: ['Nepali', 'नेपाली'].freeze,
ng: ['Ndonga', 'Owambo'].freeze,
nl: ['Dutch', 'Nederlands'].freeze,
nn: ['Norwegian Nynorsk', 'Norsk nynorsk'].freeze,
no: ['Norwegian', 'Norsk'].freeze,
nr: ['Southern Ndebele', 'isiNdebele'].freeze,
nv: ['Navajo', 'Diné bizaad'].freeze,
ny: ['Chichewa', 'chiCheŵa'].freeze,
oc: ['Occitan', 'occitan'].freeze,
oj: ['Ojibwe', 'ᐊᓂᔑᓈᐯᒧᐎᓐ'].freeze,
om: ['Oromo', 'Afaan Oromoo'].freeze,
or: ['Oriya', 'ଓଡ଼ିଆ'].freeze,
os: ['Ossetian', 'ирон æвзаг'].freeze,
pa: ['Panjabi', 'ਪੰਜਾਬੀ'].freeze,
pi: ['Pāli', 'पाऴि'].freeze,
pl: ['Polish', 'Polski'].freeze,
ps: ['Pashto', 'پښتو'].freeze,
pt: ['Portuguese', 'Português'].freeze,
qu: ['Quechua', 'Runa Simi'].freeze,
rm: ['Romansh', 'rumantsch grischun'].freeze,
rn: ['Kirundi', 'Ikirundi'].freeze,
ro: ['Romanian', 'Română'].freeze,
ru: ['Russian', 'Русский'].freeze,
rw: ['Kinyarwanda', 'Ikinyarwanda'].freeze,
sa: ['Sanskrit', 'संस्कृतम्'].freeze,
sc: ['Sardinian', 'sardu'].freeze,
sd: ['Sindhi', 'सिन्धी'].freeze,
se: ['Northern Sami', 'Davvisámegiella'].freeze,
sg: ['Sango', 'yângâ tî sängö'].freeze,
si: ['Sinhala', 'සිංහල'].freeze,
sk: ['Slovak', 'slovenčina'].freeze,
sl: ['Slovenian', 'slovenščina'].freeze,
sn: ['Shona', 'chiShona'].freeze,
so: ['Somali', 'Soomaaliga'].freeze,
sq: ['Albanian', 'Shqip'].freeze,
sr: ['Serbian', 'српски језик'].freeze,
ss: ['Swati', 'SiSwati'].freeze,
st: ['Southern Sotho', 'Sesotho'].freeze,
su: ['Sundanese', 'Basa Sunda'].freeze,
sv: ['Swedish', 'Svenska'].freeze,
sw: ['Swahili', 'Kiswahili'].freeze,
ta: ['Tamil', 'தமிழ்'].freeze,
te: ['Telugu', 'తెలుగు'].freeze,
tg: ['Tajik', 'тоҷикӣ'].freeze,
th: ['Thai', 'ไทย'].freeze,
ti: ['Tigrinya', 'ትግርኛ'].freeze,
tk: ['Turkmen', 'Türkmen'].freeze,
tl: ['Tagalog', 'Wikang Tagalog'].freeze,
tn: ['Tswana', 'Setswana'].freeze,
to: ['Tonga', 'faka Tonga'].freeze,
tr: ['Turkish', 'Türkçe'].freeze,
ts: ['Tsonga', 'Xitsonga'].freeze,
tt: ['Tatar', 'татар теле'].freeze,
tw: ['Twi', 'Twi'].freeze,
ty: ['Tahitian', 'Reo Tahiti'].freeze,
ug: ['Uyghur', 'ئۇيغۇرچە‎'].freeze,
uk: ['Ukrainian', 'Українська'].freeze,
ur: ['Urdu', 'اردو'].freeze,
uz: ['Uzbek', 'Ўзбек'].freeze,
ve: ['Venda', 'Tshivenḓa'].freeze,
vi: ['Vietnamese', 'Tiếng Việt'].freeze,
vo: ['Volapük', 'Volapük'].freeze,
wa: ['Walloon', 'walon'].freeze,
wo: ['Wolof', 'Wollof'].freeze,
xh: ['Xhosa', 'isiXhosa'].freeze,
yi: ['Yiddish', 'ייִדיש'].freeze,
yo: ['Yoruba', 'Yorùbá'].freeze,
za: ['Zhuang', 'Saɯ cueŋƅ'].freeze,
zh: ['Chinese', '中文'].freeze,
zu: ['Zulu', 'isiZulu'].freeze,
}.freeze
ISO_639_3 = {
ast: ['Asturian', 'Asturianu'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
kmr: ['Northern Kurdish', 'Kurmancî'].freeze,
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
}.freeze
SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_3).freeze
# For ISO-639-1 and ISO-639-3 language codes, we have their official
# names, but for some translations, we need the names of the
# regional variants specifically
REGIONAL_LOCALE_NAMES = {
'es-AR': 'Español (Argentina)',
'es-MX': 'Español (México)',
es: 'Español',
et: 'Eesti',
eu: 'Euskara',
fa: 'فارسی',
fi: 'Suomi',
fr: 'Français',
ga: 'Gaeilge',
gd: 'Gàidhlig',
gl: 'Galego',
he: 'עברית',
hi: 'हिन्दी',
hr: 'Hrvatski',
hu: 'Magyar',
hy: 'Հայերեն',
id: 'Bahasa Indonesia',
io: 'Ido',
is: 'Íslenska',
it: 'Italiano',
ja: '日本語',
ka: 'ქართული',
kab: 'Taqbaylit',
kk: 'Қазақша',
kmr: 'Kurmancî',
kn: 'ಕನ್ನಡ',
ko: '한국어',
ku: 'سۆرانی',
lt: 'Lietuvių',
lv: 'Latviešu',
mk: 'Македонски',
ml: 'മലയാളം',
mr: 'मराठी',
ms: 'Bahasa Melayu',
nl: 'Nederlands',
nn: 'Nynorsk',
no: 'Norsk',
oc: 'Occitan',
pl: 'Polski',
'pt-BR': 'Português (Brasil)',
'pt-PT': 'Português (Portugal)',
pt: 'Português',
ro: 'Română',
ru: 'Русский',
sa: 'संस्कृतम्',
sc: 'Sardu',
si: 'සිංහල',
sk: 'Slovenčina',
sl: 'Slovenščina',
sq: 'Shqip',
'sr-Latn': 'Srpski (latinica)',
sr: 'Српски',
sv: 'Svenska',
ta: 'தமிழ்',
te: 'తెలుగు',
th: 'ไทย',
tr: 'Türkçe',
uk: 'Українська',
ur: 'اُردُو',
vi: 'Tiếng Việt',
zgh: 'ⵜⴰⵎⴰⵣⵉⵖⵜ',
'zh-CN': '简体中文',
'zh-HK': '繁體中文(香港)',
'zh-TW': '繁體中文(臺灣)',
zh: '中文',
}.freeze
def human_locale(locale)
if locale == 'und'
def native_locale_name(locale)
if locale.blank? || locale == 'und'
I18n.t('generic.none')
elsif (supported_locale = SUPPORTED_LOCALES[locale.to_sym])
supported_locale[1]
elsif (regional_locale = REGIONAL_LOCALE_NAMES[locale.to_sym])
regional_locale
else
HUMAN_LOCALES[locale.to_sym] || locale
locale
end
end
def standard_locale_name(locale)
if locale.blank?
I18n.t('generic.none')
elsif (supported_locale = SUPPORTED_LOCALES[locale.to_sym])
supported_locale[0]
else
locale
end
end
def valid_locale_or_nil(str)
return if str.blank?
code, = str.to_s.split(/[_-]/) # Strip out the region from e.g. en_US or ja-JP
return unless valid_locale?(code)
code
end
def valid_locale?(locale)
SUPPORTED_LOCALES.key?(locale.to_sym)
end
# Maps a 2 letter language code to the corresponding Postgres FTS locale
def language_code_to_fts_locale(language)
case language

2
app/helpers/settings_helper.rb

@ -2,7 +2,7 @@
module SettingsHelper
def filterable_languages
LanguageDetector.instance.language_names.select(&LanguagesHelper::HUMAN_LOCALES.method(:key?))
LanguagesHelper::SUPPORTED_LOCALES.keys
end
def hash_to_object(hash)

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

@ -71,6 +71,8 @@ export const INIT_MEDIA_EDIT_MODAL = 'INIT_MEDIA_EDIT_MODAL';
export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTION';
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
uploadErrorPoll: { id: &#