mirror of https://github.com/Siphonay/mastodon
Merge branch 'main' of https://github.com/glitch-soc/mastodon into glitch-soc-main
This commit is contained in:
commit
b5f281557d
52
CHANGELOG.md
52
CHANGELOG.md
|
@ -3,6 +3,58 @@ Changelog
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.5.2] - 2022-05-04
|
||||
### Added
|
||||
|
||||
- Add warning on direct messages screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18289))
|
||||
- We already had a warning when composing a direct message, it has now been reworded to be more clear
|
||||
- Same warning is now displayed when viewing sent and received direct messages
|
||||
- Add ability to set approval-based registration through tootctl ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18248))
|
||||
- Add pre-filling of domain from search filter in domain allow/block admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18172))
|
||||
|
||||
## Changed
|
||||
|
||||
- Change name of “Direct” visibility to “Mentioned people only” in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18146), [Gargron](https://github.com/mastodon/mastodon/pull/18289), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18291))
|
||||
- Change trending posts to only show one post from each account ([Gargron](https://github.com/mastodon/mastodon/pull/18181))
|
||||
- Change half-life of trending posts from 6 hours to 2 hours ([Gargron](https://github.com/mastodon/mastodon/pull/18182))
|
||||
- Change full-text search feature to also include polls you have voted in ([tribela](https://github.com/mastodon/mastodon/pull/18070))
|
||||
- Change Redis from using one connection per process, to using a connection pool ([Gargron](https://github.com/mastodon/mastodon/pull/18135), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18160), [Gargron](https://github.com/mastodon/mastodon/pull/18171))
|
||||
- Different threads no longer have to wait on a mutex over a single connection
|
||||
- However, this does increase the number of Redis connections by a fair amount
|
||||
- We are planning to optimize Redis use so that the pool can be made smaller in the future
|
||||
|
||||
## Removed
|
||||
|
||||
- Remove IP matching from e-mail domain blocks ([Gargron](https://github.com/mastodon/mastodon/pull/18190))
|
||||
- The IPs of the blocked e-mail domain or its MX records are no longer checked
|
||||
- Previously it was too easy to block e-mail providers by mistake
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fix compatibility with Friendica's pinned posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18254), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18260))
|
||||
- Fix error when looking up handle with surrounding spaces in REST API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18225))
|
||||
- Fix double render error when authorizing interaction ([Gargron](https://github.com/mastodon/mastodon/pull/18203))
|
||||
- Fix error when a post references an invalid media attachment ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18211))
|
||||
- Fix error when trying to revoke OAuth token without supplying a token ([Gargron](https://github.com/mastodon/mastodon/pull/18205))
|
||||
- Fix error caused by missing subject in Webfinger response ([Gargron](https://github.com/mastodon/mastodon/pull/18204))
|
||||
- Fix error on attempting to delete an account moderation note ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18196))
|
||||
- Fix light-mode emoji borders in web UI ([Gaelan](https://github.com/mastodon/mastodon/pull/18131))
|
||||
- Fix being able to scroll away from the loading bar in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18170))
|
||||
- Fix error when a bookmark or favorite has been reported and deleted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18174))
|
||||
- Fix being offered empty “Server rules violation” report option in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18165))
|
||||
- Fix temporary network errors preventing from authorizing interactions with remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18161))
|
||||
- Fix incorrect link in "new trending tags" email ([cdzombak](https://github.com/mastodon/mastodon/pull/18156))
|
||||
- Fix missing indexes on some foreign keys ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18157))
|
||||
- Fix n+1 query on feed merge and populate operations ([Gargron](https://github.com/mastodon/mastodon/pull/18111))
|
||||
- Fix feed unmerge worker being exceptionally slow in some conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18110))
|
||||
- Fix PeerTube videos appearing with an erroneous “Edited at” marker ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18100))
|
||||
- Fix instance actor being created incorrectly when running through migrations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18109))
|
||||
- Fix web push notifications containing HTML entities ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18071))
|
||||
- Fix inconsistent parsing of `TRUSTED_PROXY_IP` ([ykzts](https://github.com/mastodon/mastodon/pull/18051))
|
||||
- Fix error when fetching pinned posts ([tribela](https://github.com/mastodon/mastodon/pull/18030))
|
||||
- Fix wrong optimization in feed populate operation ([dogelover911](https://github.com/mastodon/mastodon/pull/18009))
|
||||
- Fix error in alias settings page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18004))
|
||||
|
||||
## [3.5.1] - 2022-04-08
|
||||
### Added
|
||||
|
||||
|
|
16
Gemfile
16
Gemfile
|
@ -1,13 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
ruby '>= 2.5.0', '< 3.1.0'
|
||||
ruby '>= 2.6.0', '< 3.1.0'
|
||||
|
||||
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'
|
||||
|
@ -26,7 +26,7 @@ gem 'blurhash', '~> 0.1'
|
|||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.8'
|
||||
gem 'bootsnap', '~> 1.10.3', require: false
|
||||
gem 'bootsnap', '~> 1.11.1', require: false
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'chewy', '~> 7.2'
|
||||
|
@ -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.1'
|
||||
gem 'sidekiq-scheduler', '~> 4.0'
|
||||
gem 'sidekiq-unique-jobs', '~> 7.1'
|
||||
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'
|
||||
|
@ -114,7 +114,7 @@ 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 'microformats', '~> 4.2'
|
||||
|
@ -134,7 +134,7 @@ group :development do
|
|||
gem 'letter_opener', '~> 1.8'
|
||||
gem 'letter_opener_web', '~> 2.0'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 1.26', require: false
|
||||
gem 'rubocop', '~> 1.28', require: false
|
||||
gem 'rubocop-rails', '~> 2.14', require: false
|
||||
gem 'brakeman', '~> 5.2', require: false
|
||||
gem 'bundler-audit', '~> 0.9', require: false
|
||||
|
|
187
Gemfile.lock
187
Gemfile.lock
|
@ -1,40 +1,40 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
actioncable (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activestorage (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
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)
|
||||
actionpack (= 6.1.5)
|
||||
actionview (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
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)
|
||||
actionview (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
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)
|
||||
actionpack (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activestorage (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
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)
|
||||
activesupport (= 6.1.5)
|
||||
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)
|
||||
activesupport (= 6.1.5)
|
||||
activejob (6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
activerecord (6.1.5)
|
||||
activemodel (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
activestorage (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
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)
|
||||
activesupport (6.1.6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -81,20 +81,20 @@ GEM
|
|||
attr_required (1.0.1)
|
||||
awrence (1.1.1)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.558.0)
|
||||
aws-sdk-core (3.127.0)
|
||||
aws-partitions (1.587.0)
|
||||
aws-sdk-core (3.130.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.55.0)
|
||||
aws-sdk-kms (1.56.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.113.0)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-sigv4 (1.5.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
bcrypt (3.1.17)
|
||||
better_errors (2.9.1)
|
||||
|
@ -114,9 +114,9 @@ GEM
|
|||
debug_inspector (>= 0.0.1)
|
||||
blurhash (0.1.6)
|
||||
ffi (~> 1.14)
|
||||
bootsnap (1.10.3)
|
||||
bootsnap (1.11.1)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (5.2.1)
|
||||
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)
|
||||
|
@ -216,7 +215,7 @@ GEM
|
|||
multi_json
|
||||
encryptor (3.0.0)
|
||||
erubi (1.10.0)
|
||||
et-orbi (1.2.6)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
excon (0.76.0)
|
||||
fabrication (2.28.0)
|
||||
|
@ -264,8 +263,8 @@ GEM
|
|||
fog-json (>= 1.0)
|
||||
ipaddress (>= 0.8)
|
||||
formatador (0.2.5)
|
||||
fugit (1.5.2)
|
||||
et-orbi (~> 1.1, >= 1.1.8)
|
||||
fugit (1.5.3)
|
||||
et-orbi (~> 1, >= 1.2.7)
|
||||
raabro (~> 1.4)
|
||||
fuubar (2.5.1)
|
||||
rspec-core (~> 3.0)
|
||||
|
@ -309,7 +308,7 @@ GEM
|
|||
rainbow (>= 2.0.0)
|
||||
i18n (1.10.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (1.0.8)
|
||||
i18n-tasks (1.0.10)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
better_html (~> 1.0)
|
||||
|
@ -322,7 +321,7 @@ GEM
|
|||
terminal-table (>= 1.5.1)
|
||||
idn-ruby (0.1.4)
|
||||
ipaddress (0.8.3)
|
||||
jmespath (1.6.0)
|
||||
jmespath (1.6.1)
|
||||
json (2.5.1)
|
||||
json-canonicalization (0.3.0)
|
||||
json-jwt (1.13.0)
|
||||
|
@ -377,7 +376,7 @@ GEM
|
|||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.16.0)
|
||||
loofah (2.18.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -399,7 +398,7 @@ GEM
|
|||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.15.0)
|
||||
msgpack (1.4.4)
|
||||
msgpack (1.5.1)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.1.1)
|
||||
net-ldap (0.17.0)
|
||||
|
@ -407,7 +406,7 @@ GEM
|
|||
net-ssh (>= 2.6.5, < 7.0.0)
|
||||
net-ssh (6.1.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.3)
|
||||
nokogiri (1.13.6)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nsa (0.2.8)
|
||||
|
@ -444,13 +443,13 @@ 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)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.3.5)
|
||||
pghero (2.8.2)
|
||||
pghero (2.8.3)
|
||||
activerecord (>= 5)
|
||||
pkg-config (1.4.7)
|
||||
posix-spawn (0.3.15)
|
||||
|
@ -470,7 +469,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)
|
||||
|
@ -478,7 +477,7 @@ GEM
|
|||
raabro (1.4.0)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3)
|
||||
rack-attack (6.6.0)
|
||||
rack-attack (6.6.1)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
|
@ -492,20 +491,20 @@ GEM
|
|||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.1.5)
|
||||
actioncable (= 6.1.5)
|
||||
actionmailbox (= 6.1.5)
|
||||
actionmailer (= 6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
actiontext (= 6.1.5)
|
||||
actionview (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activemodel (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activestorage (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
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)
|
||||
railties (= 6.1.6)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -521,9 +520,9 @@ GEM
|
|||
railties (>= 6.0.0, < 7)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
railties (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
|
@ -537,7 +536,7 @@ GEM
|
|||
redis (4.5.1)
|
||||
redis-namespace (1.8.2)
|
||||
redis (>= 3.0.4)
|
||||
regexp_parser (2.2.1)
|
||||
regexp_parser (2.4.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.1)
|
||||
|
@ -555,10 +554,10 @@ GEM
|
|||
rspec-expectations (3.11.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-mocks (3.11.0)
|
||||
rspec-mocks (3.11.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-rails (5.1.1)
|
||||
rspec-rails (5.1.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
|
@ -572,16 +571,16 @@ GEM
|
|||
rspec-support (3.11.0)
|
||||
rspec_junit_formatter (0.5.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rubocop (1.26.1)
|
||||
rubocop (1.28.2)
|
||||
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)
|
||||
rubocop-ast (>= 1.17.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.16.0)
|
||||
rubocop-ast (1.17.0)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-rails (2.14.2)
|
||||
activesupport (>= 4.2.0)
|
||||
|
@ -610,14 +609,12 @@ GEM
|
|||
redis (>= 4.2.0)
|
||||
sidekiq-bulk (0.2.0)
|
||||
sidekiq
|
||||
sidekiq-scheduler (3.1.1)
|
||||
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.16)
|
||||
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)
|
||||
|
@ -646,7 +643,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)
|
||||
|
@ -659,8 +656,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)
|
||||
|
@ -737,11 +732,11 @@ 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)
|
||||
bootsnap (~> 1.10.3)
|
||||
bootsnap (~> 1.11.1)
|
||||
brakeman (~> 5.2)
|
||||
browser
|
||||
bullet (~> 7.0)
|
||||
|
@ -750,7 +745,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)
|
||||
|
@ -817,7 +812,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)
|
||||
|
@ -830,14 +825,14 @@ DEPENDENCIES
|
|||
rspec-rails (~> 5.1)
|
||||
rspec-sidekiq (~> 3.1)
|
||||
rspec_junit_formatter (~> 0.5)
|
||||
rubocop (~> 1.26)
|
||||
rubocop (~> 1.28)
|
||||
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.1)
|
||||
sidekiq-scheduler (~> 4.0)
|
||||
sidekiq-unique-jobs (~> 7.1)
|
||||
simple-navigation (~> 4.3)
|
||||
simple_form (~> 5.1)
|
||||
|
@ -845,7 +840,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)
|
||||
|
|
|
@ -55,6 +55,11 @@ class StatusesIndex < Chewy::Index
|
|||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
end
|
||||
|
||||
crutch :votes do |collection|
|
||||
data = ::PollVote.joins(:poll).where(poll: { status_id: collection.map(&:id) }).where(account: Account.local).pluck(:status_id, :account_id)
|
||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
end
|
||||
|
||||
root date_detection: false do
|
||||
field :id, type: 'long'
|
||||
field :account_id, type: 'long'
|
||||
|
|
|
@ -45,7 +45,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,8 @@
|
|||
|
||||
module Admin
|
||||
class DashboardController < BaseController
|
||||
include Redisable
|
||||
|
||||
def index
|
||||
@system_checks = Admin::SystemCheck.perform
|
||||
@time_period = (29.days.ago.to_date...Time.now.utc.to_date)
|
||||
|
@ -15,10 +17,10 @@ module Admin
|
|||
|
||||
def redis_info
|
||||
@redis_info ||= begin
|
||||
if Redis.current.is_a?(Redis::Namespace)
|
||||
Redis.current.redis.info
|
||||
if redis.is_a?(Redis::Namespace)
|
||||
redis.redis.info
|
||||
else
|
||||
Redis.current.info
|
||||
redis.info
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,17 @@ module Admin
|
|||
class DomainBlocksController < BaseController
|
||||
before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
|
||||
|
||||
def batch
|
||||
@form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.email_domain_blocks.no_domain_block_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.domain_blocks.created_msg')
|
||||
else
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :domain_block, :create?
|
||||
@domain_block = DomainBlock.new(domain: params[:_domain])
|
||||
|
@ -76,5 +87,15 @@ module Admin
|
|||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
end
|
||||
|
||||
def form_domain_block_batch_params
|
||||
params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:save]
|
||||
'save'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
|
||||
module Admin
|
||||
class ExportDomainAllowsController < BaseController
|
||||
include AdminExportControllerConcern
|
||||
|
||||
before_action :set_dummy_import!, only: [:new]
|
||||
|
||||
ROWS_PROCESSING_LIMIT = 20_000
|
||||
|
||||
def new
|
||||
authorize :domain_allow, :create?
|
||||
end
|
||||
|
||||
def export
|
||||
authorize :instance, :index?
|
||||
send_export_file
|
||||
end
|
||||
|
||||
def import
|
||||
authorize :domain_allow, :create?
|
||||
begin
|
||||
@import = Admin::Import.new(import_params)
|
||||
parse_import_data!(export_headers)
|
||||
|
||||
@data.take(ROWS_PROCESSING_LIMIT).each do |row|
|
||||
domain = row['#domain'].strip
|
||||
next if DomainAllow.allowed?(domain)
|
||||
|
||||
domain_allow = DomainAllow.new(domain: domain)
|
||||
log_action :create, domain_allow if domain_allow.save
|
||||
end
|
||||
flash[:notice] = I18n.t('admin.domain_allows.created_msg')
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:error] = I18n.t('admin.export_domain_allows.no_file')
|
||||
end
|
||||
redirect_to admin_instances_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_filename
|
||||
'domain_allows.csv'
|
||||
end
|
||||
|
||||
def export_headers
|
||||
%w(#domain)
|
||||
end
|
||||
|
||||
def export_data
|
||||
CSV.generate(headers: export_headers, write_headers: true) do |content|
|
||||
DomainAllow.allowed_domains.each do |instance|
|
||||
content << [instance.domain]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
|
||||
module Admin
|
||||
class ExportDomainBlocksController < BaseController
|
||||
include AdminExportControllerConcern
|
||||
|
||||
before_action :set_dummy_import!, only: [:new]
|
||||
|
||||
ROWS_PROCESSING_LIMIT = 20_000
|
||||
|
||||
def new
|
||||
authorize :domain_block, :create?
|
||||
end
|
||||
|
||||
def export
|
||||
authorize :instance, :index?
|
||||
send_export_file
|
||||
end
|
||||
|
||||
def import
|
||||
authorize :domain_block, :create?
|
||||
|
||||
@import = Admin::Import.new(import_params)
|
||||
parse_import_data!(export_headers)
|
||||
|
||||
@global_private_comment = I18n.t('admin.export_domain_blocks.import.private_comment_template', source: @import.data_file_name, date: I18n.l(Time.now.utc))
|
||||
|
||||
@form = Form::DomainBlockBatch.new
|
||||
@domain_blocks = @data.take(ROWS_PROCESSING_LIMIT).filter_map do |row|
|
||||
domain = row['#domain'].strip
|
||||
next if DomainBlock.rule_for(domain).present?
|
||||
|
||||
domain_block = DomainBlock.new(domain: domain,
|
||||
severity: row['#severity'].strip,
|
||||
reject_media: row['#reject_media'].strip,
|
||||
reject_reports: row['#reject_reports'].strip,
|
||||
private_comment: @global_private_comment,
|
||||
public_comment: row['#public_comment']&.strip,
|
||||
obfuscate: row['#obfuscate'].strip)
|
||||
|
||||
domain_block if domain_block.valid?
|
||||
end
|
||||
|
||||
@warning_domains = Instance.where(domain: @domain_blocks.map(&:domain)).where('EXISTS (SELECT 1 FROM follows JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id WHERE accounts.domain = instances.domain)').pluck(:domain)
|
||||
rescue ActionController::ParameterMissing
|
||||
flash.now[:alert] = I18n.t('admin.export_domain_blocks.no_file')
|
||||
set_dummy_import!
|
||||
render :new
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_filename
|
||||
'domain_blocks.csv'
|
||||
end
|
||||
|
||||
def export_headers
|
||||
%w(#domain #severity #reject_media #reject_reports #public_comment #obfuscate)
|
||||
end
|
||||
|
||||
def export_data
|
||||
CSV.generate(headers: export_headers, write_headers: true) do |content|
|
||||
DomainBlock.with_user_facing_limitations.each do |instance|
|
||||
content << [instance.domain, instance.severity, instance.reject_media, instance.reject_reports, instance.public_comment, instance.obfuscate]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,5 +12,7 @@ class Api::V1::Accounts::LookupController < Api::BaseController
|
|||
|
||||
def set_account
|
||||
@account = ResolveAccountService.new.call(params[:acct], skip_webfinger: true) || raise(ActiveRecord::RecordNotFound)
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ class Api::V1::BookmarksController < Api::BaseController
|
|||
end
|
||||
|
||||
def results
|
||||
@_results ||= account_bookmarks.eager_load(:status).to_a_paginated_by_id(
|
||||
@_results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params_slice(:max_id, :since_id, :min_id)
|
||||
)
|
||||
|
|
|
@ -21,7 +21,7 @@ class Api::V1::FavouritesController < Api::BaseController
|
|||
end
|
||||
|
||||
def results
|
||||
@_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id(
|
||||
@_results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params_slice(:max_id, :since_id, :min_id)
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ class AuthorizeInteractionsController < ApplicationController
|
|||
if @resource.is_a?(Account)
|
||||
render :show
|
||||
elsif @resource.is_a?(Status)
|
||||
redirect_to web_url("statuses/#{@resource.id}")
|
||||
redirect_to web_url("@#{@resource.account.pretty_acct}/#{@resource.id}")
|
||||
else
|
||||
render :error
|
||||
end
|
||||
|
@ -26,15 +26,17 @@ class AuthorizeInteractionsController < ApplicationController
|
|||
else
|
||||
render :error
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :error
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_resource
|
||||
@resource = located_resource || render(:error)
|
||||
@resource = located_resource
|
||||
authorize(@resource, :show?) if @resource.is_a?(Status)
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def located_resource
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AdminExportControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def send_export_file
|
||||
respond_to do |format|
|
||||
format.csv { send_data export_data, filename: export_filename }
|
||||
end
|
||||
end
|
||||
|
||||
def export_data
|
||||
raise 'Override in controller'
|
||||
end
|
||||
|
||||
def export_filename
|
||||
raise 'Override in controller'
|
||||
end
|
||||
|
||||
def set_dummy_import!
|
||||
@import = Admin::Import.new
|
||||
end
|
||||
|
||||
def import_params
|
||||
params.require(:admin_import).permit(:data)
|
||||
end
|
||||
|
||||
def import_data
|
||||
Paperclip.io_adapters.for(@import.data).read
|
||||
end
|
||||
|
||||
def parse_import_data!(default_headers)
|
||||
data = CSV.parse(import_data, headers: true)
|
||||
data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(default_headers[0])
|
||||
@data = data.reject(&:blank?)
|
||||
end
|
||||
end
|
|
@ -22,7 +22,10 @@ class FollowingAccountsController < ApplicationController
|
|||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections?
|
||||
if page_requested? && @account.hide_collections?
|
||||
forbidden
|
||||
next
|
||||
end
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
class MediaProxyController < ApplicationController
|
||||
include RoutingHelper
|
||||
include Authorization
|
||||
include Redisable
|
||||
include Lockable
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
@ -15,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))
|
||||
|
@ -44,10 +42,6 @@ class MediaProxyController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def lock_options
|
||||
{ redis: Redis.current, key: "media_download:#{params[:id]}", autorelease: 15.minutes.seconds }
|
||||
end
|
||||
|
||||
def reject_media?
|
||||
DomainBlock.reject_media?(@media_attachment.account.domain)
|
||||
end
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
class Oauth::TokensController < Doorkeeper::TokensController
|
||||
def revoke
|
||||
unsubscribe_for_token if authorized? && token.accessible?
|
||||
unsubscribe_for_token if token.present? && authorized? && token.accessible?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
class Settings::ExportsController < Settings::BaseController
|
||||
include Authorization
|
||||
include Redisable
|
||||
include Lockable
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
|
@ -13,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.current, key: "backup:#{current_user.id}" }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,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
|
||||
|
||||
|
@ -244,7 +244,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) : []), content_type: status.content_type)
|
||||
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: valid_locale_or_nil(status.language))}</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
|
||||
|
|
|
@ -101,7 +101,7 @@ module StatusesHelper
|
|||
when 'private'
|
||||
fa_icon 'lock fw'
|
||||
when 'direct'
|
||||
fa_icon 'envelope fw'
|
||||
fa_icon 'at fw'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -101,4 +101,20 @@ ready(() => {
|
|||
|
||||
const registrationMode = document.getElementById('form_admin_settings_registrations_mode');
|
||||
if (registrationMode) onChangeRegistrationMode(registrationMode);
|
||||
|
||||
const checkAllElement = document.querySelector('#batch_checkbox_all');
|
||||
if (checkAllElement) {
|
||||
checkAllElement.checked = [].every.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked);
|
||||
checkAllElement.indeterminate = !checkAllElement.checked && [].some.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked);
|
||||
}
|
||||
|
||||
document.querySelector('a#add-instance-button')?.addEventListener('click', (e) => {
|
||||
const domain = document.getElementById('by_domain')?.value;
|
||||
|
||||
if (domain) {
|
||||
const url = new URL(event.target.href);
|
||||
url.searchParams.set('_domain', domain);
|
||||
e.target.href = url;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -88,6 +88,8 @@ export const PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE = 'PINNED_ACCOUNTS_EDITOR
|
|||
export const PINNED_ACCOUNTS_EDITOR_RESET = 'PINNED_ACCOUNTS_EDITOR_RESET';
|
||||
|
||||
|
||||
export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';
|
||||
|
||||
export function fetchAccount(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchRelationships([id]));
|
||||
|
@ -798,6 +800,11 @@ export function unpinAccountFail(error) {
|
|||
};
|
||||
};
|
||||
|
||||
export const revealAccount = id => ({
|
||||
type: ACCOUNT_REVEAL,
|
||||
id,
|
||||
});
|
||||
|
||||
export function fetchPinnedAccounts() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchPinnedAccountsRequest());
|
||||
|
|
|
@ -48,12 +48,13 @@ export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
|
|||
export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
|
||||
|
||||
export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE';
|
||||
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_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
||||
export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
|
||||
export const COMPOSE_CONTENT_TYPE_CHANGE = 'COMPOSE_CONTENT_TYPE_CHANGE';
|
||||
export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE';
|
||||
|
||||
export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
|
||||
|
||||
|
@ -183,6 +184,7 @@ export function submitCompose(routerHistory) {
|
|||
spoiler_text: spoilerText,
|
||||
visibility: getState().getIn(['compose', 'privacy']),
|
||||
poll: getState().getIn(['compose', 'poll'], null),
|
||||
language: getState().getIn(['compose', 'language']),
|
||||
},
|
||||
headers: {
|
||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||
|
@ -669,6 +671,11 @@ export function changeComposeSensitivity() {
|
|||
};
|
||||
};
|
||||
|
||||
export const changeComposeLanguage = language => ({
|
||||
type: COMPOSE_LANGUAGE_CHANGE,
|
||||
language,
|
||||
});
|
||||
|
||||
export function changeComposeSpoilerness() {
|
||||
return {
|
||||
type: COMPOSE_SPOILERNESS_CHANGE,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import emojify from 'flavours/glitch/util/emoji';
|
||||
import { unescapeHTML } from 'flavours/glitch/util/html';
|
||||
import { expandSpoilers } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
|
|
|
@ -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());
|
||||
};
|
|
@ -1,4 +1,46 @@
|
|||
import { expandSpoilers, disableSwiping } from 'flavours/glitch/util/initial_state';
|
||||
import { openModal } from './modal';
|
||||
|
||||
export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE';
|
||||
export const LOCAL_SETTING_DELETE = 'LOCAL_SETTING_DELETE';
|
||||
|
||||
export function checkDeprecatedLocalSettings() {
|
||||
return (dispatch, getState) => {
|
||||
const local_auto_unfold = getState().getIn(['local_settings', 'content_warnings', 'auto_unfold']);
|
||||
const local_swipe_to_change_columns = getState().getIn(['local_settings', 'swipe_to_change_columns']);
|
||||
let changed_settings = [];
|
||||
|
||||
if (local_auto_unfold !== null && local_auto_unfold !== undefined) {
|
||||
if (local_auto_unfold === expandSpoilers) {
|
||||
dispatch(deleteLocalSetting(['content_warnings', 'auto_unfold']));
|
||||
} else {
|
||||
changed_settings.push('user_setting_expand_spoilers');
|
||||
}
|
||||
}
|
||||
|
||||
if (local_swipe_to_change_columns !== null && local_swipe_to_change_columns !== undefined) {
|
||||
if (local_swipe_to_change_columns === !disableSwiping) {
|
||||
dispatch(deleteLocalSetting(['swipe_to_change_columns']));
|
||||
} else {
|
||||
changed_settings.push('user_setting_disable_swiping');
|
||||
}
|
||||
}
|
||||
|
||||
if (changed_settings.length > 0) {
|
||||
dispatch(openModal('DEPRECATED_SETTINGS', {
|
||||
settings: changed_settings,
|
||||
onConfirm: () => dispatch(clearDeprecatedLocalSettings()),
|
||||
}));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export function clearDeprecatedLocalSettings() {
|
||||
return (dispatch) => {
|
||||
dispatch(deleteLocalSetting(['content_warnings', 'auto_unfold']));
|
||||
dispatch(deleteLocalSetting(['swipe_to_change_columns']));
|
||||
};
|
||||
};
|
||||
|
||||
export function changeLocalSetting(key, value) {
|
||||
return dispatch => {
|
||||
|
@ -12,6 +54,17 @@ export function changeLocalSetting(key, value) {
|
|||
};
|
||||
};
|
||||
|
||||
export function deleteLocalSetting(key) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: LOCAL_SETTING_DELETE,
|
||||
key,
|
||||
});
|
||||
|
||||
dispatch(saveLocalSettings());
|
||||
};
|
||||
};
|
||||
|
||||
// __TODO :__
|
||||
// Right now `saveLocalSettings()` doesn't keep track of which user
|
||||
// is currently signed in, but it might be better to give each user
|
||||
|
|
|
@ -70,7 +70,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' });
|
||||
|
|
|
@ -16,8 +16,10 @@ const messages = defineMessages({
|
|||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'You are not currently muting notifications from @{name}. Click to mute notifications' },
|
||||
unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'You are currently muting notifications from @{name}. Click to unmute notifications' },
|
||||
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
|
||||
|
@ -34,6 +36,7 @@ class Account extends ImmutablePureComponent {
|
|||
small: PropTypes.bool,
|
||||
actionIcon: PropTypes.string,
|
||||
actionTitle: PropTypes.string,
|
||||
defaultAction: PropTypes.string,
|
||||
onActionClick: PropTypes.func,
|
||||
};
|
||||
|
||||
|
@ -70,6 +73,7 @@ class Account extends ImmutablePureComponent {
|
|||
onActionClick,
|
||||
actionIcon,
|
||||
actionTitle,
|
||||
defaultAction,
|
||||
} = this.props;
|
||||
|
||||
if (!account) {
|
||||
|
@ -114,6 +118,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} />;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class Avatar extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
className: PropTypes.string,
|
||||
size: PropTypes.number.isRequired,
|
||||
style: PropTypes.object,
|
||||
|
@ -45,11 +45,6 @@ export default class Avatar extends React.PureComponent {
|
|||
} = this.props;
|
||||
const { hovering } = this.state;
|
||||
|
||||
const src = account.get('avatar');
|
||||
const staticSrc = account.get('avatar_static');
|
||||
|
||||
const computedClass = classNames('account__avatar', { 'account__avatar-inline': inline }, className);
|
||||
|
||||
const style = {
|
||||
...this.props.style,
|
||||
width: `${size}px`,
|
||||
|
@ -57,19 +52,24 @@ 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={computedClass}
|
||||
className={classNames('account__avatar', { 'account__avatar-inline': inline }, className)}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
style={style}
|
||||
data-avatar-of={`@${account.get('acct')}`}
|
||||
data-avatar-of={account && `@${account.get('acct')}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export function counterRenderer(counterType, isBold = true) {
|
|||
return (displayNumber, pluralReady) => (
|
||||
<FormattedMessage
|
||||
id='account.statuses_counter'
|
||||
defaultMessage='{count, plural, one {{counter} Toot} other {{counter} Toots}}'
|
||||
defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}'
|
||||
values={{
|
||||
count: pluralReady,
|
||||
counter: renderCounter(displayNumber),
|
||||
|
|
|
@ -55,6 +55,10 @@ export default class ModalRoot extends React.PureComponent {
|
|||
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
this.history = this.context.router ? this.context.router.history : createBrowserHistory();
|
||||
|
||||
if (this.props.children) {
|
||||
this._handleModalOpen();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
|
|
|
@ -581,10 +581,7 @@ class Status extends ImmutablePureComponent {
|
|||
// backgrounds for collapsed statuses are enabled.
|
||||
|
||||
attachments = status.get('media_attachments');
|
||||
if (status.get('poll')) {
|
||||
media.push(<PollContainer pollId={status.get('poll')} />);
|
||||
mediaIcons.push('tasks');
|
||||
}
|
||||
|
||||
if (usingPiP) {
|
||||
media.push(<PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />);
|
||||
mediaIcons.push('video-camera');
|
||||
|
@ -684,6 +681,11 @@ class Status extends ImmutablePureComponent {
|
|||
mediaIcons.push('link');
|
||||
}
|
||||
|
||||
if (status.get('poll')) {
|
||||
media.push(<PollContainer pollId={status.get('poll')} />);
|
||||
mediaIcons.push('tasks');
|
||||
}
|
||||
|
||||
// Here we prepare extra data-* attributes for CSS selectors.
|
||||
// Users can use those for theming, hiding avatars etc via UserStyle
|
||||
const selectorAttribs = {
|
||||
|
|
|
@ -38,7 +38,7 @@ export default class StatusPrepend extends React.PureComponent {
|
|||
switch (type) {
|
||||
case 'featured':
|
||||
return (
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
|
||||
);
|
||||
case 'reblogged_by':
|
||||
return (
|
||||
|
|
|
@ -9,7 +9,7 @@ const messages = defineMessages({
|
|||
public: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
direct: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
|
|
|
@ -8,6 +8,7 @@ import UI from 'flavours/glitch/features/ui';
|
|||
import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
|
||||
import { hydrateStore } from 'flavours/glitch/actions/store';
|
||||
import { connectUserStream } from 'flavours/glitch/actions/streaming';
|
||||
import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_settings';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from 'locales';
|
||||
import initialState from 'flavours/glitch/util/initial_state';
|
||||
|
@ -20,6 +21,9 @@ export const store = configureStore();
|
|||
const hydrateAction = hydrateStore(initialState);
|
||||
store.dispatch(hydrateAction);
|
||||
|
||||
// check for deprecated local settings
|
||||
store.dispatch(checkDeprecatedLocalSettings());
|
||||
|
||||
// load custom emojis
|
||||
store.dispatch(fetchCustomEmojis());
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ const messages = defineMessages({
|
|||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
||||
enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' },
|
||||
disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' },
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
|
@ -82,6 +82,7 @@ class Header extends ImmutablePureComponent {
|
|||
onEditAccountNote: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
openEditProfile = () => {
|
||||
|
@ -115,7 +116,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, domain, identity_proofs } = this.props;
|
||||
const { account, hidden, intl, domain } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
|
@ -270,23 +271,29 @@ class Header extends ImmutablePureComponent {
|
|||
{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' />
|
||||
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{actionBtn}
|
||||
{bellBtn}
|
||||
{!suspended && (
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{!hidden && (
|
||||
<React.Fragment>
|
||||
{actionBtn}
|
||||
{bellBtn}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||
</div>
|
||||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='account__header__tabs__name'>
|
||||
|
@ -298,23 +305,11 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
<AccountNoteContainer account={account} />
|
||||
|
||||
{!suspended && (
|
||||
{!(suspended || hidden) && (
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio'>
|
||||
{ (fields.size > 0 || identity_proofs.size > 0) && (
|
||||
{ fields.size > 0 && (
|
||||
<div className='account__header__fields'>
|
||||
{identity_proofs.map((proof, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} className='translate' />
|
||||
|
||||
<dd className='verified'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener noreferrer'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||
<Icon id='check' className='verified__mark' />
|
||||
</span></a>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener noreferrer'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} className='translate' /></a>
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||
|
|
|
@ -12,7 +12,6 @@ export default class Header extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
identity_proofs: ImmutablePropTypes.list,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
|
@ -26,6 +25,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onAddToList: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -93,7 +93,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, hideTabs, identity_proofs } = this.props;
|
||||
const { account, hidden, hideTabs } = this.props;
|
||||
|
||||
if (account === null) {
|
||||
return null;
|
||||
|
@ -101,11 +101,10 @@ 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}
|
||||
identity_proofs={identity_proofs}
|
||||
onFollow={this.handleFollow}
|
||||
onBlock={this.handleBlock}
|
||||
onMention={this.handleMention}
|
||||
|
@ -120,16 +119,17 @@ export default class Header extends ImmutablePureComponent {
|
|||
onAddToList={this.handleAddToList}
|
||||
onEditAccountNote={this.handleEditAccountNote}
|
||||
domain={this.props.domain}
|
||||
hidden={hidden}
|
||||
/>
|
||||
|
||||
<ActionBar
|
||||
account={account}
|
||||
/>
|
||||
|
||||
{!hideTabs && (
|
||||
{!(hideTabs || hidden) && (
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
|
||||
<NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
|
||||
<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 with replies' /></NavLink>
|
||||
<NavLink exact to={`/@${account.get('acct')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { revealAccount } from 'flavours/glitch/actions/accounts';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Button from 'flavours/glitch/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' defaultMessage='This profile has been hidden by the moderators of your server.' /></p>
|
||||
<Button onClick={reveal}><FormattedMessage id='limited_account_hint.action' defaultMessage='Show profile anyway' /></Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeGetAccount } from 'flavours/glitch/selectors';
|
||||
import { makeGetAccount, getAccountHidden } from 'flavours/glitch/selectors';
|
||||
import Header from '../components/header';
|
||||
import {
|
||||
followAccount,
|
||||
|
@ -22,7 +22,6 @@ import { blockDomain, unblockDomain } from 'flavours/glitch/actions/domain_block
|
|||
import { initEditAccountNote } from 'flavours/glitch/actions/account_notes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { unfollowModal } from 'flavours/glitch/util/initial_state';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||
|
@ -35,7 +34,7 @@ const makeMapStateToProps = () => {
|
|||
const mapStateToProps = (state, { accountId }) => ({
|
||||
account: getAccount(state, accountId),
|
||||
domain: state.getIn(['meta', 'domain']),
|
||||
identity_proofs: state.getIn(['identity_proofs', accountId], ImmutableList()),
|
||||
hidden: getAccountHidden(state, accountId),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
|
|
|
@ -13,9 +13,10 @@ import ColumnBackButton from 'flavours/glitch/components/column_back_button';
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
|
||||
import MissingIndicator from 'flavours/glitch/components/missing_indicator';
|
||||
import TimelineHint from 'flavours/glitch/components/timeline_hint';
|
||||
import LimitedAccountHint from './components/limited_account_hint';
|
||||
import { getAccountHidden } from 'flavours/glitch/selectors';
|
||||
|
||||
const emptyList = ImmutableList();
|
||||
|
||||
|
@ -40,11 +41,12 @@ const mapStateToProps = (state, { params: { acct, id }, withReplies = false }) =
|
|||
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
hidden: getAccountHidden(state, accountId),
|
||||
};
|
||||
};
|
||||
|
||||
const RemoteHint = ({ url }) => (
|
||||
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.statuses' defaultMessage='Older toots' />} />
|
||||
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.statuses' defaultMessage='Older posts' />} />
|
||||
);
|
||||
|
||||
RemoteHint.propTypes = {
|
||||
|
@ -68,6 +70,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
withReplies: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
suspended: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
remote: PropTypes.bool,
|
||||
remoteUrl: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
@ -77,7 +80,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
const { accountId, withReplies, dispatch } = this.props;
|
||||
|
||||
dispatch(fetchAccount(accountId));
|
||||
dispatch(fetchAccountIdentityProofs(accountId));
|
||||
|
||||
if (!withReplies) {
|
||||
dispatch(expandAccountFeaturedTimeline(accountId));
|
||||
}
|
||||
|
@ -109,10 +112,11 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
|
||||
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
|
||||
dispatch(fetchAccount(nextProps.params.accountId));
|
||||
dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
|
||||
|
||||
if (!nextProps.withReplies) {
|
||||
dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
|
||||
}
|
||||
|
||||
dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +134,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, featuredStatusIds, isLoading, hasMore, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props;
|
||||
const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -151,12 +155,16 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
|
||||
let emptyMessage;
|
||||
|
||||
const forceEmptyState = suspended || hidden;
|
||||
|
||||
if (suspended) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
|
||||
} else if (hidden) {
|
||||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||
} else if (remote && statusIds.isEmpty()) {
|
||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||
} else {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts found' />;
|
||||
}
|
||||
|
||||
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
|
||||
|
@ -166,14 +174,14 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
||||
|
||||
<StatusList
|
||||
prepend={<HeaderContainer accountId={this.props.accountId} />}
|
||||
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs={forceEmptyState} />}
|
||||
alwaysPrepend
|
||||
append={remoteMessage}
|
||||
scrollKey='account_timeline'
|
||||
statusIds={suspended ? emptyList : statusIds}
|
||||
statusIds={forceEmptyState ? emptyList : statusIds}
|
||||
featuredStatusIds={featuredStatusIds}
|
||||
isLoading={isLoading}
|
||||
hasMore={hasMore}
|
||||
hasMore={!forceEmptyState && hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
|
|
|
@ -69,7 +69,7 @@ class Blocks extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} />,
|
||||
<AccountContainer key={id} id={id} defaultAction='block' />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
|
|
@ -70,7 +70,7 @@ class Bookmarks extends ImmutablePureComponent {
|
|||
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked toots yet. When you bookmark one, it will show up here." />;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked posts yet. When you bookmark one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} name='bookmarks'>
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import TextIconButton from './text_icon_button';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
import Motion from 'flavours/glitch/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import classNames from 'classnames';
|
||||
import { languages as preloadedLanguages } from 'flavours/glitch/util/initial_state';
|
||||
import fuzzysort from 'fuzzysort';
|
||||
|
||||
const messages = defineMessages({
|
||||
changeLanguage: { id: 'compose.language.change', defaultMessage: 'Change language' },
|
||||
search: { id: 'compose.language.search', defaultMessage: 'Search languages...' },
|
||||
clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' },
|
||||
});
|
||||
|
||||
// Copied from emoji-mart for consistency with emoji picker and since
|
||||
// they don't export the icons in the package
|
||||
const icons = {
|
||||
loupe: (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'>
|
||||
<path d='M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z' />
|
||||
</svg>
|
||||
),
|
||||
|
||||
delete: (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'>
|
||||
<path d='M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z' />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
class LanguageDropdownMenu extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
style: PropTypes.object,
|
||||
value: PropTypes.string.isRequired,
|
||||
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
placement: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
||||
intl: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
languages: preloadedLanguages,
|
||||
};
|
||||
|
||||
state = {
|
||||
mounted: false,
|
||||
searchValue: '',
|
||||
};
|
||||
|
||||
handleDocumentClick = e => {
|
||||
if (this.node && !this.node.contains(e.target)) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
this.setState({ mounted: true });
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('click', this.handleDocumentClick, false);
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
setListRef = c => {
|
||||
this.listNode = c;
|
||||
}
|
||||
|
||||
handleSearchChange = ({ target }) => {
|
||||
this.setState({ searchValue: target.value });
|
||||
}
|
||||
|
||||
search () {
|
||||
const { languages, value, frequentlyUsedLanguages } = this.props;
|
||||
const { searchValue } = this.state;
|
||||
|
||||
if (searchValue === '') {
|
||||
return [...languages].sort((a, b) => {
|
||||
// Push current selection to the top of the list
|
||||
|
||||
if (a[0] === value) {
|
||||
return -1;
|
||||
} else if (b[0] === value) {
|
||||
return 1;
|
||||
} else {
|
||||
// Sort according to frequently used languages
|
||||
|
||||
const indexOfA = frequentlyUsedLanguages.indexOf(a[0]);
|
||||
const indexOfB = frequentlyUsedLanguages.indexOf(b[0]);
|
||||
|
||||
return ((indexOfA > -1 ? indexOfA : Infinity) - (indexOfB > -1 ? indexOfB : Infinity));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fuzzysort.go(searchValue, languages, {
|
||||
keys: ['0', '1', '2'],
|
||||
limit: 5,
|
||||
threshold: -10000,
|
||||
}).map(result => result.obj);
|
||||
}
|
||||
|
||||
frequentlyUsed () {
|
||||
const { languages, value } = this.props;
|
||||
const current = languages.find(lang => lang[0] === value);
|
||||
const results = [];
|
||||
|
||||
if (current) {
|
||||
results.push(current);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
this.props.onClose();
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
const { onClose } = this.props;
|
||||
const index = Array.from(this.listNode.childNodes).findIndex(node => node === e.currentTarget);
|
||||
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
onClose();
|
||||
break;
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
|
||||
} else {
|
||||
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = this.listNode.firstChild;
|
||||
break;
|
||||
case 'End':
|
||||
element = this.listNode.lastChild;
|
||||
break;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchKeyDown = e => {
|
||||
const { onChange, onClose } = this.props;
|
||||
const { searchValue } = this.state;
|
||||
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Tab':
|
||||
case 'ArrowDown':
|
||||
element = this.listNode.firstChild;
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
element = this.listNode.firstChild;
|
||||
|
||||
if (element) {
|
||||
onChange(element.getAttribute('data-index'));
|
||||
onClose();
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
if (searchValue !== '') {
|
||||
e.preventDefault();
|
||||
this.handleClear();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleClear = () => {
|
||||
this.setState({ searchValue: '' });
|
||||
}
|
||||
|
||||
renderItem = lang => {
|
||||
const { value } = this.props;
|
||||
|
||||
return (
|
||||
<div key={lang[0]} role='option' tabIndex='0' data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
|
||||
<span className='language-dropdown__dropdown__results__item__native-name'>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { style, placement, intl } = this.props;
|
||||
const { mounted, searchValue } = this.state;
|
||||
const isSearching = searchValue !== '';
|
||||
const results = this.search();
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
||||
{({ opacity, scaleX, scaleY }) => (
|
||||
// It should not be transformed when mounting because the resulting
|
||||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
||||
<div className='emoji-mart-search'>
|
||||
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
|
||||
<button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? icons.loupe : icons.delete}</button>
|
||||
</div>
|
||||
|
||||
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
|
||||
{results.map(this.renderItem)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class LanguageDropdown extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string),
|
||||
intl: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
open: false,
|
||||
placement: 'bottom',
|
||||
};
|
||||
|
||||
handleToggle = ({ target }) => {
|
||||
const { top } = target.getBoundingClientRect();
|
||||
|
||||
if (this.state.open && this.activeElement) {
|
||||
this.activeElement.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
||||
this.setState({ open: !this.state.open });
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
const { value, onClose } = this.props;
|
||||
|
||||
if (this.state.open && this.activeElement) {
|
||||
this.activeElement.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
this.setState({ open: false });
|
||||
onClose(value);
|
||||
}
|
||||
|
||||
handleChange = value => {
|
||||
const { onChange } = this.props;
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, intl, frequentlyUsedLanguages } = this.props;
|
||||
const { open, placement } = this.state;
|
||||
|
||||
return (
|
||||
<div className={classNames('privacy-dropdown', { active: open })}>
|
||||
<div className='privacy-dropdown__value'>
|
||||
<TextIconButton
|
||||
className='privacy-dropdown__value-icon'
|
||||
label={value && value.toUpperCase()}
|
||||
title={intl.formatMessage(messages.changeLanguage)}
|
||||
active={open}
|
||||
onClick={this.handleToggle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Overlay show={open} placement={placement} target={this}>
|
||||
<LanguageDropdownMenu
|
||||
value={value}
|
||||
frequentlyUsedLanguages={frequentlyUsedLanguages}
|
||||
onClose={this.handleClose}
|
||||
onChange={this.handleChange}
|
||||
placement={placement}
|
||||
intl={intl}
|
||||
/>
|
||||
</Overlay>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ import IconButton from 'flavours/glitch/components/icon_button';
|
|||
import TextIconButton from './text_icon_button';
|
||||
import Dropdown from './dropdown';
|
||||
import PrivacyDropdown from './privacy_dropdown';
|
||||
import LanguageDropdown from '../containers/language_dropdown_container';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
// Utils.
|
||||
|
@ -306,6 +307,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
title={formatMessage(messages.spoiler)}
|
||||
/>
|
||||
)}
|
||||
<LanguageDropdown />
|
||||
<Dropdown
|
||||
active={advancedOptions && advancedOptions.some(value => !!value)}
|
||||
disabled={disabled || isEditing}
|
||||
|
|
|
@ -6,12 +6,12 @@ import Dropdown from './dropdown';
|
|||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all, shown in public timelines' },
|
||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but not in public timelines' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but opted-out of discovery features' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Only people I mention' },
|
||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||
});
|
||||
|
|
|
@ -72,10 +72,10 @@ class SearchResults extends ImmutablePureComponent {
|
|||
} else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
|
||||
statuses = (
|
||||
<section className='search-results__section'>
|
||||
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
|
||||
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></h5>
|
||||
|
||||
<div className='search-results__info'>
|
||||
<FormattedMessage id='search_results.statuses_fts_disabled' defaultMessage='Searching toots by their content is not enabled on this Mastodon server.' />
|
||||
<FormattedMessage id='search_results.statuses_fts_disabled' defaultMessage='Searching posts by their content is not enabled on this Mastodon server.' />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@ -101,7 +101,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||
count += results.get('statuses').size;
|
||||
statuses = (
|
||||
<section className='search-results__section'>
|
||||
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
|
||||
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></h5>
|
||||
|
||||
{results.get('statuses').map(statusId => <StatusContainer id={statusId} key={statusId}/>)}
|
||||
|
||||
|
|
|
@ -17,11 +17,6 @@ export default class TextIconButton extends React.PureComponent {
|
|||
ariaControls: PropTypes.string,
|
||||
};
|
||||
|
||||
handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.onClick();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { label, title, active, ariaControls } = this.props;
|
||||
|
||||
|
@ -31,7 +26,7 @@ export default class TextIconButton extends React.PureComponent {
|
|||
aria-label={title}
|
||||
className={`text-icon-button ${active ? 'active' : ''}`}
|
||||
aria-expanded={active}
|
||||
onClick={this.handleClick}
|
||||
onClick={this.props.onClick}
|
||||
aria-controls={ariaControls}
|
||||
style={iconStyle}
|
||||
>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { connect } from 'react-redux';
|
||||
import LanguageDropdown from '../components/language_dropdown';
|
||||
import { changeComposeLanguage } from 'flavours/glitch/actions/compose';
|
||||
import { useLanguage } from 'flavours/glitch/actions/languages';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
const getFrequentlyUsedLanguages = createSelector([
|
||||
state => state.getIn(['settings', 'frequentlyUsedLanguages'], ImmutableMap()),
|
||||
], languageCounters => (
|
||||
languageCounters.keySeq()
|
||||
.sort((a, b) => languageCounters.get(a) - languageCounters.get(b))
|
||||
.reverse()
|
||||
.toArray()
|
||||
));
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
frequentlyUsedLanguages: getFrequentlyUsedLanguages(state),
|
||||
value: state.getIn(['compose', 'language']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (value) {
|
||||
dispatch(changeComposeLanguage(value));
|
||||
},
|
||||
|
||||
onClose (value) {
|
||||
dispatch(useLanguage(value));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LanguageDropdown);
|
|
@ -43,13 +43,13 @@ const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning
|
|||
}
|
||||
|
||||
if (hashtagWarning) {
|
||||
return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag." />} />;
|
||||
return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag." />} />;
|
||||
}
|
||||
|
||||
if (directMessageWarning) {
|
||||
const message = (
|
||||
<span>
|
||||
<FormattedMessage id='compose_form.direct_message_warning' defaultMessage='This toot will only be sent to all the mentioned users.' /> {!!termsLink && <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a>}
|
||||
<FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> {!!termsLink && <a href={termsLink} target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a>}
|
||||
</span>
|
||||
);
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import { me, mascot } from 'flavours/glitch/util/initial_state';
|
|||
import HeaderContainer from './containers/header_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
|
||||
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle';
|
||||
import SettingText from '../../../components/setting_text';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -23,6 +24,12 @@ class ColumnSettings extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle settings={settings} settingPath={['conversations']} onChange={onChange} label={<FormattedMessage id='direct.group_by_conversations' defaultMessage='Group by conversation' />} />
|
||||
</div>
|
||||
|
||||
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
|
|
|
@ -10,7 +10,6 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
|
|||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import { connectDirectStream } from 'flavours/glitch/actions/streaming';
|
||||
import { changeSetting } from 'flavours/glitch/actions/settings';
|
||||
import ConversationsListContainer from './containers/conversations_list_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -99,14 +98,6 @@ class DirectTimeline extends React.PureComponent {
|
|||
this.props.dispatch(expandConversations({ maxId }));
|
||||
}
|
||||
|
||||
handleTimelineClick = () => {
|
||||
this.props.dispatch(changeSetting(['direct', 'conversations'], false));
|
||||
}
|
||||
|
||||
handleConversationsClick = () => {
|
||||
this.props.dispatch(changeSetting(['direct', 'conversations'], true));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn, conversationsMode } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
@ -119,6 +110,7 @@ class DirectTimeline extends React.PureComponent {
|
|||
scrollKey={`direct_timeline-${columnId}`}
|
||||
timelineId='direct'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
prepend={<div className='follow_requests-unlocked_explanation'><span><FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a></span></div>}
|
||||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
|
||||
/>
|
||||
);
|
||||
|
@ -129,6 +121,7 @@ class DirectTimeline extends React.PureComponent {
|
|||
scrollKey={`direct_timeline-${columnId}`}
|
||||
timelineId='direct'
|
||||
onLoadMore={this.handleLoadMoreTimeline}
|
||||
prepend={<div className='follow_requests-unlocked_explanation'><span><FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a></span></div>}
|
||||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
|
||||
/>
|
||||
);
|
||||
|
@ -149,27 +142,6 @@ class DirectTimeline extends React.PureComponent {
|
|||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<div className='notification__filter-bar'>
|
||||
<button
|
||||
className={conversationsMode ? 'active' : ''}
|
||||
onClick={this.handleConversationsClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='direct.conversations_mode'
|
||||
defaultMessage='Conversations'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={conversationsMode ? '' : 'active'}
|
||||
onClick={this.handleTimelineClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='direct.timeline_mode'
|
||||
defaultMessage='Timeline'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{contents}
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -191,7 +191,7 @@ class AccountCard extends ImmutablePureComponent {
|
|||
<div className='account-card__counters__item'>
|
||||
<ShortNumber value={account.get('statuses_count')} />
|
||||
<small>
|
||||
<FormattedMessage id='account.posts' defaultMessage='Toots' />
|
||||
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite posts yet. When you favourite one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} name='favourites' label={intl.formatMessage(messages.heading)}>
|
||||
|
|
|
@ -68,7 +68,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this toot yet. When someone does, they will show up here.' />;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
|
|
|
@ -19,6 +19,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import MissingIndicator from 'flavours/glitch/components/missing_indicator';
|
||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||
import TimelineHint from 'flavours/glitch/components/timeline_hint';
|
||||
import LimitedAccountHint from '../account_timeline/components/limited_account_hint';
|
||||
import { getAccountHidden } from 'flavours/glitch/selectors';
|
||||
|
||||
const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||
const accountId = id || state.getIn(['accounts_map', acct]);
|
||||
|
@ -37,6 +39,8 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
|
|||
accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
hidden: getAccountHidden(state, accountId),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -62,6 +66,8 @@ class Followers extends ImmutablePureComponent {
|
|||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
suspended: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
remote: PropTypes.bool,
|
||||
remoteUrl: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
@ -107,7 +113,7 @@ class Followers extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
|
||||
const { accountId, accountIds, hasMore, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -127,7 +133,13 @@ class Followers extends ImmutablePureComponent {
|
|||
|
||||
let emptyMessage;
|
||||
|
||||
if (remote && accountIds.isEmpty()) {
|
||||
const forceEmptyState = suspended || hidden;
|
||||
|
||||
if (suspended) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
|
||||
} else if (hidden) {
|
||||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||
} else if (remote && accountIds.isEmpty()) {
|
||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||
} else {
|
||||
emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
|
||||
|
@ -141,7 +153,7 @@ class Followers extends ImmutablePureComponent {
|
|||
|
||||
<ScrollableList
|
||||
scrollKey='followers'
|
||||
hasMore={hasMore}
|
||||
hasMore={!forceEmptyState && hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
|
||||
|
|
|
@ -19,6 +19,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import MissingIndicator from 'flavours/glitch/components/missing_indicator';
|
||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||
import TimelineHint from 'flavours/glitch/components/timeline_hint';
|
||||
import LimitedAccountHint from '../account_timeline/components/limited_account_hint';
|
||||
import { getAccountHidden } from 'flavours/glitch/selectors';
|
||||
|
||||
const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||
const accountId = id || state.getIn(['accounts_map', acct]);
|
||||
|
@ -37,6 +39,8 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
|
|||
accountIds: state.getIn(['user_lists', 'following', accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
hidden: getAccountHidden(state, accountId),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -62,6 +66,8 @@ class Following extends ImmutablePureComponent {
|
|||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
suspended: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
remote: PropTypes.bool,
|
||||
remoteUrl: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
@ -107,7 +113,7 @@ class Following extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
|
||||
const { accountId, accountIds, hasMore, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -127,7 +133,13 @@ class Following extends ImmutablePureComponent {
|
|||
|
||||
let emptyMessage;
|
||||
|
||||
if (remote && accountIds.isEmpty()) {
|
||||
const forceEmptyState = suspended || hidden;
|
||||
|
||||
if (suspended) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
|
||||
} else if (hidden) {
|
||||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||
} else if (remote && accountIds.isEmpty()) {
|
||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||
} else {
|
||||
emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
|
||||
|
@ -141,7 +153,7 @@ class Following extends ImmutablePureComponent {
|
|||
|
||||
<ScrollableList
|
||||
scrollKey='following'
|
||||
hasMore={hasMore}
|
||||
hasMore={!forceEmptyState && hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
|
||||
|
|
|
@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import IconButton from 'flavours/glitch/components/icon_button';
|
||||
import Icon from 'flavours/glitch/components/icon';
|
||||
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
|
||||
import { autoPlayGif, reduceMotion } from 'flavours/glitch/util/initial_state';
|
||||
import { autoPlayGif, reduceMotion, disableSwiping } from 'flavours/glitch/util/initial_state';
|
||||
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
|
||||
import { mascot } from 'flavours/glitch/util/initial_state';
|
||||
import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light';
|
||||
|
@ -430,6 +430,7 @@ class Announcements extends ImmutablePureComponent {
|
|||
removeReaction={this.props.removeReaction}
|
||||
intl={intl}
|
||||
selected={index === idx}
|
||||
disabled={disableSwiping}
|
||||
/>
|
||||
))}
|
||||
</ReactSwipeableViews>
|
||||
|
|
|
@ -18,7 +18,7 @@ const messages = defineMessages({
|
|||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
|
||||
show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' },
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
||||
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
|
||||
keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Keyboard shortcuts' },
|
||||
featured_users: { id: 'navigation_bar.featured_users', defaultMessage: 'Featured users' },
|
||||
|
|
|
@ -103,7 +103,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
</tr>
|
||||
<tr>
|
||||
<td><kbd>alt</kbd>+<kbd>n</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new toot' /></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new post' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>alt</kbd>+<kbd>x</kbd></td>
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// Package imports
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
export default class LocalSettingsPageItem extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
value: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
hint: PropTypes.string,
|
||||
})),
|
||||
value: PropTypes.any,
|
||||
placeholder: PropTypes.string,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { id, options, children, placeholder, value } = this.props;
|
||||
|
||||
if (options && options.length > 0) {
|
||||
const currentValue = value;
|
||||
const optionElems = options && options.length > 0 && options.map((opt) => {
|
||||
let optionId = `${id}--${opt.value}`;
|
||||
return (
|
||||
<label htmlFor={optionId}>
|
||||
<input
|
||||
type='radio'
|
||||
name={id}
|
||||
id={optionId}
|
||||
value={opt.value}
|
||||
checked={currentValue === opt.value}
|
||||
disabled
|
||||
/>
|
||||
{opt.message}
|
||||
{opt.hint && <span className='hint'>{opt.hint}</span>}
|
||||
</label>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className='glitch local-settings__page__item radio_buttons'>
|
||||
<fieldset>
|
||||
<legend>{children}</legend>
|
||||
{optionElems}
|
||||
</fieldset>
|
||||
</div>
|
||||
);
|
||||
} else if (placeholder) {
|
||||
return (
|
||||
<div className='glitch local-settings__page__item string'>
|
||||
<label htmlFor={id}>
|
||||
<p>{children}</p>
|
||||
<p>
|
||||
<input
|
||||
id={id}
|
||||
type='text'
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
disabled
|
||||
/>
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
} else return (
|
||||
<div className='glitch local-settings__page__item boolean'>
|
||||
<label htmlFor={id}>
|
||||
<input
|
||||
id={id}
|
||||
type='checkbox'
|
||||
checked={value}
|
||||
disabled
|
||||
/>
|
||||
{children}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,7 +5,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
// Our imports
|
||||
import { expandSpoilers, disableSwiping } from 'flavours/glitch/util/initial_state';
|
||||
import { preferenceLink } from 'flavours/glitch/util/backend_links';
|
||||
import LocalSettingsPageItem from './item';
|
||||
import DeprecatedLocalSettingsPageItem from './deprecated_item';
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
|
@ -146,14 +149,28 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
>
|
||||
<FormattedMessage id='settings.navbar_under' defaultMessage='Navbar at the bottom (Mobile only)' />
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
item={['swipe_to_change_columns']}
|
||||
<DeprecatedLocalSettingsPageItem
|
||||
id='mastodon-settings--swipe_to_change_columns'
|
||||
onChange={onChange}
|
||||
value={!disableSwiping}
|
||||
>
|
||||
<FormattedMessage id='settings.swipe_to_change_columns' defaultMessage='Allow swiping to change columns (Mobile only)' />
|
||||
</LocalSettingsPageItem>
|
||||
<span className='hint'>
|
||||
<FormattedMessage
|
||||
id='settings.deprecated_setting'
|
||||
defaultMessage="This setting is now controlled from Mastodon's {settings_page_link}"
|
||||
values={{
|
||||
settings_page_link: (
|
||||
<a href={preferenceLink('user_setting_disable_swiping')}>
|
||||
<FormattedMessage
|
||||
id='settings.shared_settings_link'
|
||||
defaultMessage='user preferences'
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</DeprecatedLocalSettingsPageItem>
|
||||
</section>
|
||||
</div>
|
||||
),
|
||||
|
@ -242,21 +259,35 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
({ intl, onChange, settings }) => (
|
||||
<div className='glitch local-settings__page content_warnings'>
|
||||
<h1><FormattedMessage id='settings.content_warnings' defaultMessage='Content warnings' /></h1>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
item={['content_warnings', 'auto_unfold']}
|
||||
<DeprecatedLocalSettingsPageItem
|
||||
id='mastodon-settings--content_warnings-auto_unfold'
|
||||
onChange={onChange}
|
||||
value={expandSpoilers}
|
||||
>
|
||||
<FormattedMessage id='settings.enable_content_warnings_auto_unfold' defaultMessage='Automatically unfold content-warnings' />
|
||||
</LocalSettingsPageItem>
|
||||
<span className='hint'>
|
||||
<FormattedMessage
|
||||
id='settings.deprecated_setting'
|
||||
defaultMessage="This setting is now controlled from Mastodon's {settings_page_link}"
|
||||
values={{
|
||||
settings_page_link: (
|
||||
<a href={preferenceLink('user_setting_expand_spoilers')}>
|
||||
<FormattedMessage
|
||||
id='settings.shared_settings_link'
|
||||
defaultMessage='user preferences'
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</DeprecatedLocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
item={['content_warnings', 'filter']}
|
||||
id='mastodon-settings--content_warnings-auto_unfold'
|
||||
onChange={onChange}
|
||||
dependsOn={[['content_warnings', 'auto_unfold']]}
|
||||
placeholder={intl.formatMessage(messages.regexp)}
|
||||
disabled={!expandSpoilers}
|
||||
>
|
||||
<FormattedMessage id='settings.content_warnings_filter' defaultMessage='Content warnings to not automatically unfold:' />
|
||||
</LocalSettingsPageItem>
|
||||
|
|
|
@ -21,6 +21,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
|
|||
})),
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
|
@ -33,8 +34,8 @@ export default class LocalSettingsPageItem extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { handleChange } = this;
|
||||
const { settings, item, id, options, children, dependsOn, dependsOnNot, placeholder } = this.props;
|
||||
let enabled = true;
|
||||
const { settings, item, id, options, children, dependsOn, dependsOnNot, placeholder, disabled } = this.props;
|
||||
let enabled = !disabled;
|
||||
|
||||
if (dependsOn) {
|
||||
for (let i = 0; i < dependsOn.length; i++) {
|
||||
|
|
|
@ -69,7 +69,7 @@ class Mutes extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} />,
|
||||
<AccountContainer key={id} id={id} defaultAction='mute' />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
|
|
@ -146,7 +146,7 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-status'>
|
||||
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.status' defaultMessage='New toots:' /></span>
|
||||
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.status' defaultMessage='New posts:' /></span>
|
||||
|
||||
<div className='column-settings__pillbar'>
|
||||
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status']} onChange={onChange} label={alertStr} />
|
||||
|
|
|
@ -10,7 +10,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.pins', defaultMessage: 'Pinned toot' },
|
||||
heading: { id: 'column.pins', defaultMessage: 'Pinned post' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
|
|
@ -68,7 +68,7 @@ class Reblogs extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />;
|
||||
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from 'flavours/glitch/components/button';
|
||||
import Option from './components/option';
|
||||
|
@ -17,11 +19,17 @@ const messages = defineMessages({
|
|||
account: { id: 'report.category.title_account', defaultMessage: 'profile' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
const mapStateToProps = state => ({
|
||||
rules: state.get('rules'),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Category extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onNextStep: PropTypes.func.isRequired,
|
||||
rules: ImmutablePropTypes.list,
|
||||
category: PropTypes.string,
|
||||
onChangeCategory: PropTypes.func.isRequired,
|
||||
startedFrom: PropTypes.oneOf(['status', 'account']),
|
||||
|
@ -53,13 +61,15 @@ class Category extends React.PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { category, startedFrom, intl } = this.props;
|
||||
const { category, startedFrom, rules, intl } = this.props;
|
||||
|
||||
const options = [
|
||||
'dislike',
|
||||
const options = rules.size > 0 ? [
|
||||
'spam',
|
||||
'violation',
|
||||
'other',
|
||||
] : [
|
||||
'spam',
|
||||
'other',
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
@ -134,10 +134,6 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
outerStyle.height = `${this.state.height}px`;
|
||||
}
|
||||
|
||||
if (status.get('poll')) {
|
||||
media.push(<PollContainer pollId={status.get('poll')} />);
|
||||
mediaIcons.push('tasks');
|
||||
}
|
||||
if (usingPiP) {
|
||||
media.push(<PictureInPicturePlaceholder />);
|
||||
mediaIcons.push('video-camera');
|
||||
|
@ -202,6 +198,11 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
mediaIcons.push('link');
|
||||
}
|
||||
|
||||
if (status.get('poll')) {
|
||||
media.push(<PollContainer pollId={status.get('poll')} />);
|
||||
mediaIcons.push('tasks');
|
||||
}
|
||||
|
||||
if (status.get('application')) {
|
||||
applicationLink = <React.Fragment> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></React.Fragment>;
|
||||
}
|
||||
|
|
|
@ -14,14 +14,11 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import PrivacyDropdown from 'flavours/glitch/features/compose/components/privacy_dropdown';
|
||||
import classNames from 'classnames';
|
||||
import { changeBoostPrivacy } from 'flavours/glitch/actions/boosts';
|
||||
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
|
@ -85,15 +82,6 @@ class BoostModal extends ImmutablePureComponent {
|
|||
const { status, missingMediaDescription, privacy, intl } = this.props;
|
||||
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
|
||||
|
||||
const visibilityIconInfo = {
|
||||
'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) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal boost-modal'>
|
||||
<div className='boost-modal__container'>
|
||||
|
@ -101,7 +89,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
<div className='boost-modal__status-header'>
|
||||
<div className='boost-modal__status-time'>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||
<VisibilityIcon visibility={status.get('visibility')} />
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
|||
import TabsBar, { links, getIndex, getLink } from './tabs_bar';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { disableSwiping } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
import BundleContainer from '../containers/bundle_container';
|
||||
import ColumnLoading from './column_loading';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
|
@ -63,7 +65,6 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
columns: ImmutablePropTypes.list.isRequired,
|
||||
swipeToChangeColumns: PropTypes.bool,
|
||||
singleColumn: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
navbarUnder: PropTypes.bool,
|
||||
|
@ -210,7 +211,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { columns, children, singleColumn, swipeToChangeColumns, intl, navbarUnder, openSettings } = this.props;
|
||||
const { columns, children, singleColumn, intl, navbarUnder, openSettings } = this.props;
|
||||
const { shouldAnimate, renderComposePanel } = this.state;
|
||||
|
||||
const columnIndex = getIndex(this.context.router.history.location.pathname);
|
||||
|
@ -219,7 +220,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||
|
||||
const content = columnIndex !== -1 ? (
|
||||
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={!swipeToChangeColumns}>
|
||||
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
|
||||
{links.map(this.renderView)}
|
||||
</ReactSwipeableViews>
|
||||
) : (
|
||||
|
@ -234,7 +235,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className='columns-area__panels__main'>
|
||||
<div className={`columns-area__panels__main ${floatingActionButton && 'with-fab'}`}>
|
||||
{!navbarUnder && <TabsBar key='tabs' />}
|
||||
{content}
|
||||
{navbarUnder && <TabsBar key='tabs' />}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { preferenceLink } from 'flavours/glitch/util/backend_links';
|
||||
import Button from 'flavours/glitch/components/button';
|
||||
import Icon from 'flavours/glitch/components/icon';
|
||||
import illustration from 'flavours/glitch/images/logo_warn_glitch.svg';
|
||||
|
||||
const messages = defineMessages({
|
||||
discardChanges: { id: 'confirmations.deprecated_settings.confirm', defaultMessage: 'Use Mastodon preferences' },
|
||||
user_setting_expand_spoilers: { id: 'settings.enable_content_warnings_auto_unfold', defaultMessage: 'Automatically unfold content-warnings' },
|
||||
user_setting_disable_swiping: { id: 'settings.swipe_to_change_columns', defaultMessage: 'Allow swiping to change columns (Mobile only)' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class DeprecatedSettingsModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.list.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.button.focus();
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onConfirm();
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.button = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { settings, intl } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal confirmation-modal'>
|
||||
<div className='confirmation-modal__container'>
|
||||
|
||||
<img src={illustration} className='modal-warning' alt='' />
|
||||
|
||||
<FormattedMessage
|
||||
id='confirmations.deprecated_settings.message'
|
||||
defaultMessage='Some of the glitch-soc device-specific {app_settings} you are using have been replaced by Mastodon {preferences} and will be overriden:'
|
||||
values={{
|
||||
app_settings: (
|
||||
<strong className='deprecated-settings-label'>
|
||||
<Icon id='cogs' /> <FormattedMessage id='navigation_bar.app_settings' defaultMessage='App settings' />
|
||||
</strong>
|
||||
),
|
||||
preferences: (
|
||||
<strong className='deprecated-settings-label'>
|
||||
<Icon id='cog' /> <FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className='deprecated-settings-info'>
|
||||
<ul>
|
||||
{ settings.map((setting_name) => (
|
||||
<li>
|
||||
<a href={preferenceLink(setting_name)}><FormattedMessage {...messages[setting_name]} /></a>
|
||||
</li>
|
||||
)) }
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className='confirmation-modal__action-bar'>
|
||||
<div />
|
||||
<Button text={intl.formatMessage(messages.discardChanges)} onClick={this.handleClick} ref={this.setRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,13 +11,10 @@ import AttachmentList from 'flavours/glitch/components/attachment_list';
|
|||
import Icon from 'flavours/glitch/components/icon';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import classNames from 'classnames';
|
||||
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
|
@ -60,15 +57,6 @@ class FavouriteModal extends ImmutablePureComponent {
|
|||
render () {
|
||||
const { status, intl } = this.props;
|
||||
|
||||
const visibilityIconInfo = {
|
||||
'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) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal favourite-modal'>
|
||||
<div className='favourite-modal__container'>
|
||||
|
@ -76,7 +64,7 @@ class FavouriteModal extends ImmutablePureComponent {
|
|||
<div className='favourite-modal__status-header'>
|
||||
<div className='favourite-modal__status-time'>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||
<VisibilityIcon visibility={status.get('visibility')} />
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -12,6 +12,7 @@ import Icon from 'flavours/glitch/components/icon';
|
|||
import GIFV from 'flavours/glitch/components/gifv';
|
||||
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
|
||||
import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
|
||||
import { disableSwiping } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -227,6 +228,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
onChangeIndex={this.handleSwipe}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
index={index}
|
||||
disabled={disableSwiping}
|
||||
>
|
||||
{content}
|
||||
</ReactSwipeableViews>
|
||||
|
|
|
@ -14,6 +14,7 @@ import AudioModal from './audio_modal';
|
|||
import DoodleModal from './doodle_modal';
|
||||
import ConfirmationModal from './confirmation_modal';
|
||||
import FocalPointModal from './focal_point_modal';
|
||||
import DeprecatedSettingsModal from './deprecated_settings_modal';
|
||||
import {
|
||||
OnboardingModal,
|
||||
MuteModal,
|
||||
|
@ -40,6 +41,7 @@ const MODAL_COMPONENTS = {
|
|||
'BLOCK': BlockModal,
|
||||
'REPORT': ReportModal,
|
||||
'SETTINGS': SettingsModal,
|
||||
'DEPRECATED_SETTINGS': () => Promise.resolve({ default: DeprecatedSettingsModal }),
|
||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||
'EMBED': EmbedModal,
|
||||
'LIST_EDITOR': ListEditor,
|
||||
|
|
|
@ -4,7 +4,6 @@ import { openModal } from 'flavours/glitch/actions/modal';
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
columns: state.getIn(['settings', 'columns']),
|
||||
swipeToChangeColumns: state.getIn(['local_settings', 'swipe_to_change_columns']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -11,6 +11,7 @@ import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/
|
|||
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
|
||||
import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
|
||||
import { fetchFilters } from 'flavours/glitch/actions/filters';
|
||||
import { fetchRules } from 'flavours/glitch/actions/rules';
|
||||
import { clearHeight } from 'flavours/glitch/actions/height_cache';
|
||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
|
||||
import { WrappedSwitch, WrappedRoute } from 'flavours/glitch/util/react_router_helpers';
|
||||
|
@ -402,6 +403,7 @@ class UI extends React.Component {
|
|||
this.props.dispatch(expandHomeTimeline());
|
||||
this.props.dispatch(expandNotifications());
|
||||
setTimeout(() => this.props.dispatch(fetchFilters()), 500);
|
||||
setTimeout(() => this.props.dispatch(fetchRules()), 3000);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 216.41507 232.00976"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="logo_warn_glitch.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.7951831"
|
||||
inkscape:cx="-30.916067"
|
||||
inkscape:cy="90.241493"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
id="g2025">
|
||||
<path
|
||||
d="M211.80683 139.0875c-3.1825 16.36625-28.4925 34.2775-57.5625 37.74875-15.16 1.80875-30.0825 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.3925 27.9425 21.115.7225 39.91625-5.20625 39.91625-5.20625l.86875 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23183 213.82 1.40558 165.31125.20808 116.09125c-.36375-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67058 3.45375 78.20308.2425 107.86433 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.97625 14.7525 32.97625 65.0825 0 0 .4125 37.13375-4.6 62.915"
|
||||
fill="#3088d4"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 124.52893,137.75645 c 0,9.01375 -7.30875,16.32125 -16.3225,16.32125 -9.01375,0 -16.32125,-7.3075 -16.32125,-16.32125 0,-9.01375 7.3075,-16.3225 16.32125,-16.3225 9.01375,0 16.3225,7.30875 16.3225,16.3225"
|
||||
fill="#ffffff"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="csssc" />
|
||||
<path
|
||||
id="path1121"
|
||||
d="m 108.20703,25.453125 c -9.013749,0 -16.322264,7.308516 -16.322264,16.322266 0,5.31808 2.555126,37.386806 6.492187,67.763669 4.100497,4.20028 15.890147,3.77063 19.660157,-0.01 3.9367,-30.375272 6.49219,-62.4364 6.49219,-67.753909 0,-9.01375 -7.30852,-16.322266 -16.32227,-16.322266 z"
|
||||
style="fill:#ffffff"
|
||||
sodipodi:nodetypes="ssccsss" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,4 +1,5 @@
|
|||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
|
||||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from 'flavours/glitch/actions/importer';
|
||||
import { ACCOUNT_REVEAL } from 'flavours/glitch/actions/accounts';
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
@ -10,6 +11,8 @@ const normalizeAccount = (state, account) => {
|
|||
delete account.following_count;
|
||||
delete account.statuses_count;
|
||||
|
||||
account.hidden = state.getIn([account.id, 'hidden']) === false ? false : account.limited;
|
||||
|
||||
return state.set(account.id, fromJS(account));
|
||||
};
|
||||
|
||||
|
@ -27,6 +30,8 @@ export default function accounts(state = initialState, action) {
|
|||
return normalizeAccount(state, action.account);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return normalizeAccounts(state, action.accounts);
|
||||
case ACCOUNT_REVEAL:
|
||||
return state.setIn([action.id, 'hidden'], false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
COMPOSE_SPOILERNESS_CHANGE,
|
||||
COMPOSE_SPOILER_TEXT_CHANGE,
|
||||
COMPOSE_VISIBILITY_CHANGE,
|
||||
COMPOSE_LANGUAGE_CHANGE,
|
||||
COMPOSE_CONTENT_TYPE_CHANGE,
|
||||
COMPOSE_EMOJI_INSERT,
|
||||
COMPOSE_UPLOAD_CHANGE_REQUEST,
|
||||
|
@ -100,6 +101,7 @@ const initialState = ImmutableMap({
|
|||
}),
|
||||
default_privacy: 'public',
|
||||
default_sensitive: false,
|
||||
default_language: 'en',
|
||||
resetFileKey: Math.floor((Math.random() * 0x10000)),
|
||||
idempotencyKey: null,
|
||||
tagHistory: ImmutableList(),
|
||||
|
@ -175,7 +177,8 @@ function clearAll(state) {
|
|||
map => map.mergeWith(overwrite, state.get('default_advanced_options'))
|
||||
);
|
||||
map.set('privacy', state.get('default_privacy'));
|
||||
map.set('sensitive', false);
|
||||
map.set('sensitive', state.get('default_sensitive'));
|
||||
map.set('language', state.get('default_language'));
|
||||
map.update('media_attachments', list => list.clear());
|
||||
map.set('poll', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
@ -557,6 +560,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('sensitive', action.status.get('sensitive'));
|
||||
map.set('language', action.status.get('language'));
|
||||
map.update(
|
||||
'advanced_options',
|
||||
map => map.merge(new ImmutableMap({ do_not_federate }))
|
||||
|
@ -589,6 +593,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('sensitive', action.status.get('sensitive'));
|
||||
map.set('language', action.status.get('language'));
|
||||
|
||||
if (action.spoiler_text.length > 0) {
|
||||
map.set('spoiler', true);
|
||||
|
@ -618,6 +623,8 @@ export default function compose(state = initialState, action) {
|
|||
return state.updateIn(['poll', 'options'], options => options.delete(action.index));
|
||||
case COMPOSE_POLL_SETTINGS_CHANGE:
|
||||
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
||||
case COMPOSE_LANGUAGE_CHANGE:
|
||||
return state.set('language', action.language);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import {
|
||||
IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST,
|
||||
IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS,
|
||||
IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
|
||||
} from '../actions/identity_proofs';
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
export default function identityProofsReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS:
|
||||
return state.update(identity_proofs => identity_proofs.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set(action.accountId, fromJS(action.identity_proofs));
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -34,7 +34,6 @@ import conversations from './conversations';
|
|||
import suggestions from './suggestions';
|
||||
import pinnedAccountsEditor from './pinned_accounts_editor';
|
||||
import polls from './polls';
|
||||
import identity_proofs from './identity_proofs';
|
||||
import trends from './trends';
|
||||
import announcements from './announcements';
|
||||
import markers from './markers';
|
||||
|
@ -73,7 +72,6 @@ const reducers = {
|
|||
notifications,
|
||||
height_cache,
|
||||
custom_emojis,
|
||||
identity_proofs,
|
||||
lists,
|
||||
listEditor,
|
||||
listAdder,
|
||||
|
|
|
@ -3,13 +3,12 @@ import { Map as ImmutableMap } from 'immutable';
|
|||
|
||||
// Our imports.
|
||||
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
|
||||
import { LOCAL_SETTING_CHANGE } from 'flavours/glitch/actions/local_settings';
|
||||
import { LOCAL_SETTING_CHANGE, LOCAL_SETTING_DELETE } from 'flavours/glitch/actions/local_settings';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
layout : 'auto',
|
||||
stretch : true,
|
||||
navbar_under : false,
|
||||
swipe_to_change_columns: true,
|
||||
side_arm : 'none',
|
||||
side_arm_reply_mode : 'keep',
|
||||
show_reply_count : false,
|
||||
|
@ -26,7 +25,6 @@ const initialState = ImmutableMap({
|
|||
tag_misleading_links: true,
|
||||
rewrite_mentions: 'no',
|
||||
content_warnings : ImmutableMap({
|
||||
auto_unfold : false,
|
||||
filter : null,
|
||||
}),
|
||||
collapsed : ImmutableMap({
|
||||
|
@ -66,6 +64,8 @@ export default function localSettings(state = initialState, action) {
|
|||
return hydrate(state, action.state.get('local_settings'));
|
||||
case LOCAL_SETTING_CHANGE:
|
||||
return state.setIn(action.key, action.value);
|
||||
case LOCAL_SETTING_DELETE:
|
||||
return state.deleteIn(action.key);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { NOTIFICATIONS_FILTER_SET } from 'flavours/glitch/actions/notifications'
|
|||
import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE, COLUMN_PARAMS_CHANGE } from 'flavours/glitch/actions/columns';
|
||||
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
|
||||
import { EMOJI_USE } from 'flavours/glitch/actions/emojis';
|
||||
import { LANGUAGE_USE } from 'flavours/glitch/actions/languages';
|
||||
import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists';
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import uuid from 'flavours/glitch/util/uuid';
|
||||
|
@ -134,6 +135,8 @@ const changeColumnParams = (state, uuid, path, value) => {
|
|||
|
||||
const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false);
|
||||
|
||||
const updateFrequentLanguages = (state, language) => state.update('frequentlyUsedLanguages', ImmutableMap(), map => map.update(language, 0, count => count + 1)).set('saved', false);
|
||||
|
||||
const filterDeadListColumns = (state, listId) => state.update('columns', columns => columns.filterNot(column => column.get('id') === 'LIST' && column.get('params').get('id') === listId));
|
||||
|
||||
export default function settings(state = initialState, action) {
|
||||
|
@ -159,6 +162,8 @@ export default function settings(state = initialState, action) {
|
|||
return changeColumnParams(state, action.uuid, action.path, action.value);
|
||||
case EMOJI_USE:
|
||||
return updateFrequentEmojis(state, action.emoji);
|
||||
case LANGUAGE_USE:
|
||||
return updateFrequentLanguages(state, action.language);
|
||||
case SETTING_SAVE:
|
||||
return state.set('saved', true);
|
||||
case LIST_FETCH_FAIL:
|
||||
|
|
|
@ -194,3 +194,11 @@ export const getAccountGallery = createSelector([
|
|||
|
||||
return medias;
|
||||
});
|
||||
|
||||
export const getAccountHidden = createSelector([
|
||||
(state, id) => state.getIn(['accounts', id, 'hidden']),
|
||||
(state, id) => state.getIn(['relationships', id, 'following']) || state.getIn(['relationships', id, 'requested']),
|
||||
(state, id) => id === me,
|
||||
], (hidden, followingOrRequested, isSelf) => {
|
||||
return hidden && !(isSelf || followingOrRequested);
|
||||
});
|
||||
|
|
|
@ -560,6 +560,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.limited-account-hint {
|
||||
p {
|
||||
color: $secondary-text-color;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-column-indicator,
|
||||
.error-column,
|
||||
.follow_requests-unlocked_explanation {
|
||||
|
@ -579,7 +588,7 @@
|
|||
}
|
||||
|
||||
& > span {
|
||||
max-width: 400px;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
@ -644,3 +644,68 @@
|
|||
& > .count { color: $warning-red }
|
||||
}
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
&__dropdown {
|
||||
position: absolute;
|
||||
background: $simple-background-color;
|
||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
|
||||
&.top {
|
||||
transform-origin: 50% 100%;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
transform-origin: 50% 0;
|
||||
}
|
||||
|
||||
.emoji-mart-search {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.emoji-mart-search-icon {
|
||||
right: 10px + 5px;
|
||||
}
|
||||
|
||||
.emoji-mart-scroll {
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
|
||||
&__results {
|
||||
&__item {
|
||||
cursor: pointer;
|
||||
color: $inverted-text-color;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover {
|
||||
background: $ui-secondary-color;
|
||||
}
|
||||
|
||||
&__common-name {
|
||||
color: $darker-text-color;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $ui-highlight-color;
|
||||
color: $primary-text-color;
|
||||
outline: 0;
|
||||
|
||||
.language-dropdown__dropdown__results__item__common-name {
|
||||
color: $secondary-text-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: lighten($ui-highlight-color, 4%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1566,7 +1566,7 @@ button.icon-button.active i.fa-retweet {
|
|||
.loading-bar {
|
||||
background-color: $ui-highlight-color;
|
||||
height: 3px;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
|
|
|
@ -98,6 +98,18 @@
|
|||
|
||||
.glitch.local-settings__page__item {
|
||||
margin-bottom: 2px;
|
||||
|
||||
.hint a {
|
||||
color: $lighter-text-color;
|
||||
font-weight: 500;
|
||||
text-decoration: underline;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.glitch.local-settings__page__item.string,
|
||||
|
@ -120,3 +132,29 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deprecated-settings-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.deprecated-settings-info {
|
||||
text-align: start;
|
||||
|
||||
ul {
|
||||
padding: 10px;
|
||||
margin-left: 12px;
|
||||
list-style: disc inside;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $lighter-text-color;
|
||||
font-weight: 500;
|
||||
text-decoration: underline;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1279,3 +1279,10 @@
|
|||
pointer-events: auto;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
img.modal-warning {
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-bottom: 15px;
|
||||
width: 60px;
|
||||
}
|
||||
|
|
|
@ -233,6 +233,10 @@
|
|||
.columns-area__panels__pane--compositional {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.with-fab .scrollable .item-list:last-child {
|
||||
padding-bottom: 5.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px + (285px * 1) + (10px * 1)) {
|
||||
|
|
|
@ -1022,68 +1022,6 @@ code {
|
|||
}
|
||||
}
|
||||
|
||||
.connection-prompt {
|
||||
margin-bottom: 25px;
|
||||
|
||||
.fa-link {
|
||||
background-color: darken($ui-base-color, 4%);
|
||||
border-radius: 100%;
|
||||
font-size: 24px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&__column {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
flex-shrink: 1;
|
||||
max-width: 50%;
|
||||
|
||||
&-sep {
|
||||
align-self: center;
|
||||
flex-grow: 0;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
p {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.account__avatar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__connection {
|
||||
background-color: lighten($ui-base-color, 8%);
|
||||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||
border-radius: 4px;
|
||||
padding: 25px 10px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&::after {
|
||||
background-color: darken($ui-base-color, 4%);
|
||||
content: '';
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.input.user_confirm_password,
|
||||
.input.user_website {
|
||||
&:not(.field_with_errors) {
|
||||
|
|
|
@ -7,3 +7,12 @@ export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${acc
|
|||
export const filterEditLink = (id) => `/filters/${id}/edit`;
|
||||
export const relationshipsLink = '/relationships';
|
||||
export const securityLink = '/auth/edit';
|
||||
export const preferenceLink = (setting_name) => {
|
||||
switch (setting_name) {
|
||||
case 'user_setting_expand_spoilers':
|
||||
case 'user_setting_disable_swiping':
|
||||
return `/settings/preferences/appearance#${setting_name}`;
|
||||
default:
|
||||
return preferencesLink;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { expandSpoilers } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
export function autoUnfoldCW (settings, status) {
|
||||
if (!settings.getIn(['content_warnings', 'auto_unfold'])) {
|
||||
if (!expandSpoilers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ const getMeta = (prop) => initialState && initialState.meta && initialState.meta
|
|||
|
||||
export const reduceMotion = getMeta('reduce_motion');
|
||||
export const autoPlayGif = getMeta('auto_play_gif');
|
||||
export const displaySensitiveMedia = getMeta('display_sensitive_media');
|
||||
export const displayMedia = getMeta('display_media') || (getMeta('display_sensitive_media') ? 'show_all' : 'default');
|
||||
export const expandSpoilers = getMeta('expand_spoilers');
|
||||
export const unfollowModal = getMeta('unfollow_modal');
|
||||
export const boostModal = getMeta('boost_modal');
|
||||
export const favouriteModal = getMeta('favourite_modal');
|
||||
|
@ -37,5 +37,7 @@ export const useBlurhash = getMeta('use_blurhash');
|
|||
export const usePendingItems = getMeta('use_pending_items');
|
||||
export const useSystemEmojiFont = getMeta('system_emoji_font');
|
||||
export const showTrends = getMeta('trends');
|
||||
export const disableSwiping = getMeta('disable_swiping');
|
||||
export const languages = initialState && initialState.languages;
|
||||
|
||||
export default initialState;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 622 B |
Binary file not shown.
After Width: | Height: | Size: 418 B |
Binary file not shown.
After Width: | Height: | Size: 488 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue