commit
ed34f4b9a4
|
@ -0,0 +1,7 @@
|
|||
[production]
|
||||
defaults
|
||||
not IE 11
|
||||
not dead
|
||||
|
||||
[development]
|
||||
supports es6-module
|
|
@ -11,7 +11,8 @@
|
|||
"extensions": [
|
||||
"EditorConfig.EditorConfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"rebornix.Ruby"
|
||||
"rebornix.Ruby",
|
||||
"webben.browserslist"
|
||||
],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -3,6 +3,53 @@ Changelog
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.5.3] - 2022-05-26
|
||||
### Added
|
||||
|
||||
- **Add language dropdown to compose form in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/18420), [ykzts](https://github.com/mastodon/mastodon/pull/18460))
|
||||
- **Add warning for limited accounts in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/18344))
|
||||
- Add `limited` attribute to accounts in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/18344))
|
||||
|
||||
### Changed
|
||||
|
||||
- **Change RSS feeds** ([Gargron](https://github.com/mastodon/mastodon/pull/18356), [tribela](https://github.com/mastodon/mastodon/pull/18406))
|
||||
- Titles are now date and time of post
|
||||
- Bodies now render all content faithfully, including polls and emojis
|
||||
- All media attachments are included with Media RSS
|
||||
- Change "dangerous" to "sensitive" in privacy policy and web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18515))
|
||||
- Change unconfirmed accounts to not be visible in REST API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17530))
|
||||
- Change `tootctl search deploy` to improve performance ([Gargron](https://github.com/mastodon/mastodon/pull/18463), [Gargron](https://github.com/mastodon/mastodon/pull/18514))
|
||||
- Change search indexing to use batches to minimize resource usage ([Gargron](https://github.com/mastodon/mastodon/pull/18451))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix follower and other counters being able to go negative ([Gargron](https://github.com/mastodon/mastodon/pull/18517))
|
||||
- Fix unnecessary query on when creating a status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17901))
|
||||
- Fix warning an account outside of a report closing all reports for that account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18387))
|
||||
- Fix error when resolving a link that redirects to a local post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18314))
|
||||
- Fix preferred posting language returning unusable value in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/18428))
|
||||
- Fix race condition error when external status is reblogged ([ykzts](https://github.com/mastodon/mastodon/pull/18424))
|
||||
- Fix missing string for appeal validation error ([Gargron](https://github.com/mastodon/mastodon/pull/18410))
|
||||
- Fix block/mute lists showing a follow button in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18364))
|
||||
- Fix Redis configuration not being changed by `mastodon:setup` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18383))
|
||||
- Fix streaming notifications not using quick filter logic in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18316))
|
||||
- Fix ambiguous wording on appeal actions in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18328))
|
||||
- Fix floating action button obscuring last element in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18332))
|
||||
- Fix account warnings not being recorded in audit log ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18338))
|
||||
- Fix leftover icons for direct visibility statuses ([Steffo99](https://github.com/mastodon/mastodon/pull/18305))
|
||||
- Fix link verification requiring case sensitivity on links ([sgolemon](https://github.com/mastodon/mastodon/pull/18320))
|
||||
- Fix embeds not setting their height correctly ([rinsuki](https://github.com/mastodon/mastodon/pull/18301))
|
||||
|
||||
### Security
|
||||
|
||||
- Fix concurrent unfollowing decrementing follower count more than once ([Gargron](https://github.com/mastodon/mastodon/pull/18527))
|
||||
- Fix being able to appeal a strike unlimited times ([Gargron](https://github.com/mastodon/mastodon/pull/18529))
|
||||
- Fix being able to report otherwise inaccessible statuses ([Gargron](https://github.com/mastodon/mastodon/pull/18528))
|
||||
- Fix empty votes arbitrarily increasing voters count in polls ([Gargron](https://github.com/mastodon/mastodon/pull/18526))
|
||||
- Fix moderator identity leak when approving appeal of sensitive marked statuses ([Gargron](https://github.com/mastodon/mastodon/pull/18525))
|
||||
- Fix suspended users being able to access APIs that don't require a user ([Gargron](https://github.com/mastodon/mastodon/pull/18524))
|
||||
- Fix confirmation redirect to app without `Location` header ([Gargron](https://github.com/mastodon/mastodon/pull/18523))
|
||||
|
||||
## [3.5.2] - 2022-05-04
|
||||
### Added
|
||||
|
||||
|
|
16
Gemfile
16
Gemfile
|
@ -7,7 +7,7 @@ gem 'pkg-config', '~> 1.4'
|
|||
gem 'rexml', '~> 3.2'
|
||||
|
||||
gem 'puma', '~> 5.6'
|
||||
gem 'rails', '~> 6.1.5'
|
||||
gem 'rails', '~> 6.1.6'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'thor', '~> 1.2'
|
||||
gem 'rack', '~> 2.2.3'
|
||||
|
@ -18,7 +18,7 @@ gem 'makara', '~> 0.5'
|
|||
gem 'pghero', '~> 2.8'
|
||||
gem 'dotenv-rails', '~> 2.7'
|
||||
|
||||
gem 'aws-sdk-s3', '~> 1.113', require: false
|
||||
gem 'aws-sdk-s3', '~> 1.114', require: false
|
||||
gem 'fog-core', '<= 2.1.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'kt-paperclip', '~> 7.1'
|
||||
|
@ -79,13 +79,13 @@ gem 'ruby-progressbar', '~> 1.11'
|
|||
gem 'sanitize', '~> 6.0'
|
||||
gem 'scenic', '~> 1.6'
|
||||
gem 'sidekiq', '~> 6.4'
|
||||
gem 'sidekiq-scheduler', '~> 3.2'
|
||||
gem 'sidekiq-scheduler', '~> 4.0'
|
||||
gem 'sidekiq-unique-jobs', '~> 7.1'
|
||||
gem 'sidekiq-bulk', '~>0.2.0'
|
||||
gem 'sidekiq-bulk', '~> 0.2.0'
|
||||
gem 'simple-navigation', '~> 4.3'
|
||||
gem 'simple_form', '~> 5.1'
|
||||
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
|
||||
gem 'stoplight', '~> 2.2.1'
|
||||
gem 'stoplight', '~> 3.0.0'
|
||||
gem 'strong_migrations', '~> 0.7'
|
||||
gem 'tty-prompt', '~> 0.23', require: false
|
||||
gem 'twitter-text', '~> 3.1.0'
|
||||
|
@ -112,9 +112,9 @@ group :production, :test do
|
|||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.36'
|
||||
gem 'capybara', '~> 3.37'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 2.20'
|
||||
gem 'faker', '~> 2.21'
|
||||
gem 'microformats', '~> 4.2'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.1'
|
||||
|
@ -132,7 +132,7 @@ group :development do
|
|||
gem 'letter_opener', '~> 1.8'
|
||||
gem 'letter_opener_web', '~> 2.0'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 1.27', require: false
|
||||
gem 'rubocop', '~> 1.29', require: false
|
||||
gem 'rubocop-rails', '~> 2.14', require: false
|
||||
gem 'brakeman', '~> 5.2', require: false
|
||||
gem 'bundler-audit', '~> 0.9', require: false
|
||||
|
|
167
Gemfile.lock
167
Gemfile.lock
|
@ -1,40 +1,40 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.5.1)
|
||||
actionpack (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
actioncable (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.5.1)
|
||||
actionpack (= 6.1.5.1)
|
||||
activejob (= 6.1.5.1)
|
||||
activerecord (= 6.1.5.1)
|
||||
activestorage (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
actionmailbox (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activestorage (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.5.1)
|
||||
actionpack (= 6.1.5.1)
|
||||
actionview (= 6.1.5.1)
|
||||
activejob (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
actionmailer (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
actionview (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.5.1)
|
||||
actionview (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
actionpack (6.1.6)
|
||||
actionview (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.5.1)
|
||||
actionpack (= 6.1.5.1)
|
||||
activerecord (= 6.1.5.1)
|
||||
activestorage (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
actiontext (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activestorage (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
actionview (6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -45,22 +45,22 @@ GEM
|
|||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
activejob (6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
activerecord (6.1.5.1)
|
||||
activemodel (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
activestorage (6.1.5.1)
|
||||
actionpack (= 6.1.5.1)
|
||||
activejob (= 6.1.5.1)
|
||||
activerecord (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
activemodel (6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
activerecord (6.1.6)
|
||||
activemodel (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
activestorage (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.5.1)
|
||||
activesupport (6.1.6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -81,7 +81,7 @@ GEM
|
|||
attr_required (1.0.1)
|
||||
awrence (1.1.1)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.582.0)
|
||||
aws-partitions (1.587.0)
|
||||
aws-sdk-core (3.130.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
|
@ -90,7 +90,7 @@ GEM
|
|||
aws-sdk-kms (1.56.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.113.2)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
|
@ -116,7 +116,7 @@ GEM
|
|||
ffi (~> 1.14)
|
||||
bootsnap (1.11.1)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (5.2.2)
|
||||
brakeman (5.2.3)
|
||||
browser (4.2.0)
|
||||
brpoplpush-redis_script (0.1.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
|
@ -144,7 +144,7 @@ GEM
|
|||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (3.36.0)
|
||||
capybara (3.37.1)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
|
@ -203,7 +203,6 @@ GEM
|
|||
dotenv-rails (2.7.6)
|
||||
dotenv (= 2.7.6)
|
||||
railties (>= 3.2)
|
||||
e2mmap (0.1.0)
|
||||
ed25519 (1.3.0)
|
||||
elasticsearch (7.13.3)
|
||||
elasticsearch-api (= 7.13.3)
|
||||
|
@ -220,7 +219,7 @@ GEM
|
|||
tzinfo
|
||||
excon (0.76.0)
|
||||
fabrication (2.28.0)
|
||||
faker (2.20.0)
|
||||
faker (2.21.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.9.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
|
@ -307,7 +306,7 @@ GEM
|
|||
rainbow (>= 2.0.0)
|
||||
i18n (1.10.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (1.0.9)
|
||||
i18n-tasks (1.0.10)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
better_html (~> 1.0)
|
||||
|
@ -375,7 +374,7 @@ GEM
|
|||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.17.0)
|
||||
loofah (2.18.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -405,7 +404,7 @@ GEM
|
|||
net-ssh (>= 2.6.5, < 7.0.0)
|
||||
net-ssh (6.1.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.4)
|
||||
nokogiri (1.13.6)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nsa (0.2.8)
|
||||
|
@ -442,7 +441,7 @@ GEM
|
|||
orm_adapter (0.5.0)
|
||||
ox (2.14.11)
|
||||
parallel (1.22.1)
|
||||
parser (3.1.1.0)
|
||||
parser (3.1.2.0)
|
||||
ast (~> 2.4.1)
|
||||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
|
@ -468,7 +467,7 @@ GEM
|
|||
pry (~> 0.13.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.6)
|
||||
public_suffix (4.0.7)
|
||||
puma (5.6.4)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.2.0)
|
||||
|
@ -490,20 +489,20 @@ GEM
|
|||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.1.5.1)
|
||||
actioncable (= 6.1.5.1)
|
||||
actionmailbox (= 6.1.5.1)
|
||||
actionmailer (= 6.1.5.1)
|
||||
actionpack (= 6.1.5.1)
|
||||
actiontext (= 6.1.5.1)
|
||||
actionview (= 6.1.5.1)
|
||||
activejob (= 6.1.5.1)
|
||||
activemodel (= 6.1.5.1)
|
||||
activerecord (= 6.1.5.1)
|
||||
activestorage (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
rails (6.1.6)
|
||||
actioncable (= 6.1.6)
|
||||
actionmailbox (= 6.1.6)
|
||||
actionmailer (= 6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
actiontext (= 6.1.6)
|
||||
actionview (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activemodel (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activestorage (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.5.1)
|
||||
railties (= 6.1.6)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -519,9 +518,9 @@ GEM
|
|||
railties (>= 6.0.0, < 7)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (6.1.5.1)
|
||||
actionpack (= 6.1.5.1)
|
||||
activesupport (= 6.1.5.1)
|
||||
railties (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
|
@ -534,7 +533,7 @@ GEM
|
|||
redis (4.5.1)
|
||||
redis-namespace (1.8.2)
|
||||
redis (>= 3.0.4)
|
||||
regexp_parser (2.3.0)
|
||||
regexp_parser (2.4.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.1)
|
||||
|
@ -569,16 +568,16 @@ GEM
|
|||
rspec-support (3.11.0)
|
||||
rspec_junit_formatter (0.5.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rubocop (1.27.0)
|
||||
rubocop (1.29.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.1.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml
|
||||
rubocop-ast (>= 1.16.0, < 2.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.17.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.17.0)
|
||||
rubocop-ast (1.18.0)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-rails (2.14.2)
|
||||
activesupport (>= 4.2.0)
|
||||
|
@ -601,20 +600,18 @@ GEM
|
|||
railties (>= 4.0.0)
|
||||
securecompare (1.0.0)
|
||||
semantic_range (3.0.0)
|
||||
sidekiq (6.4.1)
|
||||
sidekiq (6.4.2)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.2.0)
|
||||
sidekiq-bulk (0.2.0)
|
||||
sidekiq
|
||||
sidekiq-scheduler (3.2.0)
|
||||
e2mmap
|
||||
redis (>= 3, < 5)
|
||||
sidekiq-scheduler (4.0.0)
|
||||
redis (>= 4.2.0)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 3)
|
||||
thwait
|
||||
sidekiq (>= 4)
|
||||
tilt (>= 1.4.0)
|
||||
sidekiq-unique-jobs (7.1.21)
|
||||
sidekiq-unique-jobs (7.1.22)
|
||||
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
sidekiq (>= 5.0, < 8.0)
|
||||
|
@ -643,7 +640,7 @@ GEM
|
|||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.19)
|
||||
statsd-ruby (1.5.0)
|
||||
stoplight (2.2.1)
|
||||
stoplight (3.0.0)
|
||||
strong_migrations (0.7.9)
|
||||
activerecord (>= 5)
|
||||
swd (1.3.0)
|
||||
|
@ -656,8 +653,6 @@ GEM
|
|||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
thor (1.2.1)
|
||||
thwait (0.2.0)
|
||||
e2mmap
|
||||
tilt (2.0.10)
|
||||
tpm-key_attestation (0.9.0)
|
||||
bindata (~> 2.4)
|
||||
|
@ -734,7 +729,7 @@ DEPENDENCIES
|
|||
active_record_query_trace (~> 1.8)
|
||||
addressable (~> 2.8)
|
||||
annotate (~> 3.2)
|
||||
aws-sdk-s3 (~> 1.113)
|
||||
aws-sdk-s3 (~> 1.114)
|
||||
better_errors (~> 2.9)
|
||||
binding_of_caller (~> 1.0)
|
||||
blurhash (~> 0.1)
|
||||
|
@ -747,7 +742,7 @@ DEPENDENCIES
|
|||
capistrano-rails (~> 1.6)
|
||||
capistrano-rbenv (~> 2.2)
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 3.36)
|
||||
capybara (~> 3.37)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
chewy (~> 7.2)
|
||||
climate_control (~> 0.2)
|
||||
|
@ -762,7 +757,7 @@ DEPENDENCIES
|
|||
dotenv-rails (~> 2.7)
|
||||
ed25519 (~> 1.3)
|
||||
fabrication (~> 2.28)
|
||||
faker (~> 2.20)
|
||||
faker (~> 2.21)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
fog-core (<= 2.1.0)
|
||||
|
@ -813,7 +808,7 @@ DEPENDENCIES
|
|||
rack (~> 2.2.3)
|
||||
rack-attack (~> 6.6)
|
||||
rack-cors (~> 1.1)
|
||||
rails (~> 6.1.5)
|
||||
rails (~> 6.1.6)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 6.0)
|
||||
rails-settings-cached (~> 0.6)
|
||||
|
@ -825,14 +820,14 @@ DEPENDENCIES
|
|||
rspec-rails (~> 5.1)
|
||||
rspec-sidekiq (~> 3.1)
|
||||
rspec_junit_formatter (~> 0.5)
|
||||
rubocop (~> 1.27)
|
||||
rubocop (~> 1.29)
|
||||
rubocop-rails (~> 2.14)
|
||||
ruby-progressbar (~> 1.11)
|
||||
sanitize (~> 6.0)
|
||||
scenic (~> 1.6)
|
||||
sidekiq (~> 6.4)
|
||||
sidekiq-bulk (~> 0.2.0)
|
||||
sidekiq-scheduler (~> 3.2)
|
||||
sidekiq-scheduler (~> 4.0)
|
||||
sidekiq-unique-jobs (~> 7.1)
|
||||
simple-navigation (~> 4.3)
|
||||
simple_form (~> 5.1)
|
||||
|
@ -840,7 +835,7 @@ DEPENDENCIES
|
|||
sprockets (~> 3.7.2)
|
||||
sprockets-rails (~> 3.4)
|
||||
stackprof
|
||||
stoplight (~> 2.2.1)
|
||||
stoplight (~> 3.0.0)
|
||||
strong_migrations (~> 0.7)
|
||||
thor (~> 1.2)
|
||||
tty-prompt (~> 0.23)
|
||||
|
|
|
@ -14,7 +14,7 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
|||
| ------- | ------------------ |
|
||||
| 3.5.x | Yes |
|
||||
| 3.4.x | Yes |
|
||||
| 3.3.x | Yes |
|
||||
| 3.3.x | No |
|
||||
| < 3.3 | No |
|
||||
|
||||
[bug-bounty]: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountsIndex < Chewy::Index
|
||||
settings index: { refresh_interval: '5m' }, analysis: {
|
||||
settings index: { refresh_interval: '30s' }, analysis: {
|
||||
analyzer: {
|
||||
content: {
|
||||
tokenizer: 'whitespace',
|
||||
|
@ -23,7 +23,7 @@ class AccountsIndex < Chewy::Index
|
|||
},
|
||||
}
|
||||
|
||||
index_scope ::Account.searchable.includes(:account_stat), delete_if: ->(account) { account.destroyed? || !account.searchable? }
|
||||
index_scope ::Account.searchable.includes(:account_stat)
|
||||
|
||||
root date_detection: false do
|
||||
field :id, type: 'long'
|
||||
|
@ -36,8 +36,8 @@ class AccountsIndex < Chewy::Index
|
|||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||
end
|
||||
|
||||
field :following_count, type: 'long', value: ->(account) { account.following.local.count }
|
||||
field :followers_count, type: 'long', value: ->(account) { account.followers.local.count }
|
||||
field :following_count, type: 'long', value: ->(account) { account.following_count }
|
||||
field :followers_count, type: 'long', value: ->(account) { account.followers_count }
|
||||
field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class StatusesIndex < Chewy::Index
|
||||
include FormattingHelper
|
||||
|
||||
settings index: { refresh_interval: '15m' }, analysis: {
|
||||
settings index: { refresh_interval: '30s' }, analysis: {
|
||||
filter: {
|
||||
english_stop: {
|
||||
type: 'stop',
|
||||
|
@ -33,6 +33,8 @@ class StatusesIndex < Chewy::Index
|
|||
},
|
||||
}
|
||||
|
||||
# We do not use delete_if option here because it would call a method that we
|
||||
# expect to be called with crutches without crutches, causing n+1 queries
|
||||
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll)
|
||||
|
||||
crutch :mentions do |collection|
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TagsIndex < Chewy::Index
|
||||
settings index: { refresh_interval: '15m' }, analysis: {
|
||||
settings index: { refresh_interval: '30s' }, analysis: {
|
||||
analyzer: {
|
||||
content: {
|
||||
tokenizer: 'keyword',
|
||||
|
@ -23,7 +23,11 @@ class TagsIndex < Chewy::Index
|
|||
},
|
||||
}
|
||||
|
||||
index_scope ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? }
|
||||
index_scope ::Tag.listable
|
||||
|
||||
crutch :time_period do
|
||||
7.days.ago.to_date..0.days.ago.to_date
|
||||
end
|
||||
|
||||
root date_detection: false do
|
||||
field :name, type: 'text', analyzer: 'content' do
|
||||
|
@ -31,7 +35,7 @@ class TagsIndex < Chewy::Index
|
|||
end
|
||||
|
||||
field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
|
||||
field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day.accounts } }
|
||||
field :usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts }
|
||||
field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,7 +44,6 @@ class AccountsController < ApplicationController
|
|||
limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
|
||||
@statuses = filtered_statuses.without_reblogs.limit(limit)
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class ActivityPub::BaseController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
skip_before_action :require_not_suspended!
|
||||
skip_around_action :set_locale
|
||||
|
||||
private
|
||||
|
|
|
@ -11,6 +11,7 @@ class Api::BaseController < ApplicationController
|
|||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||
before_action :require_not_suspended!
|
||||
before_action :set_cache_headers
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
@ -97,6 +98,10 @@ class Api::BaseController < ApplicationController
|
|||
render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
render json: { error: 'Your login is currently disabled' }, status: 403 if current_user&.account&.suspended?
|
||||
end
|
||||
|
||||
def require_user!
|
||||
if !current_user
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||
|
|
|
@ -9,6 +9,8 @@ class Api::V1::AccountsController < Api::BaseController
|
|||
|
||||
before_action :require_user!, except: [:show, :create]
|
||||
before_action :set_account, except: [:create]
|
||||
before_action :check_account_approval, except: [:create]
|
||||
before_action :check_account_confirmation, except: [:create]
|
||||
before_action :check_enabled_registrations, only: [:create]
|
||||
|
||||
skip_before_action :require_authenticated_user!, only: :create
|
||||
|
@ -74,6 +76,14 @@ class Api::V1::AccountsController < Api::BaseController
|
|||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def check_account_approval
|
||||
raise(ActiveRecord::RecordNotFound) if @account.local? && @account.user_pending?
|
||||
end
|
||||
|
||||
def check_account_confirmation
|
||||
raise(ActiveRecord::RecordNotFound) if @account.local? && !@account.user_confirmed?
|
||||
end
|
||||
|
||||
def relationships(**options)
|
||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options)
|
||||
end
|
||||
|
|
|
@ -40,7 +40,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
|||
|
||||
def after_confirmation_path_for(_resource_name, user)
|
||||
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||
user.created_by_application.redirect_uri
|
||||
user.created_by_application.confirmation_redirect_uri
|
||||
else
|
||||
super
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ class MediaProxyController < ApplicationController
|
|||
include RoutingHelper
|
||||
include Authorization
|
||||
include Redisable
|
||||
include Lockable
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
@ -16,14 +17,10 @@ class MediaProxyController < ApplicationController
|
|||
rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
|
||||
|
||||
def show
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
@media_attachment = MediaAttachment.remote.attached.find(params[:id])
|
||||
authorize @media_attachment.status, :show?
|
||||
redownload! if @media_attachment.needs_redownload? && !reject_media?
|
||||
else
|
||||
raise Mastodon::RaceConditionError
|
||||
end
|
||||
with_lock("media_download:#{params[:id]}") do
|
||||
@media_attachment = MediaAttachment.remote.attached.find(params[:id])
|
||||
authorize @media_attachment.status, :show?
|
||||
redownload! if @media_attachment.needs_redownload? && !reject_media?
|
||||
end
|
||||
|
||||
redirect_to full_asset_url(@media_attachment.file.url(version))
|
||||
|
@ -45,10 +42,6 @@ class MediaProxyController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def lock_options
|
||||
{ redis: redis, key: "media_download:#{params[:id]}", autorelease: 15.minutes.seconds }
|
||||
end
|
||||
|
||||
def reject_media?
|
||||
DomainBlock.reject_media?(@media_attachment.account.domain)
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class Settings::ExportsController < Settings::BaseController
|
||||
include Authorization
|
||||
include Redisable
|
||||
include Lockable
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
|
@ -14,21 +15,13 @@ class Settings::ExportsController < Settings::BaseController
|
|||
def create
|
||||
backup = nil
|
||||
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
authorize :backup, :create?
|
||||
backup = current_user.backups.create!
|
||||
else
|
||||
raise Mastodon::RaceConditionError
|
||||
end
|
||||
with_lock("backup:#{current_user.id}") do
|
||||
authorize :backup, :create?
|
||||
backup = current_user.backups.create!
|
||||
end
|
||||
|
||||
BackupWorker.perform_async(backup.id)
|
||||
|
||||
redirect_to settings_export_path
|
||||
end
|
||||
|
||||
def lock_options
|
||||
{ redis: redis, key: "backup:#{current_user.id}" }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,6 @@ class TagsController < ApplicationController
|
|||
|
||||
format.rss do
|
||||
expires_in 0, public: true
|
||||
render xml: RSS::TagSerializer.render(@tag, @statuses)
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
|
|
@ -132,7 +132,7 @@ module ApplicationHelper
|
|||
elsif status.private_visibility? || status.limited_visibility?
|
||||
fa_icon('lock', title: I18n.t('statuses.visibilities.private'))
|
||||
elsif status.direct_visibility?
|
||||
fa_icon('envelope', title: I18n.t('statuses.visibilities.direct'))
|
||||
fa_icon('at', title: I18n.t('statuses.visibilities.direct'))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -243,7 +243,7 @@ module ApplicationHelper
|
|||
end.values
|
||||
end
|
||||
|
||||
def prerender_custom_emojis(html, custom_emojis)
|
||||
EmojiFormatter.new(html, custom_emojis, animate: prefers_autoplay?).to_s
|
||||
def prerender_custom_emojis(html, custom_emojis, other_options = {})
|
||||
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,6 +18,32 @@ module FormattingHelper
|
|||
html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []))
|
||||
end
|
||||
|
||||
def rss_status_content_format(status)
|
||||
html = status_content_format(status)
|
||||
|
||||
before_html = begin
|
||||
if status.spoiler_text?
|
||||
"<p><strong>#{I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)}</strong> #{h(status.spoiler_text)}</p><hr />"
|
||||
else
|
||||
''
|
||||
end
|
||||
end.html_safe # rubocop:disable Rails/OutputSafety
|
||||
|
||||
after_html = begin
|
||||
if status.preloadable_poll
|
||||
"<p>#{status.preloadable_poll.options.map { |o| "<input type=#{status.preloadable_poll.multiple? ? 'checkbox' : 'radio'} disabled /> #{h(o)}" }.join('<br />')}</p>"
|
||||
else
|
||||
''
|
||||
end
|
||||
end.html_safe # rubocop:disable Rails/OutputSafety
|
||||
|
||||
prerender_custom_emojis(
|
||||
safe_join([before_html, html, after_html]),
|
||||
status.emojis,
|
||||
style: 'width: 1.1em; height: 1.1em; object-fit: contain; vertical-align: middle; margin: -.2ex .15em .2ex'
|
||||
).to_str
|
||||
end
|
||||
|
||||
def account_bio_format(account)
|
||||
html_aware_format(account.note, account.local?)
|
||||
end
|
||||
|
|
|
@ -254,4 +254,8 @@ module LanguagesHelper
|
|||
def valid_locale?(locale)
|
||||
locale.present? && SUPPORTED_LOCALES.key?(locale.to_sym)
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -101,7 +101,7 @@ module StatusesHelper
|
|||
when 'private'
|
||||
fa_icon 'lock fw'
|
||||
when 'direct'
|
||||
fa_icon 'envelope fw'
|
||||
fa_icon 'at fw'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST';
|
|||
export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS';
|
||||
export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL';
|
||||
|
||||
export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';
|
||||
|
||||
export function fetchAccount(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchRelationships([id]));
|
||||
|
@ -780,3 +782,8 @@ export function unpinAccountFail(error) {
|
|||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export const revealAccount = id => ({
|
||||
type: ACCOUNT_REVEAL,
|
||||
id,
|
||||
});
|
||||
|
|
|
@ -45,12 +45,12 @@ export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
|
|||
export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
|
||||
export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
|
||||
|
||||
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
||||
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
||||
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
|
||||
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
||||
export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
|
||||
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
||||
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
||||
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
||||
export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE';
|
||||
|
||||
export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
|
||||
|
||||
|
@ -169,6 +169,7 @@ export function submitCompose(routerHistory) {
|
|||
spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '',
|
||||
visibility: getState().getIn(['compose', 'privacy']),
|
||||
poll: getState().getIn(['compose', 'poll'], null),
|
||||
language: getState().getIn(['compose', 'language']),
|
||||
},
|
||||
headers: {
|
||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||
|
@ -635,6 +636,11 @@ export function changeComposeSensitivity() {
|
|||
};
|
||||
};
|
||||
|
||||
export const changeComposeLanguage = language => ({
|
||||
type: COMPOSE_LANGUAGE_CHANGE,
|
||||
language,
|
||||
});
|
||||
|
||||
export function changeComposeSpoilerness() {
|
||||
return {
|
||||
type: COMPOSE_SPOILERNESS_CHANGE,
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { saveSettings } from './settings';
|
||||
|
||||
export const LANGUAGE_USE = 'LANGUAGE_USE';
|
||||
|
||||
export const useLanguage = language => dispatch => {
|
||||
dispatch({
|
||||
type: LANGUAGE_USE,
|
||||
language,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
|
@ -58,7 +58,8 @@ export const loadPending = () => ({
|
|||
|
||||
export function updateNotifications(notification, intlMessages, intlLocale) {
|
||||
return (dispatch, getState) => {
|
||||
const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
|
||||
const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
|
||||
const showInColumn = activeFilter === 'all' ? getState().getIn(['settings', 'notifications', 'shows', notification.type], true) : activeFilter === notification.type;
|
||||
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
|
||||
const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
|
||||
const filters = getFiltersRegex(getState(), { contextType: 'notifications' });
|
||||
|
|
|
@ -18,6 +18,8 @@ const messages = defineMessages({
|
|||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' },
|
||||
unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
|
@ -33,6 +35,7 @@ class Account extends ImmutablePureComponent {
|
|||
hidden: PropTypes.bool,
|
||||
actionIcon: PropTypes.string,
|
||||
actionTitle: PropTypes.string,
|
||||
defaultAction: PropTypes.string,
|
||||
onActionClick: PropTypes.func,
|
||||
};
|
||||
|
||||
|
@ -61,7 +64,7 @@ class Account extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle } = this.props;
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return <div />;
|
||||
|
@ -105,6 +108,10 @@ class Account extends ImmutablePureComponent {
|
|||
{hidingNotificationsButton}
|
||||
</Fragment>
|
||||
);
|
||||
} else if (defaultAction === 'mute') {
|
||||
buttons = <IconButton icon='volume-off' title={intl.formatMessage(messages.mute, { name: account.get('username') })} onClick={this.handleMute} />;
|
||||
} else if (defaultAction === 'block') {
|
||||
buttons = <IconButton icon='lock' title={intl.formatMessage(messages.block, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||
} else if (!account.get('moved') || following) {
|
||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class Avatar extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
size: PropTypes.number.isRequired,
|
||||
style: PropTypes.object,
|
||||
inline: PropTypes.bool,
|
||||
|
@ -37,15 +38,6 @@ export default class Avatar extends React.PureComponent {
|
|||
const { account, size, animate, inline } = this.props;
|
||||
const { hovering } = this.state;
|
||||
|
||||
const src = account.get('avatar');
|
||||
const staticSrc = account.get('avatar_static');
|
||||
|
||||
let className = 'account__avatar';
|
||||
|
||||
if (inline) {
|
||||
className = className + ' account__avatar-inline';
|
||||
}
|
||||
|
||||
const style = {
|
||||
...this.props.style,
|
||||
width: `${size}px`,
|
||||
|
@ -53,15 +45,21 @@ export default class Avatar extends React.PureComponent {
|
|||
backgroundSize: `${size}px ${size}px`,
|
||||
};
|
||||
|
||||
if (hovering || animate) {
|
||||
style.backgroundImage = `url(${src})`;
|
||||
} else {
|
||||
style.backgroundImage = `url(${staticSrc})`;
|
||||
if (account) {
|
||||
const src = account.get('avatar');
|
||||
const staticSrc = account.get('avatar_static');
|
||||
|
||||
if (hovering || animate) {
|
||||
style.backgroundImage = `url(${src})`;
|
||||
} else {
|
||||
style.backgroundImage = `url(${staticSrc})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
className={classNames('account__avatar', { 'account__avatar-inline': inline })}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
style={style}
|
||||
|
|
|
@ -468,7 +468,7 @@ class Status extends ImmutablePureComponent {
|
|||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
|
|
|
@ -82,6 +82,7 @@ class Header extends ImmutablePureComponent {
|
|||
onEditAccountNote: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
openEditProfile = () => {
|
||||
|
@ -123,7 +124,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, domain } = this.props;
|
||||
const { account, hidden, intl, domain } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
|
@ -267,21 +268,25 @@ class Header extends ImmutablePureComponent {
|
|||
{!suspended && info}
|
||||
</div>
|
||||
|
||||
<img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />
|
||||
{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
|
||||
</div>
|
||||
|
||||
<div className='account__header__bar'>
|
||||
<div className='account__header__tabs'>
|
||||
<a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
|
||||
<Avatar account={account} size={90} />
|
||||
<Avatar account={suspended || hidden ? undefined : account} size={90} />
|
||||
</a>
|
||||
|
||||
<div className='spacer' />
|
||||
|
||||
{!suspended && (
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{actionBtn}
|
||||
{bellBtn}
|
||||
{!hidden && (
|
||||
<React.Fragment>
|
||||
{actionBtn}
|
||||
{bellBtn}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||
</div>
|
||||
|
@ -295,30 +300,30 @@ class Header extends ImmutablePureComponent {
|
|||
</h1>
|
||||
</div>
|
||||
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio'>
|
||||
{fields.size > 0 && (
|
||||
<div className='account__header__fields'>
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
||||
{!(suspended || hidden) && (
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio'>
|
||||
{fields.size > 0 && (
|
||||
<div className='account__header__fields'>
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
||||
|
||||
<dd className={`${pair.get('verified_at') ? 'verified' : ''} translate`} title={pair.get('value_plain')}>
|
||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<dd className={`${pair.get('verified_at') ? 'verified' : ''} translate`} title={pair.get('value_plain')}>
|
||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{account.get('id') !== me && !suspended && <AccountNoteContainer account={account} />}
|
||||
{account.get('id') !== me && <AccountNoteContainer account={account} />}
|
||||
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
||||
|
||||
<div className='account__header__joined'><FormattedMessage id='account.joined' defaultMessage='Joined {date}' values={{ date: intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' }) }} /></div>
|
||||
</div>
|
||||
<div className='account__header__joined'><FormattedMessage id='account.joined' defaultMessage='Joined {date}' values={{ date: intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' }) }} /></div>
|
||||
</div>
|
||||
|
||||
{!suspended && (
|
||||
<div className='account__header__extra__links'>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
|
||||
<ShortNumber
|
||||
|
@ -341,8 +346,8 @@ class Header extends ImmutablePureComponent {
|
|||
/>
|
||||
</NavLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -24,6 +24,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onAddToList: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -91,7 +92,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, hideTabs } = this.props;
|
||||
const { account, hidden, hideTabs } = this.props;
|
||||
|
||||
if (account === null) {
|
||||
return null;
|
||||
|
@ -99,7 +100,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='account-timeline__header'>
|
||||
{account.get('moved') && <MovedNote from={account} to={account.get('moved')} />}
|
||||
{(!hidden && account.get('moved')) && <MovedNote from={account} to={account.get('moved')} />}
|
||||
|
||||
<InnerHeader
|
||||
account={account}
|
||||
|
@ -117,9 +118,10 @@ export default class Header extends ImmutablePureComponent {
|
|||
onAddToList={this.handleAddToList}
|
||||
onEditAccountNote={this.handleEditAccountNote}
|
||||
domain={this.props.domain}
|
||||
hidden={hidden}
|
||||
/>
|
||||
|
||||
{!hideTabs && (
|
||||
{!(hideTabs || hidden) && (
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Posts' /></NavLink>
|
||||
<NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Posts and replies' /></NavLink>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { revealAccount } from 'mastodon/actions/accounts';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Button from 'mastodon/components/button';
|
||||
|
||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||
|
||||
reveal () {
|
||||
dispatch(revealAccount(accountId));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default @connect(() => {}, mapDispatchToProps)
|
||||
class LimitedAccountHint extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
reveal: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { reveal } = this.props;
|
||||
|
||||
return (
|
||||
<div className='limited-account-hint'>
|
||||
<p><FormattedMessage id='limited_account_hint.title' |