Merge branch 'main' into glitch-soc/merge-upstream
commit
4d40685850
|
@ -30,7 +30,7 @@ plugins:
|
||||||
channel: eslint-7
|
channel: eslint-7
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: rubocop-1-70
|
channel: rubocop-1-8-1
|
||||||
sass-lint:
|
sass-lint:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude_patterns:
|
exclude_patterns:
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
test_patterns = ["/app/javascript/mastodon/**/__tests__/**"]
|
||||||
|
|
||||||
|
[[analyzers]]
|
||||||
|
name = "ruby"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[[analyzers]]
|
||||||
|
name = "javascript"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[analyzers.meta]
|
||||||
|
environment = [
|
||||||
|
"nodejs",
|
||||||
|
"browser",
|
||||||
|
"jest"
|
||||||
|
]
|
|
@ -4,7 +4,7 @@ FROM ubuntu:20.04 as build-dep
|
||||||
SHELL ["/usr/bin/bash", "-c"]
|
SHELL ["/usr/bin/bash", "-c"]
|
||||||
|
|
||||||
# Install Node v12 (LTS)
|
# Install Node v12 (LTS)
|
||||||
ENV NODE_VER="12.20.0"
|
ENV NODE_VER="12.20.1"
|
||||||
RUN ARCH= && \
|
RUN ARCH= && \
|
||||||
dpkgArch="$(dpkg --print-architecture)" && \
|
dpkgArch="$(dpkg --print-architecture)" && \
|
||||||
case "${dpkgArch##*-}" in \
|
case "${dpkgArch##*-}" in \
|
||||||
|
|
12
Gemfile
12
Gemfile
|
@ -8,7 +8,7 @@ gem 'pkg-config', '~> 1.4'
|
||||||
gem 'puma', '~> 5.1'
|
gem 'puma', '~> 5.1'
|
||||||
gem 'rails', '~> 5.2.4.4'
|
gem 'rails', '~> 5.2.4.4'
|
||||||
gem 'sprockets', '~> 3.7.2'
|
gem 'sprockets', '~> 3.7.2'
|
||||||
gem 'thor', '~> 1.0'
|
gem 'thor', '~> 1.1'
|
||||||
gem 'rack', '~> 2.2.3'
|
gem 'rack', '~> 2.2.3'
|
||||||
|
|
||||||
gem 'hamlit-rails', '~> 0.2'
|
gem 'hamlit-rails', '~> 0.2'
|
||||||
|
@ -31,7 +31,7 @@ gem 'bootsnap', '~> 1.5', require: false
|
||||||
gem 'browser'
|
gem 'browser'
|
||||||
gem 'charlock_holmes', '~> 0.7.7'
|
gem 'charlock_holmes', '~> 0.7.7'
|
||||||
gem 'iso-639'
|
gem 'iso-639'
|
||||||
gem 'chewy', '~> 5.1'
|
gem 'chewy', '~> 5.2'
|
||||||
gem 'cld3', '~> 3.4.1'
|
gem 'cld3', '~> 3.4.1'
|
||||||
gem 'devise', '~> 4.7'
|
gem 'devise', '~> 4.7'
|
||||||
gem 'devise-two-factor', '~> 3.1'
|
gem 'devise-two-factor', '~> 3.1'
|
||||||
|
@ -73,7 +73,7 @@ gem 'parallel', '~> 1.20'
|
||||||
gem 'posix-spawn'
|
gem 'posix-spawn'
|
||||||
gem 'pundit', '~> 2.1'
|
gem 'pundit', '~> 2.1'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'rack-attack', '~> 6.3'
|
gem 'rack-attack', '~> 6.4'
|
||||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||||
gem 'rails-i18n', '~> 5.1'
|
gem 'rails-i18n', '~> 5.1'
|
||||||
gem 'rails-settings-cached', '~> 0.6'
|
gem 'rails-settings-cached', '~> 0.6'
|
||||||
|
@ -94,7 +94,7 @@ gem 'stoplight', '~> 2.2.1'
|
||||||
gem 'strong_migrations', '~> 0.7'
|
gem 'strong_migrations', '~> 0.7'
|
||||||
gem 'tty-prompt', '~> 0.23', require: false
|
gem 'tty-prompt', '~> 0.23', require: false
|
||||||
gem 'twitter-text', '~> 1.14'
|
gem 'twitter-text', '~> 1.14'
|
||||||
gem 'tzinfo-data', '~> 1.2020'
|
gem 'tzinfo-data', '~> 1.2021'
|
||||||
gem 'webpacker', '~> 5.2'
|
gem 'webpacker', '~> 5.2'
|
||||||
gem 'webpush'
|
gem 'webpush'
|
||||||
gem 'webauthn', '~> 3.0.0.alpha1'
|
gem 'webauthn', '~> 3.0.0.alpha1'
|
||||||
|
@ -119,7 +119,7 @@ group :production, :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.34'
|
gem 'capybara', '~> 3.35'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 2.15'
|
gem 'faker', '~> 2.15'
|
||||||
gem 'microformats', '~> 4.2'
|
gem 'microformats', '~> 4.2'
|
||||||
|
@ -140,7 +140,7 @@ group :development do
|
||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.7'
|
||||||
gem 'letter_opener_web', '~> 1.4'
|
gem 'letter_opener_web', '~> 1.4'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 1.7', require: false
|
gem 'rubocop', '~> 1.8', require: false
|
||||||
gem 'rubocop-rails', '~> 2.9', require: false
|
gem 'rubocop-rails', '~> 2.9', require: false
|
||||||
gem 'brakeman', '~> 4.10', require: false
|
gem 'brakeman', '~> 4.10', require: false
|
||||||
gem 'bundler-audit', '~> 0.7', require: false
|
gem 'bundler-audit', '~> 0.7', require: false
|
||||||
|
|
64
Gemfile.lock
64
Gemfile.lock
|
@ -109,7 +109,7 @@ GEM
|
||||||
brakeman (4.10.1)
|
brakeman (4.10.1)
|
||||||
browser (4.2.0)
|
browser (4.2.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bullet (6.1.2)
|
bullet (6.1.3)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
bundler-audit (0.7.0.1)
|
bundler-audit (0.7.0.1)
|
||||||
|
@ -131,20 +131,20 @@ GEM
|
||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (3.34.0)
|
capybara (3.35.3)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
rack (>= 1.6.0)
|
rack (>= 1.6.0)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
regexp_parser (~> 1.5)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
case_transform (0.2)
|
case_transform (0.2)
|
||||||
activesupport
|
activesupport
|
||||||
cbor (0.5.9.6)
|
cbor (0.5.9.6)
|
||||||
charlock_holmes (0.7.7)
|
charlock_holmes (0.7.7)
|
||||||
chewy (5.1.0)
|
chewy (5.2.0)
|
||||||
activesupport (>= 4.0)
|
activesupport (>= 5.2)
|
||||||
elasticsearch (>= 2.0.0)
|
elasticsearch (>= 2.0.0)
|
||||||
elasticsearch-dsl
|
elasticsearch-dsl
|
||||||
chunky_png (1.3.15)
|
chunky_png (1.3.15)
|
||||||
|
@ -195,13 +195,13 @@ GEM
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
e2mmap (0.1.0)
|
e2mmap (0.1.0)
|
||||||
ed25519 (1.2.4)
|
ed25519 (1.2.4)
|
||||||
elasticsearch (7.9.0)
|
elasticsearch (7.10.1)
|
||||||
elasticsearch-api (= 7.9.0)
|
elasticsearch-api (= 7.10.1)
|
||||||
elasticsearch-transport (= 7.9.0)
|
elasticsearch-transport (= 7.10.1)
|
||||||
elasticsearch-api (7.9.0)
|
elasticsearch-api (7.10.1)
|
||||||
multi_json
|
multi_json
|
||||||
elasticsearch-dsl (0.1.9)
|
elasticsearch-dsl (0.1.9)
|
||||||
elasticsearch-transport (7.9.0)
|
elasticsearch-transport (7.10.1)
|
||||||
faraday (~> 1)
|
faraday (~> 1)
|
||||||
multi_json
|
multi_json
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
|
@ -212,10 +212,13 @@ GEM
|
||||||
fabrication (2.21.1)
|
fabrication (2.21.1)
|
||||||
faker (2.15.1)
|
faker (2.15.1)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
faraday (1.0.1)
|
faraday (1.3.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
|
ruby2_keywords
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
fastimage (2.2.1)
|
fastimage (2.2.2)
|
||||||
ffi (1.10.0)
|
ffi (1.10.0)
|
||||||
ffi-compiler (1.0.1)
|
ffi-compiler (1.0.1)
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
|
@ -369,7 +372,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.11.0)
|
oj (3.11.2)
|
||||||
omniauth (1.9.1)
|
omniauth (1.9.1)
|
||||||
hashie (>= 3.4.6)
|
hashie (>= 3.4.6)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
|
@ -436,7 +439,7 @@ GEM
|
||||||
raabro (1.3.3)
|
raabro (1.3.3)
|
||||||
racc (1.5.2)
|
racc (1.5.2)
|
||||||
rack (2.2.3)
|
rack (2.2.3)
|
||||||
rack-attack (6.3.1)
|
rack-attack (6.4.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
|
@ -504,7 +507,7 @@ GEM
|
||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-store (1.9.0)
|
redis-store (1.9.0)
|
||||||
redis (>= 4, < 5)
|
redis (>= 4, < 5)
|
||||||
regexp_parser (1.8.2)
|
regexp_parser (2.0.3)
|
||||||
request_store (1.5.0)
|
request_store (1.5.0)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.0.1)
|
responders (3.0.1)
|
||||||
|
@ -539,16 +542,16 @@ GEM
|
||||||
rspec-support (3.10.1)
|
rspec-support (3.10.1)
|
||||||
rspec_junit_formatter (0.4.1)
|
rspec_junit_formatter (0.4.1)
|
||||||
rspec-core (>= 2, < 4, != 2.12.0)
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
rubocop (1.7.0)
|
rubocop (1.8.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.7.1.5)
|
parser (>= 3.0.0.0)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml
|
rexml
|
||||||
rubocop-ast (>= 1.2.0, < 2.0)
|
rubocop-ast (>= 1.2.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 2.0)
|
unicode-display_width (>= 1.4.0, < 3.0)
|
||||||
rubocop-ast (1.3.0)
|
rubocop-ast (1.4.0)
|
||||||
parser (>= 2.7.1.5)
|
parser (>= 2.7.1.5)
|
||||||
rubocop-rails (2.9.1)
|
rubocop-rails (2.9.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
|
@ -557,6 +560,7 @@ GEM
|
||||||
ruby-progressbar (1.11.0)
|
ruby-progressbar (1.11.0)
|
||||||
ruby-saml (1.11.0)
|
ruby-saml (1.11.0)
|
||||||
nokogiri (>= 1.5.10)
|
nokogiri (>= 1.5.10)
|
||||||
|
ruby2_keywords (0.0.4)
|
||||||
rufus-scheduler (3.6.0)
|
rufus-scheduler (3.6.0)
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.1.6)
|
||||||
safety_net_attestation (0.4.0)
|
safety_net_attestation (0.4.0)
|
||||||
|
@ -570,7 +574,7 @@ GEM
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
securecompare (1.0.0)
|
securecompare (1.0.0)
|
||||||
semantic_range (2.3.0)
|
semantic_range (2.3.0)
|
||||||
sidekiq (6.1.2)
|
sidekiq (6.1.3)
|
||||||
connection_pool (>= 2.2.2)
|
connection_pool (>= 2.2.2)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
redis (>= 4.2.0)
|
redis (>= 4.2.0)
|
||||||
|
@ -620,7 +624,7 @@ GEM
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
thor (1.0.1)
|
thor (1.1.0)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
thwait (0.2.0)
|
thwait (0.2.0)
|
||||||
e2mmap
|
e2mmap
|
||||||
|
@ -642,13 +646,13 @@ GEM
|
||||||
unf (~> 0.1.0)
|
unf (~> 0.1.0)
|
||||||
tzinfo (1.2.9)
|
tzinfo (1.2.9)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2020.6)
|
tzinfo-data (1.2021.1)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
uniform_notifier (1.13.0)
|
uniform_notifier (1.13.2)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
webauthn (3.0.0.alpha1)
|
webauthn (3.0.0.alpha1)
|
||||||
|
@ -661,7 +665,7 @@ GEM
|
||||||
safety_net_attestation (~> 0.4.0)
|
safety_net_attestation (~> 0.4.0)
|
||||||
securecompare (~> 1.0)
|
securecompare (~> 1.0)
|
||||||
tpm-key_attestation (~> 0.9.0)
|
tpm-key_attestation (~> 0.9.0)
|
||||||
webmock (3.11.1)
|
webmock (3.11.2)
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
|
@ -702,9 +706,9 @@ DEPENDENCIES
|
||||||
capistrano-rails (~> 1.6)
|
capistrano-rails (~> 1.6)
|
||||||
capistrano-rbenv (~> 2.2)
|
capistrano-rbenv (~> 2.2)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 3.34)
|
capybara (~> 3.35)
|
||||||
charlock_holmes (~> 0.7.7)
|
charlock_holmes (~> 0.7.7)
|
||||||
chewy (~> 5.1)
|
chewy (~> 5.2)
|
||||||
cld3 (~> 3.4.1)
|
cld3 (~> 3.4.1)
|
||||||
climate_control (~> 0.2)
|
climate_control (~> 0.2)
|
||||||
color_diff (~> 0.1)
|
color_diff (~> 0.1)
|
||||||
|
@ -773,7 +777,7 @@ DEPENDENCIES
|
||||||
puma (~> 5.1)
|
puma (~> 5.1)
|
||||||
pundit (~> 2.1)
|
pundit (~> 2.1)
|
||||||
rack (~> 2.2.3)
|
rack (~> 2.2.3)
|
||||||
rack-attack (~> 6.3)
|
rack-attack (~> 6.4)
|
||||||
rack-cors (~> 1.1)
|
rack-cors (~> 1.1)
|
||||||
rails (~> 5.2.4.4)
|
rails (~> 5.2.4.4)
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
|
@ -788,7 +792,7 @@ DEPENDENCIES
|
||||||
rspec-rails (~> 4.0)
|
rspec-rails (~> 4.0)
|
||||||
rspec-sidekiq (~> 3.1)
|
rspec-sidekiq (~> 3.1)
|
||||||
rspec_junit_formatter (~> 0.4)
|
rspec_junit_formatter (~> 0.4)
|
||||||
rubocop (~> 1.7)
|
rubocop (~> 1.8)
|
||||||
rubocop-rails (~> 2.9)
|
rubocop-rails (~> 2.9)
|
||||||
ruby-progressbar (~> 1.11)
|
ruby-progressbar (~> 1.11)
|
||||||
sanitize (~> 5.2)
|
sanitize (~> 5.2)
|
||||||
|
@ -806,10 +810,10 @@ DEPENDENCIES
|
||||||
stoplight (~> 2.2.1)
|
stoplight (~> 2.2.1)
|
||||||
streamio-ffmpeg (~> 3.0)
|
streamio-ffmpeg (~> 3.0)
|
||||||
strong_migrations (~> 0.7)
|
strong_migrations (~> 0.7)
|
||||||
thor (~> 1.0)
|
thor (~> 1.1)
|
||||||
tty-prompt (~> 0.23)
|
tty-prompt (~> 0.23)
|
||||||
twitter-text (~> 1.14)
|
twitter-text (~> 1.14)
|
||||||
tzinfo-data (~> 1.2020)
|
tzinfo-data (~> 1.2021)
|
||||||
webauthn (~> 3.0.0.alpha1)
|
webauthn (~> 3.0.0.alpha1)
|
||||||
webmock (~> 3.11)
|
webmock (~> 3.11)
|
||||||
webpacker (~> 5.2)
|
webpacker (~> 5.2)
|
||||||
|
|
|
@ -40,12 +40,13 @@ class Api::V1::NotificationsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_notifications
|
def load_notifications
|
||||||
cache_collection_paginated_by_id(
|
notifications = browserable_account_notifications.includes(from_account: :account_stat).to_a_paginated_by_id(
|
||||||
browserable_account_notifications,
|
|
||||||
Notification,
|
|
||||||
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
|
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
params_slice(:max_id, :since_id, :min_id)
|
||||||
)
|
)
|
||||||
|
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
|
||||||
|
cache_collection(target_statuses, Status)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def browserable_account_notifications
|
def browserable_account_notifications
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
exports[`<DisplayName /> renders display name + account name 1`] = `
|
exports[`<DisplayName /> renders display name + account name 1`] = `
|
||||||
<span
|
<span
|
||||||
className="display-name"
|
className="display-name"
|
||||||
|
onMouseEnter={[Function]}
|
||||||
|
onMouseLeave={[Function]}
|
||||||
>
|
>
|
||||||
<bdi>
|
<bdi>
|
||||||
<strong
|
<strong
|
||||||
|
|
|
@ -11,45 +11,30 @@ export default class DisplayName extends React.PureComponent {
|
||||||
localDomain: PropTypes.string,
|
localDomain: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateEmojis () {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
const node = this.node;
|
if (autoPlayGif) {
|
||||||
|
|
||||||
if (!node || autoPlayGif) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = node.querySelectorAll('.custom-emoji');
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
let emoji = emojis[i];
|
let emoji = emojis[i];
|
||||||
if (emoji.classList.contains('status-emoji')) {
|
emoji.src = emoji.getAttribute('data-original');
|
||||||
continue;
|
|
||||||
}
|
|
||||||
emoji.classList.add('status-emoji');
|
|
||||||
|
|
||||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
|
||||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
handleMouseLeave = ({ currentTarget }) => {
|
||||||
this._updateEmojis();
|
if (autoPlayGif) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
this._updateEmojis();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
target.src = target.getAttribute('data-original');
|
let emoji = emojis[i];
|
||||||
}
|
emoji.src = emoji.getAttribute('data-static');
|
||||||
|
}
|
||||||
handleEmojiMouseLeave = ({ target }) => {
|
|
||||||
target.src = target.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -81,7 +66,7 @@ export default class DisplayName extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className='display-name' ref={this.setRef}>
|
<span className='display-name' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
{displayName} {suffix}
|
{displayName} {suffix}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -75,35 +75,38 @@ export default class StatusContent extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateStatusEmojis () {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
const node = this.node;
|
if (autoPlayGif) {
|
||||||
|
|
||||||
if (!node || autoPlayGif) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = node.querySelectorAll('.custom-emoji');
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
let emoji = emojis[i];
|
let emoji = emojis[i];
|
||||||
if (emoji.classList.contains('status-emoji')) {
|
emoji.src = emoji.getAttribute('data-original');
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
emoji.classList.add('status-emoji');
|
|
||||||
|
|
||||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
handleMouseLeave = ({ currentTarget }) => {
|
||||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
|
let emoji = emojis[i];
|
||||||
|
emoji.src = emoji.getAttribute('data-static');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this._updateStatusLinks();
|
this._updateStatusLinks();
|
||||||
this._updateStatusEmojis();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
this._updateStatusLinks();
|
this._updateStatusLinks();
|
||||||
this._updateStatusEmojis();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMentionClick = (mention, e) => {
|
onMentionClick = (mention, e) => {
|
||||||
|
@ -122,14 +125,6 @@ export default class StatusContent extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
|
||||||
target.src = target.getAttribute('data-original');
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmojiMouseLeave = ({ target }) => {
|
|
||||||
target.src = target.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseDown = (e) => {
|
handleMouseDown = (e) => {
|
||||||
this.startXY = [e.clientX, e.clientY];
|
this.startXY = [e.clientX, e.clientY];
|
||||||
}
|
}
|
||||||
|
@ -219,7 +214,7 @@ export default class StatusContent extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||||
<span dangerouslySetInnerHTML={spoilerContent} className='translate' />
|
<span dangerouslySetInnerHTML={spoilerContent} className='translate' />
|
||||||
{' '}
|
{' '}
|
||||||
|
@ -237,7 +232,7 @@ export default class StatusContent extends React.PureComponent {
|
||||||
);
|
);
|
||||||
} else if (this.props.onClick) {
|
} else if (this.props.onClick) {
|
||||||
const output = [
|
const output = [
|
||||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
|
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
||||||
|
|
||||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||||
|
@ -253,7 +248,7 @@ export default class StatusContent extends React.PureComponent {
|
||||||
return output;
|
return output;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className={classNames} ref={this.setRef} tabIndex='0'>
|
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
||||||
|
|
||||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||||
|
|
|
@ -96,45 +96,30 @@ class Header extends ImmutablePureComponent {
|
||||||
return !location.pathname.match(/\/(followers|following)\/?$/);
|
return !location.pathname.match(/\/(followers|following)\/?$/);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateEmojis () {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
const node = this.node;
|
if (autoPlayGif) {
|
||||||
|
|
||||||
if (!node || autoPlayGif) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = node.querySelectorAll('.custom-emoji');
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
let emoji = emojis[i];
|
let emoji = emojis[i];
|
||||||
if (emoji.classList.contains('status-emoji')) {
|
emoji.src = emoji.getAttribute('data-original');
|
||||||
continue;
|
|
||||||
}
|
|
||||||
emoji.classList.add('status-emoji');
|
|
||||||
|
|
||||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
|
||||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
handleMouseLeave = ({ currentTarget }) => {
|
||||||
this._updateEmojis();
|
if (autoPlayGif) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
this._updateEmojis();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
target.src = target.getAttribute('data-original');
|
let emoji = emojis[i];
|
||||||
}
|
emoji.src = emoji.getAttribute('data-static');
|
||||||
|
}
|
||||||
handleEmojiMouseLeave = ({ target }) => {
|
|
||||||
target.src = target.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -276,7 +261,7 @@ class Header extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} ref={this.setRef}>
|
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<div className='account__header__image'>
|
<div className='account__header__image'>
|
||||||
<div className='account__header__info'>
|
<div className='account__header__info'>
|
||||||
{!suspended && info}
|
{!suspended && info}
|
||||||
|
@ -330,7 +315,7 @@ class Header extends ImmutablePureComponent {
|
||||||
<dl key={i}>
|
<dl key={i}>
|
||||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
||||||
|
|
||||||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')} className='translate'>
|
<dd className={`${pair.get('verified_at') ? 'verified' : ''} translate`} title={pair.get('value_plain')}>
|
||||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
|
@ -44,41 +44,30 @@ class Conversation extends ImmutablePureComponent {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateEmojis () {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
const node = this.namesNode;
|
if (autoPlayGif) {
|
||||||
|
|
||||||
if (!node || autoPlayGif) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = node.querySelectorAll('.custom-emoji');
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
let emoji = emojis[i];
|
let emoji = emojis[i];
|
||||||
if (emoji.classList.contains('status-emoji')) {
|
emoji.src = emoji.getAttribute('data-original');
|
||||||
continue;
|
|
||||||
}
|
|
||||||
emoji.classList.add('status-emoji');
|
|
||||||
|
|
||||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
|
||||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
handleMouseLeave = ({ currentTarget }) => {
|
||||||
this._updateEmojis();
|
if (autoPlayGif) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
this._updateEmojis();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
target.src = target.getAttribute('data-original');
|
let emoji = emojis[i];
|
||||||
}
|
emoji.src = emoji.getAttribute('data-static');
|
||||||
|
}
|
||||||
handleEmojiMouseLeave = ({ target }) => {
|
|
||||||
target.src = target.getAttribute('data-static');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
|
@ -123,10 +112,6 @@ class Conversation extends ImmutablePureComponent {
|
||||||
this.props.onToggleHidden(this.props.lastStatus);
|
this.props.onToggleHidden(this.props.lastStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
setNamesRef = (c) => {
|
|
||||||
this.namesNode = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
|
const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
|
||||||
|
|
||||||
|
@ -171,7 +156,7 @@ class Conversation extends ImmutablePureComponent {
|
||||||
{unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
|
{unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='conversation__content__names' ref={this.setNamesRef}>
|
<div className='conversation__content__names' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
|
<FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -102,43 +102,32 @@ class AccountCard extends ImmutablePureComponent {
|
||||||
onMute: PropTypes.func.isRequired,
|
onMute: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateEmojis() {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
const node = this.node;
|
if (autoPlayGif) {
|
||||||
|
|
||||||
if (!node || autoPlayGif) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = node.querySelectorAll('.custom-emoji');
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
let emoji = emojis[i];
|
let emoji = emojis[i];
|
||||||
if (emoji.classList.contains('status-emoji')) {
|
emoji.src = emoji.getAttribute('data-original');
|
||||||
continue;
|
|
||||||
}
|
|
||||||
emoji.classList.add('status-emoji');
|
|
||||||
|
|
||||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
|
||||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
handleMouseLeave = ({ currentTarget }) => {
|
||||||
this._updateEmojis();
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
|
let emoji = emojis[i];
|
||||||
|
emoji.src = emoji.getAttribute('data-static');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this._updateEmojis();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
|
||||||
target.src = target.getAttribute('data-original');
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEmojiMouseLeave = ({ target }) => {
|
|
||||||
target.src = target.getAttribute('data-static');
|
|
||||||
};
|
|
||||||
|
|
||||||
handleFollow = () => {
|
handleFollow = () => {
|
||||||
this.props.onFollow(this.props.account);
|
this.props.onFollow(this.props.account);
|
||||||
};
|
};
|
||||||
|
@ -151,10 +140,6 @@ class AccountCard extends ImmutablePureComponent {
|
||||||
this.props.onMute(this.props.account);
|
this.props.onMute(this.props.account);
|
||||||
};
|
};
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { account, intl } = this.props;
|
const { account, intl } = this.props;
|
||||||
|
|
||||||
|
@ -239,7 +224,7 @@ class AccountCard extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='directory__card__extra' ref={this.setRef}>
|
<div className='directory__card__extra' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<div
|
<div
|
||||||
className='account__header__content translate'
|
className='account__header__content translate'
|
||||||
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
||||||
|
|
|
@ -39,35 +39,10 @@ class Content extends ImmutablePureComponent {
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this._updateLinks();
|
this._updateLinks();
|
||||||
this._updateEmojis();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
this._updateLinks();
|
this._updateLinks();
|
||||||
this._updateEmojis();
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateEmojis () {
|
|
||||||
const node = this.node;
|
|
||||||
|
|
||||||
if (!node || autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = node.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
|
|
||||||
if (emoji.classList.contains('status-emoji')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
emoji.classList.add('status-emoji');
|
|
||||||
|
|
||||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
|
||||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateLinks () {
|
_updateLinks () {
|
||||||
|
@ -132,12 +107,30 @@ class Content extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
target.src = target.getAttribute('data-original');
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
|
let emoji = emojis[i];
|
||||||
|
emoji.src = emoji.getAttribute('data-original');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiMouseLeave = ({ target }) => {
|
handleMouseLeave = ({ currentTarget }) => {
|
||||||
target.src = target.getAttribute('data-static');
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||||
|
|
||||||
|
for (var i = 0; i < emojis.length; i++) {
|
||||||
|
let emoji = emojis[i];
|
||||||
|
emoji.src = emoji.getAttribute('data-static');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -148,6 +141,8 @@ class Content extends ImmutablePureComponent {
|
||||||
className='announcements__item__content translate'
|
className='announcements__item__content translate'
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
|
dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,8 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
|
||||||
PROCESSING_COOLDOWN = 7.days.seconds
|
PROCESSING_COOLDOWN = 7.days.seconds
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
return if origin_account.uri != object_uri || processed?
|
return if origin_account.uri != object_uri
|
||||||
|
return unless mark_as_processing!
|
||||||
mark_as_processing!
|
|
||||||
|
|
||||||
target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
|
target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
|
||||||
|
|
||||||
|
@ -35,12 +34,8 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
|
||||||
value_or_id(@json['target'])
|
value_or_id(@json['target'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def processed?
|
|
||||||
redis.exists?("move_in_progress:#{@account.id}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_as_processing!
|
def mark_as_processing!
|
||||||
redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true)
|
redis.set("move_in_progress:#{@account.id}", true, nx: true, ex: PROCESSING_COOLDOWN)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmark_as_processing!
|
def unmark_as_processing!
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
class AccountMigration < ApplicationRecord
|
class AccountMigration < ApplicationRecord
|
||||||
|
include Redisable
|
||||||
|
|
||||||
COOLDOWN_PERIOD = 30.days.freeze
|
COOLDOWN_PERIOD = 30.days.freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
@ -39,7 +41,13 @@ class AccountMigration < ApplicationRecord
|
||||||
|
|
||||||
return false unless errors.empty?
|
return false unless errors.empty?
|
||||||
|
|
||||||
save
|
RedisLock.acquire(lock_options) do |lock|
|
||||||
|
if lock.acquired?
|
||||||
|
save
|
||||||
|
else
|
||||||
|
raise Mastodon::RaceConditionError
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cooldown_at
|
def cooldown_at
|
||||||
|
@ -75,4 +83,8 @@ class AccountMigration < ApplicationRecord
|
||||||
def validate_migration_cooldown
|
def validate_migration_cooldown
|
||||||
errors.add(:base, I18n.t('migrations.errors.on_cooldown')) if account.migrations.within_cooldown.exists?
|
errors.add(:base, I18n.t('migrations.errors.on_cooldown')) if account.migrations.within_cooldown.exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lock_options
|
||||||
|
{ redis: redis, key: "account_migration:#{account.id}" }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,6 @@ class Notification < ApplicationRecord
|
||||||
self.inheritance_column = nil
|
self.inheritance_column = nil
|
||||||
|
|
||||||
include Paginable
|
include Paginable
|
||||||
include Cacheable
|
|
||||||
|
|
||||||
LEGACY_TYPE_CLASS_MAP = {
|
LEGACY_TYPE_CLASS_MAP = {
|
||||||
'Mention' => :mention,
|
'Mention' => :mention,
|
||||||
|
@ -38,7 +37,13 @@ class Notification < ApplicationRecord
|
||||||
poll
|
poll
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
STATUS_INCLUDES = [:account, :application, :preloadable_poll, :media_attachments, :tags, active_mentions: :account, reblog: [:account, :application, :preloadable_poll, :media_attachments, :tags, active_mentions: :account]].freeze
|
TARGET_STATUS_INCLUDES_BY_TYPE = {
|
||||||
|
status: :status,
|
||||||
|
reblog: [status: :reblog],
|
||||||
|
mention: [mention: :status],
|
||||||
|
favourite: [favourite: :status],
|
||||||
|
poll: [poll: :status],
|
||||||
|
}.freeze
|
||||||
|
|
||||||
belongs_to :account, optional: true
|
belongs_to :account, optional: true
|
||||||
belongs_to :from_account, class_name: 'Account', optional: true
|
belongs_to :from_account, class_name: 'Account', optional: true
|
||||||
|
@ -65,8 +70,6 @@ class Notification < ApplicationRecord
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account, follow_request: :account, poll: [status: STATUS_INCLUDES]
|
|
||||||
|
|
||||||
def type
|
def type
|
||||||
@type ||= (super || LEGACY_TYPE_CLASS_MAP[activity_type]).to_sym
|
@type ||= (super || LEGACY_TYPE_CLASS_MAP[activity_type]).to_sym
|
||||||
end
|
end
|
||||||
|
@ -87,21 +90,40 @@ class Notification < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def cache_ids
|
def preload_cache_collection_target_statuses(notifications, &_block)
|
||||||
select(:id, :updated_at, :activity_type, :activity_id)
|
notifications.group_by(&:type).each do |type, grouped_notifications|
|
||||||
end
|
associations = TARGET_STATUS_INCLUDES_BY_TYPE[type]
|
||||||
|
next unless associations
|
||||||
|
|
||||||
def reload_stale_associations!(cached_items)
|
# Instead of using the usual `includes`, manually preload each type.
|
||||||
account_ids = (cached_items.map(&:from_account_id) + cached_items.filter_map { |item| item.target_status&.account_id }).uniq
|
# If polymorphic associations are loaded with the usual `includes`, other types of associations will be loaded more.
|
||||||
|
ActiveRecord::Associations::Preloader.new.preload(grouped_notifications, associations)
|
||||||
return if account_ids.empty?
|
|
||||||
|
|
||||||
accounts = Account.where(id: account_ids).includes(:account_stat).index_by(&:id)
|
|
||||||
|
|
||||||
cached_items.each do |item|
|
|
||||||
item.from_account = accounts[item.from_account_id]
|
|
||||||
item.target_status.account = accounts[item.target_status.account_id] if item.target_status
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unique_target_statuses = notifications.map(&:target_status).compact.uniq
|
||||||
|
# Call cache_collection in block
|
||||||
|
cached_statuses_by_id = yield(unique_target_statuses).index_by(&:id)
|
||||||
|
|
||||||
|
notifications.each do |notification|
|
||||||
|
next if notification.target_status.nil?
|
||||||
|
|
||||||
|
cached_status = cached_statuses_by_id[notification.target_status.id]
|
||||||
|
|
||||||
|
case notification.type
|
||||||
|
when :status
|
||||||
|
notification.status = cached_status
|
||||||
|
when :reblog
|
||||||
|
notification.status.reblog = cached_status
|
||||||
|
when :favourite
|
||||||
|
notification.favourite.status = cached_status
|
||||||
|
when :mention
|
||||||
|
notification.mention.status = cached_status
|
||||||
|
when :poll
|
||||||
|
notification.poll.status = cached_status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
notifications
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ class Status < ApplicationRecord
|
||||||
:tags,
|
:tags,
|
||||||
:preview_cards,
|
:preview_cards,
|
||||||
:preloadable_poll,
|
:preloadable_poll,
|
||||||
account: :account_stat,
|
account: [:account_stat, :user],
|
||||||
active_mentions: { account: :account_stat },
|
active_mentions: { account: :account_stat },
|
||||||
reblog: [
|
reblog: [
|
||||||
:application,
|
:application,
|
||||||
|
@ -130,7 +130,7 @@ class Status < ApplicationRecord
|
||||||
:conversation,
|
:conversation,
|
||||||
:status_stat,
|
:status_stat,
|
||||||
:preloadable_poll,
|
:preloadable_poll,
|
||||||
account: :account_stat,
|
account: [:account_stat, :user],
|
||||||
active_mentions: { account: :account_stat },
|
active_mentions: { account: :account_stat },
|
||||||
],
|
],
|
||||||
thread: { account: :account_stat }
|
thread: { account: :account_stat }
|
||||||
|
@ -354,7 +354,7 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
return if account_ids.empty?
|
return if account_ids.empty?
|
||||||
|
|
||||||
accounts = Account.where(id: account_ids).includes(:account_stat).index_by(&:id)
|
accounts = Account.where(id: account_ids).includes(:account_stat, :user).index_by(&:id)
|
||||||
|
|
||||||
cached_items.each do |item|
|
cached_items.each do |item|
|
||||||
item.account = accounts[item.account_id]
|
item.account = accounts[item.account_id]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
if String.method_defined?(:blank_as?)
|
if String.method_defined?(:blank_as?)
|
||||||
class String
|
class String
|
||||||
alias_method :blank?, :blank_as?
|
alias blank? blank_as?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Use this setup block to configure all options available in SimpleForm.
|
# Use this setup block to configure all options available in SimpleForm.
|
||||||
|
|
||||||
module AppendComponent
|
module AppendComponent
|
||||||
def append(wrapper_options = nil)
|
def append(_wrapper_options = nil)
|
||||||
@append ||= begin
|
@append ||= begin
|
||||||
options[:append].to_s.html_safe if options[:append].present?
|
options[:append].to_s.html_safe if options[:append].present?
|
||||||
end
|
end
|
||||||
|
@ -9,7 +9,7 @@ module AppendComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
module RecommendedComponent
|
module RecommendedComponent
|
||||||
def recommended(wrapper_options = nil)
|
def recommended(_wrapper_options = nil)
|
||||||
return unless options[:recommended]
|
return unless options[:recommended]
|
||||||
options[:label_text] = ->(raw_label_text, _required_label_text, _label_present) { safe_join([raw_label_text, ' ', content_tag(:span, I18n.t('simple_form.recommended'), class: 'recommended')]) }
|
options[:label_text] = ->(raw_label_text, _required_label_text, _label_present) { safe_join([raw_label_text, ' ', content_tag(:span, I18n.t('simple_form.recommended'), class: 'recommended')]) }
|
||||||
nil
|
nil
|
||||||
|
|
|
@ -75,7 +75,7 @@ module Twitter
|
||||||
# XMPP or magnet URIs an empty array will be returned.
|
# XMPP or magnet URIs an empty array will be returned.
|
||||||
#
|
#
|
||||||
# If a block is given then it will be called for each XMPP URI.
|
# If a block is given then it will be called for each XMPP URI.
|
||||||
def extract_extra_uris_with_indices(text, options = {}) # :yields: uri, start, end
|
def extract_extra_uris_with_indices(text, _options = {}) # :yields: uri, start, end
|
||||||
return [] unless text && text.index(":")
|
return [] unless text && text.index(":")
|
||||||
urls = []
|
urls = []
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,10 @@ en:
|
||||||
invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.
|
invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.
|
||||||
invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
|
invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
|
||||||
invalid_redirect_uri: The redirect uri included is not valid.
|
invalid_redirect_uri: The redirect uri included is not valid.
|
||||||
invalid_request: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
|
invalid_request:
|
||||||
|
missing_param: 'Missing required parameter: %{value}.'
|
||||||
|
request_not_authorized: Request need to be authorized. Required parameter for authorizing request is missing or invalid.
|
||||||
|
unknown: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
|
||||||
invalid_resource_owner: The provided resource owner credentials are not valid, or resource owner cannot be found
|
invalid_resource_owner: The provided resource owner credentials are not valid, or resource owner cannot be found
|
||||||
invalid_scope: The requested scope is invalid, unknown, or malformed.
|
invalid_scope: The requested scope is invalid, unknown, or malformed.
|
||||||
invalid_token:
|
invalid_token:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
# This file generated automatically from http://w3id.org/identity/v1
|
# This file generated automatically from http://w3id.org/identity/v1
|
||||||
require 'json/ld'
|
require 'json/ld'
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
# This file generated automatically from http://w3id.org/security/v1
|
# This file generated automatically from http://w3id.org/security/v1
|
||||||
require 'json/ld'
|
require 'json/ld'
|
||||||
|
|
|
@ -402,7 +402,7 @@ module Mastodon
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
parallelize_with_progress(target_account.followers.local) do |account|
|
processed, = parallelize_with_progress(target_account.followers.local) do |account|
|
||||||
UnfollowService.new.call(account, target_account)
|
UnfollowService.new.call(account, target_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ module Mastodon
|
||||||
result = entry.destroy
|
result = entry.destroy
|
||||||
|
|
||||||
if result
|
if result
|
||||||
processed += 1 + children_count
|
processed += children_count + 1
|
||||||
else
|
else
|
||||||
say("#{domain} could not be unblocked.", :red)
|
say("#{domain} could not be unblocked.", :red)
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
|
@ -55,7 +55,7 @@ module Paperclip
|
||||||
# If we don't have enough colors for accent and foreground, generate
|
# If we don't have enough colors for accent and foreground, generate
|
||||||
# new ones by manipulating the background color
|
# new ones by manipulating the background color
|
||||||
(2 - foreground_colors.size).times do |i|
|
(2 - foreground_colors.size).times do |i|
|
||||||
foreground_colors << lighten_or_darken(background_color, 35 + (15 * i))
|
foreground_colors << lighten_or_darken(background_color, 35 + (i * 15))
|
||||||
end
|
end
|
||||||
|
|
||||||
# We want the color with the highest contrast to background to be the foreground one,
|
# We want the color with the highest contrast to background to be the foreground one,
|
||||||
|
@ -147,7 +147,7 @@ module Paperclip
|
||||||
g = l.to_f
|
g = l.to_f
|
||||||
b = l.to_f # achromatic
|
b = l.to_f # achromatic
|
||||||
else
|
else
|
||||||
q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
q = l < 0.5 ? l * (s + 1) : l + s - l * s
|
||||||
p = 2 * l - q
|
p = 2 * l - q
|
||||||
r = hue_to_rgb(p, q, h + 1 / 3.0)
|
r = hue_to_rgb(p, q, h + 1 / 3.0)
|
||||||
g = hue_to_rgb(p, q, h)
|
g = hue_to_rgb(p, q, h)
|
||||||
|
|
|
@ -114,7 +114,7 @@
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
"mark-loader": "^0.1.6",
|
"mark-loader": "^0.1.6",
|
||||||
"marky": "^1.2.1",
|
"marky": "^1.2.1",
|
||||||
"mini-css-extract-plugin": "^1.3.4",
|
"mini-css-extract-plugin": "^1.3.5",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"npmlog": "^4.1.2",
|
"npmlog": "^4.1.2",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
|
@ -168,7 +168,7 @@
|
||||||
"uuid": "^8.3.1",
|
"uuid": "^8.3.1",
|
||||||
"webpack": "^4.46.0",
|
"webpack": "^4.46.0",
|
||||||
"webpack-assets-manifest": "^4.0.1",
|
"webpack-assets-manifest": "^4.0.1",
|
||||||
"webpack-bundle-analyzer": "^4.3.0",
|
"webpack-bundle-analyzer": "^4.4.0",
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-cli": "^3.3.12",
|
||||||
"webpack-merge": "^5.7.3",
|
"webpack-merge": "^5.7.3",
|
||||||
"wicg-inert": "^3.1.0"
|
"wicg-inert": "^3.1.0"
|
||||||
|
|
|
@ -353,10 +353,6 @@ describe ApplicationController, type: :controller do
|
||||||
expect(C.new.cache_collection(raw, Object)).to eq raw
|
expect(C.new.cache_collection(raw, Object)).to eq raw
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Notification' do
|
|
||||||
include_examples 'cacheable', :notification, Notification
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'Status' do
|
context 'Status' do
|
||||||
include_examples 'cacheable', :status, Status
|
include_examples 'cacheable', :status, Status
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,7 +51,7 @@ describe Settings::MigrationsController do
|
||||||
it_behaves_like 'authenticate user'
|
it_behaves_like 'authenticate user'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user is sign in' do
|
context 'when user is signed in' do
|
||||||
subject { post :create, params: { account_migration: { acct: acct, current_password: '12345678' } } }
|
subject { post :create, params: { account_migration: { acct: acct, current_password: '12345678' } } }
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, password: '12345678') }
|
let(:user) { Fabricate(:user, password: '12345678') }
|
||||||
|
@ -67,12 +67,45 @@ describe Settings::MigrationsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when acct is a current account' do
|
context 'when acct is the current account' do
|
||||||
let(:acct) { user.account }
|
let(:acct) { user.account }
|
||||||
|
|
||||||
it 'renders show' do
|
it 'renders show' do
|
||||||
is_expected.to render_template :show
|
is_expected.to render_template :show
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not update the moved account' do
|
||||||
|
expect(user.account.reload.moved_to_account_id).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when target account does not reference the account being moved from' do
|
||||||
|
let(:acct) { Fabricate(:account, also_known_as: []) }
|
||||||
|
|
||||||
|
it 'renders show' do
|
||||||
|
is_expected.to render_template :show
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update the moved account' do
|
||||||
|
expect(user.account.reload.moved_to_account_id).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a recent migration already exists ' do
|
||||||
|
let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
moved_to = Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)])
|
||||||
|
user.account.migrations.create!(acct: moved_to.acct)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders show' do
|
||||||
|
is_expected.to render_template :show
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update the moved account' do
|
||||||
|
expect(user.account.reload.moved_to_account_id).to be_nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ActivityPub::Activity::Move do
|
RSpec.describe ActivityPub::Activity::Move do
|
||||||
let(:follower) { Fabricate(:account) }
|
let(:follower) { Fabricate(:account) }
|
||||||
let(:old_account) { Fabricate(:account) }
|
let(:old_account) { Fabricate(:account, uri: 'https://example.org/alice', domain: 'example.org', protocol: :activitypub, inbox_url: 'https://example.org/inbox') }
|
||||||
let(:new_account) { Fabricate(:account) }
|
let(:new_account) { Fabricate(:account, uri: 'https://example.com/alice', domain: 'example.com', protocol: :activitypub, inbox_url: 'https://example.com/inbox', also_known_as: also_known_as) }
|
||||||
|
let(:also_known_as) { [old_account.uri] }
|
||||||
before do
|
let(:returned_account) { new_account }
|
||||||
follower.follow!(old_account)
|
|
||||||
|
|
||||||
old_account.update!(uri: 'https://example.org/alice', domain: 'example.org', protocol: :activitypub, inbox_url: 'https://example.org/inbox')
|
|
||||||
new_account.update!(uri: 'https://example.com/alice', domain: 'example.com', protocol: :activitypub, inbox_url: 'https://example.com/inbox', also_known_as: [old_account.uri])
|
|
||||||
|
|
||||||
stub_request(:post, 'https://example.org/inbox').to_return(status: 200)
|
|
||||||
stub_request(:post, 'https://example.com/inbox').to_return(status: 200)
|
|
||||||
|
|
||||||
service_stub = double
|
|
||||||
allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_stub)
|
|
||||||
allow(service_stub).to receive(:call).and_return(new_account)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:json) do
|
let(:json) do
|
||||||
{
|
{
|
||||||
|
@ -30,6 +18,17 @@ RSpec.describe ActivityPub::Activity::Move do
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
follower.follow!(old_account)
|
||||||
|
|
||||||
|
stub_request(:post, old_account.inbox_url).to_return(status: 200)
|
||||||
|
stub_request(:post, new_account.inbox_url).to_return(status: 200)
|
||||||
|
|
||||||
|
service_stub = double
|
||||||
|
allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_stub)
|
||||||
|
allow(service_stub).to receive(:call).and_return(returned_account)
|
||||||
|
end
|
||||||
|
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
subject { described_class.new(json, old_account) }
|
subject { described_class.new(json, old_account) }
|
||||||
|
|
||||||
|
@ -37,16 +36,70 @@ RSpec.describe ActivityPub::Activity::Move do
|
||||||
subject.perform
|
subject.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets moved account on old account' do
|
context 'when all conditions are met' do
|
||||||
expect(old_account.reload.moved_to_account_id).to eq new_account.id
|
it 'sets moved account on old account' do
|
||||||
|
expect(old_account.reload.moved_to_account_id).to eq new_account.id
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'makes followers unfollow old account' do
|
||||||
|
expect(follower.following?(old_account)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'makes followers follow-request the new account' do
|
||||||
|
expect(follower.requested?(new_account)).to be true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'makes followers unfollow old account' do
|
context "when the new account can't be resolved" do
|
||||||
expect(follower.following?(old_account)).to be false
|
let(:returned_account) { nil }
|
||||||
|
|
||||||
|
it 'does not set moved account on old account' do
|
||||||
|
expect(old_account.reload.moved_to_account_id).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not make followers unfollow old account' do
|
||||||
|
expect(follower.following?(old_account)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not make followers follow-request the new account' do
|
||||||
|
expect(follower.requested?(new_account)).to be false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'makes followers follow-request the new account' do
|
context 'when the new account does not references the old account' do
|
||||||
expect(follower.requested?(new_account)).to be true
|
let(:also_known_as) { [] }
|
||||||
|
|
||||||
|
it 'does not set moved account on old account' do
|
||||||
|
expect(old_account.reload.moved_to_account_id).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not make followers unfollow old account' do
|
||||||
|
expect(follower.following?(old_account)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not make followers follow-request the new account' do
|
||||||
|
expect(follower.requested?(new_account)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a Move has been recently processed' do
|
||||||
|
around do |example|
|
||||||
|
Redis.current.set("move_in_progress:#{old_account.id}", true, nx: true, ex: 7.days.seconds)
|
||||||
|
example.run
|
||||||
|
Redis.current.del("move_in_progress:#{old_account.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not set moved account on old account' do
|
||||||
|
expect(old_account.reload.moved_to_account_id).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not make followers unfollow old account' do
|
||||||
|
expect(follower.following?(old_account)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not make followers follow-request the new account' do
|
||||||
|
expect(follower.requested?(new_account)).to be false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -81,6 +81,6 @@ RSpec.describe ActivityPub::LinkedDataSignature do
|
||||||
options_hash = Digest::SHA256.hexdigest(canonicalize(options.merge('@context' => ActivityPub::LinkedDataSignature::CONTEXT)))
|
options_hash = Digest::SHA256.hexdigest(canonicalize(options.merge('@context' => ActivityPub::LinkedDataSignature::CONTEXT)))
|
||||||
document_hash = Digest::SHA256.hexdigest(canonicalize(document))
|
document_hash = Digest::SHA256.hexdigest(canonicalize(document))
|
||||||
to_be_verified = options_hash + document_hash
|
to_be_verified = options_hash + document_hash
|
||||||
Base64.strict_encode64(from_account.keypair.sign(OpenSSL::Digest::SHA256.new, to_be_verified))
|
Base64.strict_encode64(from_account.keypair.sign(OpenSSL::Digest.new('SHA256'), to_be_verified))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,47 +56,114 @@ RSpec.describe Notification, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.reload_stale_associations!' do
|
describe '.preload_cache_collection_target_statuses' do
|
||||||
context 'account_ids are empty' do
|
subject do
|
||||||
let(:cached_items) { [] }
|
described_class.preload_cache_collection_target_statuses(notifications) do |target_statuses|
|
||||||
|
# preload account for testing instead of using cache_collection
|
||||||
subject { described_class.reload_stale_associations!(cached_items) }
|
Status.preload(:account).where(id: target_statuses.map(&:id))
|
||||||
|
|
||||||
it 'returns nil' do
|
|
||||||
is_expected.to be nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'account_ids are present' do
|
context 'notifications are empty' do
|
||||||
|
let(:notifications) { [] }
|
||||||
|
|
||||||
|
it 'returns []' do
|
||||||
|
is_expected.to eq []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'notifications are present' do
|
||||||
before do
|
before do
|
||||||
allow(accounts_with_ids).to receive(:[]).with(stale_account1.id).and_return(account1)
|
notifications.each(&:reload)
|
||||||
allow(accounts_with_ids).to receive(:[]).with(stale_account2.id).and_return(account2)
|
|
||||||
allow(Account).to receive_message_chain(:where, :includes, :index_by).and_return(accounts_with_ids)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:cached_items) do
|
let(:mention) { Fabricate(:mention) }
|
||||||
|
let(:status) { Fabricate(:status) }
|
||||||
|
let(:reblog) { Fabricate(:status, reblog: Fabricate(:status)) }
|
||||||
|
let(:follow) { Fabricate(:follow) }
|
||||||
|
let(:follow_request) { Fabricate(:follow_request) }
|
||||||
|
let(:favourite) { Fabricate(:favourite) }
|
||||||
|
let(:poll) { Fabricate(:poll) }
|
||||||
|
|
||||||
|
let(:notifications) do
|
||||||
[
|
[
|
||||||
Fabricate(:notification, activity: Fabricate(:status)),
|
Fabricate(:notification, type: :mention, activity: mention),
|
||||||
Fabricate(:notification, activity: Fabricate(:follow)),
|
Fabricate(:notification, type: :status, activity: status),
|
||||||
|
Fabricate(:notification, type: :reblog, activity: reblog),
|
||||||
|
Fabricate(:notification, type: :follow, activity: follow),
|
||||||
|
Fabricate(:notification, type: :follow_request, activity: follow_request),
|
||||||
|
Fabricate(:notification, type: :favourite, activity: favourite),
|
||||||
|
Fabricate(:notification, type: :poll, activity: poll),
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:stale_account1) { cached_items[0].from_account }
|
it 'preloads target status' do
|
||||||
let(:stale_account2) { cached_items[1].from_account }
|
# mention
|
||||||
|
expect(subject[0].type).to eq :mention
|
||||||
|
expect(subject[0].association(:mention)).to be_loaded
|
||||||
|
expect(subject[0].mention.association(:status)).to be_loaded
|
||||||
|
|
||||||
let(:account1) { Fabricate(:account) }
|
# status
|
||||||
let(:account2) { Fabricate(:account) }
|
expect(subject[1].type).to eq :status
|
||||||
|
expect(subject[1].association(:status)).to be_loaded
|
||||||
|
|
||||||
let(:accounts_with_ids) { { account1.id => account1, account2.id => account2 } }
|
# reblog
|
||||||
|
expect(subject[2].type).to eq :reblog
|
||||||
|
expect(subject[2].association(:status)).to be_loaded
|
||||||
|
expect(subject[2].status.association(:reblog)).to be_loaded
|
||||||
|
|
||||||
it 'reloads associations' do
|
# follow: nothing
|
||||||
expect(cached_items[0].from_account).to be stale_account1
|
expect(subject[3].type).to eq :follow
|
||||||
expect(cached_items[1].from_account).to be stale_account2
|
expect(subject[3].target_status).to be_nil
|
||||||
|
|
||||||
described_class.reload_stale_associations!(cached_items)
|
# follow_request: nothing
|
||||||
|
expect(subject[4].type).to eq :follow_request
|
||||||
|
expect(subject[4].target_status).to be_nil
|
||||||
|
|
||||||
expect(cached_items[0].from_account).to be account1
|
# favourite
|
||||||
expect(cached_items[1].from_account).to be account2
|
expect(subject[5].type).to eq :favourite
|
||||||
|
expect(subject[5].association(:favourite)).to be_loaded
|
||||||
|
expect(subject[5].favourite.association(:status)).to be_loaded
|
||||||
|
|
||||||
|
# poll
|
||||||
|
expect(subject[6].type).to eq :poll
|
||||||
|
expect(subject[6].association(:poll)).to be_loaded
|
||||||
|
expect(subject[6].poll.association(:status)).to be_loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'replaces to cached status' do
|
||||||
|
# mention
|
||||||
|
expect(subject[0].type).to eq :mention
|
||||||
|
expect(subject[0].target_status.association(:account)).to be_loaded
|
||||||
|
expect(subject[0].target_status).to eq mention.status
|
||||||
|
|
||||||
|
# status
|
||||||
|
expect(subject[1].type).to eq :status
|
||||||
|
expect(subject[1].target_status.association(:account)).to be_loaded
|
||||||
|
expect(subject[1].target_status).to eq status
|
||||||
|
|
||||||
|
# reblog
|
||||||
|
expect(subject[2].type).to eq :reblog
|
||||||
|
expect(subject[2].target_status.association(:account)).to be_loaded
|
||||||
|
expect(subject[2].target_status).to eq reblog.reblog
|
||||||
|
|
||||||
|
# follow: nothing
|
||||||
|
expect(subject[3].type).to eq :follow
|
||||||
|
expect(subject[3].target_status).to be_nil
|
||||||
|
|
||||||
|
# follow_request: nothing
|
||||||
|
expect(subject[4].type).to eq :follow_request
|
||||||
|
expect(subject[4].target_status).to be_nil
|
||||||
|
|
||||||
|
# favourite
|
||||||
|
expect(subject[5].type).to eq :favourite
|
||||||
|
expect(subject[5].target_status.association(:account)).to be_loaded
|
||||||
|
expect(subject[5].target_status).to eq favourite.status
|
||||||
|
|
||||||
|
# poll
|
||||||
|
expect(subject[6].type).to eq :poll
|
||||||
|
expect(subject[6].target_status.association(:account)).to be_loaded
|
||||||
|
expect(subject[6].target_status).to eq poll.status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -7115,10 +7115,10 @@ min-indent@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||||
|
|
||||||
mini-css-extract-plugin@^1.3.4:
|
mini-css-extract-plugin@^1.3.5:
|
||||||
version "1.3.4"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.4.tgz#706e69632cdcdb8b15bf8e638442a0dba304a9c8"
|
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.5.tgz#252166e78879c106e0130f229d44e0cbdfcebed3"
|
||||||
integrity sha512-dNjqyeogUd8ucUgw5sxm1ahvSfSUgef7smbmATRSbDm4EmNx5kQA6VdUEhEeCKSjX6CTYjb5vxgMUvRjqP3uHg==
|
integrity sha512-tvmzcwqJJXau4OQE5vT72pRT18o2zF+tQJp8CWchqvfQnTlflkzS+dANYcRdyPRWUWRkfmeNTKltx0NZI/b5dQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
schema-utils "^3.0.0"
|
schema-utils "^3.0.0"
|
||||||
|
@ -11079,10 +11079,10 @@ webpack-assets-manifest@^4.0.1:
|
||||||
tapable "^1.0"
|
tapable "^1.0"
|
||||||
webpack-sources "^1.0"
|
webpack-sources "^1.0"
|
||||||
|
|
||||||
webpack-bundle-analyzer@^4.3.0:
|
webpack-bundle-analyzer@^4.4.0:
|
||||||
version "4.3.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz#2f3c0ca9041d5ee47fa418693cf56b4a518b578b"
|
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz#74013106e7e2b07cbd64f3a5ae847f7e814802c7"
|
||||||
integrity sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==
|
integrity sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^8.0.4"
|
acorn "^8.0.4"
|
||||||
acorn-walk "^8.0.0"
|
acorn-walk "^8.0.0"
|
||||||
|
|
Loading…
Reference in New Issue