forked from cybrespace/mastodon
Compare commits
50 Commits
cybrespace
...
fix-backup
Author | SHA1 | Date |
---|---|---|
|
3821241c46 | |
|
685b0db882 | |
|
741a85c064 | |
|
0745fa8449 | |
|
291106e11c | |
|
52c9044ea9 | |
|
008d15d2cd | |
|
e551274897 | |
|
0caa707726 | |
|
254ddfc08a | |
|
4ecfd4308d | |
|
da67b1fa37 | |
|
3565fc1a00 | |
|
91b02afe4a | |
|
7be994e0a9 | |
|
a4301b5202 | |
|
5b46467474 | |
|
7512f3a3e0 | |
|
4988ebba4e | |
|
9b36f62df6 | |
|
91945aa78a | |
|
48f75b86ae | |
|
a6269b2f83 | |
|
a9530e29a2 | |
|
15c192ce40 | |
|
bcf694dce7 | |
|
d3145ced1f | |
|
f4be89e24d | |
|
488dd0ff7a | |
|
3a929dbedd | |
|
547a5bac9d | |
|
5966d1b5a3 | |
|
aa884e0484 | |
|
9762fe382c | |
|
3ebd903535 | |
|
d2919f7e94 | |
|
bd684e25d9 | |
|
fccf83e1f2 | |
|
237293fd8c | |
|
6bee7b820d | |
|
e9e6ca9041 | |
|
f7f6f0726c | |
|
1a7cf80b7f | |
|
183fc9d3cb | |
|
bc9c116e60 | |
|
912496de07 | |
|
7840844919 | |
|
13b6d5d01b | |
|
1ecd71c53e | |
|
ae945cb1f4 |
|
@ -1,2 +1,3 @@
|
||||||
VAGRANT=true
|
VAGRANT=true
|
||||||
LOCAL_DOMAIN=mastodon.local
|
LOCAL_DOMAIN=mastodon.local
|
||||||
|
BIND=0.0.0.0
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
daysUntilStale: 120
|
||||||
|
daysUntilClose: 7
|
||||||
|
exemptLabels:
|
||||||
|
- security
|
||||||
|
staleLabel: wontfix
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
only: pulls
|
16
Gemfile
16
Gemfile
|
@ -3,7 +3,7 @@
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
ruby '>= 2.4.0', '< 2.7.0'
|
ruby '>= 2.4.0', '< 2.7.0'
|
||||||
|
|
||||||
gem 'pkg-config', '~> 1.3'
|
gem 'pkg-config', '~> 1.4'
|
||||||
|
|
||||||
gem 'puma', '~> 4.2'
|
gem 'puma', '~> 4.2'
|
||||||
gem 'rails', '~> 5.2.3'
|
gem 'rails', '~> 5.2.3'
|
||||||
|
@ -15,7 +15,7 @@ gem 'makara', '~> 0.4'
|
||||||
gem 'pghero', '~> 2.3'
|
gem 'pghero', '~> 2.3'
|
||||||
gem 'dotenv-rails', '~> 2.7'
|
gem 'dotenv-rails', '~> 2.7'
|
||||||
|
|
||||||
gem 'aws-sdk-s3', '~> 1.48', require: false
|
gem 'aws-sdk-s3', '~> 1.52', 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'
|
||||||
|
@ -85,7 +85,7 @@ gem 'sidekiq-scheduler', '~> 3.0'
|
||||||
gem 'sidekiq-unique-jobs', '~> 6.0'
|
gem 'sidekiq-unique-jobs', '~> 6.0'
|
||||||
gem 'sidekiq-bulk', '~>0.2.0'
|
gem 'sidekiq-bulk', '~>0.2.0'
|
||||||
gem 'simple-navigation', '~> 4.1'
|
gem 'simple-navigation', '~> 4.1'
|
||||||
gem 'simple_form', '~> 4.1'
|
gem 'simple_form', '~> 5.0'
|
||||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||||
gem 'stoplight', '~> 2.1.3'
|
gem 'stoplight', '~> 2.1.3'
|
||||||
gem 'strong_migrations', '~> 0.4'
|
gem 'strong_migrations', '~> 0.4'
|
||||||
|
@ -106,7 +106,7 @@ group :development, :test do
|
||||||
gem 'i18n-tasks', '~> 0.9', require: false
|
gem 'i18n-tasks', '~> 0.9', require: false
|
||||||
gem 'pry-byebug', '~> 3.7'
|
gem 'pry-byebug', '~> 3.7'
|
||||||
gem 'pry-rails', '~> 0.3'
|
gem 'pry-rails', '~> 0.3'
|
||||||
gem 'rspec-rails', '~> 3.8'
|
gem 'rspec-rails', '~> 3.9'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :production, :test do
|
group :production, :test do
|
||||||
|
@ -116,7 +116,7 @@ end
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.29'
|
gem 'capybara', '~> 3.29'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 2.5'
|
gem 'faker', '~> 2.6'
|
||||||
gem 'microformats', '~> 4.1'
|
gem 'microformats', '~> 4.1'
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
gem 'rspec-sidekiq', '~> 3.0'
|
gem 'rspec-sidekiq', '~> 3.0'
|
||||||
|
@ -126,15 +126,15 @@ group :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'active_record_query_trace', '~> 1.6'
|
gem 'active_record_query_trace', '~> 1.7'
|
||||||
gem 'annotate', '~> 2.7'
|
gem 'annotate', '~> 3.0'
|
||||||
gem 'better_errors', '~> 2.5'
|
gem 'better_errors', '~> 2.5'
|
||||||
gem 'binding_of_caller', '~> 0.7'
|
gem 'binding_of_caller', '~> 0.7'
|
||||||
gem 'bullet', '~> 6.0'
|
gem 'bullet', '~> 6.0'
|
||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.7'
|
||||||
gem 'letter_opener_web', '~> 1.3'
|
gem 'letter_opener_web', '~> 1.3'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 0.74', require: false
|
gem 'rubocop', '~> 0.75', require: false
|
||||||
gem 'rubocop-rails', '~> 2.3', require: false
|
gem 'rubocop-rails', '~> 2.3', require: false
|
||||||
gem 'brakeman', '~> 4.6', require: false
|
gem 'brakeman', '~> 4.6', require: false
|
||||||
gem 'bundler-audit', '~> 0.6', require: false
|
gem 'bundler-audit', '~> 0.6', require: false
|
||||||
|
|
98
Gemfile.lock
98
Gemfile.lock
|
@ -72,7 +72,7 @@ GEM
|
||||||
activemodel (>= 4.1, < 6.1)
|
activemodel (>= 4.1, < 6.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
active_record_query_trace (1.6.2)
|
active_record_query_trace (1.7)
|
||||||
activejob (5.2.3)
|
activejob (5.2.3)
|
||||||
activesupport (= 5.2.3)
|
activesupport (= 5.2.3)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
|
@ -95,7 +95,7 @@ GEM
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
airbrussh (1.3.4)
|
airbrussh (1.3.4)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
annotate (2.7.5)
|
annotate (3.0.2)
|
||||||
activerecord (>= 3.2, < 7.0)
|
activerecord (>= 3.2, < 7.0)
|
||||||
rake (>= 10.4, < 13.0)
|
rake (>= 10.4, < 13.0)
|
||||||
arel (9.0.0)
|
arel (9.0.0)
|
||||||
|
@ -105,17 +105,17 @@ GEM
|
||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-eventstream (1.0.3)
|
aws-eventstream (1.0.3)
|
||||||
aws-partitions (1.207.0)
|
aws-partitions (1.230.0)
|
||||||
aws-sdk-core (3.65.1)
|
aws-sdk-core (3.72.0)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
aws-partitions (~> 1.0)
|
aws-partitions (~> 1, >= 1.228.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.24.0)
|
aws-sdk-kms (1.25.0)
|
||||||
aws-sdk-core (~> 3, >= 3.61.1)
|
aws-sdk-core (~> 3, >= 3.71.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.48.0)
|
aws-sdk-s3 (1.52.0)
|
||||||
aws-sdk-core (~> 3, >= 3.61.1)
|
aws-sdk-core (~> 3, >= 3.71.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.1.0)
|
aws-sigv4 (1.1.0)
|
||||||
|
@ -184,17 +184,17 @@ GEM
|
||||||
connection_pool (2.2.2)
|
connection_pool (2.2.2)
|
||||||
crack (0.4.3)
|
crack (0.4.3)
|
||||||
safe_yaml (~> 1.0.0)
|
safe_yaml (~> 1.0.0)
|
||||||
crass (1.0.4)
|
crass (1.0.5)
|
||||||
css_parser (1.7.0)
|
css_parser (1.7.0)
|
||||||
addressable
|
addressable
|
||||||
debug_inspector (0.0.3)
|
debug_inspector (0.0.3)
|
||||||
derailed_benchmarks (1.4.0)
|
derailed_benchmarks (1.4.1)
|
||||||
benchmark-ips (~> 2)
|
benchmark-ips (~> 2)
|
||||||
get_process_mem (~> 0)
|
get_process_mem (~> 0)
|
||||||
heapy (~> 0)
|
heapy (~> 0)
|
||||||
memory_profiler (~> 0)
|
memory_profiler (~> 0)
|
||||||
rack (>= 1)
|
rack (>= 1)
|
||||||
rake (> 10, < 13)
|
rake (> 10, < 14)
|
||||||
ruby-statistics (>= 2.1)
|
ruby-statistics (>= 2.1)
|
||||||
thor (~> 0.19)
|
thor (~> 0.19)
|
||||||
devise (4.7.1)
|
devise (4.7.1)
|
||||||
|
@ -235,13 +235,13 @@ GEM
|
||||||
multi_json
|
multi_json
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
equatable (0.6.1)
|
equatable (0.6.1)
|
||||||
erubi (1.8.0)
|
erubi (1.9.0)
|
||||||
et-orbi (1.1.6)
|
et-orbi (1.1.6)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.62.0)
|
excon (0.62.0)
|
||||||
fabrication (2.20.2)
|
fabrication (2.20.2)
|
||||||
faker (2.5.0)
|
faker (2.6.0)
|
||||||
i18n (~> 1.6.0)
|
i18n (>= 1.6, < 1.8)
|
||||||
faraday (0.15.4)
|
faraday (0.15.4)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
|
@ -305,7 +305,7 @@ GEM
|
||||||
httplog (1.3.2)
|
httplog (1.3.2)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
i18n (1.6.0)
|
i18n (1.7.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (0.9.29)
|
i18n-tasks (0.9.29)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
|
@ -356,7 +356,7 @@ GEM
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.2.3)
|
loofah (2.3.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.7.1)
|
mail (2.7.1)
|
||||||
|
@ -378,7 +378,7 @@ GEM
|
||||||
mimemagic (0.3.3)
|
mimemagic (0.3.3)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.12.0)
|
minitest (5.12.2)
|
||||||
msgpack (1.3.1)
|
msgpack (1.3.1)
|
||||||
multi_json (1.13.1)
|
multi_json (1.13.1)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
|
@ -397,7 +397,7 @@ GEM
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
sidekiq (>= 3.5)
|
sidekiq (>= 3.5)
|
||||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||||
oj (3.9.1)
|
oj (3.9.2)
|
||||||
omniauth (1.9.0)
|
omniauth (1.9.0)
|
||||||
hashie (>= 3.4.6, < 3.7.0)
|
hashie (>= 3.4.6, < 3.7.0)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
|
@ -426,7 +426,7 @@ GEM
|
||||||
parallel (1.17.0)
|
parallel (1.17.0)
|
||||||
parallel_tests (2.29.2)
|
parallel_tests (2.29.2)
|
||||||
parallel
|
parallel
|
||||||
parser (2.6.4.0)
|
parser (2.6.5.0)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
parslet (1.8.2)
|
parslet (1.8.2)
|
||||||
pastel (0.7.3)
|
pastel (0.7.3)
|
||||||
|
@ -435,7 +435,7 @@ GEM
|
||||||
pg (1.1.4)
|
pg (1.1.4)
|
||||||
pghero (2.3.0)
|
pghero (2.3.0)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
pkg-config (1.3.9)
|
pkg-config (1.4.0)
|
||||||
premailer (1.11.1)
|
premailer (1.11.1)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.6.0)
|
css_parser (>= 1.6.0)
|
||||||
|
@ -462,7 +462,7 @@ GEM
|
||||||
rack-attack (6.1.0)
|
rack-attack (6.1.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.0.3)
|
rack-cors (1.0.3)
|
||||||
rack-protection (2.0.5)
|
rack-protection (2.0.7)
|
||||||
rack
|
rack
|
||||||
rack-proxy (0.6.5)
|
rack-proxy (0.6.5)
|
||||||
rack
|
rack
|
||||||
|
@ -488,8 +488,8 @@ GEM
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.2.0)
|
rails-html-sanitizer (1.3.0)
|
||||||
loofah (~> 2.2, >= 2.2.2)
|
loofah (~> 2.3)
|
||||||
rails-i18n (5.1.3)
|
rails-i18n (5.1.3)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 5.0, < 6)
|
railties (>= 5.0, < 6)
|
||||||
|
@ -537,27 +537,27 @@ GEM
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (0.10.1)
|
rqrcode (0.10.1)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rspec-core (3.8.0)
|
rspec-core (3.9.0)
|
||||||
rspec-support (~> 3.8.0)
|
rspec-support (~> 3.9.0)
|
||||||
rspec-expectations (3.8.2)
|
rspec-expectations (3.9.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.8.0)
|
rspec-support (~> 3.9.0)
|
||||||
rspec-mocks (3.8.0)
|
rspec-mocks (3.9.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.8.0)
|
rspec-support (~> 3.9.0)
|
||||||
rspec-rails (3.8.2)
|
rspec-rails (3.9.0)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
railties (>= 3.0)
|
railties (>= 3.0)
|
||||||
rspec-core (~> 3.8.0)
|
rspec-core (~> 3.9.0)
|
||||||
rspec-expectations (~> 3.8.0)
|
rspec-expectations (~> 3.9.0)
|
||||||
rspec-mocks (~> 3.8.0)
|
rspec-mocks (~> 3.9.0)
|
||||||
rspec-support (~> 3.8.0)
|
rspec-support (~> 3.9.0)
|
||||||
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.8.0)
|
rspec-support (3.9.0)
|
||||||
rubocop (0.74.0)
|
rubocop (0.75.1)
|
||||||
jaro_winkler (~> 1.5.1)
|
jaro_winkler (~> 1.5.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.6)
|
parser (>= 2.6)
|
||||||
|
@ -590,13 +590,13 @@ GEM
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 3)
|
sidekiq (>= 3)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (6.0.13)
|
sidekiq-unique-jobs (6.0.15)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
sidekiq (>= 4.0, < 7.0)
|
sidekiq (>= 4.0, < 7.0)
|
||||||
thor (~> 0)
|
thor (~> 0)
|
||||||
simple-navigation (4.1.0)
|
simple-navigation (4.1.0)
|
||||||
activesupport (>= 2.3.2)
|
activesupport (>= 2.3.2)
|
||||||
simple_form (4.1.0)
|
simple_form (5.0.1)
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
activemodel (>= 5.0)
|
activemodel (>= 5.0)
|
||||||
simplecov (0.17.1)
|
simplecov (0.17.1)
|
||||||
|
@ -614,12 +614,12 @@ GEM
|
||||||
sshkit (1.20.0)
|
sshkit (1.20.0)
|
||||||
net-scp (>= 1.1.2)
|
net-scp (>= 1.1.2)
|
||||||
net-ssh (>= 2.8.0)
|
net-ssh (>= 2.8.0)
|
||||||
stackprof (0.2.12)
|
stackprof (0.2.13)
|
||||||
statsd-ruby (1.4.0)
|
statsd-ruby (1.4.0)
|
||||||
stoplight (2.1.3)
|
stoplight (2.1.3)
|
||||||
streamio-ffmpeg (3.0.2)
|
streamio-ffmpeg (3.0.2)
|
||||||
multi_json (~> 1.8)
|
multi_json (~> 1.8)
|
||||||
strong_migrations (0.4.1)
|
strong_migrations (0.4.2)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
temple (0.8.1)
|
temple (0.8.1)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
|
@ -678,10 +678,10 @@ PLATFORMS
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10)
|
active_model_serializers (~> 0.10)
|
||||||
active_record_query_trace (~> 1.6)
|
active_record_query_trace (~> 1.7)
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.7)
|
||||||
annotate (~> 2.7)
|
annotate (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.48)
|
aws-sdk-s3 (~> 1.52)
|
||||||
better_errors (~> 2.5)
|
better_errors (~> 2.5)
|
||||||
binding_of_caller (~> 0.7)
|
binding_of_caller (~> 0.7)
|
||||||
blurhash (~> 0.1)
|
blurhash (~> 0.1)
|
||||||
|
@ -709,7 +709,7 @@ DEPENDENCIES
|
||||||
doorkeeper (~> 5.2)
|
doorkeeper (~> 5.2)
|
||||||
dotenv-rails (~> 2.7)
|
dotenv-rails (~> 2.7)
|
||||||
fabrication (~> 2.20)
|
fabrication (~> 2.20)
|
||||||
faker (~> 2.5)
|
faker (~> 2.6)
|
||||||
fast_blank (~> 1.0)
|
fast_blank (~> 1.0)
|
||||||
fastimage
|
fastimage
|
||||||
fog-core (<= 2.1.0)
|
fog-core (<= 2.1.0)
|
||||||
|
@ -756,7 +756,7 @@ DEPENDENCIES
|
||||||
parslet
|
parslet
|
||||||
pg (~> 1.1)
|
pg (~> 1.1)
|
||||||
pghero (~> 2.3)
|
pghero (~> 2.3)
|
||||||
pkg-config (~> 1.3)
|
pkg-config (~> 1.4)
|
||||||
posix-spawn!
|
posix-spawn!
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
|
@ -775,9 +775,9 @@ DEPENDENCIES
|
||||||
redis-namespace (~> 1.5)
|
redis-namespace (~> 1.5)
|
||||||
redis-rails (~> 5.0)
|
redis-rails (~> 5.0)
|
||||||
rqrcode (~> 0.10)
|
rqrcode (~> 0.10)
|
||||||
rspec-rails (~> 3.8)
|
rspec-rails (~> 3.9)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rubocop (~> 0.74)
|
rubocop (~> 0.75)
|
||||||
rubocop-rails (~> 2.3)
|
rubocop-rails (~> 2.3)
|
||||||
ruby-progressbar (~> 1.10)
|
ruby-progressbar (~> 1.10)
|
||||||
sanitize (~> 5.1)
|
sanitize (~> 5.1)
|
||||||
|
@ -786,7 +786,7 @@ DEPENDENCIES
|
||||||
sidekiq-scheduler (~> 3.0)
|
sidekiq-scheduler (~> 3.0)
|
||||||
sidekiq-unique-jobs (~> 6.0)
|
sidekiq-unique-jobs (~> 6.0)
|
||||||
simple-navigation (~> 4.1)
|
simple-navigation (~> 4.1)
|
||||||
simple_form (~> 4.1)
|
simple_form (~> 5.0)
|
||||||
simplecov (~> 0.17)
|
simplecov (~> 0.17)
|
||||||
sprockets-rails (~> 3.2)
|
sprockets-rails (~> 3.2)
|
||||||
stackprof
|
stackprof
|
||||||
|
|
|
@ -78,7 +78,7 @@ A **Vagrant** configuration is included for development purposes.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Mastodon is **free, open source software** licensed under **AGPLv3**.
|
Mastodon is **free, open-source software** licensed under **AGPLv3**.
|
||||||
|
|
||||||
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
|
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
|
||||||
|
|
||||||
before_action :skip_unknown_actor_delete
|
before_action :skip_unknown_actor_delete
|
||||||
before_action :require_signature!
|
before_action :require_signature!
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
|
||||||
def create
|
def create
|
||||||
upgrade_account
|
upgrade_account
|
||||||
|
|
|
@ -57,6 +57,7 @@ class Settings::PreferencesController < Settings::BaseController
|
||||||
:setting_use_blurhash,
|
:setting_use_blurhash,
|
||||||
:setting_use_pending_items,
|
:setting_use_pending_items,
|
||||||
:setting_trends,
|
:setting_trends,
|
||||||
|
:setting_crop_images,
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
|
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
|
||||||
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module AccountsHelper
|
||||||
|
def display_name(account, **options)
|
||||||
|
if options[:custom_emojify]
|
||||||
|
Formatter.instance.format_display_name(account, options)
|
||||||
|
else
|
||||||
|
account.display_name.presence || account.username
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def acct(account)
|
||||||
|
if account.local?
|
||||||
|
"@#{account.acct}@#{Rails.configuration.x.local_domain}"
|
||||||
|
else
|
||||||
|
"@#{account.acct}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_action_button(account)
|
||||||
|
if user_signed_in?
|
||||||
|
if account.id == current_user.account_id
|
||||||
|
link_to settings_profile_url, class: 'button logo-button' do
|
||||||
|
safe_join([svg_logo, t('settings.edit_profile')])
|
||||||
|
end
|
||||||
|
elsif current_account.following?(account) || current_account.requested?(account)
|
||||||
|
link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do
|
||||||
|
safe_join([svg_logo, t('accounts.unfollow')])
|
||||||
|
end
|
||||||
|
elsif !(account.memorial? || account.moved?)
|
||||||
|
link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
|
||||||
|
safe_join([svg_logo, t('accounts.follow')])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elsif !(account.memorial? || account.moved?)
|
||||||
|
link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do
|
||||||
|
safe_join([svg_logo, t('accounts.follow')])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def minimal_account_action_button(account)
|
||||||
|
if user_signed_in?
|
||||||
|
return if account.id == current_user.account_id
|
||||||
|
|
||||||
|
if current_account.following?(account) || current_account.requested?(account)
|
||||||
|
link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do
|
||||||
|
fa_icon('user-times fw')
|
||||||
|
end
|
||||||
|
elsif !(account.memorial? || account.moved?)
|
||||||
|
link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do
|
||||||
|
fa_icon('user-plus fw')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elsif !(account.memorial? || account.moved?)
|
||||||
|
link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do
|
||||||
|
fa_icon('user-plus fw')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_badge(account, all: false)
|
||||||
|
if account.bot?
|
||||||
|
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
|
||||||
|
elsif (Setting.show_staff_badge && account.user_staff?) || all
|
||||||
|
content_tag(:div, class: 'roles') do
|
||||||
|
if all && !account.user_staff?
|
||||||
|
content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role')
|
||||||
|
elsif account.user_admin?
|
||||||
|
content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin')
|
||||||
|
elsif account.user_moderator?
|
||||||
|
content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_description(account)
|
||||||
|
prepend_str = [
|
||||||
|
[
|
||||||
|
number_to_human(account.statuses_count, strip_insignificant_zeros: true),
|
||||||
|
I18n.t('accounts.posts', count: account.statuses_count),
|
||||||
|
].join(' '),
|
||||||
|
|
||||||
|
[
|
||||||
|
number_to_human(account.following_count, strip_insignificant_zeros: true),
|
||||||
|
I18n.t('accounts.following', count: account.following_count),
|
||||||
|
].join(' '),
|
||||||
|
|
||||||
|
[
|
||||||
|
number_to_human(account.followers_count, strip_insignificant_zeros: true),
|
||||||
|
I18n.t('accounts.followers', count: account.followers_count),
|
||||||
|
].join(' '),
|
||||||
|
].join(', ')
|
||||||
|
|
||||||
|
[prepend_str, account.note].join(' · ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def svg_logo
|
||||||
|
content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976')
|
||||||
|
end
|
||||||
|
|
||||||
|
def svg_logo_full
|
||||||
|
content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo-full'), 'viewBox' => '0 0 713.35878 175.8678')
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,80 +4,6 @@ module StatusesHelper
|
||||||
EMBEDDED_CONTROLLER = 'statuses'
|
EMBEDDED_CONTROLLER = 'statuses'
|
||||||
EMBEDDED_ACTION = 'embed'
|
EMBEDDED_ACTION = 'embed'
|
||||||
|
|
||||||
def display_name(account, **options)
|
|
||||||
if options[:custom_emojify]
|
|
||||||
Formatter.instance.format_display_name(account, options)
|
|
||||||
else
|
|
||||||
account.display_name.presence || account.username
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_action_button(account)
|
|
||||||
if user_signed_in?
|
|
||||||
if account.id == current_user.account_id
|
|
||||||
link_to settings_profile_url, class: 'button logo-button' do
|
|
||||||
safe_join([svg_logo, t('settings.edit_profile')])
|
|
||||||
end
|
|
||||||
elsif current_account.following?(account) || current_account.requested?(account)
|
|
||||||
link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do
|
|
||||||
safe_join([svg_logo, t('accounts.unfollow')])
|
|
||||||
end
|
|
||||||
elsif !(account.memorial? || account.moved?)
|
|
||||||
link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
|
|
||||||
safe_join([svg_logo, t('accounts.follow')])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elsif !(account.memorial? || account.moved?)
|
|
||||||
link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do
|
|
||||||
safe_join([svg_logo, t('accounts.follow')])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def minimal_account_action_button(account)
|
|
||||||
if user_signed_in?
|
|
||||||
return if account.id == current_user.account_id
|
|
||||||
|
|
||||||
if current_account.following?(account) || current_account.requested?(account)
|
|
||||||
link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do
|
|
||||||
fa_icon('user-times fw')
|
|
||||||
end
|
|
||||||
elsif !(account.memorial? || account.moved?)
|
|
||||||
link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do
|
|
||||||
fa_icon('user-plus fw')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elsif !(account.memorial? || account.moved?)
|
|
||||||
link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do
|
|
||||||
fa_icon('user-plus fw')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def svg_logo
|
|
||||||
content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976')
|
|
||||||
end
|
|
||||||
|
|
||||||
def svg_logo_full
|
|
||||||
content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo-full'), 'viewBox' => '0 0 713.35878 175.8678')
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_badge(account, all: false)
|
|
||||||
if account.bot?
|
|
||||||
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
|
|
||||||
elsif (Setting.show_staff_badge && account.user_staff?) || all
|
|
||||||
content_tag(:div, class: 'roles') do
|
|
||||||
if all && !account.user_staff?
|
|
||||||
content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role')
|
|
||||||
elsif account.user_admin?
|
|
||||||
content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin')
|
|
||||||
elsif account.user_moderator?
|
|
||||||
content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def link_to_more(url)
|
def link_to_more(url)
|
||||||
link_to t('statuses.show_more'), url, class: 'load-more load-gap'
|
link_to t('statuses.show_more'), url, class: 'load-more load-gap'
|
||||||
end
|
end
|
||||||
|
@ -88,27 +14,6 @@ module StatusesHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_description(account)
|
|
||||||
prepend_str = [
|
|
||||||
[
|
|
||||||
number_to_human(account.statuses_count, strip_insignificant_zeros: true),
|
|
||||||
I18n.t('accounts.posts', count: account.statuses_count),
|
|
||||||
].join(' '),
|
|
||||||
|
|
||||||
[
|
|
||||||
number_to_human(account.following_count, strip_insignificant_zeros: true),
|
|
||||||
I18n.t('accounts.following', count: account.following_count),
|
|
||||||
].join(' '),
|
|
||||||
|
|
||||||
[
|
|
||||||
number_to_human(account.followers_count, strip_insignificant_zeros: true),
|
|
||||||
I18n.t('accounts.followers', count: account.followers_count),
|
|
||||||
].join(' '),
|
|
||||||
].join(', ')
|
|
||||||
|
|
||||||
[prepend_str, account.note].join(' · ')
|
|
||||||
end
|
|
||||||
|
|
||||||
def media_summary(status)
|
def media_summary(status)
|
||||||
attachments = { image: 0, video: 0 }
|
attachments = { image: 0, video: 0 }
|
||||||
|
|
||||||
|
@ -154,14 +59,6 @@ module StatusesHelper
|
||||||
embedded_view? ? '_blank' : nil
|
embedded_view? ? '_blank' : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def acct(account)
|
|
||||||
if account.local?
|
|
||||||
"@#{account.acct}@#{Rails.configuration.x.local_domain}"
|
|
||||||
else
|
|
||||||
"@#{account.acct}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def style_classes(status, is_predecessor, is_successor, include_threads)
|
def style_classes(status, is_predecessor, is_successor, include_threads)
|
||||||
classes = ['entry']
|
classes = ['entry']
|
||||||
classes << 'entry-predecessor' if is_predecessor
|
classes << 'entry-predecessor' if is_predecessor
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={attachment.get('id')}>
|
<li key={attachment.get('id')}>
|
||||||
<a href={displayUrl} target='_blank' rel='noopener'><Icon id='link' /> {filename(displayUrl)}</a>
|
<a href={displayUrl} target='_blank' rel='noopener noreferrer'><Icon id='link' /> {filename(displayUrl)}</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -46,7 +46,7 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={attachment.get('id')}>
|
<li key={attachment.get('id')}>
|
||||||
<a href={displayUrl} target='_blank' rel='noopener'>{filename(displayUrl)}</a>
|
<a href={displayUrl} target='_blank' rel='noopener noreferrer'>{filename(displayUrl)}</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -143,7 +143,7 @@ class DropdownMenu extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
||||||
<a href={href} target={target} data-method={method} rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||||
{text}
|
{text}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
<div>
|
<div>
|
||||||
<p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
|
<p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
|
||||||
<p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
|
<p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
|
||||||
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
|
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Motion from '../features/ui/util/optional_motion';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
@ -37,6 +35,21 @@ export default class IconButton extends React.PureComponent {
|
||||||
tabIndex: '0',
|
tabIndex: '0',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
activate: false,
|
||||||
|
deactivate: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (!nextProps.animate) return;
|
||||||
|
|
||||||
|
if (this.props.active && !nextProps.active) {
|
||||||
|
this.setState({ activate: false, deactivate: true });
|
||||||
|
} else if (!this.props.active && nextProps.active) {
|
||||||
|
this.setState({ activate: true, deactivate: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleClick = (e) => {
|
handleClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -75,7 +88,6 @@ export default class IconButton extends React.PureComponent {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
active,
|
active,
|
||||||
animate,
|
|
||||||
className,
|
className,
|
||||||
disabled,
|
disabled,
|
||||||
expanded,
|
expanded,
|
||||||
|
@ -87,16 +99,20 @@ export default class IconButton extends React.PureComponent {
|
||||||
title,
|
title,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
activate,
|
||||||
|
deactivate,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const classes = classNames(className, 'icon-button', {
|
const classes = classNames(className, 'icon-button', {
|
||||||
active,
|
active,
|
||||||
disabled,
|
disabled,
|
||||||
inverted,
|
inverted,
|
||||||
|
activate,
|
||||||
|
deactivate,
|
||||||
overlayed: overlay,
|
overlayed: overlay,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!animate) {
|
|
||||||
// Perf optimization: avoid unnecessary <Motion> components unless
|
|
||||||
// we actually need to animate.
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
aria-label={title}
|
aria-label={title}
|
||||||
|
@ -117,28 +133,4 @@ export default class IconButton extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<Motion defaultStyle={{ rotate: active ? -360 : 0 }} style={{ rotate: animate ? spring(active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}>
|
|
||||||
{({ rotate }) => (
|
|
||||||
<button
|
|
||||||
aria-label={title}
|
|
||||||
aria-pressed={pressed}
|
|
||||||
aria-expanded={expanded}
|
|
||||||
title={title}
|
|
||||||
className={classes}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
onKeyPress={this.handleKeyPress}
|
|
||||||
style={style}
|
|
||||||
tabIndex={tabIndex}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
<Icon id={icon} style={{ transform: `rotate(${rotate}deg)` }} fixedWidth aria-hidden='true' />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import IconButton from './icon_button';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { isIOS } from '../is_mobile';
|
import { isIOS } from '../is_mobile';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
|
import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state';
|
||||||
import { decode } from 'blurhash';
|
import { decode } from 'blurhash';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -159,7 +159,7 @@ class Item extends React.PureComponent {
|
||||||
if (attachment.get('type') === 'unknown') {
|
if (attachment.get('type') === 'unknown') {
|
||||||
return (
|
return (
|
||||||
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}>
|
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
|
||||||
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
|
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -187,6 +187,7 @@ class Item extends React.PureComponent {
|
||||||
href={attachment.get('remote_url') || originalUrl}
|
href={attachment.get('remote_url') || originalUrl}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={previewUrl}
|
src={previewUrl}
|
||||||
|
@ -280,7 +281,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRef = (node) => {
|
handleRef = (node) => {
|
||||||
if (node /*&& this.isStandaloneEligible()*/) {
|
if (node) {
|
||||||
// offsetWidth triggers a layout, so only calculate when we need to
|
// offsetWidth triggers a layout, so only calculate when we need to
|
||||||
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
|
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
|
||||||
|
|
||||||
|
@ -290,13 +291,13 @@ class MediaGallery extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isStandaloneEligible() {
|
isFullSizeEligible() {
|
||||||
const { media, standalone } = this.props;
|
const { media } = this.props;
|
||||||
return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
|
return media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, intl, sensitive, height, defaultWidth } = this.props;
|
const { media, intl, sensitive, height, defaultWidth, standalone } = this.props;
|
||||||
const { visible } = this.state;
|
const { visible } = this.state;
|
||||||
|
|
||||||
const width = this.state.width || defaultWidth;
|
const width = this.state.width || defaultWidth;
|
||||||
|
@ -305,7 +306,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
|
|
||||||
const style = {};
|
const style = {};
|
||||||
|
|
||||||
if (this.isStandaloneEligible()) {
|
if (this.isFullSizeEligible() && (standalone || !cropImages)) {
|
||||||
if (width) {
|
if (width) {
|
||||||
style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
|
style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
|
||||||
}
|
}
|
||||||
|
@ -318,7 +319,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
const size = media.take(4).size;
|
const size = media.take(4).size;
|
||||||
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
||||||
|
|
||||||
if (this.isStandaloneEligible()) {
|
if (standalone && this.isFullSizeEligible()) {
|
||||||
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
|
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
|
||||||
} else {
|
} else {
|
||||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} />);
|
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} />);
|
||||||
|
|
|
@ -39,7 +39,8 @@ class Poll extends ImmutablePureComponent {
|
||||||
|
|
||||||
static getDerivedStateFromProps (props, state) {
|
static getDerivedStateFromProps (props, state) {
|
||||||
const { poll, intl } = props;
|
const { poll, intl } = props;
|
||||||
const expired = poll.get('expired') || (new Date(poll.get('expires_at'))).getTime() < intl.now();
|
const expires_at = poll.get('expires_at');
|
||||||
|
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < intl.now();
|
||||||
return (expired === state.expired) ? null : { expired };
|
return (expired === state.expired) ? null : { expired };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -437,9 +437,9 @@ class Status extends ImmutablePureComponent {
|
||||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
||||||
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
|
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
|
||||||
<div className='status__info'>
|
<div className='status__info'>
|
||||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||||
|
|
||||||
<a onClick={this.handleAccountClick} target='_blank' data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name'>
|
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||||
<div className='status__avatar'>
|
<div className='status__avatar'>
|
||||||
{statusAvatar}
|
{statusAvatar}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default class StatusContent extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
link.setAttribute('target', '_blank');
|
link.setAttribute('target', '_blank');
|
||||||
link.setAttribute('rel', 'noopener');
|
link.setAttribute('rel', 'noopener noreferrer');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -253,7 +253,7 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='account__header__bar'>
|
<div className='account__header__bar'>
|
||||||
<div className='account__header__tabs'>
|
<div className='account__header__tabs'>
|
||||||
<a className='avatar' href={account.get('url')} rel='noopener' target='_blank'>
|
<a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
|
||||||
<Avatar account={account} size={90} />
|
<Avatar account={account} size={90} />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -282,10 +282,10 @@ class Header extends ImmutablePureComponent {
|
||||||
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
|
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
|
||||||
|
|
||||||
<dd className='verified'>
|
<dd className='verified'>
|
||||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
<a href={proof.get('proof_url')} target='_blank' rel='noopener noreferrer'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||||
<Icon id='check' className='verified__mark' />
|
<Icon id='check' className='verified__mark' />
|
||||||
</span></a>
|
</span></a>
|
||||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} /></a>
|
<a href={proof.get('profile_url')} target='_blank' rel='noopener noreferrer'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} /></a>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react';
|
import { decode } from 'blurhash';
|
||||||
import PropTypes from 'prop-types';
|
import classNames from 'classnames';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
import { autoPlayGif, displayMedia } from 'mastodon/initial_state';
|
import { autoPlayGif, displayMedia } from 'mastodon/initial_state';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { decode } from 'blurhash';
|
|
||||||
import { isIOS } from 'mastodon/is_mobile';
|
import { isIOS } from 'mastodon/is_mobile';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
export default class MediaItem extends ImmutablePureComponent {
|
export default class MediaItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='account-gallery__item' style={{ width, height }}>
|
<div className='account-gallery__item' style={{ width, height }}>
|
||||||
<a className='media-gallery__item-thumbnail' href={status.get('url')} target='_blank' onClick={this.handleClick} title={title}>
|
<a className='media-gallery__item-thumbnail' href={status.get('url')} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
|
||||||
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} />
|
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} />
|
||||||
{visible && thumbnail}
|
{visible && thumbnail}
|
||||||
{!visible && icon}
|
{!visible && icon}
|
||||||
|
|
|
@ -12,6 +12,7 @@ const messages = defineMessages({
|
||||||
pause: { id: 'video.pause', defaultMessage: 'Pause' },
|
pause: { id: 'video.pause', defaultMessage: 'Pause' },
|
||||||
mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
|
mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
|
||||||
unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
|
unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
|
||||||
|
download: { id: 'video.download', defaultMessage: 'Download file' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @injectIntl
|
export default @injectIntl
|
||||||
|
@ -202,6 +203,7 @@ class Audio extends React.PureComponent {
|
||||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
|
|
||||||
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
||||||
|
|
||||||
<span
|
<span
|
||||||
|
@ -217,6 +219,14 @@ class Audio extends React.PureComponent {
|
||||||
<span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span>
|
<span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='video-player__buttons right'>
|
||||||
|
<button type='button' aria-label={intl.formatMessage(messages.download)}>
|
||||||
|
<a className='video-player__download__icon' href={this.props.src} download>
|
||||||
|
<Icon id={'download'} fixedWidth />
|
||||||
|
</a>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import Toggle from 'react-toggle';
|
import Toggle from 'react-toggle';
|
||||||
import AsyncSelect from 'react-select/lib/Async';
|
import AsyncSelect from 'react-select/async';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
|
placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import StatusContainer from '../../../containers/status_container';
|
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||||
import AccountContainer from '../../../containers/account_container';
|
|
||||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import Permalink from '../../../components/permalink';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { me } from 'mastodon/initial_state';
|
||||||
|
import StatusContainer from 'mastodon/containers/status_container';
|
||||||
|
import AccountContainer from 'mastodon/containers/account_container';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
import Permalink from 'mastodon/components/permalink';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
favourite: { id: 'notification.favourite', defaultMessage: '{name} favourited your status' },
|
||||||
|
follow: { id: 'notification.follow', defaultMessage: '{name} followed you' },
|
||||||
|
ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' },
|
||||||
|
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
|
||||||
|
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
|
||||||
|
});
|
||||||
|
|
||||||
const notificationForScreenReader = (intl, message, timestamp) => {
|
const notificationForScreenReader = (intl, message, timestamp) => {
|
||||||
const output = [message];
|
const output = [message];
|
||||||
|
@ -107,7 +116,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={this.getHandlers()}>
|
<HotKeys handlers={this.getHandlers()}>
|
||||||
<div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow', defaultMessage: '{name} followed you' }, { name: account.get('acct') }), notification.get('created_at'))}>
|
<div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}>
|
||||||
<div className='notification__message'>
|
<div className='notification__message'>
|
||||||
<div className='notification__favourite-icon-wrapper'>
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
<Icon id='user-plus' fixedWidth />
|
<Icon id='user-plus' fixedWidth />
|
||||||
|
@ -146,7 +155,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={this.getHandlers()}>
|
<HotKeys handlers={this.getHandlers()}>
|
||||||
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favourite', defaultMessage: '{name} favourited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||||
<div className='notification__message'>
|
<div className='notification__message'>
|
||||||
<div className='notification__favourite-icon-wrapper'>
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
<Icon id='star' className='star-icon' fixedWidth />
|
<Icon id='star' className='star-icon' fixedWidth />
|
||||||
|
@ -178,7 +187,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={this.getHandlers()}>
|
<HotKeys handlers={this.getHandlers()}>
|
||||||
<div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.reblog', defaultMessage: '{name} boosted your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
<div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||||
<div className='notification__message'>
|
<div className='notification__message'>
|
||||||
<div className='notification__favourite-icon-wrapper'>
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
<Icon id='retweet' fixedWidth />
|
<Icon id='retweet' fixedWidth />
|
||||||
|
@ -205,25 +214,31 @@ class Notification extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPoll (notification) {
|
renderPoll (notification, account) {
|
||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
|
const ownPoll = me === account.get('id');
|
||||||
|
const message = ownPoll ? intl.formatMessage(messages.ownPoll) : intl.formatMessage(messages.poll);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={this.getHandlers()}>
|
<HotKeys handlers={this.getHandlers()}>
|
||||||
<div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }), notification.get('created_at'))}>
|
<div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
|
||||||
<div className='notification__message'>
|
<div className='notification__message'>
|
||||||
<div className='notification__favourite-icon-wrapper'>
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
<Icon id='tasks' fixedWidth />
|
<Icon id='tasks' fixedWidth />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span title={notification.get('created_at')}>
|
<span title={notification.get('created_at')}>
|
||||||
|
{ownPoll ? (
|
||||||
|
<FormattedMessage id='notification.ownPoll' defaultMessage='Your poll has ended' />
|
||||||
|
) : (
|
||||||
<FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' />
|
<FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer
|
<StatusContainer
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
account={notification.get('account')}
|
account={account}
|
||||||
muted
|
muted
|
||||||
withDismiss
|
withDismiss
|
||||||
hidden={this.props.hidden}
|
hidden={this.props.hidden}
|
||||||
|
@ -253,7 +268,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
case 'reblog':
|
case 'reblog':
|
||||||
return this.renderReblog(notification, link);
|
return this.renderReblog(notification, link);
|
||||||
case 'poll':
|
case 'poll':
|
||||||
return this.renderPoll(notification);
|
return this.renderPoll(notification, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -148,7 +148,7 @@ export default class Card extends React.PureComponent {
|
||||||
const horizontal = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
|
const horizontal = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
|
||||||
const interactive = card.get('type') !== 'link';
|
const interactive = card.get('type') !== 'link';
|
||||||
const className = classnames('status-card', { horizontal, compact, interactive });
|
const className = classnames('status-card', { horizontal, compact, interactive });
|
||||||
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
|
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
|
||||||
const ratio = card.get('width') / card.get('height');
|
const ratio = card.get('width') / card.get('height');
|
||||||
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
|
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ export default class Card extends React.PureComponent {
|
||||||
<div className='status-card__actions'>
|
<div className='status-card__actions'>
|
||||||
<div>
|
<div>
|
||||||
<button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
|
<button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
|
||||||
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><Icon id='external-link' /></a>}
|
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -208,7 +208,7 @@ export default class Card extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
|
<a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
|
||||||
{embed}
|
{embed}
|
||||||
{description}
|
{description}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -156,7 +156,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('application')) {
|
if (status.get('application')) {
|
||||||
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
|
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('visibility') === 'direct') {
|
if (status.get('visibility') === 'direct') {
|
||||||
|
@ -220,7 +220,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
{media}
|
{media}
|
||||||
|
|
||||||
<div className='detailed-status__meta'>
|
<div className='detailed-status__meta'>
|
||||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'>
|
||||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||||
</a>{applicationLink} · {reblogLink} · {favouriteLink}
|
</a>{applicationLink} · {reblogLink} · {favouriteLink}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default class ActionsModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={`${text}-${i}`}>
|
<li key={`${text}-${i}`}>
|
||||||
<a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
|
<a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
|
||||||
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />}
|
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />}
|
||||||
<div>
|
<div>
|
||||||
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
|
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
|
||||||
|
@ -42,7 +42,7 @@ export default class ActionsModal extends ImmutablePureComponent {
|
||||||
<div className='status light'>
|
<div className='status light'>
|
||||||
<div className='boost-modal__status-header'>
|
<div className='boost-modal__status-header'>
|
||||||
<div className='boost-modal__status-time'>
|
<div className='boost-modal__status-time'>
|
||||||
<a href={this.props.status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>
|
<a href={this.props.status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||||
<RelativeTimestamp timestamp={this.props.status.get('created_at')} />
|
<RelativeTimestamp timestamp={this.props.status.get('created_at')} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -61,7 +61,7 @@ class BoostModal extends ImmutablePureComponent {
|
||||||
<div className='status light'>
|
<div className='status light'>
|
||||||
<div className='boost-modal__status-header'>
|
<div className='boost-modal__status-header'>
|
||||||
<div className='boost-modal__status-time'>
|
<div className='boost-modal__status-time'>
|
||||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
|
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
|
||||||
|
|
|
@ -182,7 +182,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||||
|
|
||||||
const content = columnIndex !== -1 ? (
|
const content = columnIndex !== -1 ? (
|
||||||
<ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
|
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
|
||||||
{links.map(this.renderView)}
|
{links.map(this.renderView)}
|
||||||
</ReactSwipeableViews>
|
</ReactSwipeableViews>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -62,7 +62,7 @@ class LinkFooter extends React.PureComponent {
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='getting_started.open_source_notice'
|
id='getting_started.open_source_notice'
|
||||||
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
|
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
|
||||||
values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
|
values={{ github: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span> }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,7 @@ const messages = defineMessages({
|
||||||
close: { id: 'video.close', defaultMessage: 'Close video' },
|
close: { id: 'video.close', defaultMessage: 'Close video' },
|
||||||
fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
|
fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
|
||||||
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
||||||
|
download: { id: 'video.download', defaultMessage: 'Download file' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const formatTime = secondsNum => {
|
export const formatTime = secondsNum => {
|
||||||
|
@ -470,6 +471,7 @@ class Video extends React.PureComponent {
|
||||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
|
|
||||||
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
||||||
<span
|
<span
|
||||||
className={classNames('video-player__volume__handle')}
|
className={classNames('video-player__volume__handle')}
|
||||||
|
@ -493,7 +495,13 @@ class Video extends React.PureComponent {
|
||||||
{(!onCloseVideo && !editable) && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
{(!onCloseVideo && !editable) && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
||||||
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
||||||
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
||||||
|
<button type='button' aria-label={intl.formatMessage(messages.download)}>
|
||||||
|
<a className='video-player__download__icon' href={this.props.src} download>
|
||||||
|
<Icon id={'download'} fixedWidth />
|
||||||
|
</a>
|
||||||
|
</button>
|
||||||
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,5 +24,6 @@ export const useBlurhash = getMeta('use_blurhash');
|
||||||
export const usePendingItems = getMeta('use_pending_items');
|
export const usePendingItems = getMeta('use_pending_items');
|
||||||
export const showTrends = getMeta('trends');
|
export const showTrends = getMeta('trends');
|
||||||
export const title = getMeta('title');
|
export const title = getMeta('title');
|
||||||
|
export const cropImages = getMeta('crop_images');
|
||||||
|
|
||||||
export default initialState;
|
export default initialState;
|
||||||
|
|
|
@ -776,6 +776,10 @@
|
||||||
{
|
{
|
||||||
"defaultMessage": "Unmute sound",
|
"defaultMessage": "Unmute sound",
|
||||||
"id": "video.unmute"
|
"id": "video.unmute"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Download file",
|
||||||
|
"id": "video.download"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/audio/index.json"
|
"path": "app/javascript/mastodon/features/audio/index.json"
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
REBLOG_FAIL,
|
REBLOG_FAIL,
|
||||||
FAVOURITE_REQUEST,
|
FAVOURITE_REQUEST,
|
||||||
FAVOURITE_FAIL,
|
FAVOURITE_FAIL,
|
||||||
|
UNFAVOURITE_SUCCESS,
|
||||||
} from '../actions/interactions';
|
} from '../actions/interactions';
|
||||||
import {
|
import {
|
||||||
STATUS_MUTE_SUCCESS,
|
STATUS_MUTE_SUCCESS,
|
||||||
|
@ -37,6 +38,9 @@ export default function statuses(state = initialState, action) {
|
||||||
return importStatuses(state, action.statuses);
|
return importStatuses(state, action.statuses);
|
||||||
case FAVOURITE_REQUEST:
|
case FAVOURITE_REQUEST:
|
||||||
return state.setIn([action.status.get('id'), 'favourited'], true);
|
return state.setIn([action.status.get('id'), 'favourited'], true);
|
||||||
|
case UNFAVOURITE_SUCCESS:
|
||||||
|
const favouritesCount = action.status.get('favourites_count');
|
||||||
|
return state.setIn([action.status.get('id'), 'favourites_count'], favouritesCount - 1);
|
||||||
case FAVOURITE_FAIL:
|
case FAVOURITE_FAIL:
|
||||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false);
|
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false);
|
||||||
case REBLOG_REQUEST:
|
case REBLOG_REQUEST:
|
||||||
|
|
|
@ -1591,6 +1591,20 @@ a.account__display-name {
|
||||||
color: $gold-star;
|
color: $gold-star;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-reduce-motion .icon-button.star-icon {
|
||||||
|
&.activate {
|
||||||
|
& > .fa-star {
|
||||||
|
animation: spring-rotate-in 1s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.deactivate {
|
||||||
|
& > .fa-star {
|
||||||
|
animation: spring-rotate-out 1s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notification__display-name {
|
.notification__display-name {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -3373,6 +3387,50 @@ a.status-card.compact:hover {
|
||||||
animation: loader-figure 1.15s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
animation: loader-figure 1.15s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spring-rotate-in {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: rotate(-484.8deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: rotate(-316.7deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
90% {
|
||||||
|
transform: rotate(-375deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spring-rotate-out {
|
||||||
|
0% {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: rotate(124.8deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: rotate(-43.27deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
90% {
|
||||||
|
transform: rotate(15deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes loader-figure {
|
@keyframes loader-figure {
|
||||||
0% {
|
0% {
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -5194,6 +5252,7 @@ a.status-card.compact:hover {
|
||||||
max-height: 100% !important;
|
max-height: 100% !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
outline: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5271,6 +5330,10 @@ a.status-card.compact:hover {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
.video-player__download__icon {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__buttons {
|
&__buttons {
|
||||||
|
|
|
@ -149,10 +149,6 @@ a.table-action-link {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__actions,
|
&__actions,
|
||||||
|
@ -174,10 +170,6 @@ a.table-action-link {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-right: 16px - 5px;
|
padding-right: 16px - 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__form {
|
&__form {
|
||||||
|
@ -198,7 +190,7 @@ a.table-action-link {
|
||||||
background: darken($ui-base-color, 4%);
|
background: darken($ui-base-color, 4%);
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
&:first-child {
|
.optional &:first-child {
|
||||||
border-top: 1px solid darken($ui-base-color, 8%);
|
border-top: 1px solid darken($ui-base-color, 8%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,6 +256,13 @@ a.table-action-link {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.optional .batch-table__toolbar,
|
||||||
|
&.optional .batch-table__row__select {
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status__content {
|
.status__content {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,14 @@ class ActivityPub::Activity
|
||||||
fetch_remote_original_status
|
fetch_remote_original_status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def follow_request_from_object
|
||||||
|
@follow_request ||= FollowRequest.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_from_object
|
||||||
|
@follow ||= Follow.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_remote_original_status
|
def fetch_remote_original_status
|
||||||
if object_uri.start_with?('http')
|
if object_uri.start_with?('http')
|
||||||
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
||||||
|
|
|
@ -2,17 +2,18 @@
|
||||||
|
|
||||||
class ActivityPub::Activity::Accept < ActivityPub::Activity
|
class ActivityPub::Activity::Accept < ActivityPub::Activity
|
||||||
def perform
|
def perform
|
||||||
|
return accept_follow_for_relay if relay_follow?
|
||||||
|
return follow_request_from_object.authorize! unless follow_request_from_object.nil?
|
||||||
|
|
||||||
case @object['type']
|
case @object['type']
|
||||||
when 'Follow'
|
when 'Follow'
|
||||||
accept_follow
|
accept_embedded_follow
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def accept_follow
|
def accept_embedded_follow
|
||||||
return accept_follow_for_relay if relay_follow?
|
|
||||||
|
|
||||||
target_account = account_from_uri(target_uri)
|
target_account = account_from_uri(target_uri)
|
||||||
|
|
||||||
return if target_account.nil? || !target_account.local?
|
return if target_account.nil? || !target_account.local?
|
||||||
|
|
|
@ -2,17 +2,19 @@
|
||||||
|
|
||||||
class ActivityPub::Activity::Reject < ActivityPub::Activity
|
class ActivityPub::Activity::Reject < ActivityPub::Activity
|
||||||
def perform
|
def perform
|
||||||
|
return reject_follow_for_relay if relay_follow?
|
||||||
|
return follow_request_from_object.reject! unless follow_request_from_object.nil?
|
||||||
|
return UnfollowService.new.call(follow_from_object.target_account, @account) unless follow_from_object.nil?
|
||||||
|
|
||||||
case @object['type']
|
case @object['type']
|
||||||
when 'Follow'
|
when 'Follow'
|
||||||
reject_follow
|
reject_embedded_follow
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def reject_follow
|
def reject_embedded_follow
|
||||||
return reject_follow_for_relay if relay_follow?
|
|
||||||
|
|
||||||
target_account = account_from_uri(target_uri)
|
target_account = account_from_uri(target_uri)
|
||||||
|
|
||||||
return if target_account.nil? || !target_account.local?
|
return if target_account.nil? || !target_account.local?
|
||||||
|
|
|
@ -251,7 +251,7 @@ class Formatter
|
||||||
|
|
||||||
def link_to_url(entity, options = {})
|
def link_to_url(entity, options = {})
|
||||||
url = Addressable::URI.parse(entity[:url])
|
url = Addressable::URI.parse(entity[:url])
|
||||||
html_attrs = { target: '_blank', rel: 'nofollow noopener' }
|
html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
|
||||||
|
|
||||||
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
|
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Sanitize
|
||||||
|
|
||||||
add_attributes: {
|
add_attributes: {
|
||||||
'a' => {
|
'a' => {
|
||||||
'rel' => 'nofollow noopener',
|
'rel' => 'nofollow noopener noreferrer',
|
||||||
'target' => '_blank',
|
'target' => '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,6 +37,7 @@ class UserSettingsDecorator
|
||||||
user.settings['use_blurhash'] = use_blurhash_preference if change?('setting_use_blurhash')
|
user.settings['use_blurhash'] = use_blurhash_preference if change?('setting_use_blurhash')
|
||||||
user.settings['use_pending_items'] = use_pending_items_preference if change?('setting_use_pending_items')
|
user.settings['use_pending_items'] = use_pending_items_preference if change?('setting_use_pending_items')
|
||||||
user.settings['trends'] = trends_preference if change?('setting_trends')
|
user.settings['trends'] = trends_preference if change?('setting_trends')
|
||||||
|
user.settings['crop_images'] = crop_images_preference if change?('setting_crop_images')
|
||||||
end
|
end
|
||||||
|
|
||||||
def merged_notification_emails
|
def merged_notification_emails
|
||||||
|
@ -127,6 +128,10 @@ class UserSettingsDecorator
|
||||||
boolean_cast_setting 'setting_trends'
|
boolean_cast_setting 'setting_trends'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def crop_images_preference
|
||||||
|
boolean_cast_setting 'setting_crop_images'
|
||||||
|
end
|
||||||
|
|
||||||
def boolean_cast_setting(key)
|
def boolean_cast_setting(key)
|
||||||
ActiveModel::Type::Boolean.new.cast(settings[key])
|
ActiveModel::Type::Boolean.new.cast(settings[key])
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class AdminMailer < ApplicationMailer
|
class AdminMailer < ApplicationMailer
|
||||||
layout 'plain_mailer'
|
layout 'plain_mailer'
|
||||||
|
|
||||||
helper :statuses
|
helper :accounts
|
||||||
|
|
||||||
def new_report(recipient, report)
|
def new_report(recipient, report)
|
||||||
@report = report
|
@report = report
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class NotificationMailer < ApplicationMailer
|
class NotificationMailer < ApplicationMailer
|
||||||
|
helper :accounts
|
||||||
helper :statuses
|
helper :statuses
|
||||||
|
|
||||||
add_template_helper RoutingHelper
|
add_template_helper RoutingHelper
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
class UserMailer < Devise::Mailer
|
class UserMailer < Devise::Mailer
|
||||||
layout 'mailer'
|
layout 'mailer'
|
||||||
|
|
||||||
|
helper :accounts
|
||||||
helper :application
|
helper :application
|
||||||
helper :instance
|
helper :instance
|
||||||
helper :statuses
|
|
||||||
|
|
||||||
add_template_helper RoutingHelper
|
add_template_helper RoutingHelper
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ class MediaAttachment < ApplicationRecord
|
||||||
convert_options: {
|
convert_options: {
|
||||||
output: {
|
output: {
|
||||||
'loglevel' => 'fatal',
|
'loglevel' => 'fatal',
|
||||||
|
'map_metadata' => '-1',
|
||||||
'q:a' => 2,
|
'q:a' => 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -108,7 +108,7 @@ class User < ApplicationRecord
|
||||||
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
|
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
|
||||||
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
|
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
|
||||||
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
||||||
:advanced_layout, :use_blurhash, :use_pending_items, :trends,
|
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
|
||||||
to: :settings, prefix: :setting, allow_nil: false
|
to: :settings, prefix: :setting, allow_nil: false
|
||||||
|
|
||||||
attr_reader :invite_code
|
attr_reader :invite_code
|
||||||
|
|
|
@ -38,11 +38,13 @@ class InitialStateSerializer < ActiveModel::Serializer
|
||||||
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
|
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
|
||||||
store[:is_staff] = object.current_account.user.staff?
|
store[:is_staff] = object.current_account.user.staff?
|
||||||
store[:trends] = Setting.trends && object.current_account.user.setting_trends
|
store[:trends] = Setting.trends && object.current_account.user.setting_trends
|
||||||
|
store[:crop_images] = object.current_account.user.setting_crop_images
|
||||||
else
|
else
|
||||||
store[:auto_play_gif] = Setting.auto_play_gif
|
store[:auto_play_gif] = Setting.auto_play_gif
|
||||||
store[:display_media] = Setting.display_media
|
store[:display_media] = Setting.display_media
|
||||||
store[:reduce_motion] = Setting.reduce_motion
|
store[:reduce_motion] = Setting.reduce_motion
|
||||||
store[:use_blurhash] = Setting.use_blurhash
|
store[:use_blurhash] = Setting.use_blurhash
|
||||||
|
store[:crop_images] = Setting.crop_images
|
||||||
end
|
end
|
||||||
|
|
||||||
store
|
store
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class RSS::AccountSerializer
|
class RSS::AccountSerializer
|
||||||
include ActionView::Helpers::NumberHelper
|
include ActionView::Helpers::NumberHelper
|
||||||
include StatusesHelper
|
include AccountsHelper
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
def render(account, statuses, tag)
|
def render(account, statuses, tag)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
class RSS::TagSerializer
|
class RSS::TagSerializer
|
||||||
include ActionView::Helpers::NumberHelper
|
include ActionView::Helpers::NumberHelper
|
||||||
include ActionView::Helpers::SanitizeHelper
|
include ActionView::Helpers::SanitizeHelper
|
||||||
include StatusesHelper
|
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
def render(tag, statuses)
|
def render(tag, statuses)
|
||||||
|
|
|
@ -143,6 +143,7 @@ class BackupService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
|
rescue Seahorse::Client::NetworkingError
|
||||||
Rails.logger.warn "Could not backup file #{filename}: file not found"
|
Rails.logger.warn "Could not backup file #{filename}: file not found"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -84,7 +84,7 @@ class FetchLinkCardService < BaseService
|
||||||
|
|
||||||
def skip_link?(a)
|
def skip_link?(a)
|
||||||
# Avoid links for hashtags and mentions (microformats)
|
# Avoid links for hashtags and mentions (microformats)
|
||||||
a['rel']&.include?('tag') || a['class']&.include?('u-url') || mention_link?(a)
|
a['rel']&.include?('tag') || a['class']&.match?(/u-url|h-card/) || mention_link?(a)
|
||||||
end
|
end
|
||||||
|
|
||||||
def attempt_oembed
|
def attempt_oembed
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
%small= t('about.browse_public_posts')
|
%small= t('about.browse_public_posts')
|
||||||
|
|
||||||
.directory__tag
|
.directory__tag
|
||||||
= link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener' do
|
= link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener noreferrer' do
|
||||||
%h4
|
%h4
|
||||||
= fa_icon 'tablet fw'
|
= fa_icon 'tablet fw'
|
||||||
= t('about.get_apps')
|
= t('about.get_apps')
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
= t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
|
= t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
|
||||||
|
|
||||||
.moved-account-widget__card
|
.moved-account-widget__card
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener' do
|
= link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do
|
||||||
.detailed-status__display-avatar
|
.detailed-status__display-avatar
|
||||||
.account__avatar-overlay
|
.account__avatar-overlay
|
||||||
.account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" }
|
.account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" }
|
||||||
|
|
|
@ -143,12 +143,15 @@
|
||||||
%th= t('admin.accounts.most_recent_ip')
|
%th= t('admin.accounts.most_recent_ip')
|
||||||
%td= @account.user_current_sign_in_ip
|
%td= @account.user_current_sign_in_ip
|
||||||
%td
|
%td
|
||||||
|
- if @account.user_current_sign_in_ip
|
||||||
|
= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: @account.user_current_sign_in_ip)
|
||||||
|
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.accounts.most_recent_activity')
|
%th= t('admin.accounts.most_recent_activity')
|
||||||
%td
|
%td
|
||||||
- if @account.user_current_sign_in_at
|
- if @account.user_current_sign_in_at
|
||||||
%time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }= l @account.user_current_sign_in_at
|
%time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }= l @account.user_current_sign_in_at
|
||||||
|
%td
|
||||||
|
|
||||||
- if @account.user&.invited?
|
- if @account.user&.invited?
|
||||||
%tr
|
%tr
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.proper.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.proper.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
||||||
|
|
||||||
.detailed-status__meta
|
.detailed-status__meta
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
|
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||||
- if status.discarded?
|
- if status.discarded?
|
||||||
·
|
·
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
- Admin::FilterHelper::TAGS_FILTERS.each do |key|
|
- Admin::FilterHelper::TAGS_FILTERS.each do |key|
|
||||||
= hidden_field_tag key, params[key] if params[key].present?
|
= hidden_field_tag key, params[key] if params[key].present?
|
||||||
|
|
||||||
.batch-table
|
.batch-table.optional
|
||||||
.batch-table__toolbar
|
.batch-table__toolbar
|
||||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||||
= check_box_tag :batch_checkbox_all, nil, false
|
= check_box_tag :batch_checkbox_all, nil, false
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
.dashboard__counters
|
.dashboard__counters
|
||||||
%div
|
%div
|
||||||
= link_to tag_url(@tag), target: '_blank', rel: 'noopener' do
|
= link_to tag_url(@tag), target: '_blank', rel: 'noopener noreferrer' do
|
||||||
.dashboard__counters__num= number_with_delimiter @accounts_today
|
.dashboard__counters__num= number_with_delimiter @accounts_today
|
||||||
.dashboard__counters__label= t 'admin.tags.accounts_today'
|
.dashboard__counters__label= t 'admin.tags.accounts_today'
|
||||||
%div
|
%div
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account)
|
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account)
|
||||||
|
|
||||||
.card.h-card
|
.card.h-card
|
||||||
= link_to account_url, target: '_blank', rel: 'noopener' do
|
= link_to account_url, target: '_blank', rel: 'noopener noreferrer' do
|
||||||
.card__img
|
.card__img
|
||||||
= image_tag account.header.url, alt: ''
|
= image_tag account.header.url, alt: ''
|
||||||
.card__bar
|
.card__bar
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
- if application.website.blank?
|
- if application.website.blank?
|
||||||
= application.name
|
= application.name
|
||||||
- else
|
- else
|
||||||
= link_to application.name, application.website, target: '_blank', rel: 'noopener'
|
= link_to application.name, application.website, target: '_blank', rel: 'noopener noreferrer'
|
||||||
%th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join(', ')
|
%th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join(', ')
|
||||||
%td= l application.created_at
|
%td= l application.created_at
|
||||||
%td
|
%td
|
||||||
|
|
|
@ -7,7 +7,11 @@
|
||||||
|
|
||||||
.page-header
|
.page-header
|
||||||
%h1= t('about.see_whats_happening')
|
%h1= t('about.see_whats_happening')
|
||||||
|
|
||||||
|
- if Setting.show_known_fediverse_at_about_page
|
||||||
%p= t('about.browse_public_posts')
|
%p= t('about.browse_public_posts')
|
||||||
|
- else
|
||||||
|
%p= t('about.browse_local_posts')
|
||||||
|
|
||||||
#mastodon-timeline{ data: { props: Oj.dump(default_props) }}
|
#mastodon-timeline{ data: { props: Oj.dump(default_props) }}
|
||||||
#modal-container
|
#modal-container
|
||||||
|
|
|
@ -25,6 +25,11 @@
|
||||||
= f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
|
= f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
|
||||||
= f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
|
= f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
|
%h4= t 'appearance.toot_layout'
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :setting_crop_images, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
%h4= t 'appearance.discovery'
|
%h4= t 'appearance.discovery'
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
|
|
|
@ -44,14 +44,14 @@
|
||||||
.detailed-status__meta
|
.detailed-status__meta
|
||||||
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
||||||
|
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
|
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||||
·
|
·
|
||||||
- if status.application && @account.user&.setting_show_application
|
- if status.application && @account.user&.setting_show_application
|
||||||
- if status.application.website.blank?
|
- if status.application.website.blank?
|
||||||
%strong.detailed-status__application= status.application.name
|
%strong.detailed-status__application= status.application.name
|
||||||
- else
|
- else
|
||||||
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener'
|
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener noreferrer'
|
||||||
·
|
·
|
||||||
= link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do
|
= link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do
|
||||||
- if status.in_reply_to_id.nil?
|
- if status.in_reply_to_id.nil?
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
.status
|
.status
|
||||||
.status__info
|
.status__info
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener' do
|
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||||
%time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
%time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||||
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
||||||
|
|
||||||
.p-author.h-card
|
.p-author.h-card
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener' do
|
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||||
.status__avatar
|
.status__avatar
|
||||||
%div
|
%div
|
||||||
- if current_account&.user&.setting_auto_play_gif || autoplay
|
- if current_account&.user&.setting_auto_play_gif || autoplay
|
||||||
|
|
|
@ -11,6 +11,7 @@ en:
|
||||||
apps: Mobile apps
|
apps: Mobile apps
|
||||||
apps_platforms: Use Mastodon from iOS, Android and other platforms
|
apps_platforms: Use Mastodon from iOS, Android and other platforms
|
||||||
browse_directory: Browse a profile directory and filter by interests
|
browse_directory: Browse a profile directory and filter by interests
|
||||||
|
browse_local_posts: Browse a live stream of public posts from this server
|
||||||
browse_public_posts: Browse a live stream of public posts on Mastodon
|
browse_public_posts: Browse a live stream of public posts on Mastodon
|
||||||
contact: Contact
|
contact: Contact
|
||||||
contact_missing: Not set
|
contact_missing: Not set
|
||||||
|
@ -175,6 +176,7 @@ en:
|
||||||
user: User
|
user: User
|
||||||
salmon_url: Salmon URL
|
salmon_url: Salmon URL
|
||||||
search: Search
|
search: Search
|
||||||
|
search_same_ip: Other users with the same IP
|
||||||
shared_inbox_url: Shared inbox URL
|
shared_inbox_url: Shared inbox URL
|
||||||
show:
|
show:
|
||||||
created_reports: Made reports
|
created_reports: Made reports
|
||||||
|
@ -575,6 +577,7 @@ en:
|
||||||
confirmation_dialogs: Confirmation dialogs
|
confirmation_dialogs: Confirmation dialogs
|
||||||
discovery: Discovery
|
discovery: Discovery
|
||||||
sensitive_content: Sensitive content
|
sensitive_content: Sensitive content
|
||||||
|
toot_layout: Toot layout
|
||||||
application_mailer:
|
application_mailer:
|
||||||
notification_preferences: Change e-mail preferences
|
notification_preferences: Change e-mail preferences
|
||||||
salutation: "%{name},"
|
salutation: "%{name},"
|
||||||
|
|
|
@ -78,7 +78,7 @@ en:
|
||||||
text: Custom warning
|
text: Custom warning
|
||||||
type: Action
|
type: Action
|
||||||
types:
|
types:
|
||||||
disable: Disable
|
disable: Disable login
|
||||||
none: Do nothing
|
none: Do nothing
|
||||||
silence: Silence
|
silence: Silence
|
||||||
suspend: Suspend and irreversibly delete account data
|
suspend: Suspend and irreversibly delete account data
|
||||||
|
@ -113,6 +113,7 @@ en:
|
||||||
setting_aggregate_reblogs: Group boosts in timelines
|
setting_aggregate_reblogs: Group boosts in timelines
|
||||||
setting_auto_play_gif: Auto-play animated GIFs
|
setting_auto_play_gif: Auto-play animated GIFs
|
||||||
setting_boost_modal: Show confirmation dialog before boosting
|
setting_boost_modal: Show confirmation dialog before boosting
|
||||||
|
setting_crop_images: Crop images in non-expanded toots to 16x9
|
||||||
setting_default_language: Posting language
|
setting_default_language: Posting language
|
||||||
setting_default_privacy: Posting privacy
|
setting_default_privacy: Posting privacy
|
||||||
setting_default_sensitive: Always mark media as sensitive
|
setting_default_sensitive: Always mark media as sensitive
|
||||||
|
|
|
@ -36,6 +36,7 @@ defaults: &defaults
|
||||||
use_pending_items: false
|
use_pending_items: false
|
||||||
trends: true
|
trends: true
|
||||||
trendable_by_default: false
|
trendable_by_default: false
|
||||||
|
crop_images: true
|
||||||
notification_emails:
|
notification_emails:
|
||||||
follow: false
|
follow: false
|
||||||
reblog: false
|
reblog: false
|
||||||
|
|
26
package.json
26
package.json
|
@ -61,7 +61,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.4.5",
|
"@babel/core": "^7.4.5",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||||
"@babel/plugin-proposal-decorators": "^7.4.4",
|
"@babel/plugin-proposal-decorators": "^7.6.0",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.4.4",
|
"@babel/plugin-proposal-object-rest-spread": "^7.4.4",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/plugin-transform-react-inline-elements": "^7.2.0",
|
"@babel/plugin-transform-react-inline-elements": "^7.2.0",
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
"@babel/preset-env": "^7.6.0",
|
"@babel/preset-env": "^7.6.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@babel/runtime": "^7.5.4",
|
"@babel/runtime": "^7.5.4",
|
||||||
"@clusterws/cws": "^0.15.0",
|
"@clusterws/cws": "^0.15.2",
|
||||||
"array-includes": "^3.0.3",
|
"array-includes": "^3.0.3",
|
||||||
"autoprefixer": "^9.6.1",
|
"autoprefixer": "^9.6.1",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
@ -85,11 +85,11 @@
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"compression-webpack-plugin": "^3.0.0",
|
"compression-webpack-plugin": "^3.0.0",
|
||||||
"copy-webpack-plugin": "^5.0.4",
|
"copy-webpack-plugin": "^5.0.4",
|
||||||
"cross-env": "^5.1.4",
|
"cross-env": "^6.0.3",
|
||||||
"css-loader": "^3.2.0",
|
"css-loader": "^3.2.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"detect-passive-events": "^1.0.2",
|
"detect-passive-events": "^1.0.2",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.2.0",
|
||||||
"emoji-mart": "Gargron/emoji-mart#build",
|
"emoji-mart": "Gargron/emoji-mart#build",
|
||||||
"es6-symbol": "^3.1.2",
|
"es6-symbol": "^3.1.2",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
|
@ -125,8 +125,8 @@
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"punycode": "^2.1.0",
|
"punycode": "^2.1.0",
|
||||||
"rails-ujs": "^5.2.3",
|
"rails-ujs": "^5.2.3",
|
||||||
"react": "^16.8.6",
|
"react": "^16.10.2",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.10.2",
|
||||||
"react-hotkeys": "^1.1.4",
|
"react-hotkeys": "^1.1.4",
|
||||||
"react-immutable-proptypes": "^2.1.0",
|
"react-immutable-proptypes": "^2.1.0",
|
||||||
"react-immutable-pure-component": "^1.1.1",
|
"react-immutable-pure-component": "^1.1.1",
|
||||||
|
@ -139,7 +139,7 @@
|
||||||
"react-redux-loading-bar": "^4.0.8",
|
"react-redux-loading-bar": "^4.0.8",
|
||||||
"react-router-dom": "^4.1.1",
|
"react-router-dom": "^4.1.1",
|
||||||
"react-router-scroll-4": "^1.0.0-beta.1",
|
"react-router-scroll-4": "^1.0.0-beta.1",
|
||||||
"react-select": "^2.4.4",
|
"react-select": "^3.0.5",
|
||||||
"react-sparklines": "^1.7.0",
|
"react-sparklines": "^1.7.0",
|
||||||
"react-swipeable-views": "^0.13.3",
|
"react-swipeable-views": "^0.13.3",
|
||||||
"react-textarea-autosize": "^7.1.0",
|
"react-textarea-autosize": "^7.1.0",
|
||||||
|
@ -152,11 +152,11 @@
|
||||||
"requestidlecallback": "^0.3.0",
|
"requestidlecallback": "^0.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"rimraf": "^3.0.0",
|
"rimraf": "^3.0.0",
|
||||||
"sass": "^1.22.12",
|
"sass": "^1.23.1",
|
||||||
"sass-loader": "^7.0.3",
|
"sass-loader": "^7.0.3",
|
||||||
"stringz": "^2.0.0",
|
"stringz": "^2.0.0",
|
||||||
"substring-trie": "^1.0.2",
|
"substring-trie": "^1.0.2",
|
||||||
"terser-webpack-plugin": "^1.4.1",
|
"terser-webpack-plugin": "^2.2.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",
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
"wavesurfer.js": "^3.0.0",
|
"wavesurfer.js": "^3.0.0",
|
||||||
"webpack": "^4.35.3",
|
"webpack": "^4.35.3",
|
||||||
"webpack-assets-manifest": "^3.1.1",
|
"webpack-assets-manifest": "^3.1.1",
|
||||||
"webpack-bundle-analyzer": "^3.3.2",
|
"webpack-bundle-analyzer": "^3.6.0",
|
||||||
"webpack-cli": "^3.3.7",
|
"webpack-cli": "^3.3.7",
|
||||||
"webpack-merge": "^4.2.1",
|
"webpack-merge": "^4.2.1",
|
||||||
"websocket.js": "^0.1.12"
|
"websocket.js": "^0.1.12"
|
||||||
|
@ -174,15 +174,15 @@
|
||||||
"babel-jest": "^24.9.0",
|
"babel-jest": "^24.9.0",
|
||||||
"enzyme": "^3.10.0",
|
"enzyme": "^3.10.0",
|
||||||
"enzyme-adapter-react-16": "^1.14.0",
|
"enzyme-adapter-react-16": "^1.14.0",
|
||||||
"eslint": "^6.5.0",
|
"eslint": "^6.5.1",
|
||||||
"eslint-plugin-import": "~2.18.2",
|
"eslint-plugin-import": "~2.18.2",
|
||||||
"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.14.3",
|
"eslint-plugin-react": "~7.16.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.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.8.6",
|
"react-test-renderer": "^16.10.2",
|
||||||
"sass-lint": "^1.13.1",
|
"sass-lint": "^1.13.1",
|
||||||
"webpack-dev-server": "^3.8.0",
|
"webpack-dev-server": "^3.8.0",
|
||||||
"yargs": "^13.3.0"
|
"yargs": "^13.3.0"
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
<published>2016-10-10T00:41:31Z</published>
|
<published>2016-10-10T00:41:31Z</published>
|
||||||
<updated>2016-10-10T00:41:31Z</updated>
|
<updated>2016-10-10T00:41:31Z</updated>
|
||||||
<title>Social media needs MOAR cats! http://kickass.zone/media/3</title>
|
<title>Social media needs MOAR cats! http://kickass.zone/media/3</title>
|
||||||
<content type="html"><p>Social media needs MOAR cats! <a rel="nofollow noopener" href="http://kickass.zone/media/3">http://kickass.zone/media/3</a></p></content>
|
<content type="html"><p>Social media needs MOAR cats! <a rel="nofollow noopener noreferrer" href="http://kickass.zone/media/3">http://kickass.zone/media/3</a></p></content>
|
||||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
<link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/9.atom"/>
|
<link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/9.atom"/>
|
||||||
<link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/9"/>
|
<link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/9"/>
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
<published>2016-10-10T00:38:39Z</published>
|
<published>2016-10-10T00:38:39Z</published>
|
||||||
<updated>2016-10-10T00:38:39Z</updated>
|
<updated>2016-10-10T00:38:39Z</updated>
|
||||||
<title>http://kickass.zone/media/2</title>
|
<title>http://kickass.zone/media/2</title>
|
||||||
<content type="html"><p><a rel="nofollow noopener" href="http://kickass.zone/media/2">http://kickass.zone/media/2</a></p></content>
|
<content type="html"><p><a rel="nofollow noopener noreferrer" href="http://kickass.zone/media/2">http://kickass.zone/media/2</a></p></content>
|
||||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
<link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/8.atom"/>
|
<link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/8.atom"/>
|
||||||
<link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/8"/>
|
<link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/8"/>
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe AccountsHelper, type: :helper do
|
||||||
|
def set_not_embedded_view
|
||||||
|
params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}"
|
||||||
|
params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_embedded_view
|
||||||
|
params[:controller] = StatusesHelper::EMBEDDED_CONTROLLER
|
||||||
|
params[:action] = StatusesHelper::EMBEDDED_ACTION
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#display_name' do
|
||||||
|
it 'uses the display name when it exists' do
|
||||||
|
account = Account.new(display_name: "Display", username: "Username")
|
||||||
|
|
||||||
|
expect(helper.display_name(account)).to eq "Display"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses the username when display name is nil' do
|
||||||
|
account = Account.new(display_name: nil, username: "Username")
|
||||||
|
|
||||||
|
expect(helper.display_name(account)).to eq "Username"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#acct' do
|
||||||
|
it 'is fully qualified for embedded local accounts' do
|
||||||
|
allow(Rails.configuration.x).to receive(:local_domain).and_return('local_domain')
|
||||||
|
set_embedded_view
|
||||||
|
account = Account.new(domain: nil, username: 'user')
|
||||||
|
|
||||||
|
acct = helper.acct(account)
|
||||||
|
|
||||||
|
expect(acct).to eq '@user@local_domain'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is fully qualified for embedded foreign accounts' do
|
||||||
|
set_embedded_view
|
||||||
|
account = Account.new(domain: 'foreign_server.com', username: 'user')
|
||||||
|
|
||||||
|
acct = helper.acct(account)
|
||||||
|
|
||||||
|
expect(acct).to eq '@user@foreign_server.com'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is fully qualified for non embedded foreign accounts' do
|
||||||
|
set_not_embedded_view
|
||||||
|
account = Account.new(domain: 'foreign_server.com', username: 'user')
|
||||||
|
|
||||||
|
acct = helper.acct(account)
|
||||||
|
|
||||||
|
expect(acct).to eq '@user@foreign_server.com'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is fully qualified for non embedded local accounts' do
|
||||||
|
allow(Rails.configuration.x).to receive(:local_domain).and_return('local_domain')
|
||||||
|
set_not_embedded_view
|
||||||
|
account = Account.new(domain: nil, username: 'user')
|
||||||
|
|
||||||
|
acct = helper.acct(account)
|
||||||
|
|
||||||
|
expect(acct).to eq '@user@local_domain'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do
|
RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do
|
||||||
include StatusesHelper
|
include AccountsHelper
|
||||||
|
|
||||||
describe '#admin_account_link_to' do
|
describe '#admin_account_link_to' do
|
||||||
context 'account is nil' do
|
context 'account is nil' do
|
||||||
|
|
|
@ -1,20 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe StatusesHelper, type: :helper do
|
RSpec.describe StatusesHelper, type: :helper do
|
||||||
describe '#display_name' do
|
|
||||||
it 'uses the display name when it exists' do
|
|
||||||
account = Account.new(display_name: "Display", username: "Username")
|
|
||||||
|
|
||||||
expect(helper.display_name(account)).to eq "Display"
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'uses the username when display name is nil' do
|
|
||||||
account = Account.new(display_name: nil, username: "Username")
|
|
||||||
|
|
||||||
expect(helper.display_name(account)).to eq "Username"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#stream_link_target' do
|
describe '#stream_link_target' do
|
||||||
it 'returns nil if it is not an embedded view' do
|
it 'returns nil if it is not an embedded view' do
|
||||||
set_not_embedded_view
|
set_not_embedded_view
|
||||||
|
@ -29,46 +15,6 @@ RSpec.describe StatusesHelper, type: :helper do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#acct' do
|
|
||||||
it 'is fully qualified for embedded local accounts' do
|
|
||||||
allow(Rails.configuration.x).to receive(:local_domain).and_return('local_domain')
|
|
||||||
set_embedded_view
|
|
||||||
account = Account.new(domain: nil, username: 'user')
|
|
||||||
|
|
||||||
acct = helper.acct(account)
|
|
||||||
|
|
||||||
expect(acct).to eq '@user@local_domain'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is fully qualified for embedded foreign accounts' do
|
|
||||||
set_embedded_view
|
|
||||||
account = Account.new(domain: 'foreign_server.com', username: 'user')
|
|
||||||
|
|
||||||
acct = helper.acct(account)
|
|
||||||
|
|
||||||
expect(acct).to eq '@user@foreign_server.com'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is fully qualified for non embedded foreign accounts' do
|
|
||||||
set_not_embedded_view
|
|
||||||
account = Account.new(domain: 'foreign_server.com', username: 'user')
|
|
||||||
|
|
||||||
acct = helper.acct(account)
|
|
||||||
|
|
||||||
expect(acct).to eq '@user@foreign_server.com'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is fully qualified for non embedded local accounts' do
|
|
||||||
allow(Rails.configuration.x).to receive(:local_domain).and_return('local_domain')
|
|
||||||
set_not_embedded_view
|
|
||||||
account = Account.new(domain: nil, username: 'user')
|
|
||||||
|
|
||||||
acct = helper.acct(account)
|
|
||||||
|
|
||||||
expect(acct).to eq '@user@local_domain'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_not_embedded_view
|
def set_not_embedded_view
|
||||||
params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}"
|
params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}"
|
||||||
params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}"
|
params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}"
|
||||||
|
|
|
@ -24,7 +24,7 @@ describe Sanitize::Config do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'keep links in lists' do
|
it 'keep links in lists' do
|
||||||
expect(Sanitize.fragment('<p>Check out:</p><ul><li><a href="https://joinmastodon.org" rel="nofollow noopener" target="_blank">joinmastodon.org</a></li><li>Bar</li></ul>', subject)).to eq '<p>Check out:</p><p><a href="https://joinmastodon.org" rel="nofollow noopener" target="_blank">joinmastodon.org</a><br>Bar</p>'
|
expect(Sanitize.fragment('<p>Check out:</p><ul><li><a href="https://joinmastodon.org" rel="nofollow noopener noreferrer" target="_blank">joinmastodon.org</a></li><li>Bar</li></ul>', subject)).to eq '<p>Check out:</p><p><a href="https://joinmastodon.org" rel="nofollow noopener noreferrer" target="_blank">joinmastodon.org</a><br>Bar</p>'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,7 +80,7 @@ RSpec.describe FetchLinkCardService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'in a remote status' do
|
context 'in a remote status' do
|
||||||
let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: 'Habt ihr ein paar gute Links zu #<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen? Ich will mal unter <br> <a href="https://github.com/qbi/WannaCry" target="_blank" rel="noopener" title="https://github.com/qbi/WannaCry">https://github.com/qbi/WannaCry</a> was sammeln. !<a href="http://sn.jonkman.ca/group/416/id" target="_blank" rel="noopener" title="http://sn.jonkman.ca/group/416/id">security</a> ') }
|
let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: 'Habt ihr ein paar gute Links zu #<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener noreferrer" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen? Ich will mal unter <br> <a href="https://github.com/qbi/WannaCry" target="_blank" rel="noopener noreferrer" title="https://github.com/qbi/WannaCry">https://github.com/qbi/WannaCry</a> was sammeln. !<a href="http://sn.jonkman.ca/group/416/id" target="_blank" rel="noopener noreferrer" title="http://sn.jonkman.ca/group/416/id">security</a> ') }
|
||||||
|
|
||||||
it 'parses out URLs' do
|
it 'parses out URLs' do
|
||||||
expect(a_request(:get, 'https://github.com/qbi/WannaCry')).to have_been_made.at_least_once
|
expect(a_request(:get, 'https://github.com/qbi/WannaCry')).to have_been_made.at_least_once
|
||||||
|
|
|
@ -28,12 +28,12 @@ RSpec.describe VerifyLinkService, type: :service do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a link contains an <a rel="noopener"> back' do
|
context 'when a link contains an <a rel="noopener noreferrer"> back' do
|
||||||
let(:html) do
|
let(:html) do
|
||||||
<<-HTML
|
<<-HTML
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<body>
|
<body>
|
||||||
<a href="#{ActivityPub::TagManager.instance.url_for(account)}" rel="noopener me" target="_blank">Follow me on Mastodon</a>
|
<a href="#{ActivityPub::TagManager.instance.url_for(account)}" rel="me noopener noreferrer" target="_blank">Follow me on Mastodon</a>
|
||||||
</body>
|
</body>
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue