Merge pull request #1327 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
commit
e1d2820234
101 changed files with 2766 additions and 1987 deletions
|
@ -30,7 +30,7 @@ plugins:
|
||||||
channel: eslint-6
|
channel: eslint-6
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: rubocop-0-76
|
channel: rubocop-0-82
|
||||||
sass-lint:
|
sass-lint:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude_patterns:
|
exclude_patterns:
|
||||||
|
|
|
@ -33,7 +33,7 @@ LOCAL_DOMAIN=example.com
|
||||||
# ALTERNATE_DOMAINS=example1.com,example2.com
|
# ALTERNATE_DOMAINS=example1.com,example2.com
|
||||||
|
|
||||||
# Application secrets
|
# Application secrets
|
||||||
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
|
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web bundle exec rake secret` if you use docker compose)
|
||||||
SECRET_KEY_BASE=
|
SECRET_KEY_BASE=
|
||||||
OTP_SECRET=
|
OTP_SECRET=
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ OTP_SECRET=
|
||||||
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
||||||
# be invalidated, requiring the users to access the website again to resubscribe.
|
# be invalidated, requiring the users to access the website again to resubscribe.
|
||||||
#
|
#
|
||||||
# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose)
|
# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web bundle exec rake mastodon:webpush:generate_vapid_key` if you use docker compose)
|
||||||
#
|
#
|
||||||
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
||||||
VAPID_PRIVATE_KEY=
|
VAPID_PRIVATE_KEY=
|
||||||
|
|
|
@ -2,7 +2,7 @@ require:
|
||||||
- rubocop-rails
|
- rubocop-rails
|
||||||
|
|
||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.3
|
TargetRubyVersion: 2.4
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/**/*'
|
- 'spec/**/*'
|
||||||
- 'db/**/*'
|
- 'db/**/*'
|
||||||
|
@ -46,7 +46,7 @@ Metrics/ClassLength:
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 25
|
Max: 25
|
||||||
|
|
||||||
Metrics/LineLength:
|
Layout/LineLength:
|
||||||
AllowURI: true
|
AllowURI: true
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
|
24
Gemfile
24
Gemfile
|
@ -20,7 +20,7 @@ gem 'makara', '~> 0.4'
|
||||||
gem 'pghero', '~> 2.4'
|
gem 'pghero', '~> 2.4'
|
||||||
gem 'dotenv-rails', '~> 2.7'
|
gem 'dotenv-rails', '~> 2.7'
|
||||||
|
|
||||||
gem 'aws-sdk-s3', '~> 1.63', require: false
|
gem 'aws-sdk-s3', '~> 1.64', require: false
|
||||||
gem 'fog-core', '<= 2.1.0'
|
gem 'fog-core', '<= 2.1.0'
|
||||||
gem 'fog-openstack', '~> 0.3', require: false
|
gem 'fog-openstack', '~> 0.3', require: false
|
||||||
gem 'paperclip', '~> 6.0'
|
gem 'paperclip', '~> 6.0'
|
||||||
|
@ -49,7 +49,7 @@ gem 'omniauth-saml', '~> 1.10'
|
||||||
gem 'omniauth', '~> 1.9'
|
gem 'omniauth', '~> 1.9'
|
||||||
|
|
||||||
gem 'discard', '~> 1.2'
|
gem 'discard', '~> 1.2'
|
||||||
gem 'doorkeeper', '~> 5.3'
|
gem 'doorkeeper', '~> 5.4'
|
||||||
gem 'fast_blank', '~> 1.0'
|
gem 'fast_blank', '~> 1.0'
|
||||||
gem 'fastimage'
|
gem 'fastimage'
|
||||||
gem 'goldfinger', '~> 2.1'
|
gem 'goldfinger', '~> 2.1'
|
||||||
|
@ -57,12 +57,12 @@ gem 'hiredis', '~> 0.6'
|
||||||
gem 'redis-namespace', '~> 1.7'
|
gem 'redis-namespace', '~> 1.7'
|
||||||
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
||||||
gem 'htmlentities', '~> 4.3'
|
gem 'htmlentities', '~> 4.3'
|
||||||
gem 'http', '~> 4.3'
|
gem 'http', '~> 4.4'
|
||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
|
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
|
||||||
gem 'httplog', '~> 1.4.2'
|
gem 'httplog', '~> 1.4.2'
|
||||||
gem 'idn-ruby', require: 'idn'
|
gem 'idn-ruby', require: 'idn'
|
||||||
gem 'kaminari', '~> 1.1'
|
gem 'kaminari', '~> 1.2'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
|
||||||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
||||||
|
@ -75,7 +75,7 @@ gem 'parallel', '~> 1.19'
|
||||||
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
||||||
gem 'pundit', '~> 2.1'
|
gem 'pundit', '~> 2.1'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'rack-attack', '~> 6.2'
|
gem 'rack-attack', '~> 6.3'
|
||||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||||
gem 'rails-i18n', '~> 5.1'
|
gem 'rails-i18n', '~> 5.1'
|
||||||
gem 'rails-settings-cached', '~> 0.6'
|
gem 'rails-settings-cached', '~> 0.6'
|
||||||
|
@ -96,8 +96,8 @@ gem 'strong_migrations', '~> 0.6'
|
||||||
gem 'tty-command', '~> 0.9', require: false
|
gem 'tty-command', '~> 0.9', require: false
|
||||||
gem 'tty-prompt', '~> 0.21', require: false
|
gem 'tty-prompt', '~> 0.21', require: false
|
||||||
gem 'twitter-text', '~> 1.14'
|
gem 'twitter-text', '~> 1.14'
|
||||||
gem 'tzinfo-data', '~> 1.2019'
|
gem 'tzinfo-data', '~> 1.2020'
|
||||||
gem 'webpacker', '~> 4.2'
|
gem 'webpacker', '~> 5.1'
|
||||||
gem 'webpush'
|
gem 'webpush'
|
||||||
|
|
||||||
gem 'json-ld'
|
gem 'json-ld'
|
||||||
|
@ -110,7 +110,7 @@ group :development, :test do
|
||||||
gem 'fabrication', '~> 2.21'
|
gem 'fabrication', '~> 2.21'
|
||||||
gem 'fuubar', '~> 2.5'
|
gem 'fuubar', '~> 2.5'
|
||||||
gem 'i18n-tasks', '~> 0.9', require: false
|
gem 'i18n-tasks', '~> 0.9', require: false
|
||||||
gem 'pry-byebug', '~> 3.8'
|
gem 'pry-byebug', '~> 3.9'
|
||||||
gem 'pry-rails', '~> 0.3'
|
gem 'pry-rails', '~> 0.3'
|
||||||
gem 'rspec-rails', '~> 4.0'
|
gem 'rspec-rails', '~> 4.0'
|
||||||
end
|
end
|
||||||
|
@ -120,7 +120,7 @@ group :production, :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.31'
|
gem 'capybara', '~> 3.32'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 2.11'
|
gem 'faker', '~> 2.11'
|
||||||
gem 'microformats', '~> 4.2'
|
gem 'microformats', '~> 4.2'
|
||||||
|
@ -135,18 +135,18 @@ end
|
||||||
group :development do
|
group :development do
|
||||||
gem 'active_record_query_trace', '~> 1.7'
|
gem 'active_record_query_trace', '~> 1.7'
|
||||||
gem 'annotate', '~> 3.1'
|
gem 'annotate', '~> 3.1'
|
||||||
gem 'better_errors', '~> 2.6'
|
gem 'better_errors', '~> 2.7'
|
||||||
gem 'binding_of_caller', '~> 0.7'
|
gem 'binding_of_caller', '~> 0.7'
|
||||||
gem 'bullet', '~> 6.1'
|
gem 'bullet', '~> 6.1'
|
||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.7'
|
||||||
gem 'letter_opener_web', '~> 1.4'
|
gem 'letter_opener_web', '~> 1.4'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 0.79', require: false
|
gem 'rubocop', '~> 0.82', require: false
|
||||||
gem 'rubocop-rails', '~> 2.5', require: false
|
gem 'rubocop-rails', '~> 2.5', require: false
|
||||||
gem 'brakeman', '~> 4.8', require: false
|
gem 'brakeman', '~> 4.8', require: false
|
||||||
gem 'bundler-audit', '~> 0.6', require: false
|
gem 'bundler-audit', '~> 0.6', require: false
|
||||||
|
|
||||||
gem 'capistrano', '~> 3.13'
|
gem 'capistrano', '~> 3.14'
|
||||||
gem 'capistrano-rails', '~> 1.4'
|
gem 'capistrano-rails', '~> 1.4'
|
||||||
gem 'capistrano-rbenv', '~> 2.1'
|
gem 'capistrano-rbenv', '~> 2.1'
|
||||||
gem 'capistrano-yarn', '~> 2.0'
|
gem 'capistrano-yarn', '~> 2.0'
|
||||||
|
|
154
Gemfile.lock
154
Gemfile.lock
|
@ -92,23 +92,23 @@ GEM
|
||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.303.0)
|
aws-partitions (1.312.0)
|
||||||
aws-sdk-core (3.94.0)
|
aws-sdk-core (3.95.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.30.0)
|
aws-sdk-kms (1.31.0)
|
||||||
aws-sdk-core (~> 3, >= 3.71.0)
|
aws-sdk-core (~> 3, >= 3.71.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.63.0)
|
aws-sdk-s3 (1.64.0)
|
||||||
aws-sdk-core (~> 3, >= 3.83.0)
|
aws-sdk-core (~> 3, >= 3.83.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.1.2)
|
aws-sigv4 (1.1.3)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
bcrypt (3.1.13)
|
bcrypt (3.1.13)
|
||||||
better_errors (2.6.0)
|
better_errors (2.7.0)
|
||||||
coderay (>= 1.0.0)
|
coderay (>= 1.0.0)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
|
@ -118,8 +118,8 @@ GEM
|
||||||
ffi (~> 1.10.0)
|
ffi (~> 1.10.0)
|
||||||
bootsnap (1.4.6)
|
bootsnap (1.4.6)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (4.8.0)
|
brakeman (4.8.1)
|
||||||
browser (4.0.0)
|
browser (4.1.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bullet (6.1.0)
|
bullet (6.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -127,8 +127,8 @@ GEM
|
||||||
bundler-audit (0.6.1)
|
bundler-audit (0.6.1)
|
||||||
bundler (>= 1.2.0, < 3)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
byebug (11.1.1)
|
byebug (11.1.3)
|
||||||
capistrano (3.13.0)
|
capistrano (3.14.0)
|
||||||
airbrussh (>= 1.0.0)
|
airbrussh (>= 1.0.0)
|
||||||
i18n
|
i18n
|
||||||
rake (>= 10.0.0)
|
rake (>= 10.0.0)
|
||||||
|
@ -143,7 +143,7 @@ GEM
|
||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (3.31.0)
|
capybara (3.32.1)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
|
@ -194,7 +194,7 @@ GEM
|
||||||
docile (1.3.2)
|
docile (1.3.2)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (5.3.1)
|
doorkeeper (5.4.0)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.5)
|
||||||
dotenv-rails (2.7.5)
|
dotenv-rails (2.7.5)
|
||||||
|
@ -213,7 +213,7 @@ GEM
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
equatable (0.6.1)
|
equatable (0.6.1)
|
||||||
erubi (1.9.0)
|
erubi (1.9.0)
|
||||||
et-orbi (1.2.3)
|
et-orbi (1.2.4)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.73.0)
|
excon (0.73.0)
|
||||||
fabrication (2.21.1)
|
fabrication (2.21.1)
|
||||||
|
@ -240,7 +240,7 @@ GEM
|
||||||
fog-json (>= 1.0)
|
fog-json (>= 1.0)
|
||||||
ipaddress (>= 0.8)
|
ipaddress (>= 0.8)
|
||||||
formatador (0.2.5)
|
formatador (0.2.5)
|
||||||
fugit (1.3.3)
|
fugit (1.3.5)
|
||||||
et-orbi (~> 1.1, >= 1.1.8)
|
et-orbi (~> 1.1, >= 1.1.8)
|
||||||
raabro (~> 1.1)
|
raabro (~> 1.1)
|
||||||
fuubar (2.5.0)
|
fuubar (2.5.0)
|
||||||
|
@ -270,7 +270,7 @@ GEM
|
||||||
hiredis (0.6.3)
|
hiredis (0.6.3)
|
||||||
hkdf (0.3.0)
|
hkdf (0.3.0)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http (4.3.0)
|
http (4.4.1)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
http-cookie (~> 1.0)
|
http-cookie (~> 1.0)
|
||||||
http-form_data (~> 2.2)
|
http-form_data (~> 2.2)
|
||||||
|
@ -303,7 +303,7 @@ GEM
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.3.0)
|
json (2.3.0)
|
||||||
json-canonicalization (0.2.0)
|
json-canonicalization (0.2.0)
|
||||||
json-ld (3.1.3)
|
json-ld (3.1.4)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
json-canonicalization (~> 0.2)
|
json-canonicalization (~> 0.2)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
|
@ -314,21 +314,21 @@ GEM
|
||||||
json-ld (~> 3.1)
|
json-ld (~> 3.1)
|
||||||
rdf (~> 3.1)
|
rdf (~> 3.1)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.1.0)
|
jwt (2.2.1)
|
||||||
kaminari (1.1.1)
|
kaminari (1.2.0)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.1.1)
|
kaminari-actionview (= 1.2.0)
|
||||||
kaminari-activerecord (= 1.1.1)
|
kaminari-activerecord (= 1.2.0)
|
||||||
kaminari-core (= 1.1.1)
|
kaminari-core (= 1.2.0)
|
||||||
kaminari-actionview (1.1.1)
|
kaminari-actionview (1.2.0)
|
||||||
actionview
|
actionview
|
||||||
kaminari-core (= 1.1.1)
|
kaminari-core (= 1.2.0)
|
||||||
kaminari-activerecord (1.1.1)
|
kaminari-activerecord (1.2.0)
|
||||||
activerecord
|
activerecord
|
||||||
kaminari-core (= 1.1.1)
|
kaminari-core (= 1.2.0)
|
||||||
kaminari-core (1.1.1)
|
kaminari-core (1.2.0)
|
||||||
launchy (2.4.3)
|
launchy (2.5.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.7)
|
||||||
letter_opener (1.7.0)
|
letter_opener (1.7.0)
|
||||||
launchy (~> 2.2)
|
launchy (~> 2.2)
|
||||||
letter_opener_web (1.4.0)
|
letter_opener_web (1.4.0)
|
||||||
|
@ -353,14 +353,14 @@ GEM
|
||||||
mario-redis-lock (1.2.1)
|
mario-redis-lock (1.2.1)
|
||||||
redis (>= 3.0.5)
|
redis (>= 3.0.5)
|
||||||
memory_profiler (0.9.14)
|
memory_profiler (0.9.14)
|
||||||
method_source (0.9.2)
|
method_source (1.0.0)
|
||||||
microformats (4.2.0)
|
microformats (4.2.0)
|
||||||
json (~> 2.2)
|
json (~> 2.2)
|
||||||
nokogiri (~> 1.10)
|
nokogiri (~> 1.10)
|
||||||
mime-types (3.3.1)
|
mime-types (3.3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2020.0425)
|
mime-types-data (3.2020.0425)
|
||||||
mimemagic (0.3.4)
|
mimemagic (0.3.5)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.14.0)
|
minitest (5.14.0)
|
||||||
|
@ -369,9 +369,9 @@ GEM
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
necromancer (0.5.1)
|
necromancer (0.5.1)
|
||||||
net-ldap (0.16.2)
|
net-ldap (0.16.2)
|
||||||
net-scp (2.0.0)
|
net-scp (3.0.0)
|
||||||
net-ssh (>= 2.6.5, < 6.0.0)
|
net-ssh (>= 2.6.5, < 7.0.0)
|
||||||
net-ssh (5.2.0)
|
net-ssh (6.0.2)
|
||||||
nio4r (2.5.2)
|
nio4r (2.5.2)
|
||||||
nokogiri (1.10.9)
|
nokogiri (1.10.9)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
|
@ -407,40 +407,40 @@ GEM
|
||||||
parallel (1.19.1)
|
parallel (1.19.1)
|
||||||
parallel_tests (2.32.0)
|
parallel_tests (2.32.0)
|
||||||
parallel
|
parallel
|
||||||
parser (2.7.1.1)
|
parser (2.7.1.2)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
pastel (0.7.3)
|
pastel (0.7.4)
|
||||||
equatable (~> 0.6)
|
equatable (~> 0.6)
|
||||||
tty-color (~> 0.5)
|
tty-color (~> 0.5)
|
||||||
pg (1.2.3)
|
pg (1.2.3)
|
||||||
pghero (2.4.1)
|
pghero (2.4.2)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
pkg-config (1.4.1)
|
pkg-config (1.4.1)
|
||||||
premailer (1.11.1)
|
premailer (1.11.1)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.6.0)
|
css_parser (>= 1.6.0)
|
||||||
htmlentities (>= 4.0.0)
|
htmlentities (>= 4.0.0)
|
||||||
premailer-rails (1.10.3)
|
premailer-rails (1.11.1)
|
||||||
actionmailer (>= 3)
|
actionmailer (>= 3)
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
pry (0.12.2)
|
pry (0.13.1)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1)
|
||||||
method_source (~> 0.9.0)
|
method_source (~> 1.0)
|
||||||
pry-byebug (3.8.0)
|
pry-byebug (3.9.0)
|
||||||
byebug (~> 11.0)
|
byebug (~> 11.0)
|
||||||
pry (~> 0.10)
|
pry (~> 0.13.0)
|
||||||
pry-rails (0.3.9)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (4.0.4)
|
public_suffix (4.0.5)
|
||||||
puma (4.3.3)
|
puma (4.3.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.1.0)
|
pundit (2.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.1.6)
|
raabro (1.3.1)
|
||||||
rack (2.2.2)
|
rack (2.2.2)
|
||||||
rack-attack (6.2.2)
|
rack-attack (6.3.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
|
@ -491,13 +491,13 @@ GEM
|
||||||
rdf-normalize (0.4.0)
|
rdf-normalize (0.4.0)
|
||||||
rdf (~> 3.1)
|
rdf (~> 3.1)
|
||||||
redcarpet (3.5.0)
|
redcarpet (3.5.0)
|
||||||
redis (4.1.3)
|
redis (4.1.4)
|
||||||
redis-actionpack (5.2.0)
|
redis-actionpack (5.2.0)
|
||||||
actionpack (>= 5, < 7)
|
actionpack (>= 5, < 7)
|
||||||
redis-rack (>= 2.1.0, < 3)
|
redis-rack (>= 2.1.0, < 3)
|
||||||
redis-store (>= 1.1.0, < 2)
|
redis-store (>= 1.1.0, < 2)
|
||||||
redis-activesupport (5.0.4)
|
redis-activesupport (5.2.0)
|
||||||
activesupport (>= 3, < 6)
|
activesupport (>= 3, < 7)
|
||||||
redis-store (>= 1.3, < 2)
|
redis-store (>= 1.3, < 2)
|
||||||
redis-namespace (1.7.0)
|
redis-namespace (1.7.0)
|
||||||
redis (>= 3.0.4)
|
redis (>= 3.0.4)
|
||||||
|
@ -516,15 +516,16 @@ GEM
|
||||||
responders (3.0.0)
|
responders (3.0.0)
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
|
rexml (3.2.4)
|
||||||
rotp (2.1.2)
|
rotp (2.1.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (1.1.2)
|
rqrcode (1.1.2)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 0.1)
|
rqrcode_core (~> 0.1)
|
||||||
rqrcode_core (0.1.2)
|
rqrcode_core (0.1.2)
|
||||||
rspec-core (3.9.1)
|
rspec-core (3.9.2)
|
||||||
rspec-support (~> 3.9.1)
|
rspec-support (~> 3.9.3)
|
||||||
rspec-expectations (3.9.1)
|
rspec-expectations (3.9.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.9.0)
|
rspec-support (~> 3.9.0)
|
||||||
rspec-mocks (3.9.1)
|
rspec-mocks (3.9.1)
|
||||||
|
@ -541,16 +542,17 @@ GEM
|
||||||
rspec-sidekiq (3.0.3)
|
rspec-sidekiq (3.0.3)
|
||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.9.2)
|
rspec-support (3.9.3)
|
||||||
rspec_junit_formatter (0.4.1)
|
rspec_junit_formatter (0.4.1)
|
||||||
rspec-core (>= 2, < 4, != 2.12.0)
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
rubocop (0.79.0)
|
rubocop (0.82.0)
|
||||||
jaro_winkler (~> 1.5.1)
|
jaro_winkler (~> 1.5.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.7.0.1)
|
parser (>= 2.7.0.1)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
rexml
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 1.7)
|
unicode-display_width (>= 1.4.0, < 2.0)
|
||||||
rubocop-rails (2.5.2)
|
rubocop-rails (2.5.2)
|
||||||
activesupport
|
activesupport
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
|
@ -565,9 +567,10 @@ GEM
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.8.0)
|
nokogiri (>= 1.8.0)
|
||||||
nokogumbo (~> 2.0)
|
nokogumbo (~> 2.0)
|
||||||
sidekiq (6.0.4)
|
semantic_range (2.3.0)
|
||||||
|
sidekiq (6.0.7)
|
||||||
connection_pool (>= 2.2.2)
|
connection_pool (>= 2.2.2)
|
||||||
rack (>= 2.0.0)
|
rack (~> 2.0)
|
||||||
rack-protection (>= 2.0.0)
|
rack-protection (>= 2.0.0)
|
||||||
redis (>= 4.1.0)
|
redis (>= 4.1.0)
|
||||||
sidekiq-bulk (0.2.0)
|
sidekiq-bulk (0.2.0)
|
||||||
|
@ -607,7 +610,7 @@ GEM
|
||||||
stoplight (2.2.0)
|
stoplight (2.2.0)
|
||||||
streamio-ffmpeg (3.0.2)
|
streamio-ffmpeg (3.0.2)
|
||||||
multi_json (~> 1.8)
|
multi_json (~> 1.8)
|
||||||
strong_migrations (0.6.2)
|
strong_migrations (0.6.6)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
temple (0.8.2)
|
temple (0.8.2)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
|
@ -635,12 +638,12 @@ GEM
|
||||||
unf (~> 0.1.0)
|
unf (~> 0.1.0)
|
||||||
tzinfo (1.2.7)
|
tzinfo (1.2.7)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2019.3)
|
tzinfo-data (1.2020.1)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.6)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.6.1)
|
unicode-display_width (1.7.0)
|
||||||
uniform_notifier (1.13.0)
|
uniform_notifier (1.13.0)
|
||||||
warden (1.2.8)
|
warden (1.2.8)
|
||||||
rack (>= 2.0.6)
|
rack (>= 2.0.6)
|
||||||
|
@ -648,10 +651,11 @@ GEM
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webpacker (4.2.2)
|
webpacker (5.1.1)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 5.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 4.2)
|
railties (>= 5.2)
|
||||||
|
semantic_range (>= 2.3.0)
|
||||||
webpush (0.3.8)
|
webpush (0.3.8)
|
||||||
hkdf (~> 0.2)
|
hkdf (~> 0.2)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
|
@ -670,8 +674,8 @@ DEPENDENCIES
|
||||||
active_record_query_trace (~> 1.7)
|
active_record_query_trace (~> 1.7)
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.7)
|
||||||
annotate (~> 3.1)
|
annotate (~> 3.1)
|
||||||
aws-sdk-s3 (~> 1.63)
|
aws-sdk-s3 (~> 1.64)
|
||||||
better_errors (~> 2.6)
|
better_errors (~> 2.7)
|
||||||
binding_of_caller (~> 0.7)
|
binding_of_caller (~> 0.7)
|
||||||
blurhash (~> 0.1)
|
blurhash (~> 0.1)
|
||||||
bootsnap (~> 1.4)
|
bootsnap (~> 1.4)
|
||||||
|
@ -679,11 +683,11 @@ DEPENDENCIES
|
||||||
browser
|
browser
|
||||||
bullet (~> 6.1)
|
bullet (~> 6.1)
|
||||||
bundler-audit (~> 0.6)
|
bundler-audit (~> 0.6)
|
||||||
capistrano (~> 3.13)
|
capistrano (~> 3.14)
|
||||||
capistrano-rails (~> 1.4)
|
capistrano-rails (~> 1.4)
|
||||||
capistrano-rbenv (~> 2.1)
|
capistrano-rbenv (~> 2.1)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 3.31)
|
capybara (~> 3.32)
|
||||||
charlock_holmes (~> 0.7.7)
|
charlock_holmes (~> 0.7.7)
|
||||||
chewy (~> 5.1)
|
chewy (~> 5.1)
|
||||||
cld3 (~> 3.3.0)
|
cld3 (~> 3.3.0)
|
||||||
|
@ -694,7 +698,7 @@ DEPENDENCIES
|
||||||
devise-two-factor (~> 3.1)
|
devise-two-factor (~> 3.1)
|
||||||
devise_pam_authenticatable2 (~> 9.2)
|
devise_pam_authenticatable2 (~> 9.2)
|
||||||
discard (~> 1.2)
|
discard (~> 1.2)
|
||||||
doorkeeper (~> 5.3)
|
doorkeeper (~> 5.4)
|
||||||
dotenv-rails (~> 2.7)
|
dotenv-rails (~> 2.7)
|
||||||
e2mmap (~> 0.1.0)
|
e2mmap (~> 0.1.0)
|
||||||
fabrication (~> 2.21)
|
fabrication (~> 2.21)
|
||||||
|
@ -709,7 +713,7 @@ DEPENDENCIES
|
||||||
health_check!
|
health_check!
|
||||||
hiredis (~> 0.6)
|
hiredis (~> 0.6)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
http (~> 4.3)
|
http (~> 4.4)
|
||||||
http_accept_language (~> 2.1)
|
http_accept_language (~> 2.1)
|
||||||
http_parser.rb (~> 0.6)!
|
http_parser.rb (~> 0.6)!
|
||||||
httplog (~> 1.4.2)
|
httplog (~> 1.4.2)
|
||||||
|
@ -718,7 +722,7 @@ DEPENDENCIES
|
||||||
iso-639
|
iso-639
|
||||||
json-ld
|
json-ld
|
||||||
json-ld-preloaded (~> 3.1)
|
json-ld-preloaded (~> 3.1)
|
||||||
kaminari (~> 1.1)
|
kaminari (~> 1.2)
|
||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
letter_opener_web (~> 1.4)
|
letter_opener_web (~> 1.4)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
|
@ -748,12 +752,12 @@ DEPENDENCIES
|
||||||
posix-spawn!
|
posix-spawn!
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
pry-byebug (~> 3.8)
|
pry-byebug (~> 3.9)
|
||||||
pry-rails (~> 0.3)
|
pry-rails (~> 0.3)
|
||||||
puma (~> 4.3)
|
puma (~> 4.3)
|
||||||
pundit (~> 2.1)
|
pundit (~> 2.1)
|
||||||
rack (~> 2.2.2)
|
rack (~> 2.2.2)
|
||||||
rack-attack (~> 6.2)
|
rack-attack (~> 6.3)
|
||||||
rack-cors (~> 1.1)
|
rack-cors (~> 1.1)
|
||||||
rails (~> 5.2.4.2)
|
rails (~> 5.2.4.2)
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
|
@ -768,7 +772,7 @@ DEPENDENCIES
|
||||||
rspec-rails (~> 4.0)
|
rspec-rails (~> 4.0)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rspec_junit_formatter (~> 0.4)
|
rspec_junit_formatter (~> 0.4)
|
||||||
rubocop (~> 0.79)
|
rubocop (~> 0.82)
|
||||||
rubocop-rails (~> 2.5)
|
rubocop-rails (~> 2.5)
|
||||||
ruby-progressbar (~> 1.10)
|
ruby-progressbar (~> 1.10)
|
||||||
sanitize (~> 5.1)
|
sanitize (~> 5.1)
|
||||||
|
@ -790,7 +794,7 @@ DEPENDENCIES
|
||||||
tty-command (~> 0.9)
|
tty-command (~> 0.9)
|
||||||
tty-prompt (~> 0.21)
|
tty-prompt (~> 0.21)
|
||||||
twitter-text (~> 1.14)
|
twitter-text (~> 1.14)
|
||||||
tzinfo-data (~> 1.2019)
|
tzinfo-data (~> 1.2020)
|
||||||
webmock (~> 3.8)
|
webmock (~> 3.8)
|
||||||
webpacker (~> 4.2)
|
webpacker (~> 5.1)
|
||||||
webpush
|
webpush
|
||||||
|
|
|
@ -41,7 +41,7 @@ class AccountsController < ApplicationController
|
||||||
format.rss do
|
format.rss do
|
||||||
expires_in 1.minute, public: true
|
expires_in 1.minute, public: true
|
||||||
|
|
||||||
@statuses = filtered_statuses.without_reblogs.without_replies.limit(PAGE_SIZE)
|
@statuses = filtered_statuses.without_reblogs.limit(PAGE_SIZE)
|
||||||
@statuses = cache_collection(@statuses, Status)
|
@statuses = cache_collection(@statuses, Status)
|
||||||
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
|
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
|
||||||
end
|
end
|
||||||
|
@ -130,11 +130,11 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def media_requested?
|
def media_requested?
|
||||||
request.path.ends_with?('/media') && !tag_requested?
|
request.path.split('.').first.ends_with?('/media') && !tag_requested?
|
||||||
end
|
end
|
||||||
|
|
||||||
def replies_requested?
|
def replies_requested?
|
||||||
request.path.ends_with?('/with_replies') && !tag_requested?
|
request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_requested?
|
def tag_requested?
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||||
return [] if hide_results?
|
return [] if hide_results?
|
||||||
|
|
||||||
scope = default_accounts
|
scope = default_accounts
|
||||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
|
||||||
scope.merge(paginated_follows).to_a
|
scope.merge(paginated_follows).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||||
return [] if hide_results?
|
return [] if hide_results?
|
||||||
|
|
||||||
scope = default_accounts
|
scope = default_accounts
|
||||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
|
||||||
scope.merge(paginated_follows).to_a
|
scope.merge(paginated_follows).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_timeline_statuses
|
def public_timeline_statuses
|
||||||
Status.as_public_timeline(current_account, truthy_param?(:local))
|
Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local))
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_pagination_headers
|
def insert_pagination_headers
|
||||||
|
@ -47,7 +47,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
|
params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def next_path
|
def next_path
|
||||||
|
|
|
@ -113,6 +113,13 @@ class Auth::SessionsController < Devise::SessionsController
|
||||||
render :two_factor
|
render :two_factor
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def require_no_authentication
|
||||||
|
super
|
||||||
|
# Delete flash message that isn't entirely useful and may be confusing in
|
||||||
|
# most cases because /web doesn't display/clear flash messages.
|
||||||
|
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_pack
|
def set_pack
|
||||||
|
|
|
@ -28,18 +28,6 @@ module Localized
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_locale
|
def request_locale
|
||||||
preferred_locale || compatible_locale
|
http_accept_language.language_region_compatible_from(I18n.available_locales)
|
||||||
end
|
|
||||||
|
|
||||||
def preferred_locale
|
|
||||||
http_accept_language.preferred_language_from(available_locales)
|
|
||||||
end
|
|
||||||
|
|
||||||
def compatible_locale
|
|
||||||
http_accept_language.compatible_language_from(available_locales)
|
|
||||||
end
|
|
||||||
|
|
||||||
def available_locales
|
|
||||||
I18n.available_locales.reverse
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,8 +22,7 @@ class Settings::IdentityProofsController < Settings::BaseController
|
||||||
if current_account.username.casecmp(params[:username]).zero?
|
if current_account.username.casecmp(params[:username]).zero?
|
||||||
render layout: 'auth'
|
render layout: 'auth'
|
||||||
else
|
else
|
||||||
flash[:alert] = I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
|
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
|
||||||
redirect_to settings_identity_proofs_path
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -35,11 +34,16 @@ class Settings::IdentityProofsController < Settings::BaseController
|
||||||
PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof?
|
PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof?
|
||||||
redirect_to @proof.on_success_path(params[:user_agent])
|
redirect_to @proof.on_success_path(params[:user_agent])
|
||||||
else
|
else
|
||||||
flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
|
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
|
||||||
redirect_to settings_identity_proofs_path
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@proof = current_account.identity_proofs.find(params[:id])
|
||||||
|
@proof.destroy!
|
||||||
|
redirect_to settings_identity_proofs_path, success: I18n.t('identity_proofs.removed')
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_enabled
|
def check_enabled
|
||||||
|
|
|
@ -7,13 +7,13 @@ module HomeHelper
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_link_to(account, button = '', size: 36, path: nil)
|
def account_link_to(account, button = '', path: nil)
|
||||||
content_tag(:div, class: 'account') do
|
content_tag(:div, class: 'account') do
|
||||||
content_tag(:div, class: 'account__wrapper') do
|
content_tag(:div, class: 'account__wrapper') do
|
||||||
section = if account.nil?
|
section = if account.nil?
|
||||||
content_tag(:div, class: 'account__display-name') do
|
content_tag(:div, class: 'account__display-name') do
|
||||||
content_tag(:div, class: 'account__avatar-wrapper') do
|
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||||
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})")
|
image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'account__avatar')
|
||||||
end +
|
end +
|
||||||
content_tag(:span, class: 'display-name') do
|
content_tag(:span, class: 'display-name') do
|
||||||
content_tag(:strong, t('about.contact_missing')) +
|
content_tag(:strong, t('about.contact_missing')) +
|
||||||
|
@ -23,7 +23,7 @@ module HomeHelper
|
||||||
else
|
else
|
||||||
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
|
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
|
||||||
content_tag(:div, class: 'account__avatar-wrapper') do
|
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||||
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)})")
|
image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar')
|
||||||
end +
|
end +
|
||||||
content_tag(:span, class: 'display-name') do
|
content_tag(:span, class: 'display-name') do
|
||||||
content_tag(:bdi) do
|
content_tag(:bdi) do
|
||||||
|
|
|
@ -68,6 +68,7 @@ module SettingsHelper
|
||||||
tr: 'Türkçe',
|
tr: 'Türkçe',
|
||||||
uk: 'Українська',
|
uk: 'Українська',
|
||||||
ur: 'اُردُو',
|
ur: 'اُردُو',
|
||||||
|
vi: 'Tiếng Việt',
|
||||||
'zh-CN': '简体中文',
|
'zh-CN': '简体中文',
|
||||||
'zh-HK': '繁體中文(香港)',
|
'zh-HK': '繁體中文(香港)',
|
||||||
'zh-TW': '繁體中文(臺灣)',
|
'zh-TW': '繁體中文(臺灣)',
|
||||||
|
|
19
app/helpers/webfinger_helper.rb
Normal file
19
app/helpers/webfinger_helper.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WebfingerHelper
|
||||||
|
def webfinger!(uri)
|
||||||
|
hidden_service_uri = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
||||||
|
|
||||||
|
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && hidden_service_uri
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
ssl: !hidden_service_uri,
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
'User-Agent': Mastodon::Version.user_agent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Goldfinger::Client.new(uri, opts.merge(Rails.configuration.x.http_client_proxy)).finger
|
||||||
|
end
|
||||||
|
end
|
|
@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
||||||
|
|
||||||
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
||||||
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
|
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
|
||||||
export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
|
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`);
|
||||||
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
|
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
|
||||||
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
||||||
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
|
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
|
||||||
|
|
|
@ -121,7 +121,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||||
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
|
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
|
export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
|
||||||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
||||||
|
|
|
@ -186,10 +186,12 @@ class Header extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
||||||
} else {
|
} else {
|
||||||
if (account.getIn(['relationship', 'following'])) {
|
if (account.getIn(['relationship', 'following'])) {
|
||||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
if (!account.getIn(['relationship', 'muting'])) {
|
||||||
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
||||||
} else {
|
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
} else {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||||
|
|
|
@ -28,6 +28,7 @@ class Option extends React.PureComponent {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
isPollMultiple: PropTypes.bool,
|
isPollMultiple: PropTypes.bool,
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onRemove: PropTypes.func.isRequired,
|
onRemove: PropTypes.func.isRequired,
|
||||||
suggestions: ImmutablePropTypes.list,
|
suggestions: ImmutablePropTypes.list,
|
||||||
|
@ -58,7 +59,7 @@ class Option extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { isPollMultiple, title, index, intl } = this.props;
|
const { isPollMultiple, title, index, autoFocus, intl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
|
@ -75,6 +76,7 @@ class Option extends React.PureComponent {
|
||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||||
onSuggestionSelected={this.onSuggestionSelected}
|
onSuggestionSelected={this.onSuggestionSelected}
|
||||||
searchTokens={[':']}
|
searchTokens={[':']}
|
||||||
|
autoFocus={autoFocus}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -125,10 +127,12 @@ class PollForm extends ImmutablePureComponent {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoFocusIndex = options.indexOf('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form__poll-wrapper'>
|
<div className='compose-form__poll-wrapper'>
|
||||||
<ul>
|
<ul>
|
||||||
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} {...other} />)}
|
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} autoFocus={i === autoFocusIndex} {...other} />)}
|
||||||
{options.size < pollLimits.max_options && (
|
{options.size < pollLimits.max_options && (
|
||||||
<label className='poll__text editable'>
|
<label className='poll__text editable'>
|
||||||
<span className={classNames('poll__input')} style={{ opacity: 0 }} />
|
<span className={classNames('poll__input')} style={{ opacity: 0 }} />
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class ColumnSettings extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
columnId: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { settings, onChange } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='column-settings__row'>
|
||||||
|
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
|
||||||
|
<SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ColumnSettings from 'flavours/glitch/features/community_timeline/components/column_settings';
|
import ColumnSettings from '../components/column_settings';
|
||||||
import { changeSetting } from 'flavours/glitch/actions/settings';
|
import { changeSetting } from 'flavours/glitch/actions/settings';
|
||||||
import { changeColumnParams } from 'flavours/glitch/actions/columns';
|
import { changeColumnParams } from 'flavours/glitch/actions/columns';
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => {
|
||||||
const columns = state.getIn(['settings', 'columns']);
|
const columns = state.getIn(['settings', 'columns']);
|
||||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
||||||
|
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
||||||
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
||||||
onlyMedia,
|
onlyMedia,
|
||||||
|
onlyRemote,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,15 +48,16 @@ class PublicTimeline extends React.PureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
hasUnread: PropTypes.bool,
|
hasUnread: PropTypes.bool,
|
||||||
onlyMedia: PropTypes.bool,
|
onlyMedia: PropTypes.bool,
|
||||||
|
onlyRemote: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePin = () => {
|
handlePin = () => {
|
||||||
const { columnId, dispatch, onlyMedia } = this.props;
|
const { columnId, dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
if (columnId) {
|
if (columnId) {
|
||||||
dispatch(removeColumn(columnId));
|
dispatch(removeColumn(columnId));
|
||||||
} else {
|
} else {
|
||||||
dispatch(addColumn('PUBLIC', { other: { onlyMedia } }));
|
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,19 +71,19 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.onlyMedia !== this.props.onlyMedia) {
|
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,13 +99,13 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = maxId => {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
|
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
|
const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -121,7 +124,7 @@ class PublicTimeline extends React.PureComponent {
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
timelineId={`public${onlyMedia ? ':media' : ''}`}
|
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
scrollKey={`public_timeline-${columnId}`}
|
scrollKey={`public_timeline-${columnId}`}
|
||||||
|
|
|
@ -37,6 +37,7 @@ const componentMap = {
|
||||||
'HOME': HomeTimeline,
|
'HOME': HomeTimeline,
|
||||||
'NOTIFICATIONS': Notifications,
|
'NOTIFICATIONS': Notifications,
|
||||||
'PUBLIC': PublicTimeline,
|
'PUBLIC': PublicTimeline,
|
||||||
|
'REMOTE': PublicTimeline,
|
||||||
'COMMUNITY': CommunityTimeline,
|
'COMMUNITY': CommunityTimeline,
|
||||||
'HASHTAG': HashtagTimeline,
|
'HASHTAG': HashtagTimeline,
|
||||||
'DIRECT': DirectTimeline,
|
'DIRECT': DirectTimeline,
|
||||||
|
|
|
@ -545,13 +545,6 @@ $small-breakpoint: 960px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
@include avatar-size(44px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-name {
|
.display-name {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|
||||||
|
@ -752,12 +745,6 @@ $small-breakpoint: 960px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__counters__wrapper {
|
&__counters__wrapper {
|
||||||
|
|
|
@ -38,9 +38,14 @@
|
||||||
|
|
||||||
.account__avatar {
|
.account__avatar {
|
||||||
@include avatar-radius();
|
@include avatar-radius();
|
||||||
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background-size: 36px 36px;
|
||||||
|
|
||||||
&-inline {
|
&-inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -145,6 +145,11 @@
|
||||||
&__avatar {
|
&__avatar {
|
||||||
left: 15px;
|
left: 15px;
|
||||||
top: 17px;
|
top: 17px;
|
||||||
|
|
||||||
|
.account__avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
|
|
|
@ -93,12 +93,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.trends__item {
|
.trends__item {
|
||||||
|
|
1
app/javascript/images/logo_transparent_white.svg
Normal file
1
app/javascript/images/logo_transparent_white.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
||||||
|
|
||||||
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
||||||
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
|
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
|
||||||
export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
|
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`);
|
||||||
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
|
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
|
||||||
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
||||||
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
|
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
|
||||||
|
|
|
@ -107,7 +107,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||||
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
|
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
||||||
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
||||||
|
|
|
@ -192,10 +192,12 @@ class Header extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
||||||
} else {
|
} else {
|
||||||
if (account.getIn(['relationship', 'following'])) {
|
if (account.getIn(['relationship', 'following'])) {
|
||||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
if (!account.getIn(['relationship', 'muting'])) {
|
||||||
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
||||||
} else {
|
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
} else {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||||
|
|
|
@ -27,6 +27,7 @@ class Option extends React.PureComponent {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
isPollMultiple: PropTypes.bool,
|
isPollMultiple: PropTypes.bool,
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onRemove: PropTypes.func.isRequired,
|
onRemove: PropTypes.func.isRequired,
|
||||||
onToggleMultiple: PropTypes.func.isRequired,
|
onToggleMultiple: PropTypes.func.isRequired,
|
||||||
|
@ -71,7 +72,7 @@ class Option extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { isPollMultiple, title, index, intl } = this.props;
|
const { isPollMultiple, title, index, autoFocus, intl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
|
@ -96,6 +97,7 @@ class Option extends React.PureComponent {
|
||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||||
onSuggestionSelected={this.onSuggestionSelected}
|
onSuggestionSelected={this.onSuggestionSelected}
|
||||||
searchTokens={[':']}
|
searchTokens={[':']}
|
||||||
|
autoFocus={autoFocus}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -146,10 +148,12 @@ class PollForm extends ImmutablePureComponent {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoFocusIndex = options.indexOf('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form__poll-wrapper'>
|
<div className='compose-form__poll-wrapper'>
|
||||||
<ul>
|
<ul>
|
||||||
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} {...other} />)}
|
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} autoFocus={i === autoFocusIndex} {...other} />)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className='poll__footer'>
|
<div className='poll__footer'>
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class ColumnSettings extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
columnId: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { settings, onChange } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='column-settings__row'>
|
||||||
|
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
|
||||||
|
<SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ColumnSettings from '../../community_timeline/components/column_settings';
|
import ColumnSettings from '../components/column_settings';
|
||||||
import { changeSetting } from '../../../actions/settings';
|
import { changeSetting } from '../../../actions/settings';
|
||||||
import { changeColumnParams } from '../../../actions/columns';
|
import { changeColumnParams } from '../../../actions/columns';
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => {
|
||||||
const columns = state.getIn(['settings', 'columns']);
|
const columns = state.getIn(['settings', 'columns']);
|
||||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
||||||
|
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
||||||
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
||||||
onlyMedia,
|
onlyMedia,
|
||||||
|
onlyRemote,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,15 +49,16 @@ class PublicTimeline extends React.PureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
hasUnread: PropTypes.bool,
|
hasUnread: PropTypes.bool,
|
||||||
onlyMedia: PropTypes.bool,
|
onlyMedia: PropTypes.bool,
|
||||||
|
onlyRemote: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePin = () => {
|
handlePin = () => {
|
||||||
const { columnId, dispatch, onlyMedia } = this.props;
|
const { columnId, dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
if (columnId) {
|
if (columnId) {
|
||||||
dispatch(removeColumn(columnId));
|
dispatch(removeColumn(columnId));
|
||||||
} else {
|
} else {
|
||||||
dispatch(addColumn('PUBLIC', { other: { onlyMedia } }));
|
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,19 +72,19 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.onlyMedia !== this.props.onlyMedia) {
|
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,13 +100,13 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = maxId => {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
|
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
|
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -122,7 +125,7 @@ class PublicTimeline extends React.PureComponent {
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
timelineId={`public${onlyMedia ? ':media' : ''}`}
|
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
scrollKey={`public_timeline-${columnId}`}
|
scrollKey={`public_timeline-${columnId}`}
|
||||||
|
|
|
@ -5,30 +5,21 @@ import ColumnHeader from '../column_header';
|
||||||
|
|
||||||
describe('<Column />', () => {
|
describe('<Column />', () => {
|
||||||
describe('<ColumnHeader /> click handler', () => {
|
describe('<ColumnHeader /> click handler', () => {
|
||||||
const originalRaf = global.requestAnimationFrame;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
global.requestAnimationFrame = jest.fn();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
global.requestAnimationFrame = originalRaf;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('runs the scroll animation if the column contains scrollable content', () => {
|
it('runs the scroll animation if the column contains scrollable content', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Column heading='notifications'>
|
<Column heading='notifications'>
|
||||||
<div className='scrollable' />
|
<div className='scrollable' />
|
||||||
</Column>,
|
</Column>,
|
||||||
);
|
);
|
||||||
|
const scrollToMock = jest.fn();
|
||||||
|
wrapper.find(Column).find('.scrollable').getDOMNode().scrollTo = scrollToMock;
|
||||||
wrapper.find(ColumnHeader).find('button').simulate('click');
|
wrapper.find(ColumnHeader).find('button').simulate('click');
|
||||||
expect(global.requestAnimationFrame.mock.calls.length).toEqual(1);
|
expect(scrollToMock).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not try to scroll if there is no scrollable content', () => {
|
it('does not try to scroll if there is no scrollable content', () => {
|
||||||
const wrapper = mount(<Column heading='notifications' />);
|
const wrapper = mount(<Column heading='notifications' />);
|
||||||
wrapper.find(ColumnHeader).find('button').simulate('click');
|
wrapper.find(ColumnHeader).find('button').simulate('click');
|
||||||
expect(global.requestAnimationFrame.mock.calls.length).toEqual(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ const componentMap = {
|
||||||
'HOME': HomeTimeline,
|
'HOME': HomeTimeline,
|
||||||
'NOTIFICATIONS': Notifications,
|
'NOTIFICATIONS': Notifications,
|
||||||
'PUBLIC': PublicTimeline,
|
'PUBLIC': PublicTimeline,
|
||||||
|
'REMOTE': PublicTimeline,
|
||||||
'COMMUNITY': CommunityTimeline,
|
'COMMUNITY': CommunityTimeline,
|
||||||
'HASHTAG': HashtagTimeline,
|
'HASHTAG': HashtagTimeline,
|
||||||
'DIRECT': DirectTimeline,
|
'DIRECT': DirectTimeline,
|
||||||
|
|
|
@ -543,12 +543,6 @@ $small-breakpoint: 960px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-name {
|
.display-name {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|
||||||
|
@ -749,12 +743,6 @@ $small-breakpoint: 960px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__counters__wrapper {
|
&__counters__wrapper {
|
||||||
|
|
|
@ -1318,8 +1318,13 @@
|
||||||
|
|
||||||
.account__avatar {
|
.account__avatar {
|
||||||
@include avatar-radius;
|
@include avatar-radius;
|
||||||
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background-size: 36px 36px;
|
||||||
|
|
||||||
&-inline {
|
&-inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -149,6 +149,11 @@
|
||||||
&__avatar {
|
&__avatar {
|
||||||
left: 15px;
|
left: 15px;
|
||||||
top: 17px;
|
top: 17px;
|
||||||
|
|
||||||
|
.account__avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
|
|
|
@ -93,12 +93,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.trends__item {
|
.trends__item {
|
||||||
|
|
|
@ -22,7 +22,12 @@ class ProofProvider::Keybase::ConfigSerializer < ActiveModel::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def logo
|
def logo
|
||||||
{ svg_black: full_asset_url(asset_pack_path('media/images/logo_transparent_black.svg')), svg_full: full_asset_url(asset_pack_path('media/images/logo.svg')) }
|
{
|
||||||
|
svg_black: full_asset_url(asset_pack_path('media/images/logo_transparent_black.svg')),
|
||||||
|
svg_white: full_asset_url(asset_pack_path('media/images/logo_transparent_white.svg')),
|
||||||
|
svg_full: full_asset_url(asset_pack_path('media/images/logo.svg')),
|
||||||
|
svg_full_darkmode: full_asset_url(asset_pack_path('media/images/logo.svg')),
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def brand_color
|
def brand_color
|
||||||
|
|
|
@ -73,8 +73,6 @@ class Request
|
||||||
response.body_with_limit if http_client.persistent?
|
response.body_with_limit if http_client.persistent?
|
||||||
|
|
||||||
yield response if block_given?
|
yield response if block_given?
|
||||||
rescue => e
|
|
||||||
raise e.class, e.message, e.backtrace[0]
|
|
||||||
ensure
|
ensure
|
||||||
http_client.close unless http_client.persistent?
|
http_client.close unless http_client.persistent?
|
||||||
end
|
end
|
||||||
|
|
38
app/lib/rss/serializer.rb
Normal file
38
app/lib/rss/serializer.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RSS::Serializer
|
||||||
|
private
|
||||||
|
|
||||||
|
def render_statuses(builder, statuses)
|
||||||
|
statuses.each do |status|
|
||||||
|
builder.item do |item|
|
||||||
|
item.title(status_title(status))
|
||||||
|
.link(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
.pub_date(status.created_at)
|
||||||
|
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
|
||||||
|
|
||||||
|
status.media_attachments.each do |media|
|
||||||
|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_title(status)
|
||||||
|
return "#{status.account.acct} deleted status" if status.destroyed?
|
||||||
|
|
||||||
|
preview = status.proper.spoiler_text.presence || status.proper.text
|
||||||
|
if preview.length > 30 || preview[0, 30].include?("\n")
|
||||||
|
preview = preview[0, 30]
|
||||||
|
preview = preview[0, preview.index("\n").presence || 30] + '…'
|
||||||
|
end
|
||||||
|
|
||||||
|
preview = "#{status.proper.spoiler_text.present? ? 'CW ' : ''}“#{preview}”#{status.proper.sensitive? ? ' (sensitive)' : ''}"
|
||||||
|
|
||||||
|
if status.reblog?
|
||||||
|
"#{status.account.acct} boosted #{status.reblog.account.acct}: #{preview}"
|
||||||
|
else
|
||||||
|
"#{status.account.acct}: #{preview}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,13 +1,24 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SidekiqErrorHandler
|
class SidekiqErrorHandler
|
||||||
|
BACKTRACE_LIMIT = 3
|
||||||
|
|
||||||
def call(*)
|
def call(*)
|
||||||
yield
|
yield
|
||||||
rescue Mastodon::HostValidationError
|
rescue Mastodon::HostValidationError
|
||||||
# Do not retry
|
# Do not retry
|
||||||
|
rescue => e
|
||||||
|
limit_backtrace_and_raise(e)
|
||||||
ensure
|
ensure
|
||||||
socket = Thread.current[:statsd_socket]
|
socket = Thread.current[:statsd_socket]
|
||||||
socket&.close
|
socket&.close
|
||||||
Thread.current[:statsd_socket] = nil
|
Thread.current[:statsd_socket] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def limit_backtrace_and_raise(e)
|
||||||
|
e.set_backtrace(e.backtrace.first(BACKTRACE_LIMIT))
|
||||||
|
raise e
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ class RelationshipFilter
|
||||||
scope = scope_for('relationship', params['relationship'].to_s.strip)
|
scope = scope_for('relationship', params['relationship'].to_s.strip)
|
||||||
|
|
||||||
params.each do |key, value|
|
params.each do |key, value|
|
||||||
next if key.to_s == 'page'
|
next if %w(relationship page).include?(key)
|
||||||
|
|
||||||
scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
|
scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class RemoteFollow
|
class RemoteFollow
|
||||||
include ActiveModel::Validations
|
include ActiveModel::Validations
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
include WebfingerHelper
|
||||||
|
|
||||||
attr_accessor :acct, :addressable_template
|
attr_accessor :acct, :addressable_template
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ class RemoteFollow
|
||||||
end
|
end
|
||||||
|
|
||||||
def acct_resource
|
def acct_resource
|
||||||
@acct_resource ||= Goldfinger.finger("acct:#{acct}")
|
@acct_resource ||= webfinger!("acct:#{acct}")
|
||||||
rescue Goldfinger::Error, HTTP::ConnectionError
|
rescue Goldfinger::Error, HTTP::ConnectionError
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -203,14 +203,6 @@ class Status < ApplicationRecord
|
||||||
preview_cards.first
|
preview_cards.first
|
||||||
end
|
end
|
||||||
|
|
||||||
def title
|
|
||||||
if destroyed?
|
|
||||||
"#{account.acct} deleted status"
|
|
||||||
else
|
|
||||||
reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hidden?
|
def hidden?
|
||||||
!distributable?
|
!distributable?
|
||||||
end
|
end
|
||||||
|
@ -342,7 +334,7 @@ class Status < ApplicationRecord
|
||||||
query = timeline_scope(local_only)
|
query = timeline_scope(local_only)
|
||||||
query = query.without_replies unless Setting.show_replies_in_public_timelines
|
query = query.without_replies unless Setting.show_replies_in_public_timelines
|
||||||
|
|
||||||
apply_timeline_filters(query, account, local_only)
|
apply_timeline_filters(query, account, [:local, true].include?(local_only))
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_tag_timeline(tag, account = nil, local_only = false)
|
def as_tag_timeline(tag, account = nil, local_only = false)
|
||||||
|
@ -434,8 +426,15 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def timeline_scope(local_only = false)
|
def timeline_scope(scope = false)
|
||||||
starting_scope = local_only ? Status.local : Status
|
starting_scope = case scope
|
||||||
|
when :local, true
|
||||||
|
Status.local
|
||||||
|
when :remote
|
||||||
|
Status.remote
|
||||||
|
else
|
||||||
|
Status
|
||||||
|
end
|
||||||
starting_scope = starting_scope.with_public_visibility
|
starting_scope = starting_scope.with_public_visibility
|
||||||
if Setting.show_reblogs_in_public_timelines
|
if Setting.show_reblogs_in_public_timelines
|
||||||
starting_scope
|
starting_scope
|
||||||
|
|
|
@ -94,11 +94,11 @@ class Web::PushSubscription < ApplicationRecord
|
||||||
|
|
||||||
def find_or_create_access_token
|
def find_or_create_access_token
|
||||||
Doorkeeper::AccessToken.find_or_create_for(
|
Doorkeeper::AccessToken.find_or_create_for(
|
||||||
Doorkeeper::Application.find_by(superapp: true),
|
application: Doorkeeper::Application.find_by(superapp: true),
|
||||||
session_activation.user_id,
|
resource_owner: session_activation.user_id,
|
||||||
Doorkeeper::OAuth::Scopes.from_string('read write follow push'),
|
scopes: Doorkeeper::OAuth::Scopes.from_string('read write follow push'),
|
||||||
Doorkeeper.configuration.access_token_expires_in,
|
expires_in: Doorkeeper.configuration.access_token_expires_in,
|
||||||
Doorkeeper.configuration.refresh_token_enabled?
|
use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ class OEmbedSerializer < ActiveModel::Serializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
include ActionView::Helpers::TagHelper
|
include ActionView::Helpers::TagHelper
|
||||||
|
|
||||||
attributes :type, :version, :title, :author_name,
|
attributes :type, :version, :author_name,
|
||||||
:author_url, :provider_name, :provider_url,
|
:author_url, :provider_name, :provider_url,
|
||||||
:cache_age, :html, :width, :height
|
:cache_age, :html, :width, :height
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RSS::AccountSerializer
|
class RSS::AccountSerializer < RSS::Serializer
|
||||||
include ActionView::Helpers::NumberHelper
|
include ActionView::Helpers::NumberHelper
|
||||||
include AccountsHelper
|
include AccountsHelper
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
@ -17,18 +17,7 @@ class RSS::AccountSerializer
|
||||||
builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar?
|
builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar?
|
||||||
builder.cover(full_asset_url(account.header.url(:original))) if account.header?
|
builder.cover(full_asset_url(account.header.url(:original))) if account.header?
|
||||||
|
|
||||||
statuses.each do |status|
|
render_statuses(builder, statuses)
|
||||||
builder.item do |item|
|
|
||||||
item.title(status.title)
|
|
||||||
.link(ActivityPub::TagManager.instance.url_for(status))
|
|
||||||
.pub_date(status.created_at)
|
|
||||||
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
|
|
||||||
|
|
||||||
status.media_attachments.each do |media|
|
|
||||||
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
builder.to_xml
|
builder.to_xml
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RSS::TagSerializer
|
class RSS::TagSerializer < RSS::Serializer
|
||||||
include ActionView::Helpers::NumberHelper
|
include ActionView::Helpers::NumberHelper
|
||||||
include ActionView::Helpers::SanitizeHelper
|
include ActionView::Helpers::SanitizeHelper
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
@ -14,18 +14,7 @@ class RSS::TagSerializer
|
||||||
.logo(full_pack_url('media/images/logo.svg'))
|
.logo(full_pack_url('media/images/logo.svg'))
|
||||||
.accent_color('2b90d9')
|
.accent_color('2b90d9')
|
||||||
|
|
||||||
statuses.each do |status|
|
render_statuses(builder, statuses)
|
||||||
builder.item do |item|
|
|
||||||
item.title(status.title)
|
|
||||||
.link(ActivityPub::TagManager.instance.url_for(status))
|
|
||||||
.pub_date(status.created_at)
|
|
||||||
.description(status.spoiler_text.presence || Formatter.instance.format(status).to_str)
|
|
||||||
|
|
||||||
status.media_attachments.each do |media|
|
|
||||||
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
builder.to_xml
|
builder.to_xml
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class ActivityPub::FetchRemoteAccountService < BaseService
|
class ActivityPub::FetchRemoteAccountService < BaseService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
include DomainControlHelper
|
include DomainControlHelper
|
||||||
|
include WebfingerHelper
|
||||||
|
|
||||||
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
|
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
|
||||||
|
|
||||||
|
@ -35,12 +36,12 @@ class ActivityPub::FetchRemoteAccountService < BaseService
|
||||||
private
|
private
|
||||||
|
|
||||||
def verified_webfinger?
|
def verified_webfinger?
|
||||||
webfinger = Goldfinger.finger("acct:#{@username}@#{@domain}")
|
webfinger = webfinger!("acct:#{@username}@#{@domain}")
|
||||||
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
|
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
|
||||||
|
|
||||||
return webfinger.link('self')&.href == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
|
return webfinger.link('self')&.href == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
|
||||||
|
|
||||||
webfinger = Goldfinger.finger("acct:#{confirmed_username}@#{confirmed_domain}")
|
webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
|
||||||
@username, @domain = split_acct(webfinger.subject)
|
@username, @domain = split_acct(webfinger.subject)
|
||||||
self_reference = webfinger.link('self')
|
self_reference = webfinger.link('self')
|
||||||
|
|
||||||
|
|
|
@ -73,11 +73,18 @@ class BatchedRemoveStatusService < BaseService
|
||||||
|
|
||||||
redis.pipelined do
|
redis.pipelined do
|
||||||
redis.publish('timeline:public', payload)
|
redis.publish('timeline:public', payload)
|
||||||
redis.publish('timeline:public:local', payload) if status.local?
|
if status.local?
|
||||||
|
redis.publish('timeline:public:local', payload)
|
||||||
|
else
|
||||||
|
redis.publish('timeline:public:remote', payload)
|
||||||
|
end
|
||||||
if status.media_attachments.any?
|
if status.media_attachments.any?
|
||||||
redis.publish('timeline:public:media', payload)
|
redis.publish('timeline:public:media', payload)
|
||||||
redis.publish('timeline:public:local:media', payload) if status.local?
|
if status.local?
|
||||||
|
redis.publish('timeline:public:local:media', payload)
|
||||||
|
else
|
||||||
|
redis.publish('timeline:public:remote:media', payload)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@tags[status.id].each do |hashtag|
|
@tags[status.id].each do |hashtag|
|
||||||
|
|
|
@ -86,14 +86,22 @@ class FanOutOnWriteService < BaseService
|
||||||
Rails.logger.debug "Delivering status #{status.id} to public timeline"
|
Rails.logger.debug "Delivering status #{status.id} to public timeline"
|
||||||
|
|
||||||
Redis.current.publish('timeline:public', @payload)
|
Redis.current.publish('timeline:public', @payload)
|
||||||
Redis.current.publish('timeline:public:local', @payload) if status.local?
|
if status.local?
|
||||||
|
Redis.current.publish('timeline:public:local', @payload)
|
||||||
|
else
|
||||||
|
Redis.current.publish('timeline:public:remote', @payload)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def deliver_to_media(status)
|
def deliver_to_media(status)
|
||||||
Rails.logger.debug "Delivering status #{status.id} to media timeline"
|
Rails.logger.debug "Delivering status #{status.id} to media timeline"
|
||||||
|
|
||||||
Redis.current.publish('timeline:public:media', @payload)
|
Redis.current.publish('timeline:public:media', @payload)
|
||||||
Redis.current.publish('timeline:public:local:media', @payload) if status.local?
|
if status.local?
|
||||||
|
Redis.current.publish('timeline:public:local:media', @payload)
|
||||||
|
else
|
||||||
|
Redis.current.publish('timeline:public:remote:media', @payload)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def deliver_to_direct_timelines(status)
|
def deliver_to_direct_timelines(status)
|
||||||
|
|
|
@ -142,14 +142,22 @@ class RemoveStatusService < BaseService
|
||||||
return unless @status.public_visibility?
|
return unless @status.public_visibility?
|
||||||
|
|
||||||
redis.publish('timeline:public', @payload)
|
redis.publish('timeline:public', @payload)
|
||||||
redis.publish('timeline:public:local', @payload) if @status.local?
|
if @status.local?
|
||||||
|
redis.publish('timeline:public:local', @payload)
|
||||||
|
else
|
||||||
|
redis.publish('timeline:public:remote', @payload)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_media
|
def remove_from_media
|
||||||
return unless @status.public_visibility?
|
return unless @status.public_visibility?
|
||||||
|
|
||||||
redis.publish('timeline:public:media', @payload)
|
redis.publish('timeline:public:media', @payload)
|
||||||
redis.publish('timeline:public:local:media', @payload) if @status.local?
|
if @status.local?
|
||||||
|
redis.publish('timeline:public:local:media', @payload)
|
||||||
|
else
|
||||||
|
redis.publish('timeline:public:remote:media', @payload)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_direct
|
def remove_from_direct
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class ResolveAccountService < BaseService
|
class ResolveAccountService < BaseService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
include DomainControlHelper
|
include DomainControlHelper
|
||||||
|
include WebfingerHelper
|
||||||
|
|
||||||
class WebfingerRedirectError < StandardError; end
|
class WebfingerRedirectError < StandardError; end
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ class ResolveAccountService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_webfinger!(uri, redirected = false)
|
def process_webfinger!(uri, redirected = false)
|
||||||
@webfinger = Goldfinger.finger("acct:#{uri}")
|
@webfinger = webfinger!("acct:#{uri}")
|
||||||
confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
|
confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
|
||||||
|
|
||||||
if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
|
if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
.avatar-stack
|
.avatar-stack
|
||||||
- @instance_presenter.sample_accounts.each do |account|
|
- @instance_presenter.sample_accounts.each do |account|
|
||||||
= image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar'
|
= image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, alt: '', class: 'account__avatar'
|
||||||
|
|
||||||
- if Setting.timeline_preview
|
- if Setting.timeline_preview
|
||||||
.directory__tag
|
.directory__tag
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
- target_account = reports.first.target_account
|
- target_account = reports.first.target_account
|
||||||
.report-card
|
.report-card
|
||||||
.report-card__profile
|
.report-card__profile
|
||||||
= account_link_to target_account, '', size: 36, path: admin_account_path(target_account.id)
|
= account_link_to target_account, '', path: admin_account_path(target_account.id)
|
||||||
.report-card__profile__stats
|
.report-card__profile__stats
|
||||||
= link_to t('admin.reports.account.notes', count: target_account.targeted_moderation_notes.count), admin_account_path(target_account.id)
|
= link_to t('admin.reports.account.notes', count: target_account.targeted_moderation_notes.count), admin_account_path(target_account.id)
|
||||||
%br/
|
%br/
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
.directory__card__bar
|
.directory__card__bar
|
||||||
= link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do
|
= link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do
|
||||||
.avatar
|
.avatar
|
||||||
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
|
= image_tag account.avatar.url, alt: '', class: 'u-photo'
|
||||||
|
|
||||||
.display-name
|
.display-name
|
||||||
%bdi
|
%bdi
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
|
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
|
|
||||||
|
= stylesheet_link_tag '/inert.css', skip_pipeline: true, media: 'all', id: 'inert-style'
|
||||||
|
|
||||||
= yield :header_tags
|
= yield :header_tags
|
||||||
|
|
||||||
-# These must come after :header_tags to ensure our initial state has been defined.
|
-# These must come after :header_tags to ensure our initial state has been defined.
|
||||||
|
|
|
@ -18,3 +18,4 @@
|
||||||
|
|
||||||
%td
|
%td
|
||||||
= table_link_to 'external-link', t('identity_proofs.view_proof'), proof.badge.proof_url if proof.badge.proof_url
|
= table_link_to 'external-link', t('identity_proofs.view_proof'), proof.badge.proof_url if proof.badge.proof_url
|
||||||
|
= table_link_to 'trash', t('identity_proofs.remove'), settings_identity_proof_path(proof), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
|
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
|
||||||
.detailed-status__display-avatar
|
.detailed-status__display-avatar
|
||||||
- if current_account&.user&.setting_auto_play_gif || autoplay
|
- if current_account&.user&.setting_auto_play_gif || autoplay
|
||||||
= image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
|
= image_tag status.account.avatar_original_url, alt: '', class: 'account__avatar u-photo'
|
||||||
- else
|
- else
|
||||||
= image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
|
= image_tag status.account.avatar_static_url, alt: '', class: 'account__avatar u-photo'
|
||||||
%span.display-name
|
%span.display-name
|
||||||
%bdi
|
%bdi
|
||||||
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
|
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
.status__avatar
|
.status__avatar
|
||||||
%div
|
%div
|
||||||
- if current_account&.user&.setting_auto_play_gif || autoplay
|
- if current_account&.user&.setting_auto_play_gif || autoplay
|
||||||
= image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar'
|
= image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar'
|
||||||
- else
|
- else
|
||||||
= image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar'
|
= image_tag status.account.avatar_static_url, alt: '', class: 'u-photo account__avatar'
|
||||||
%span.display-name
|
%span.display-name
|
||||||
%bdi
|
%bdi
|
||||||
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
|
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
|
||||||
|
|
|
@ -52,13 +52,9 @@ class ActivityPub::DeliveryWorker
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD)
|
||||||
light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD)
|
.with_cool_off_time(STOPLIGHT_COOLDOWN)
|
||||||
.with_cool_off_time(STOPLIGHT_COOLDOWN)
|
.run
|
||||||
.run
|
|
||||||
rescue Stoplight::Error::RedLight => e
|
|
||||||
raise e.class, e.message, e.backtrace.first(3)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def failure_tracker
|
def failure_tracker
|
||||||
|
|
|
@ -11,7 +11,7 @@ class RedownloadMediaWorker
|
||||||
|
|
||||||
return if media_attachment.remote_url.blank?
|
return if media_attachment.remote_url.blank?
|
||||||
|
|
||||||
media_attachment.reset_file!
|
media_attachment.file_remote_url = media_attachment.remote_url
|
||||||
media_attachment.save
|
media_attachment.save
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
true
|
true
|
||||||
|
|
|
@ -55,8 +55,8 @@ module Mastodon
|
||||||
:el,
|
:el,
|
||||||
:en,
|
:en,
|
||||||
:eo,
|
:eo,
|
||||||
:'es-AR',
|
|
||||||
:es,
|
:es,
|
||||||
|
:'es-AR',
|
||||||
:et,
|
:et,
|
||||||
:eu,
|
:eu,
|
||||||
:fa,
|
:fa,
|
||||||
|
@ -97,8 +97,8 @@ module Mastodon
|
||||||
:sk,
|
:sk,
|
||||||
:sl,
|
:sl,
|
||||||
:sq,
|
:sq,
|
||||||
:'sr-Latn',
|
|
||||||
:sr,
|
:sr,
|
||||||
|
:'sr-Latn',
|
||||||
:sv,
|
:sv,
|
||||||
:ta,
|
:ta,
|
||||||
:te,
|
:te,
|
||||||
|
@ -106,6 +106,7 @@ module Mastodon
|
||||||
:tr,
|
:tr,
|
||||||
:uk,
|
:uk,
|
||||||
:ur,
|
:ur,
|
||||||
|
:vi,
|
||||||
:'zh-CN',
|
:'zh-CN',
|
||||||
:'zh-HK',
|
:'zh-HK',
|
||||||
:'zh-TW',
|
:'zh-TW',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
lock '3.12.1'
|
lock '3.14.0'
|
||||||
|
|
||||||
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
||||||
set :branch, ENV.fetch('BRANCH', 'master')
|
set :branch, ENV.fetch('BRANCH', 'master')
|
||||||
|
@ -12,3 +12,21 @@ set :migration_role, :app
|
||||||
|
|
||||||
append :linked_files, '.env.production', 'public/robots.txt'
|
append :linked_files, '.env.production', 'public/robots.txt'
|
||||||
append :linked_dirs, 'vendor/bundle', 'node_modules', 'public/system'
|
append :linked_dirs, 'vendor/bundle', 'node_modules', 'public/system'
|
||||||
|
|
||||||
|
namespace :systemd do
|
||||||
|
%i[sidekiq streaming web].each do |service|
|
||||||
|
%i[reload restart status].each do |action|
|
||||||
|
desc "Perform a #{action} on #{service} service"
|
||||||
|
task "#{service}:#{action}".to_sym do
|
||||||
|
on roles(:app) do
|
||||||
|
# runs e.g. "sudo restart mastodon-sidekiq.service"
|
||||||
|
sudo :systemctl, action, "#{fetch(:application)}-#{service}.service"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
after 'deploy:publishing', 'systemd:web:reload'
|
||||||
|
after 'deploy:publishing', 'systemd:sidekiq:restart'
|
||||||
|
after 'deploy:publishing', 'systemd:streaming:restart'
|
||||||
|
|
|
@ -34,7 +34,7 @@ if Rails.env.production?
|
||||||
p.script_src :self, assets_host
|
p.script_src :self, assets_host
|
||||||
p.font_src :self, assets_host
|
p.font_src :self, assets_host
|
||||||
p.img_src :self, :data, :blob, *data_hosts
|
p.img_src :self, :data, :blob, *data_hosts
|
||||||
p.style_src :self, :unsafe_inline, assets_host
|
p.style_src :self, assets_host
|
||||||
p.media_src :self, :data, *data_hosts
|
p.media_src :self, :data, *data_hosts
|
||||||
p.frame_src :self, :https
|
p.frame_src :self, :https
|
||||||
p.child_src :self, :blob, assets_host
|
p.child_src :self, :blob, assets_host
|
||||||
|
@ -48,3 +48,8 @@ end
|
||||||
# For further information see the following documentation:
|
# For further information see the following documentation:
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
||||||
# Rails.application.config.content_security_policy_report_only = true
|
# Rails.application.config.content_security_policy_report_only = true
|
||||||
|
|
||||||
|
PgHero::HomeController.content_security_policy do |p|
|
||||||
|
p.script_src :self, :unsafe_inline, assets_host
|
||||||
|
p.style_src :self, :unsafe_inline, assets_host
|
||||||
|
end
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
config.x.http_client_proxy = {}
|
config.x.http_client_proxy = {}
|
||||||
|
|
||||||
if ENV['http_proxy'].present?
|
if ENV['http_proxy'].present?
|
||||||
proxy = URI.parse(ENV['http_proxy'])
|
proxy = URI.parse(ENV['http_proxy'])
|
||||||
|
|
||||||
raise "Unsupported proxy type: #{proxy.scheme}" unless %w(http https).include? proxy.scheme
|
raise "Unsupported proxy type: #{proxy.scheme}" unless %w(http https).include? proxy.scheme
|
||||||
raise "No proxy host" unless proxy.host
|
raise "No proxy host" unless proxy.host
|
||||||
|
|
||||||
host = proxy.host
|
host = proxy.host
|
||||||
host = host[1...-1] if host[0] == '[' # for IPv6 address
|
host = host[1...-1] if host[0] == '[' # for IPv6 address
|
||||||
config.x.http_client_proxy[:proxy] = { proxy_address: host, proxy_port: proxy.port, proxy_username: proxy.user, proxy_password: proxy.password }.compact
|
|
||||||
|
config.x.http_client_proxy[:proxy] = {
|
||||||
|
proxy_address: host,
|
||||||
|
proxy_port: proxy.port,
|
||||||
|
proxy_username: proxy.user,
|
||||||
|
proxy_password: proxy.password,
|
||||||
|
}.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
config.x.access_to_hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
|
config.x.access_to_hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
|
||||||
end
|
end
|
||||||
|
|
||||||
module Goldfinger
|
|
||||||
def self.finger(uri, opts = {})
|
|
||||||
to_hidden = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
|
||||||
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && to_hidden
|
|
||||||
opts = { ssl: !to_hidden, headers: {} }.merge(Rails.configuration.x.http_client_proxy).merge(opts)
|
|
||||||
opts[:headers]['User-Agent'] ||= Mastodon::Version.user_agent
|
|
||||||
Goldfinger::Client.new(uri, opts).finger
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -19,4 +19,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
inflect.acronym 'ActivityStreams'
|
inflect.acronym 'ActivityStreams'
|
||||||
inflect.acronym 'JsonLd'
|
inflect.acronym 'JsonLd'
|
||||||
inflect.acronym 'NodeInfo'
|
inflect.acronym 'NodeInfo'
|
||||||
|
|
||||||
|
inflect.singular 'data', 'data'
|
||||||
end
|
end
|
||||||
|
|
|
@ -858,12 +858,14 @@ en:
|
||||||
invalid_token: Keybase tokens are hashes of signatures and must be 66 hex characters
|
invalid_token: Keybase tokens are hashes of signatures and must be 66 hex characters
|
||||||
verification_failed: Keybase does not recognize this token as a signature of Keybase user %{kb_username}. Please retry from Keybase.
|
verification_failed: Keybase does not recognize this token as a signature of Keybase user %{kb_username}. Please retry from Keybase.
|
||||||
wrong_user: Cannot create a proof for %{proving} while logged in as %{current}. Log in as %{proving} and try again.
|
wrong_user: Cannot create a proof for %{proving} while logged in as %{current}. Log in as %{proving} and try again.
|
||||||
explanation_html: Here you can cryptographically connect your other identities, such as a Keybase profile. This lets other people send you encrypted messages and trust content you send them.
|
explanation_html: Here you can cryptographically connect your other identities from other platforms, such as Keybase. This lets other people send you encrypted messages on those platforms and allows them to trust that the content you send them comes from you.
|
||||||
i_am_html: I am %{username} on %{service}.
|
i_am_html: I am %{username} on %{service}.
|
||||||
identity: Identity
|
identity: Identity
|
||||||
inactive: Inactive
|
inactive: Inactive
|
||||||
publicize_checkbox: 'And toot this:'
|
publicize_checkbox: 'And toot this:'
|
||||||
publicize_toot: 'It is proven! I am %{username} on %{service}: %{url}'
|
publicize_toot: 'It is proven! I am %{username} on %{service}: %{url}'
|
||||||
|
remove: Remove proof from account
|
||||||
|
removed: Successfully removed proof from account
|
||||||
status: Verification status
|
status: Verification status
|
||||||
view_proof: View proof
|
view_proof: View proof
|
||||||
imports:
|
imports:
|
||||||
|
@ -918,7 +920,7 @@ en:
|
||||||
cancelled_msg: Successfully cancelled the redirect.
|
cancelled_msg: Successfully cancelled the redirect.
|
||||||
errors:
|
errors:
|
||||||
already_moved: is the same account you have already moved to
|
already_moved: is the same account you have already moved to
|
||||||
missing_also_known_as: is not back-referencing this account
|
missing_also_known_as: is not an alias of this account
|
||||||
move_to_self: cannot be current account
|
move_to_self: cannot be current account
|
||||||
not_found: could not be found
|
not_found: could not be found
|
||||||
on_cooldown: You are on cooldown
|
on_cooldown: You are on cooldown
|
||||||
|
|
|
@ -38,4 +38,4 @@ databases:
|
||||||
# aws_secret_access_key: ...
|
# aws_secret_access_key: ...
|
||||||
# aws_region: us-east-1
|
# aws_region: us-east-1
|
||||||
|
|
||||||
override_csp: true
|
override_csp: false
|
||||||
|
|
|
@ -130,7 +130,7 @@ Rails.application.routes.draw do
|
||||||
resource :confirmation, only: [:new, :create]
|
resource :confirmation, only: [:new, :create]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :identity_proofs, only: [:index, :show, :new, :create, :update]
|
resources :identity_proofs, only: [:index, :new, :create, :destroy]
|
||||||
|
|
||||||
resources :applications, except: [:edit] do
|
resources :applications, except: [:edit] do
|
||||||
member do
|
member do
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class AddInviteIdToUsers < ActiveRecord::Migration[5.1]
|
class AddInviteIdToUsers < ActiveRecord::Migration[5.1]
|
||||||
def change
|
def change
|
||||||
add_reference :users, :invite, null: true, default: nil, foreign_key: { on_delete: :nullify }, index: false
|
safety_assured { add_reference :users, :invite, null: true, default: nil, foreign_key: { on_delete: :nullify }, index: false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class AddAssignedAccountIdToReports < ActiveRecord::Migration[5.1]
|
class AddAssignedAccountIdToReports < ActiveRecord::Migration[5.1]
|
||||||
def change
|
def change
|
||||||
add_reference :reports, :assigned_account, null: true, default: nil, foreign_key: { on_delete: :nullify, to_table: :accounts }, index: false
|
safety_assured { add_reference :reports, :assigned_account, null: true, default: nil, foreign_key: { on_delete: :nullify, to_table: :accounts }, index: false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
class AddAccessTokenIdToWebPushSubscriptions < ActiveRecord::Migration[5.2]
|
class AddAccessTokenIdToWebPushSubscriptions < ActiveRecord::Migration[5.2]
|
||||||
def change
|
def change
|
||||||
add_reference :web_push_subscriptions, :access_token, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :oauth_access_tokens }, index: false
|
safety_assured do
|
||||||
add_reference :web_push_subscriptions, :user, null: true, default: nil, foreign_key: { on_delete: :cascade }, index: false
|
add_reference :web_push_subscriptions, :access_token, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :oauth_access_tokens }, index: false
|
||||||
|
add_reference :web_push_subscriptions, :user, null: true, default: nil, foreign_key: { on_delete: :cascade }, index: false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@ class AddCreatedByApplicationIdToUsers < ActiveRecord::Migration[5.2]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def change
|
def change
|
||||||
add_reference :users, :created_by_application, foreign_key: { to_table: 'oauth_applications', on_delete: :nullify }, index: false
|
safety_assured { add_reference :users, :created_by_application, foreign_key: { to_table: 'oauth_applications', on_delete: :nullify }, index: false }
|
||||||
add_index :users, :created_by_application_id, algorithm: :concurrently
|
add_index :users, :created_by_application_id, algorithm: :concurrently
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@ class AddScheduledStatusIdToMediaAttachments < ActiveRecord::Migration[5.2]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def change
|
def change
|
||||||
add_reference :media_attachments, :scheduled_status, foreign_key: { on_delete: :nullify }, index: false
|
safety_assured { add_reference :media_attachments, :scheduled_status, foreign_key: { on_delete: :nullify }, index: false }
|
||||||
add_index :media_attachments, :scheduled_status_id, algorithm: :concurrently
|
add_index :media_attachments, :scheduled_status_id, algorithm: :concurrently
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class AddParentIdToEmailDomainBlocks < ActiveRecord::Migration[5.2]
|
class AddParentIdToEmailDomainBlocks < ActiveRecord::Migration[5.2]
|
||||||
def change
|
def change
|
||||||
add_reference :email_domain_blocks, :parent, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :email_domain_blocks }, index: false
|
safety_assured { add_reference :email_domain_blocks, :parent, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :email_domain_blocks }, index: false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
12
db/migrate/20200508212852_reset_unique_jobs_locks.rb
Normal file
12
db/migrate/20200508212852_reset_unique_jobs_locks.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class ResetUniqueJobsLocks < ActiveRecord::Migration[5.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
# We do this to clean up unique job digests that were not properly
|
||||||
|
# disposed of prior to https://github.com/tootsuite/mastodon/pull/13361
|
||||||
|
|
||||||
|
SidekiqUniqueJobs::Digests.delete_by_pattern('*', count: SidekiqUniqueJobs::Digests.count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down; end
|
||||||
|
end
|
15
db/migrate/20200510110808_reset_web_app_secret.rb
Normal file
15
db/migrate/20200510110808_reset_web_app_secret.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class ResetWebAppSecret < ActiveRecord::Migration[5.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
web_app = Doorkeeper::Application.find_by(superapp: true)
|
||||||
|
|
||||||
|
return if web_app.nil?
|
||||||
|
|
||||||
|
web_app.renew_secret
|
||||||
|
web_app.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2020_04_17_125749) do
|
ActiveRecord::Schema.define(version: 2020_05_10_110808) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
|
@ -144,7 +144,14 @@ module Mastodon
|
||||||
begin
|
begin
|
||||||
size = File.size(path)
|
size = File.size(path)
|
||||||
|
|
||||||
File.delete(path) unless options[:dry_run]
|
unless options[:dry_run]
|
||||||
|
File.delete(path)
|
||||||
|
begin
|
||||||
|
FileUtils.rmdir(File.dirname(path), parents: true)
|
||||||
|
rescue Errno::ENOTEMPTY
|
||||||
|
# OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
reclaimed_bytes += size
|
reclaimed_bytes += size
|
||||||
removed += 1
|
removed += 1
|
||||||
|
|
|
@ -121,7 +121,7 @@ module Mastodon
|
||||||
FileUtils.mv(previous_path, upgraded_path)
|
FileUtils.mv(previous_path, upgraded_path)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
FileUtils.rmdir(previous_path, parents: true)
|
FileUtils.rmdir(File.dirname(previous_path), parents: true)
|
||||||
rescue Errno::ENOTEMPTY
|
rescue Errno::ENOTEMPTY
|
||||||
# OK
|
# OK
|
||||||
end
|
end
|
||||||
|
@ -131,7 +131,7 @@ module Mastodon
|
||||||
|
|
||||||
unless dry_run?
|
unless dry_run?
|
||||||
begin
|
begin
|
||||||
FileUtils.rmdir(upgraded_path, parents: true)
|
FileUtils.rmdir(File.dirname(upgraded_path), parents: true)
|
||||||
rescue Errno::ENOTEMPTY
|
rescue Errno::ENOTEMPTY
|
||||||
# OK
|
# OK
|
||||||
end
|
end
|
||||||
|
|
26
package.json
26
package.json
|
@ -60,17 +60,17 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.9.6",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
"@babel/plugin-proposal-decorators": "^7.8.3",
|
"@babel/plugin-proposal-decorators": "^7.8.3",
|
||||||
"@babel/plugin-transform-react-inline-elements": "^7.9.0",
|
"@babel/plugin-transform-react-inline-elements": "^7.9.0",
|
||||||
"@babel/plugin-transform-runtime": "^7.9.0",
|
"@babel/plugin-transform-runtime": "^7.9.0",
|
||||||
"@babel/preset-env": "^7.9.0",
|
"@babel/preset-env": "^7.9.6",
|
||||||
"@babel/preset-react": "^7.9.4",
|
"@babel/preset-react": "^7.9.4",
|
||||||
"@babel/runtime": "^7.8.4",
|
"@babel/runtime": "^7.8.4",
|
||||||
"@clusterws/cws": "^0.17.3",
|
"@clusterws/cws": "^2.0.0",
|
||||||
"@gamestdio/websocket": "^0.3.2",
|
"@gamestdio/websocket": "^0.3.2",
|
||||||
"@rails/ujs": "^6.0.2",
|
"@rails/ujs": "^6.0.3",
|
||||||
"array-includes": "^3.1.1",
|
"array-includes": "^3.1.1",
|
||||||
"arrow-key-navigation": "^1.1.0",
|
"arrow-key-navigation": "^1.1.0",
|
||||||
"atrament": "0.2.4",
|
"atrament": "0.2.4",
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"babel-plugin-preval": "^5.0.0",
|
"babel-plugin-preval": "^5.0.0",
|
||||||
"babel-plugin-react-intl": "^3.4.1",
|
"babel-plugin-react-intl": "^6.2.0",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"blurhash": "^1.1.3",
|
"blurhash": "^1.1.3",
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"punycode": "^2.1.0",
|
"punycode": "^2.1.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.0",
|
"react-dom": "^16.13.1",
|
||||||
"react-hotkeys": "^1.1.4",
|
"react-hotkeys": "^1.1.4",
|
||||||
"react-immutable-proptypes": "^2.2.0",
|
"react-immutable-proptypes": "^2.2.0",
|
||||||
"react-immutable-pure-component": "^1.1.1",
|
"react-immutable-pure-component": "^1.1.1",
|
||||||
|
@ -159,15 +159,15 @@
|
||||||
"stacktrace-js": "^2.0.2",
|
"stacktrace-js": "^2.0.2",
|
||||||
"stringz": "^2.1.0",
|
"stringz": "^2.1.0",
|
||||||
"substring-trie": "^1.0.2",
|
"substring-trie": "^1.0.2",
|
||||||
"terser-webpack-plugin": "^2.3.5",
|
"terser-webpack-plugin": "^3.0.1",
|
||||||
"tesseract.js": "^2.0.0-alpha.16",
|
"tesseract.js": "^2.0.0-alpha.16",
|
||||||
"throng": "^4.0.0",
|
"throng": "^4.0.0",
|
||||||
"tiny-queue": "^0.2.1",
|
"tiny-queue": "^0.2.1",
|
||||||
"uuid": "^7.0.3",
|
"uuid": "^8.0.0",
|
||||||
"wavesurfer.js": "^3.3.1",
|
"wavesurfer.js": "^3.3.3",
|
||||||
"webpack": "^4.42.1",
|
"webpack": "^4.43.0",
|
||||||
"webpack-assets-manifest": "^3.1.1",
|
"webpack-assets-manifest": "^3.1.1",
|
||||||
"webpack-bundle-analyzer": "^3.6.1",
|
"webpack-bundle-analyzer": "^3.7.0",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
"webpack-merge": "^4.2.1",
|
"webpack-merge": "^4.2.1",
|
||||||
"wicg-inert": "^3.0.2"
|
"wicg-inert": "^3.0.2"
|
||||||
|
@ -182,10 +182,10 @@
|
||||||
"eslint-plugin-jsx-a11y": "~6.2.3",
|
"eslint-plugin-jsx-a11y": "~6.2.3",
|
||||||
"eslint-plugin-promise": "~4.2.1",
|
"eslint-plugin-promise": "~4.2.1",
|
||||||
"eslint-plugin-react": "~7.19.0",
|
"eslint-plugin-react": "~7.19.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^25.4.0",
|
||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
"react-intl-translations-manager": "^5.0.3",
|
"react-intl-translations-manager": "^5.0.3",
|
||||||
"react-test-renderer": "^16.13.0",
|
"react-test-renderer": "^16.13.1",
|
||||||
"sass-lint": "^1.13.1",
|
"sass-lint": "^1.13.1",
|
||||||
"webpack-dev-server": "^3.10.3",
|
"webpack-dev-server": "^3.10.3",
|
||||||
"yargs": "^15.3.1"
|
"yargs": "^15.3.1"
|
||||||
|
|
11
public/inert.css
Normal file
11
public/inert.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[inert] {
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[inert], [inert] * {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
|
@ -3,108 +3,608 @@ require 'rails_helper'
|
||||||
RSpec.describe AccountsController, type: :controller do
|
RSpec.describe AccountsController, type: :controller do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:alice) { Fabricate(:account, username: 'alice', user: Fabricate(:user)) }
|
let(:account) { Fabricate(:user).account }
|
||||||
let(:eve) { Fabricate(:user) }
|
|
||||||
|
|
||||||
describe 'GET #show' do
|
describe 'GET #show' do
|
||||||
let!(:status1) { Status.create!(account: alice, text: 'Hello world') }
|
let(:format) { 'html' }
|
||||||
let!(:status2) { Status.create!(account: alice, text: 'Boop', thread: status1) }
|
|
||||||
let!(:status3) { Status.create!(account: alice, text: 'Picture!') }
|
|
||||||
let!(:status4) { Status.create!(account: alice, text: 'Mentioning @alice') }
|
|
||||||
let!(:status5) { Status.create!(account: alice, text: 'Kitsune') }
|
|
||||||
let!(:status6) { Status.create!(account: alice, text: 'Neko') }
|
|
||||||
let!(:status7) { Status.create!(account: alice, text: 'Tanuki') }
|
|
||||||
|
|
||||||
let!(:status_pin1) { StatusPin.create!(account: alice, status: status5, created_at: 5.days.ago) }
|
let!(:status) { Fabricate(:status, account: account) }
|
||||||
let!(:status_pin2) { StatusPin.create!(account: alice, status: status6, created_at: 2.years.ago) }
|
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
|
||||||
let!(:status_pin3) { StatusPin.create!(account: alice, status: status7, created_at: 10.minutes.ago) }
|
let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
|
||||||
|
let!(:status_media) { Fabricate(:status, account: account) }
|
||||||
|
let!(:status_pinned) { Fabricate(:status, account: account) }
|
||||||
|
let!(:status_private) { Fabricate(:status, account: account, visibility: :private) }
|
||||||
|
let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) }
|
||||||
|
let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
alice.block!(eve.account)
|
status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image)
|
||||||
status3.media_attachments.create!(account: alice, file: fixture_file_upload('files/attachment.jpg', 'image/jpeg'))
|
account.pinned_statuses << status_pinned
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'responses' do
|
shared_examples 'preliminary checks' do
|
||||||
before do
|
context 'when account is not approved' do
|
||||||
sign_in(current_user) if defined? current_user
|
before do
|
||||||
get :show, params: {
|
account.user.update(approved: false)
|
||||||
username: alice.username,
|
end
|
||||||
max_id: (max_id if defined? max_id),
|
|
||||||
since_id: (since_id if defined? since_id),
|
it 'returns http not found' do
|
||||||
current_user: (current_user if defined? current_user),
|
get :show, params: { username: account.username, format: format }
|
||||||
}, format: format
|
expect(response).to have_http_status(404)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns @account' do
|
context 'when account is suspended' do
|
||||||
expect(assigns(:account)).to eq alice
|
before do
|
||||||
end
|
account.suspend!
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'returns http gone' do
|
||||||
expect(response).to have_http_status(200)
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
expect(response).to have_http_status(410)
|
||||||
|
end
|
||||||
it 'returns correct format' do
|
|
||||||
expect(response.content_type).to eq content_type
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'activitystreams2' do
|
context 'as HTML' do
|
||||||
|
let(:format) { 'html' }
|
||||||
|
|
||||||
|
it_behaves_like 'preliminary checks'
|
||||||
|
|
||||||
|
shared_examples 'common response characteristics' do
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns Link header' do
|
||||||
|
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders show template' do
|
||||||
|
expect(response).to render_template(:show)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context do
|
||||||
|
before do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reblog' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders pinned status' do
|
||||||
|
expect(response.body).to include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when signed-in' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user follows account' do
|
||||||
|
before do
|
||||||
|
user.account.follow!(account)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is blocked' do
|
||||||
|
before do
|
||||||
|
account.block!(user.account)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders unavailable message' do
|
||||||
|
expect(response.body).to include(I18n.t('accounts.unavailable'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render status with media' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with replies' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reblog' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reply to someone else' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with media' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:media_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with tag' do
|
||||||
|
let(:tag) { Fabricate(:tag) }
|
||||||
|
|
||||||
|
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||||
|
status_tag.tags << tag
|
||||||
|
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render status with media' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with tag' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as JSON' do
|
||||||
|
let(:authorized_fetch_mode) { false }
|
||||||
let(:format) { 'json' }
|
let(:format) { 'json' }
|
||||||
let(:content_type) { 'application/activity+json' }
|
|
||||||
|
|
||||||
include_examples 'responses'
|
before do
|
||||||
|
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'preliminary checks'
|
||||||
|
|
||||||
|
context do
|
||||||
|
before do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in authorized fetch mode' do
|
||||||
|
let(:authorized_fetch_mode) { true }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns Vary header with Signature' do
|
||||||
|
expect(response.headers['Vary']).to include 'Signature'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders bare minimum account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey)
|
||||||
|
expect(json).to_not include(:name, :summary)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when signed in' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with signature' do
|
||||||
|
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:signed_request_account).and_return(remote_account)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in authorized fetch mode' do
|
||||||
|
let(:authorized_fetch_mode) { true }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns private Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'private'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns Vary header with Signature' do
|
||||||
|
expect(response.headers['Vary']).to include 'Signature'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'html' do
|
context 'as RSS' do
|
||||||
let(:format) { nil }
|
let(:format) { 'rss' }
|
||||||
let(:content_type) { 'text/html' }
|
|
||||||
|
|
||||||
shared_examples 'responsed statuses' do
|
it_behaves_like 'preliminary checks'
|
||||||
it 'assigns @pinned_statuses' do
|
|
||||||
pinned_statuses = assigns(:pinned_statuses).to_a
|
shared_examples 'common response characteristics' do
|
||||||
expect(pinned_statuses.size).to eq expected_pinned_statuses.size
|
it 'returns http success' do
|
||||||
pinned_statuses.each.zip(expected_pinned_statuses.each) do |pinned_status, expected_pinned_status|
|
expect(response).to have_http_status(200)
|
||||||
expect(pinned_status).to eq expected_pinned_status
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns @statuses' do
|
it 'returns public Cache-Control header' do
|
||||||
statuses = assigns(:statuses).to_a
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
expect(statuses.size).to eq expected_statuses.size
|
|
||||||
statuses.each.zip(expected_statuses.each) do |status, expected_status|
|
|
||||||
expect(status).to eq expected_status
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
include_examples 'responses'
|
context do
|
||||||
|
before do
|
||||||
context 'with anonymous visitor' do
|
get :show, params: { username: account.username, format: format }
|
||||||
context 'without since_id nor max_id' do
|
|
||||||
let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
|
|
||||||
let(:expected_pinned_statuses) { [status7, status5, status6] }
|
|
||||||
|
|
||||||
include_examples 'responsed statuses'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with since_id nor max_id' do
|
it_behaves_like 'common response characteristics'
|
||||||
let(:max_id) { status4.id }
|
|
||||||
let(:since_id) { status1.id }
|
|
||||||
let(:expected_statuses) { [status3, status2] }
|
|
||||||
let(:expected_pinned_statuses) { [] }
|
|
||||||
|
|
||||||
include_examples 'responsed statuses'
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with blocked visitor' do
|
context 'with replies' do
|
||||||
let(:current_user) { eve }
|
before do
|
||||||
|
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
context 'without since_id nor max_id' do
|
it_behaves_like 'common response characteristics'
|
||||||
let(:expected_statuses) { [] }
|
|
||||||
let(:expected_pinned_statuses) { [] }
|
|
||||||
|
|
||||||
include_examples 'responsed statuses'
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reply to someone else' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with media' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:media_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with tag' do
|
||||||
|
let(:tag) { Fabricate(:tag) }
|
||||||
|
|
||||||
|
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||||
|
status_tag.tags << tag
|
||||||
|
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render status with media' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with tag' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,5 +36,28 @@ describe Api::V1::Accounts::FollowerAccountsController do
|
||||||
expect(body_as_json.size).to eq 1
|
expect(body_as_json.size).to eq 1
|
||||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is blocked' do
|
||||||
|
before do
|
||||||
|
account.block!(user.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hides results' do
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
expect(body_as_json.size).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is the account owner' do
|
||||||
|
let(:user) { Fabricate(:user, account: account) }
|
||||||
|
|
||||||
|
it 'returns all accounts, including muted accounts' do
|
||||||
|
user.account.mute!(bob)
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
|
||||||
|
expect(body_as_json.size).to eq 2
|
||||||
|
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,5 +36,28 @@ describe Api::V1::Accounts::FollowingAccountsController do
|
||||||
expect(body_as_json.size).to eq 1
|
expect(body_as_json.size).to eq 1
|
||||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is blocked' do
|
||||||
|
before do
|
||||||
|
account.block!(user.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hides results' do
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
expect(body_as_json.size).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is the account owner' do
|
||||||
|
let(:user) { Fabricate(:user, account: account) }
|
||||||
|
|
||||||
|
it 'returns all accounts, including muted accounts' do
|
||||||
|
user.account.mute!(bob)
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
|
||||||
|
expect(body_as_json.size).to eq 2
|
||||||
|
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
|
||||||
|
|
||||||
describe 'POST #create' do
|
describe 'POST #create' do
|
||||||
let(:app) { Fabricate(:application) }
|
let(:app) { Fabricate(:application) }
|
||||||
let(:token) { Doorkeeper::AccessToken.find_or_create_for(app, nil, 'read write', nil, false) }
|
let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) }
|
||||||
let(:agreement) { nil }
|
let(:agreement) { nil }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -16,10 +16,16 @@ describe ApplicationController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'default locale' do
|
shared_examples 'default locale' do
|
||||||
|
it 'sets available and preferred language' do
|
||||||
|
request.headers['Accept-Language'] = 'sr-Latn'
|
||||||
|
get 'success'
|
||||||
|
expect(response.body).to eq 'sr-Latn'
|
||||||
|
end
|
||||||
|
|
||||||
it 'sets available and preferred language' do
|
it 'sets available and preferred language' do
|
||||||
request.headers['Accept-Language'] = 'ca-ES, fa'
|
request.headers['Accept-Language'] = 'ca-ES, fa'
|
||||||
get 'success'
|
get 'success'
|
||||||
expect(response.body).to eq 'fa'
|
expect(response.body).to eq 'ca'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets available and compatible language if none of available languages are preferred' do
|
it 'sets available and compatible language if none of available languages are preferred' do
|
||||||
|
|
|
@ -41,11 +41,11 @@ RSpec.describe Oauth::AuthorizationsController, type: :controller do
|
||||||
context 'when app is already authorized' do
|
context 'when app is already authorized' do
|
||||||
before do
|
before do
|
||||||
Doorkeeper::AccessToken.find_or_create_for(
|
Doorkeeper::AccessToken.find_or_create_for(
|
||||||
app,
|
application: app,
|
||||||
user.id,
|
resource_owner: user.id,
|
||||||
app.scopes,
|
scopes: app.scopes,
|
||||||
Doorkeeper.configuration.access_token_expires_in,
|
expires_in: Doorkeeper.configuration.access_token_expires_in,
|
||||||
Doorkeeper.configuration.refresh_token_enabled?
|
use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ require 'rails_helper'
|
||||||
RSpec.describe Oauth::TokensController, type: :controller do
|
RSpec.describe Oauth::TokensController, type: :controller do
|
||||||
describe 'POST #revoke' do
|
describe 'POST #revoke' do
|
||||||
let!(:user) { Fabricate(:user) }
|
let!(:user) { Fabricate(:user) }
|
||||||
let!(:access_token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
|
let!(:application) { Fabricate(:application, confidential: false) }
|
||||||
|
let!(:access_token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: application) }
|
||||||
let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) }
|
let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
post :revoke, params: { token: access_token.token }
|
post :revoke, params: { client_id: application.uid, token: access_token.token }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'revokes the token' do
|
it 'revokes the token' do
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe RemoteFollowController do
|
||||||
context 'when webfinger values are wrong' do
|
context 'when webfinger values are wrong' do
|
||||||
it 'renders new when redirect url is nil' do
|
it 'renders new when redirect url is nil' do
|
||||||
resource_with_nil_link = double(link: nil)
|
resource_with_nil_link = double(link: nil)
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_nil_link)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_nil_link)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
@ -45,7 +45,7 @@ describe RemoteFollowController do
|
||||||
it 'renders new when template is nil' do
|
it 'renders new when template is nil' do
|
||||||
link_with_nil_template = double(template: nil)
|
link_with_nil_template = double(template: nil)
|
||||||
resource_with_link = double(link: link_with_nil_template)
|
resource_with_link = double(link: link_with_nil_template)
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_link)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
@ -57,7 +57,7 @@ describe RemoteFollowController do
|
||||||
before do
|
before do
|
||||||
link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
|
link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
|
||||||
resource_with_link = double(link: link_with_template)
|
resource_with_link = double(link: link_with_template)
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_link)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ describe RemoteFollowController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders new with error when goldfinger fails' do
|
it 'renders new with error when goldfinger fails' do
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_raise(Goldfinger::Error)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Goldfinger::Error)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
@ -87,7 +87,7 @@ describe RemoteFollowController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders new when occur HTTP::ConnectionError' do
|
it 'renders new when occur HTTP::ConnectionError' do
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
|
|
@ -151,7 +151,7 @@ describe Settings::IdentityProofsController do
|
||||||
@proof1 = Fabricate(:account_identity_proof, account: user.account)
|
@proof1 = Fabricate(:account_identity_proof, account: user.account)
|
||||||
@proof2 = Fabricate(:account_identity_proof, account: user.account)
|
@proof2 = Fabricate(:account_identity_proof, account: user.account)
|
||||||
allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
|
allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
|
||||||
allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) { }
|
allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) {}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has the first proof username on the page' do
|
it 'has the first proof username on the page' do
|
||||||
|
@ -165,4 +165,22 @@ describe Settings::IdentityProofsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'DELETE #destroy' do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
|
||||||
|
@proof1 = Fabricate(:account_identity_proof, account: user.account)
|
||||||
|
allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
|
||||||
|
allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) {}
|
||||||
|
delete :destroy, params: { id: @proof1.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to :index' do
|
||||||
|
expect(response).to redirect_to settings_identity_proofs_path
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes the proof' do
|
||||||
|
expect(AccountIdentityProof.where(id: @proof1.id).count).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
63
spec/lib/rss/serializer_spec.rb
Normal file
63
spec/lib/rss/serializer_spec.rb
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe RSS::Serializer do
|
||||||
|
describe '#status_title' do
|
||||||
|
let(:text) { 'This is a toot' }
|
||||||
|
let(:spoiler) { '' }
|
||||||
|
let(:sensitive) { false }
|
||||||
|
let(:reblog) { nil }
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let(:status) { Fabricate(:status, account: account, text: text, spoiler_text: spoiler, sensitive: sensitive, reblog: reblog) }
|
||||||
|
|
||||||
|
subject { RSS::Serializer.new.send(:status_title, status) }
|
||||||
|
|
||||||
|
context 'if destroyed?' do
|
||||||
|
it 'returns "#{account.acct} deleted status"' do
|
||||||
|
status.destroy!
|
||||||
|
expect(subject).to eq "#{account.acct} deleted status"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with long text' do
|
||||||
|
let(:text) { "This toot's text is longer than the allowed number of characters" }
|
||||||
|
|
||||||
|
it 'truncates toot text appropriately' do
|
||||||
|
expect(subject).to eq "#{account.acct}: “This toot's text is longer tha…”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with long text with a newline' do
|
||||||
|
let(:text) { "This toot's text is longer\nthan the allowed number of characters" }
|
||||||
|
|
||||||
|
it 'truncates toot text appropriately' do
|
||||||
|
expect(subject).to eq "#{account.acct}: “This toot's text is longer…”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with a content warning' do
|
||||||
|
let(:spoiler) { 'long toot' }
|
||||||
|
|
||||||
|
it 'displays spoiler text instead of toot content' do
|
||||||
|
expect(subject).to eq "#{account.acct}: CW “long toot”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with sensitive media' do
|
||||||
|
let(:sensitive) { true }
|
||||||
|
|
||||||
|
it 'displays that the media is sensitive' do
|
||||||
|
expect(subject).to eq "#{account.acct}: “This is a toot” (sensitive)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a reblog' do
|
||||||
|
let(:reblog) { Fabricate(:status, text: 'This is a toot') }
|
||||||
|
|
||||||
|
it 'display that the toot is a reblog' do
|
||||||
|
expect(subject).to eq "#{account.acct} boosted #{reblog.account.acct}: “This is a toot”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
37
spec/models/relationship_filter_spec.rb
Normal file
37
spec/models/relationship_filter_spec.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe RelationshipFilter do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
describe '#results' do
|
||||||
|
context 'when default params are used' do
|
||||||
|
let(:subject) do
|
||||||
|
RelationshipFilter.new(account, 'order' => 'active').results
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
add_following_account_with(last_status_at: 7.days.ago)
|
||||||
|
add_following_account_with(last_status_at: 1.day.ago)
|
||||||
|
add_following_account_with(last_status_at: 3.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns followings ordered by last activity' do
|
||||||
|
expected_result = account.following.eager_load(:account_stat).reorder(nil).by_recent_status
|
||||||
|
|
||||||
|
expect(subject).to eq expected_result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_following_account_with(last_status_at:)
|
||||||
|
following_account = Fabricate(:account)
|
||||||
|
Fabricate(:account_stat, account: following_account,
|
||||||
|
last_status_at: last_status_at,
|
||||||
|
statuses_count: 1,
|
||||||
|
following_count: 0,
|
||||||
|
followers_count: 0)
|
||||||
|
Fabricate(:follow, account: account, target_account: following_account).account
|
||||||
|
end
|
||||||
|
end
|
|
@ -82,35 +82,6 @@ RSpec.describe Status, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#title' do
|
|
||||||
# rubocop:disable Style/InterpolationCheck
|
|
||||||
|
|
||||||
let(:account) { subject.account }
|
|
||||||
|
|
||||||
context 'if destroyed?' do
|
|
||||||
it 'returns "#{account.acct} deleted status"' do
|
|
||||||
subject.destroy!
|
|
||||||
expect(subject.title).to eq "#{account.acct} deleted status"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'unless destroyed?' do
|
|
||||||
context 'if reblog?' do
|
|
||||||
it 'returns "#{account.acct} shared a status by #{reblog.account.acct}"' do
|
|
||||||
reblog = subject.reblog = other
|
|
||||||
expect(subject.title).to eq "#{account.acct} shared a status by #{reblog.account.acct}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'unless reblog?' do
|
|
||||||
it 'returns "New status by #{account.acct}"' do
|
|
||||||
subject.reblog = nil
|
|
||||||
expect(subject.title).to eq "New status by #{account.acct}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#hidden?' do
|
describe '#hidden?' do
|
||||||
context 'if private_visibility?' do
|
context 'if private_visibility?' do
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
|
@ -490,6 +461,33 @@ RSpec.describe Status, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a remote_only option set' do
|
||||||
|
let!(:local_account) { Fabricate(:account, domain: nil) }
|
||||||
|
let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
|
||||||
|
let!(:local_status) { Fabricate(:status, account: local_account) }
|
||||||
|
let!(:remote_status) { Fabricate(:status, account: remote_account) }
|
||||||
|
|
||||||
|
subject { Status.as_public_timeline(viewer, :remote) }
|
||||||
|
|
||||||
|
context 'without a viewer' do
|
||||||
|
let(:viewer) { nil }
|
||||||
|
|
||||||
|
it 'does not include local instances statuses' do
|
||||||
|
expect(subject).not_to include(local_status)
|
||||||
|
expect(subject).to include(remote_status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a viewer' do
|
||||||
|
let(:viewer) { Fabricate(:account, username: 'viewer') }
|
||||||
|
|
||||||
|
it 'does not include local instances statuses' do
|
||||||
|
expect(subject).not_to include(local_status)
|
||||||
|
expect(subject).to include(remote_status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'with an account passed in' do
|
describe 'with an account passed in' do
|
||||||
before do
|
before do
|
||||||
@account = Fabricate(:account)
|
@account = Fabricate(:account)
|
||||||
|
|
|
@ -266,6 +266,8 @@ const startWorker = (workerId) => {
|
||||||
'public:media',
|
'public:media',
|
||||||
'public:local',
|
'public:local',
|
||||||
'public:local:media',
|
'public:local:media',
|
||||||
|
'public:remote',
|
||||||
|
'public:remote:media',
|
||||||
'hashtag',
|
'hashtag',
|
||||||
'hashtag:local',
|
'hashtag:local',
|
||||||
];
|
];
|
||||||
|
@ -297,6 +299,7 @@ const startWorker = (workerId) => {
|
||||||
const PUBLIC_ENDPOINTS = [
|
const PUBLIC_ENDPOINTS = [
|
||||||
'/api/v1/streaming/public',
|
'/api/v1/streaming/public',
|
||||||
'/api/v1/streaming/public/local',
|
'/api/v1/streaming/public/local',
|
||||||
|
'/api/v1/streaming/public/remote',
|
||||||
'/api/v1/streaming/hashtag',
|
'/api/v1/streaming/hashtag',
|
||||||
'/api/v1/streaming/hashtag/local',
|
'/api/v1/streaming/hashtag/local',
|
||||||
];
|
];
|
||||||
|
@ -541,6 +544,13 @@ const startWorker = (workerId) => {
|
||||||
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true);
|
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/api/v1/streaming/public/remote', (req, res) => {
|
||||||
|
const onlyMedia = req.query.only_media === '1' || req.query.only_media === 'true';
|
||||||
|
const channel = onlyMedia ? 'timeline:public:remote:media' : 'timeline:public:remote';
|
||||||
|
|
||||||
|
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true);
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/v1/streaming/direct', (req, res) => {
|
app.get('/api/v1/streaming/direct', (req, res) => {
|
||||||
const channel = `timeline:direct:${req.accountId}`;
|
const channel = `timeline:direct:${req.accountId}`;
|
||||||
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)), true);
|
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)), true);
|
||||||
|
@ -605,12 +615,18 @@ const startWorker = (workerId) => {
|
||||||
case 'public:local':
|
case 'public:local':
|
||||||
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
break;
|
break;
|
||||||
|
case 'public:remote':
|
||||||
|
streamFrom('timeline:public:remote', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
|
break;
|
||||||
case 'public:media':
|
case 'public:media':
|
||||||
streamFrom('timeline:public:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
streamFrom('timeline:public:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
break;
|
break;
|
||||||
case 'public:local:media':
|
case 'public:local:media':
|
||||||
streamFrom('timeline:public:local:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
streamFrom('timeline:public:local:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
break;
|
break;
|
||||||
|
case 'public:remote:media':
|
||||||
|
streamFrom('timeline:public:remote:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
|
break;
|
||||||
case 'direct':
|
case 'direct':
|
||||||
channel = `timeline:direct:${req.accountId}`;
|
channel = `timeline:direct:${req.accountId}`;
|
||||||
streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)), true);
|
streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)), true);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue