diff options
233 files changed, 4502 insertions, 2341 deletions
diff --git a/.env.nanobox b/.env.nanobox index 7920c47b9..48204a6bf 100644 --- a/.env.nanobox +++ b/.env.nanobox @@ -35,6 +35,17 @@ PAPERCLIP_SECRET=$PAPERCLIP_SECRET SECRET_KEY_BASE=$SECRET_KEY_BASE OTP_SECRET=$OTP_SECRET +# VAPID keys (used for push notifications) +# You can generate the keys using the following command (first is the private key, second is the public one) +# You should only generate this once per instance. If you later decide to change it, all push subscription will +# be invalidated, requiring the users to access the website again to resubscribe. +# +# Generate with `rake mastodon:webpush:generate_vapid_key` task (`nanobox run bundle exec rake mastodon:webpush:generate_vapid_key`) +# +# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html +VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY +VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY + # Registrations # Single user mode will disable registrations and redirect frontpage to the first profile # SINGLE_USER_MODE=true @@ -62,7 +73,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io #SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt #SMTP_OPENSSL_VERIFY_MODE=peer #SMTP_ENABLE_STARTTLS_AUTO=true - +#SMTP_TLS=true # Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files. # PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system @@ -91,6 +102,23 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io # S3_ENDPOINT= # S3_SIGNATURE_VERSION= +# Swift (optional) +# SWIFT_ENABLED=true +# SWIFT_USERNAME= +# For Keystone V3, the value for SWIFT_TENANT should be the project name +# SWIFT_TENANT= +# SWIFT_PASSWORD= +# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid +# issues with token rate-limiting during high load. +# SWIFT_AUTH_URL= +# SWIFT_CONTAINER= +# SWIFT_OBJECT_URL= +# SWIFT_REGION= +# Defaults to 'default' +# SWIFT_DOMAIN_NAME= +# Defaults to 60 seconds. Set to 0 to disable +# SWIFT_CACHE_TTL= + # Optional alias for S3 if you want to use Cloudfront or Cloudflare in front # S3_CLOUDFRONT_HOST= diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..7cec57180 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eugen@zeonfederated.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Gemfile b/Gemfile index 7b359af1d..d0b7aaef1 100644 --- a/Gemfile +++ b/Gemfile @@ -14,8 +14,10 @@ gem 'pg', '~> 0.20' gem 'pghero', '~> 1.7' gem 'dotenv-rails', '~> 2.2' -gem 'aws-sdk', '~> 2.9' -gem 'fog-openstack', '~> 0.1' +gem 'fog-aws', '~> 1.4', require: false +gem 'fog-core', '~> 1.45' +gem 'fog-local', '~> 0.4', require: false +gem 'fog-openstack', '~> 0.1', require: false gem 'paperclip', '~> 5.1' gem 'paperclip-av-transcoder', '~> 0.6' @@ -38,14 +40,14 @@ gem 'http', '~> 2.2' gem 'http_accept_language', '~> 2.1' gem 'httplog', '~> 0.99' gem 'idn-ruby', require: 'idn' -gem 'kaminari', '~> 1.0' +gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' gem 'mime-types', '~> 3.1' -gem 'nokogiri', '~> 1.7' +gem 'nokogiri', '~> 1.8' gem 'nsa', '~> 0.2' -gem 'oj', '~> 3.0' +gem 'oj', '~> 3.3' gem 'ostatus2', '~> 2.0' -gem 'ox', '~> 2.5' +gem 'ox', '~> 2.8' gem 'pundit', '~> 1.1' gem 'rabl', '~> 0.13' gem 'rack-attack', '~> 5.0' @@ -75,15 +77,15 @@ gem 'json-ld-preloaded', '~> 2.2.1' gem 'rdf-normalize', '~> 0.3.1' group :development, :test do - gem 'fabrication', '~> 2.16' + gem 'fabrication', '~> 2.18' gem 'fuubar', '~> 2.2' gem 'i18n-tasks', '~> 0.9', require: false gem 'pry-rails', '~> 0.3' - gem 'rspec-rails', '~> 3.6' + gem 'rspec-rails', '~> 3.7' end group :test do - gem 'capybara', '~> 2.14' + gem 'capybara', '~> 2.15' gem 'climate_control', '~> 0.2' gem 'faker', '~> 1.7' gem 'microformats', '~> 4.0' @@ -91,13 +93,13 @@ group :test do gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.14', require: false gem 'webmock', '~> 3.0' - gem 'parallel_tests', '~> 2.14' + gem 'parallel_tests', '~> 2.17' end group :development do gem 'active_record_query_trace', '~> 1.5' gem 'annotate', '~> 2.7' - gem 'better_errors', '~> 2.1' + gem 'better_errors', '~> 2.4' gem 'binding_of_caller', '~> 0.7' gem 'bullet', '~> 5.5' gem 'letter_opener', '~> 1.4' @@ -105,15 +107,15 @@ group :development do gem 'rubocop', require: false gem 'brakeman', '~> 4.0', require: false gem 'bundler-audit', '~> 0.6', require: false - gem 'scss_lint', '~> 0.53', require: false + gem 'scss_lint', '~> 0.55', require: false - gem 'capistrano', '~> 3.8' - gem 'capistrano-rails', '~> 1.2' + gem 'capistrano', '~> 3.10' + gem 'capistrano-rails', '~> 1.3' gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-yarn', '~> 2.0' end group :production do - gem 'lograge', '~> 0.5' + gem 'lograge', '~> 0.7' gem 'redis-rails', '~> 5.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 14ed0d309..f9c69d538 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,25 +57,17 @@ GEM encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) - aws-sdk (2.10.46) - aws-sdk-resources (= 2.10.46) - aws-sdk-core (2.10.46) - aws-sigv4 (~> 1.0) - jmespath (~> 1.0) - aws-sdk-resources (2.10.46) - aws-sdk-core (= 2.10.46) - aws-sigv4 (1.0.2) bcrypt (3.1.11) - better_errors (2.3.0) + better_errors (2.4.0) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) - binding_of_caller (0.7.2) + binding_of_caller (0.7.3) debug_inspector (>= 0.0.1) - bootsnap (1.1.3) + bootsnap (1.1.5) msgpack (~> 1.0) brakeman (4.0.1) - browser (2.5.1) + browser (2.5.2) builder (3.2.3) bullet (5.6.1) activesupport (>= 3.0.0) @@ -83,23 +75,23 @@ GEM bundler-audit (0.6.0) bundler (~> 1.2) thor (~> 0.18) - capistrano (3.9.1) + capistrano (3.10.0) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.2.0) + capistrano-bundler (1.3.0) capistrano (~> 3.1) sshkit (~> 1.2) capistrano-rails (1.3.0) capistrano (~> 3.1) capistrano-bundler (~> 1.1) - capistrano-rbenv (2.1.1) + capistrano-rbenv (2.1.2) capistrano (~> 3.1) sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (2.15.1) + capybara (2.15.4) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) @@ -110,7 +102,7 @@ GEM activesupport charlock_holmes (0.7.5) chunky_png (1.3.8) - cld3 (3.2.0) + cld3 (3.2.1) ffi (>= 1.1.0, < 1.10.0) climate_control (0.2.0) cocaine (0.5.8) @@ -150,16 +142,21 @@ GEM thread thread_safe encryptor (3.0.0) - erubi (1.6.1) - et-orbi (1.0.5) + erubi (1.7.0) + et-orbi (1.0.8) tzinfo excon (0.59.0) execjs (2.7.0) - fabrication (2.16.3) + fabrication (2.18.0) faker (1.8.4) i18n (~> 0.5) fast_blank (1.0.0) ffi (1.9.18) + fog-aws (1.4.1) + fog-core (~> 1.38) + fog-json (~> 1.0) + fog-xml (~> 0.1) + ipaddress (~> 0.8) fog-core (1.45.0) builder excon (~> 0.58) @@ -167,15 +164,20 @@ GEM fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) - fog-openstack (0.1.21) + fog-local (0.4.0) + fog-core (~> 1.27) + fog-openstack (0.1.22) fog-core (>= 1.40) fog-json (>= 1.0) ipaddress (>= 0.8) + fog-xml (0.1.3) + fog-core + nokogiri (>= 1.5.11, < 2.0.0) formatador (0.2.5) fuubar (2.2.0) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - globalid (0.4.0) + globalid (0.4.1) activesupport (>= 4.2.0) goldfinger (2.0.1) addressable (~> 2.5) @@ -211,7 +213,8 @@ GEM httplog (0.99.7) colorize rack - i18n (0.8.6) + i18n (0.9.0) + concurrent-ruby (~> 1.0) i18n-tasks (0.9.18) activesupport (>= 4.0.2) ast (>= 2.1.0) @@ -225,29 +228,28 @@ GEM idn-ruby (0.1.0) ipaddress (0.8.3) iso-639 (0.2.8) - jmespath (1.3.1) json (2.1.0) - json-ld (2.1.5) + json-ld (2.1.7) multi_json (~> 1.12) - rdf (~> 2.2) + rdf (~> 2.2, >= 2.2.8) json-ld-preloaded (2.2.2) json-ld (~> 2.1, >= 2.1.5) multi_json (~> 1.11) rdf (~> 2.2) jsonapi-renderer (0.1.3) jwt (1.5.6) - kaminari (1.0.1) + kaminari (1.1.1) activesupport (>= 4.1.0) - kaminari-actionview (= 1.0.1) - kaminari-activerecord (= 1.0.1) - kaminari-core (= 1.0.1) - kaminari-actionview (1.0.1) + kaminari-actionview (= 1.1.1) + kaminari-activerecord (= 1.1.1) + kaminari-core (= 1.1.1) + kaminari-actionview (1.1.1) actionview - kaminari-core (= 1.0.1) - kaminari-activerecord (1.0.1) + kaminari-core (= 1.1.1) + kaminari-activerecord (1.1.1) activerecord - kaminari-core (= 1.0.1) - kaminari-core (1.0.1) + kaminari-core (= 1.1.1) + kaminari-core (1.1.1) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.4.1) @@ -257,18 +259,19 @@ GEM letter_opener (~> 1.0) railties (>= 3.2) link_header (0.0.8) - lograge (0.6.0) + lograge (0.7.1) actionpack (>= 4, < 5.2) activesupport (>= 4, < 5.2) railties (>= 4, < 5.2) request_store (~> 1.0) - loofah (2.0.3) + loofah (2.1.1) + crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.6.6) mime-types (>= 1.16, < 4) mario-redis-lock (1.2.0) redis (~> 3, >= 3.0.5) - method_source (0.8.2) + method_source (0.9.0) microformats (4.0.7) json nokogiri @@ -277,7 +280,7 @@ GEM mime-types-data (3.2016.0521) mimemagic (0.3.2) mini_mime (0.1.4) - mini_portile2 (2.2.0) + mini_portile2 (2.3.0) minitest (5.10.3) msgpack (1.1.0) multi_json (1.12.2) @@ -285,8 +288,8 @@ GEM net-ssh (>= 2.6.5) net-ssh (4.2.0) nio4r (2.1.0) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) nokogumbo (1.4.13) nokogiri nsa (0.2.4) @@ -294,15 +297,15 @@ GEM concurrent-ruby (~> 1.0.0) sidekiq (>= 3.5.0) statsd-ruby (~> 1.2.0) - oj (3.3.5) - openssl (2.0.5) + oj (3.3.9) + openssl (2.0.6) orm_adapter (0.5.0) ostatus2 (2.0.1) addressable (~> 2.4) http (~> 2.0) nokogiri (~> 1.6) openssl (~> 2.0) - ox (2.6.0) + ox (2.8.1) paperclip (5.1.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -313,19 +316,18 @@ GEM av (~> 0.9.0) paperclip (>= 2.5.2) parallel (1.12.0) - parallel_tests (2.15.0) + parallel_tests (2.17.0) parallel parser (2.4.0.0) ast (~> 2.2) pg (0.21.0) pghero (1.7.0) activerecord - pkg-config (1.2.7) + pkg-config (1.2.8) powerpack (0.1.1) - pry (0.10.4) + pry (0.11.2) coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) + method_source (~> 0.9.0) pry-rails (0.3.6) pry (>= 0.10.4) public_suffix (3.0.0) @@ -379,31 +381,31 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (2.2.2) rake - rake (12.1.0) - rdf (2.2.9) + rake (12.2.1) + rdf (2.2.11) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.3.2) rdf (~> 2.0) - redis (3.3.3) - redis-actionpack (5.0.1) + redis (3.3.5) + redis-actionpack (5.0.2) actionpack (>= 4.0, < 6) redis-rack (>= 1, < 3) - redis-store (>= 1.1.0, < 1.4.0) - redis-activesupport (5.0.3) + redis-store (>= 1.1.0, < 2) + redis-activesupport (5.0.4) activesupport (>= 3, < 6) - redis-store (~> 1.3.0) + redis-store (>= 1.3, < 2) redis-namespace (1.5.3) redis (~> 3.0, >= 3.0.4) - redis-rack (2.0.2) + redis-rack (2.0.3) rack (>= 1.5, < 3) - redis-store (>= 1.2, < 1.4) + redis-store (>= 1.2, < 2) redis-rails (5.0.2) redis-actionpack (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6) redis-store (>= 1.2, < 2) - redis-store (1.3.0) - redis (>= 2.2) + redis-store (1.4.1) + redis (>= 2.2, < 5) request_store (1.3.2) responders (2.4.0) actionpack (>= 4.2.0, < 5.3) @@ -411,27 +413,27 @@ GEM rotp (2.1.2) rqrcode (0.10.1) chunky_png (~> 1.0) - rspec-core (3.6.0) - rspec-support (~> 3.6.0) - rspec-expectations (3.6.0) + rspec-core (3.7.0) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-mocks (3.6.0) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-rails (3.6.1) + rspec-support (~> 3.7.0) + rspec-rails (3.7.1) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 3.6.0) - rspec-expectations (~> 3.6.0) - rspec-mocks (~> 3.6.0) - rspec-support (~> 3.6.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-support (~> 3.7.0) rspec-sidekiq (3.0.3) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) - rspec-support (3.6.0) - rubocop (0.50.0) + rspec-support (3.7.0) + rubocop (0.51.0) parallel (~> 1.10) parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) @@ -439,7 +441,7 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-oembed (0.12.0) - ruby-progressbar (1.8.3) + ruby-progressbar (1.9.0) rufus-scheduler (3.4.2) et-orbi (~> 1.0) safe_yaml (1.0.4) @@ -448,19 +450,19 @@ GEM nokogiri (>= 1.4.4) nokogumbo (~> 1.4.1) sass (3.4.25) - scss_lint (0.54.0) + scss_lint (0.55.0) rake (>= 0.9, < 13) sass (~> 3.4.20) - sidekiq (5.0.4) + sidekiq (5.0.5) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (~> 3.3, >= 3.3.3) + redis (>= 3.3.4, < 5) sidekiq-bulk (0.1.1) activesupport sidekiq - sidekiq-scheduler (2.1.9) - redis (~> 3) + sidekiq-scheduler (2.1.10) + redis (>= 3, < 5) rufus-scheduler (~> 3.2) sidekiq (>= 3) tilt (>= 1.4.0) @@ -477,7 +479,6 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - slop (3.6.0) sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -500,9 +501,9 @@ GEM tilt (2.0.8) twitter-text (1.14.7) unf (~> 0.1.0) - tzinfo (1.2.3) + tzinfo (1.2.4) thread_safe (~> 0.1) - tzinfo-data (1.2017.2) + tzinfo-data (1.2017.3) tzinfo (>= 1.0.0) uglifier (3.2.0) execjs (>= 0.3.0, < 3) @@ -517,7 +518,7 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - webpacker (3.0.1) + webpacker (3.0.2) activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) @@ -538,19 +539,18 @@ DEPENDENCIES active_record_query_trace (~> 1.5) addressable (~> 2.5) annotate (~> 2.7) - aws-sdk (~> 2.9) - better_errors (~> 2.1) + better_errors (~> 2.4) binding_of_caller (~> 0.7) bootsnap brakeman (~> 4.0) browser bullet (~> 5.5) bundler-audit (~> 0.6) - capistrano (~> 3.8) - capistrano-rails (~> 1.2) + capistrano (~> 3.10) + capistrano-rails (~> 1.3) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 2.14) + capybara (~> 2.15) charlock_holmes (~> 0.7.5) cld3 (~> 3.2.0) climate_control (~> 0.2) @@ -558,9 +558,12 @@ DEPENDENCIES devise-two-factor (~> 3.0) doorkeeper (~> 4.2) dotenv-rails (~> 2.2) - fabrication (~> 2.16) + fabrication (~> 2.18) faker (~> 1.7) fast_blank (~> 1.0) + fog-aws (~> 1.4) + fog-core (~> 1.45) + fog-local (~> 0.4) fog-openstack (~> 0.1) fuubar (~> 2.2) goldfinger (~> 2.0) @@ -574,22 +577,22 @@ DEPENDENCIES idn-ruby iso-639 json-ld-preloaded (~> 2.2.1) - kaminari (~> 1.0) + kaminari (~> 1.1) letter_opener (~> 1.4) letter_opener_web (~> 1.3) link_header (~> 0.0) - lograge (~> 0.5) + lograge (~> 0.7) mario-redis-lock (~> 1.2) microformats (~> 4.0) mime-types (~> 3.1) - nokogiri (~> 1.7) + nokogiri (~> 1.8) nsa (~> 0.2) - oj (~> 3.0) + oj (~> 3.3) ostatus2 (~> 2.0) - ox (~> 2.5) + ox (~> 2.8) paperclip (~> 5.1) paperclip-av-transcoder (~> 0.6) - parallel_tests (~> 2.14) + parallel_tests (~> 2.17) pg (~> 0.20) pghero (~> 1.7) pkg-config (~> 1.2) @@ -609,12 +612,12 @@ DEPENDENCIES redis-namespace (~> 1.5) redis-rails (~> 5.0) rqrcode (~> 0.10) - rspec-rails (~> 3.6) + rspec-rails (~> 3.7) rspec-sidekiq (~> 3.0) rubocop ruby-oembed (~> 0.12) sanitize (~> 4.4) - scss_lint (~> 0.53) + scss_lint (~> 0.55) sidekiq (~> 5.0) sidekiq-bulk (~> 0.1.1) sidekiq-scheduler (~> 2.1) diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb index 414a875d0..7f69a3363 100644 --- a/app/controllers/admin/account_moderation_notes_controller.rb +++ b/app/controllers/admin/account_moderation_notes_controller.rb @@ -1,31 +1,41 @@ # frozen_string_literal: true -class Admin::AccountModerationNotesController < Admin::BaseController - def create - @account_moderation_note = current_account.account_moderation_notes.new(resource_params) - if @account_moderation_note.save - @target_account = @account_moderation_note.target_account - redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.created_msg') - else - @account = @account_moderation_note.target_account - @moderation_notes = @account.targeted_moderation_notes.latest - render template: 'admin/accounts/show' +module Admin + class AccountModerationNotesController < BaseController + before_action :set_account_moderation_note, only: [:destroy] + + def create + authorize AccountModerationNote, :create? + + @account_moderation_note = current_account.account_moderation_notes.new(resource_params) + + if @account_moderation_note.save + redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.created_msg') + else + @account = @account_moderation_note.target_account + @moderation_notes = @account.targeted_moderation_notes.latest + + render template: 'admin/accounts/show' + end end - end - def destroy - @account_moderation_note = AccountModerationNote.find(params[:id]) - @target_account = @account_moderation_note.target_account - @account_moderation_note.destroy - redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') - end + def destroy + authorize @account_moderation_note, :destroy? + @account_moderation_note.destroy + redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') + end - private + private - def resource_params - params.require(:account_moderation_note).permit( - :content, - :target_account_id - ) + def resource_params + params.require(:account_moderation_note).permit( + :content, + :target_account_id + ) + end + + def set_account_moderation_note + @account_moderation_note = AccountModerationNote.find(params[:id]) + end end end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index ffa4dc850..0829bc769 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -2,29 +2,54 @@ module Admin class AccountsController < BaseController - before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload] + before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :enable, :disable, :memorialize] before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload] + before_action :require_local_account!, only: [:enable, :disable, :memorialize] def index + authorize :account, :index? @accounts = filtered_accounts.page(params[:page]) end def show + authorize @account, :show? @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) @moderation_notes = @account.targeted_moderation_notes.latest end def subscribe + authorize @account, :subscribe? Pubsubhubbub::SubscribeWorker.perform_async(@account.id) redirect_to admin_account_path(@account.id) end def unsubscribe + authorize @account, :unsubscribe? Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id) redirect_to admin_account_path(@account.id) end + def memorialize + authorize @account, :memorialize? + @account.memorialize! + redirect_to admin_account_path(@account.id) + end + + def enable + authorize @account.user, :enable? + @account.user.enable! + redirect_to admin_account_path(@account.id) + end + + def disable + authorize @account.user, :disable? + @account.user.disable! + redirect_to admin_account_path(@account.id) + end + def redownload + authorize @account, :redownload? + @account.reset_avatar! @account.reset_header! @account.save! @@ -42,6 +67,10 @@ module Admin redirect_to admin_account_path(@account.id) if @account.local? end + def require_local_account! + redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present? + end + def filtered_accounts AccountFilter.new(filter_params).results end diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 11fe326bc..db4839a8f 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -2,7 +2,9 @@ module Admin class BaseController < ApplicationController - before_action :require_admin! + include Authorization + + before_action :require_staff! layout 'admin' end diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb index 2542e21ee..c10b0ebee 100644 --- a/app/controllers/admin/confirmations_controller.rb +++ b/app/controllers/admin/confirmations_controller.rb @@ -2,15 +2,18 @@ module Admin class ConfirmationsController < BaseController + before_action :set_user + def create - account_user.confirm + authorize @user, :confirm? + @user.confirm! redirect_to admin_accounts_path end private - def account_user - Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) + def set_user + @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) end end end diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index cbd7abe95..693d28b1f 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -5,14 +5,18 @@ module Admin before_action :set_custom_emoji, except: [:index, :new, :create] def index - @custom_emojis = filtered_custom_emojis.page(params[:page]) + authorize :custom_emoji, :index? + @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]) end def new + authorize :custom_emoji, :create? @custom_emoji = CustomEmoji.new end def create + authorize :custom_emoji, :create? + @custom_emoji = CustomEmoji.new(resource_params) if @custom_emoji.save @@ -23,6 +27,8 @@ module Admin end def update + authorize @custom_emoji, :update? + if @custom_emoji.update(resource_params) redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg') else @@ -31,14 +37,17 @@ module Admin end def destroy + authorize @custom_emoji, :destroy? @custom_emoji.destroy redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg') end def copy - emoji = CustomEmoji.new(domain: nil, shortcode: @custom_emoji.shortcode, image: @custom_emoji.image) + authorize @custom_emoji, :copy? + + emoji = CustomEmoji.find_or_create_by(domain: nil, shortcode: @custom_emoji.shortcode) - if emoji.save + if emoji.update(image: @custom_emoji.image) flash[:notice] = I18n.t('admin.custom_emojis.copied_msg') else flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg') @@ -48,11 +57,13 @@ module Admin end def enable + authorize @custom_emoji, :enable? @custom_emoji.update!(disabled: false) redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg') end def disable + authorize @custom_emoji, :disable? @custom_emoji.update!(disabled: true) redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg') end diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 1ab620e03..e383dc831 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -5,14 +5,18 @@ module Admin before_action :set_domain_block, only: [:show, :destroy] def index + authorize :domain_block, :index? @domain_blocks = DomainBlock.page(params[:page]) end def new + authorize :domain_block, :create? @domain_block = DomainBlock.new end def create + authorize :domain_block, :create? + @domain_block = DomainBlock.new(resource_params) if @domain_block.save @@ -23,9 +27,12 @@ module Admin end end - def show; end + def show + authorize @domain_block, :show? + end def destroy + authorize @domain_block, :destroy? UnblockDomainService.new.call(@domain_block, retroactive_unblock?) redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb index 09275d5dc..01058bf46 100644 --- a/app/controllers/admin/email_domain_blocks_controller.rb +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -5,14 +5,18 @@ module Admin before_action :set_email_domain_block, only: [:show, :destroy] def index + authorize :email_domain_block, :index? @email_domain_blocks = EmailDomainBlock.page(params[:page]) end def new + authorize :email_domain_block, :create? @email_domain_block = EmailDomainBlock.new end def create + authorize :email_domain_block, :create? + @email_domain_block = EmailDomainBlock.new(resource_params) if @email_domain_block.save @@ -23,6 +27,7 @@ module Admin end def destroy + authorize @email_domain_block, :destroy? @email_domain_block.destroy redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 22f02e5d0..8ed0ea421 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -3,10 +3,12 @@ module Admin class InstancesController < BaseController def index + authorize :instance, :index? @instances = ordered_instances end def resubscribe + authorize :instance, :resubscribe? params.require(:by_domain) Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id)) redirect_to admin_instances_path diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 5a31adecf..4f66ce708 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -2,19 +2,20 @@ module Admin class ReportedStatusesController < BaseController - include Authorization - before_action :set_report before_action :set_status, only: [:update, :destroy] def create - @form = Form::StatusBatch.new(form_status_batch_params) - flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save + authorize :status, :update? + + @form = Form::StatusBatch.new(form_status_batch_params) + flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) end def update + authorize @status, :update? @status.update(status_params) redirect_to admin_report_path(@report) end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 226467739..745757ee8 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -5,14 +5,17 @@ module Admin before_action :set_report, except: [:index] def index + authorize :report, :index? @reports = filtered_reports.page(params[:page]) end def show + authorize @report, :show? @form = Form::StatusBatch.new end def update + authorize @report, :update? process_report redirect_to admin_report_path(@report) end diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb index 6db648403..00b590bf6 100644 --- a/app/controllers/admin/resets_controller.rb +++ b/app/controllers/admin/resets_controller.rb @@ -2,17 +2,18 @@ module Admin class ResetsController < BaseController - before_action :set_account + before_action :set_user def create - @account.user.send_reset_password_instructions + authorize @user, :reset_password? + @user.send_reset_password_instructions redirect_to admin_accounts_path end private - def set_account - @account = Account.find(params[:account_id]) + def set_user + @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) end end end diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb new file mode 100644 index 000000000..8f8685827 --- /dev/null +++ b/app/controllers/admin/roles_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Admin + class RolesController < BaseController + before_action :set_user + + def promote + authorize @user, :promote? + @user.promote! + redirect_to admin_account_path(@user.account_id) + end + + def demote + authorize @user, :demote? + @user.demote! + redirect_to admin_account_path(@user.account_id) + end + + private + + def set_user + @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) + end + end +end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index a2f86b8a9..e81290228 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -28,10 +28,13 @@ module Admin ).freeze def edit + authorize :settings, :show? @admin_settings = Form::AdminSettings.new end def update + authorize :settings, :update? + settings_params.each do |key, value| if UPLOAD_SETTINGS.include?(key) upload = SiteUpload.where(var: key).first_or_initialize(var: key) diff --git a/app/controllers/admin/silences_controller.rb b/app/controllers/admin/silences_controller.rb index 81a3008b9..01fb292de 100644 --- a/app/controllers/admin/silences_controller.rb +++ b/app/controllers/admin/silences_controller.rb @@ -5,11 +5,13 @@ module Admin before_action :set_account def create + authorize @account, :silence? @account.update(silenced: true) redirect_to admin_accounts_path end def destroy + authorize @account, :unsilence? @account.update(silenced: false) redirect_to admin_accounts_path end diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index b05000b16..b54a9b824 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -2,8 +2,6 @@ module Admin class StatusesController < BaseController - include Authorization - helper_method :current_params before_action :set_account @@ -12,24 +10,30 @@ module Admin PER_PAGE = 20 def index + authorize :status, :index? + @statuses = @account.statuses + if params[:media] account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct @statuses.merge!(Status.where(id: account_media_status_ids)) end - @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE) - @form = Form::StatusBatch.new + @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE) + @form = Form::StatusBatch.new end def create - @form = Form::StatusBatch.new(form_status_batch_params) - flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save + authorize :status, :update? + + @form = Form::StatusBatch.new(form_status_batch_params) + flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_account_statuses_path(@account.id, current_params) end def update + authorize @status, :update? @status.update(status_params) redirect_to admin_account_statuses_path(@account.id, current_params) end @@ -60,6 +64,7 @@ module Admin def current_params page = (params[:page] || 1).to_i + { media: params[:media], page: page > 1 && page, diff --git a/app/controllers/admin/subscriptions_controller.rb b/app/controllers/admin/subscriptions_controller.rb index 624a475a3..40500ef43 100644 --- a/app/controllers/admin/subscriptions_controller.rb +++ b/app/controllers/admin/subscriptions_controller.rb @@ -3,6 +3,7 @@ module Admin class SubscriptionsController < BaseController def index + authorize :subscription, :index? @subscriptions = ordered_subscriptions.page(requested_page) end diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb index 5d9048d94..778feea5e 100644 --- a/app/controllers/admin/suspensions_controller.rb +++ b/app/controllers/admin/suspensions_controller.rb @@ -5,12 +5,14 @@ module Admin before_action :set_account def create + authorize @account, :suspend? Admin::SuspensionWorker.perform_async(@account.id) redirect_to admin_accounts_path end def destroy - @account.update(suspended: false) + authorize @account, :unsuspend? + @account.unsuspend! redirect_to admin_accounts_path end diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb index 69c08f605..5a45d25cd 100644 --- a/app/controllers/admin/two_factor_authentications_controller.rb +++ b/app/controllers/admin/two_factor_authentications_controller.rb @@ -5,6 +5,7 @@ module Admin before_action :set_user def destroy + authorize @user, :disable_2fa? @user.disable_two_factor! redirect_to admin_accounts_path end diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index 9592cd4bd..22828217d 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -19,7 +19,7 @@ class Api::V1::ReportsController < Api::BaseController comment: report_params[:comment] ) - User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later } + User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later } render json: @report, serializer: REST::ReportSerializer end diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb index e183a71d7..d1b4e0402 100644 --- a/app/controllers/api/v1/search_controller.rb +++ b/app/controllers/api/v1/search_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Api::V1::SearchController < Api::BaseController + include Authorization + RESULTS_LIMIT = 10 before_action -> { doorkeeper_authorize! :read } @@ -9,12 +11,24 @@ class Api::V1::SearchController < Api::BaseController respond_to :json def index - @search = Search.new(search_results) + @search = Search.new(search) render json: @search, serializer: REST::SearchSerializer end private + def search + search_results.tap do |search| + search[:statuses].keep_if do |status| + begin + authorize status, :show? + rescue Mastodon::NotPermittedError + false + end + end + end + end + def search_results SearchService.new.call( params[:q], diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d5eca6ffb..f41a7f9be 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -18,6 +18,7 @@ class ApplicationController < ActionController::Base rescue_from ActionController::RoutingError, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity + rescue_from Mastodon::NotPermittedError, with: :forbidden before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? before_action :check_suspension, if: :user_signed_in? @@ -40,6 +41,10 @@ class ApplicationController < ActionController::Base redirect_to root_path unless current_user&.admin? end + def require_staff! + redirect_to root_path unless current_user&.staff? + end + def check_suspension forbidden if current_user.account.suspended? end diff --git a/app/controllers/concerns/authorization.rb b/app/controllers/concerns/authorization.rb index 7828fe48d..95a37e379 100644 --- a/app/controllers/concerns/authorization.rb +++ b/app/controllers/concerns/authorization.rb @@ -2,6 +2,7 @@ module Authorization extend ActiveSupport::Concern + include Pundit def pundit_user diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb index 09839f16e..ce2530c54 100644 --- a/app/controllers/settings/notifications_controller.rb +++ b/app/controllers/settings/notifications_controller.rb @@ -26,7 +26,7 @@ class Settings::NotificationsController < ApplicationController def user_settings_params params.require(:user).permit( notification_emails: %i(follow follow_request reblog favourite mention digest), - interactions: %i(must_be_follower must_be_following) + interactions: %i(must_be_follower must_be_following must_be_following_dm) ) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6d625e7db..7dfab1df1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -35,6 +35,11 @@ module ApplicationHelper Rails.env.production? ? site_title : "#{site_title} (Dev)" end + def can?(action, record) + return false if record.nil? + policy(record).public_send("#{action}?") + end + def fa_icon(icon, attributes = {}) class_names = attributes[:class]&.split(' ') || [] class_names << 'fa' @@ -43,6 +48,10 @@ module ApplicationHelper content_tag(:i, nil, attributes.merge(class: class_names.join(' '))) end + def custom_emoji_tag(custom_emoji) + image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:") + end + def opengraph(property, content) tag(:meta, content: content, property: property) end diff --git a/app/javascript/glitch/components/account/header.js b/app/javascript/glitch/components/account/header.js index c94fb0851..7bc1a2189 100644 --- a/app/javascript/glitch/components/account/header.js +++ b/app/javascript/glitch/components/account/header.js @@ -51,6 +51,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import emojify from '../../../mastodon/features/emoji/emoji'; import IconButton from '../../../mastodon/components/icon_button'; import Avatar from '../../../mastodon/components/avatar'; +import { me } from '../../../mastodon/initial_state'; // Our imports // import { processBio } from '../../util/bio_metadata'; @@ -88,7 +89,6 @@ export default class AccountHeader extends ImmutablePureComponent { static propTypes = { account : ImmutablePropTypes.map, - me : PropTypes.string.isRequired, onFollow : PropTypes.func.isRequired, intl : PropTypes.object.isRequired, }; @@ -102,7 +102,7 @@ The `render()` function is used to render our component. */ render () { - const { account, me, intl } = this.props; + const { account, intl } = this.props; /* diff --git a/app/javascript/glitch/components/status/action_bar.js b/app/javascript/glitch/components/status/action_bar.js index f4450d31b..34588b008 100644 --- a/app/javascript/glitch/components/status/action_bar.js +++ b/app/javascript/glitch/components/status/action_bar.js @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import RelativeTimestamp from '../../../mastodon/components/relative_timestamp'; import IconButton from '../../../mastodon/components/icon_button'; import DropdownMenuContainer from '../../../mastodon/containers/dropdown_menu_container'; +import { me } from '../../../mastodon/initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -50,7 +51,6 @@ export default class StatusActionBar extends ImmutablePureComponent { onEmbed: PropTypes.func, onMuteConversation: PropTypes.func, onPin: PropTypes.func, - me: PropTypes.string, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -59,7 +59,6 @@ export default class StatusActionBar extends ImmutablePureComponent { // evaluate to false. See react-immutable-pure-component for usage. updateOnProps = [ 'status', - 'me', 'withDismiss', ] @@ -119,7 +118,7 @@ export default class StatusActionBar extends ImmutablePureComponent { } render () { - const { status, me, intl, withDismiss } = this.props; + const { status, intl, withDismiss } = this.props; const mutingConversation = status.get('muted'); const anonymousAccess = !me; diff --git a/app/javascript/glitch/components/status/container.js b/app/javascript/glitch/components/status/container.js index 24261e763..0054abd14 100644 --- a/app/javascript/glitch/components/status/container.js +++ b/app/javascript/glitch/components/status/container.js @@ -140,12 +140,10 @@ Here are the props we pass to `<Status>`. return { status : status, account : account || ownProps.account, - me : state.getIn(['meta', 'me']), settings : state.get('local_settings'), prepend : prepend || ownProps.prepend, reblogModal : state.getIn(['meta', 'boost_modal']), deleteModal : state.getIn(['meta', 'delete_modal']), - autoPlayGif : state.getIn(['meta', 'auto_play_gif']), }; }; diff --git a/app/javascript/glitch/components/status/index.js b/app/javascript/glitch/components/status/index.js index 6bd95b051..33a9730e5 100644 --- a/app/javascript/glitch/components/status/index.js +++ b/app/javascript/glitch/components/status/index.js @@ -39,6 +39,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; // Mastodon imports // import scheduleIdleTask from '../../../mastodon/features/ui/util/schedule_idle_task'; +import { autoPlayGif } from '../../../mastodon/initial_state'; // Our imports // import StatusPrepend from './prepend'; @@ -89,9 +90,6 @@ few parts: These are our local settings, fetched from our store. We need this to determine how best to collapse our statuses, among other things. - - __`me` (`PropTypes.number`) :__ - This is the id of the currently-signed-in user. - - __`onFavourite`, `onReblog`, `onModalReblog`, `onDelete`, `onMention`, `onMute`, `onMuteConversation`, onBlock`, `onReport`, `onOpenMedia`, `onOpenVideo` (`PropTypes.func`) :__ @@ -103,9 +101,6 @@ few parts: reblogging and deleting statuses. They are used by the `onReblog` and `onDelete` functions, but we don't deal with them here. - - __`autoPlayGif` (`PropTypes.bool`) :__ - This tells the frontend whether or not to autoplay gifs! - - __`muted` (`PropTypes.bool`) :__ This has nothing to do with a user or conversation mute! "Muted" is what Mastodon internally calls the subdued look of statuses in the @@ -160,7 +155,6 @@ export default class Status extends ImmutablePureComponent { account : ImmutablePropTypes.map, settings : ImmutablePropTypes.map, notification : ImmutablePropTypes.map, - me : PropTypes.string, onFavourite : PropTypes.func, onReblog : PropTypes.func, onModalReblog : PropTypes.func, @@ -177,7 +171,6 @@ export default class Status extends ImmutablePureComponent { onOpenVideo : PropTypes.func, reblogModal : PropTypes.bool, deleteModal : PropTypes.bool, - autoPlayGif : PropTypes.bool, muted : PropTypes.bool, collapse : PropTypes.bool, prepend : PropTypes.string, @@ -211,9 +204,7 @@ to remember to specify it here. 'account', 'settings', 'prepend', - 'me', 'boostModal', - 'autoPlayGif', 'muted', 'collapse', 'notification', @@ -560,7 +551,6 @@ this operation are further explained in the code below. intersectionObserverWrapper, onOpenVideo, onOpenMedia, - autoPlayGif, notification, ...other } = this.props; diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js index 01bf8930b..3f40f6c2d 100644 --- a/app/javascript/mastodon/actions/pin_statuses.js +++ b/app/javascript/mastodon/actions/pin_statuses.js @@ -4,12 +4,13 @@ export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; +import { me } from '../initial_state'; + export function fetchPinnedStatuses() { return (dispatch, getState) => { dispatch(fetchPinnedStatusesRequest()); - const accountId = getState().getIn(['meta', 'me']); - api(getState).get(`/api/v1/accounts/${accountId}/statuses`, { params: { pinned: true } }).then(response => { + api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { dispatch(fetchPinnedStatusesSuccess(response.data, null)); }).catch(error => { dispatch(fetchPinnedStatusesFail(error)); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index a2e25c930..e60ddacd9 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -1,4 +1,4 @@ -import createStream from '../stream'; +import { connectStream } from '../stream'; import { updateTimeline, deleteFromTimelines, @@ -12,42 +12,19 @@ import { getLocale } from '../locales'; const { messages } = getLocale(); export function connectTimelineStream (timelineId, path, pollingRefresh = null) { - return (dispatch, getState) => { - const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']); - const accessToken = getState().getIn(['meta', 'access_token']); - const locale = getState().getIn(['meta', 'locale']); - let polling = null; - - const setupPolling = () => { - polling = setInterval(() => { - pollingRefresh(dispatch); - }, 20000); - }; - - const clearPolling = () => { - if (polling) { - clearInterval(polling); - polling = null; - } - }; - - const subscription = createStream(streamingAPIBaseURL, accessToken, path, { - connected () { - if (pollingRefresh) { - clearPolling(); - } + return connectStream (path, pollingRefresh, (dispatch, getState) => { + const locale = getState().getIn(['meta', 'locale']); + return { + onConnect() { dispatch(connectTimeline(timelineId)); }, - disconnected () { - if (pollingRefresh) { - setupPolling(); - } + onDisconnect() { dispatch(disconnectTimeline(timelineId)); }, - received (data) { + onReceive (data) { switch(data.event) { case 'update': dispatch(updateTimeline(timelineId, JSON.parse(data.payload))); @@ -60,26 +37,8 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null) break; } }, - - reconnected () { - if (pollingRefresh) { - clearPolling(); - pollingRefresh(dispatch); - } - dispatch(connectTimeline(timelineId)); - }, - - }); - - const disconnect = () => { - if (subscription) { - subscription.close(); - } - clearPolling(); }; - - return disconnect; - }; + }); } function refreshHomeTimelineAndNotification (dispatch) { diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 376e544fb..2c3a00064 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -7,6 +7,7 @@ import Permalink from './permalink'; import IconButton from './icon_button'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { me } from '../initial_state'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, @@ -23,7 +24,6 @@ export default class Account extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - me: PropTypes.string.isRequired, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -52,7 +52,7 @@ export default class Account extends ImmutablePureComponent { } render () { - const { account, me, intl, hidden } = this.props; + const { account, intl, hidden } = this.props; if (!account) { return <div />; @@ -82,7 +82,7 @@ export default class Account extends ImmutablePureComponent { } else if (muting) { let hidingNotificationsButton; if (muting.get('notifications')) { - hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username') })} onClick={this.handleUnmuteNotifications} />; + hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username') })} onClick={this.handleUnmuteNotifications} />; } else { hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username') })} onClick={this.handleMuteNotifications} />; } diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js index 76b0da12f..d0c1b049f 100644 --- a/app/javascript/mastodon/components/icon_button.js +++ b/app/javascript/mastodon/components/icon_button.js @@ -65,6 +65,7 @@ export default class IconButton extends React.PureComponent { expanded, icon, inverted, + flip, overlay, pressed, tabIndex, @@ -78,8 +79,8 @@ export default class IconButton extends React.PureComponent { overlayed: overlay, }); - const flipDeg = this.props.flip ? -180 : -360; - const rotateDeg = this.props.active ? flipDeg : 0; + const flipDeg = flip ? -180 : -360; + const rotateDeg = active ? flipDeg : 0; const motionDefaultStyle = { rotate: rotateDeg, @@ -93,6 +94,25 @@ export default class IconButton extends React.PureComponent { rotate: animate ? spring(rotateDeg, springOpts) : 0, }; + if (!animate) { + // Perf optimization: avoid unnecessary <Motion> components unless + // we actually need to animate. + return ( + <button + aria-label={title} + aria-pressed={pressed} + aria-expanded={expanded} + title={title} + className={classes} + onClick={this.handleClick} + style={style} + tabIndex={tabIndex} + > + <i className={`fa fa-fw fa-${icon}`} aria-hidden='true' /> + </button> + ); + } + return ( <Motion defaultStyle={motionDefaultStyle} style={motionStyle}> {({ rotate }) => diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 83cf8b871..5ed46dc93 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -9,6 +9,7 @@ import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from '../is_mobile'; import classNames from 'classnames'; +import { autoPlayGif } from '../initial_state'; const messages = defineMessages({ toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, @@ -26,11 +27,9 @@ class Item extends React.PureComponent { index: PropTypes.number.isRequired, size: PropTypes.number.isRequired, onClick: PropTypes.func.isRequired, - autoPlayGif: PropTypes.bool, }; static defaultProps = { - autoPlayGif: false, standalone: false, index: 0, size: 1, @@ -50,7 +49,7 @@ class Item extends React.PureComponent { } hoverToPlay () { - const { attachment, autoPlayGif } = this.props; + const { attachment } = this.props; return !autoPlayGif && attachment.get('type') === 'gifv'; } @@ -142,7 +141,7 @@ class Item extends React.PureComponent { </a> ); } else if (attachment.get('type') === 'gifv') { - const autoPlay = !isIOS() && this.props.autoPlayGif; + const autoPlay = !isIOS() && autoPlayGif; thumbnail = ( <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> @@ -184,11 +183,9 @@ export default class MediaGallery extends React.PureComponent { height: PropTypes.number.isRequired, onOpenMedia: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - autoPlayGif: PropTypes.bool, }; static defaultProps = { - autoPlayGif: false, standalone: false, }; @@ -264,9 +261,9 @@ export default class MediaGallery extends React.PureComponent { const size = media.take(4).size; if (this.isStandaloneEligible()) { - children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} autoPlayGif={this.props.autoPlayGif} />; + children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />; } else { - children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />); + children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />); } } diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index ab9d48510..71228ca6c 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import PropTypes from 'prop-types'; import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container'; import LoadMore from './load_more'; diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index b9be20033..5a01c0cdd 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -39,9 +39,6 @@ export default class Status extends ImmutablePureComponent { onBlock: PropTypes.func, onEmbed: PropTypes.func, onHeightChange: PropTypes.func, - me: PropTypes.string, - boostModal: PropTypes.bool, - autoPlayGif: PropTypes.bool, muted: PropTypes.bool, hidden: PropTypes.bool, onMoveUp: PropTypes.func, @@ -57,9 +54,6 @@ export default class Status extends ImmutablePureComponent { updateOnProps = [ 'status', 'account', - 'me', - 'boostModal', - 'autoPlayGif', 'muted', 'hidden', ] @@ -200,7 +194,7 @@ export default class Status extends ImmutablePureComponent { } else { media = ( <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} > - {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />} + {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />} </Bundle> ); } diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index af152cc32..35daf70b9 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -8,6 +8,7 @@ import IconButton from './icon_button'; import DropdownMenuContainer from '../containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { me } from '../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -50,7 +51,6 @@ export default class StatusActionBar extends ImmutablePureComponent { onEmbed: PropTypes.func, onMuteConversation: PropTypes.func, onPin: PropTypes.func, - me: PropTypes.string, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -59,7 +59,6 @@ export default class StatusActionBar extends ImmutablePureComponent { // evaluate to false. See react-immutable-pure-component for usage. updateOnProps = [ 'status', - 'me', 'withDismiss', ] @@ -119,7 +118,7 @@ export default class StatusActionBar extends ImmutablePureComponent { } render () { - const { status, me, intl, withDismiss } = this.props; + const { status, intl, withDismiss } = this.props; const mutingConversation = status.get('muted'); const anonymousAccess = !me; diff --git a/app/javascript/mastodon/containers/account_container.js b/app/javascript/mastodon/containers/account_container.js index 5728c878e..5a5136dd1 100644 --- a/app/javascript/mastodon/containers/account_container.js +++ b/app/javascript/mastodon/containers/account_container.js @@ -13,6 +13,7 @@ import { } from '../actions/accounts'; import { openModal } from '../actions/modal'; import { initMuteModal } from '../actions/mutes'; +import { unfollowModal } from '../initial_state'; const messages = defineMessages({ unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, @@ -23,8 +24,6 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, props) => ({ account: getAccount(state, props.id), - me: state.getIn(['meta', 'me']), - unfollowModal: state.getIn(['meta', 'unfollow_modal']), }); return mapStateToProps; @@ -34,7 +33,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow (account) { if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { - if (this.unfollowModal) { + if (unfollowModal) { dispatch(openModal('CONFIRM', { message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, confirm: intl.formatMessage(messages.unfollowConfirm), diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js index db452d03a..5ee1d2f14 100644 --- a/app/javascript/mastodon/containers/compose_container.js +++ b/app/javascript/mastodon/containers/compose_container.js @@ -6,15 +6,14 @@ import { hydrateStore } from '../actions/store'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import Compose from '../features/standalone/compose'; +import initialState from '../initial_state'; const { localeData, messages } = getLocale(); addLocaleData(localeData); const store = configureStore(); -const initialStateContainer = document.getElementById('initial-state'); -if (initialStateContainer !== null) { - const initialState = JSON.parse(initialStateContainer.textContent); +if (initialState) { store.dispatch(hydrateStore(initialState)); } diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index a7138e62d..d1710445b 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -4,23 +4,18 @@ import PropTypes from 'prop-types'; import configureStore from '../store/configureStore'; import { showOnboardingOnce } from '../actions/onboarding'; import { BrowserRouter, Route } from 'react-router-dom'; -import { ScrollContext } from 'react-router-scroll'; +import { ScrollContext } from 'react-router-scroll-4'; import UI from '../features/ui'; import { hydrateStore } from '../actions/store'; import { connectUserStream } from '../actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; +import initialState from '../initial_state'; const { localeData, messages } = getLocale(); addLocaleData(localeData); export const store = configureStore(); -const initialState = JSON.parse(document.getElementById('initial-state').textContent); -try { - initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings')); -} catch (e) { - initialState.local_settings = {}; -} const hydrateAction = hydrateStore(initialState); store.dispatch(hydrateAction); diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index e8821223d..b9c461f31 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -17,20 +17,18 @@ import { pin, unpin, } from '../actions/interactions'; -import { - blockAccount, - muteAccount, -} from '../actions/accounts'; +import { blockAccount } from '../actions/accounts'; import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses'; +import { initMuteModal } from '../actions/mutes'; import { initReport } from '../actions/reports'; import { openModal } from '../actions/modal'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { boostModal, deleteModal } from '../initial_state'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, - muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }, }); const makeMapStateToProps = () => { @@ -38,10 +36,6 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, props) => ({ status: getStatus(state, props.id), - me: state.getIn(['meta', 'me']), - boostModal: state.getIn(['meta', 'boost_modal']), - deleteModal: state.getIn(['meta', 'delete_modal']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); return mapStateToProps; @@ -61,7 +55,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (status.get('reblogged')) { dispatch(unreblog(status)); } else { - if (e.shiftKey || !this.boostModal) { + if (e.shiftKey || !boostModal) { this.onModalReblog(status); } else { dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); @@ -90,7 +84,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onDelete (status) { - if (!this.deleteModal) { + if (!deleteModal) { dispatch(deleteStatus(status.get('id'))); } else { dispatch(openModal('CONFIRM', { @@ -126,11 +120,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onMute (account) { - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.muteConfirm), - onConfirm: () => dispatch(muteAccount(account.get('id'))), - })); + dispatch(initMuteModal(account)); }, onMuteConversation (status) { diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js index 4be037955..e84c921ee 100644 --- a/app/javascript/mastodon/containers/timeline_container.js +++ b/app/javascript/mastodon/containers/timeline_container.js @@ -7,15 +7,14 @@ import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import PublicTimeline from '../features/standalone/public_timeline'; import HashtagTimeline from '../features/standalone/hashtag_timeline'; +import initialState from '../initial_state'; const { localeData, messages } = getLocale(); addLocaleData(localeData); const store = configureStore(); -const initialStateContainer = document.getElementById('initial-state'); -if (initialStateContainer !== null) { - const initialState = JSON.parse(initialStateContainer.textContent); +if (initialState) { store.dispatch(hydrateStore(initialState)); } diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index 718e7fbad..389296c42 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import { Link } from 'react-router-dom'; import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl'; +import { me } from '../../../initial_state'; const messages = defineMessages({ mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, @@ -28,7 +29,6 @@ export default class ActionBar extends React.PureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - me: PropTypes.string.isRequired, onFollow: PropTypes.func, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, @@ -47,7 +47,7 @@ export default class ActionBar extends React.PureComponent { } render () { - const { account, me, intl } = this.props; + const { account, intl } = this.props; let menu = []; let extraInfo = ''; diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 57678d162..b3a73a590 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -8,8 +8,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import IconButton from '../../../components/icon_button'; import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; -import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { autoPlayGif, me } from '../../../initial_state'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -17,19 +17,10 @@ const messages = defineMessages({ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, }); -const makeMapStateToProps = () => { - const mapStateToProps = state => ({ - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), - }); - - return mapStateToProps; -}; - class Avatar extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - autoPlayGif: PropTypes.bool.isRequired, }; state = { @@ -47,7 +38,7 @@ class Avatar extends ImmutablePureComponent { } render () { - const { account, autoPlayGif } = this.props; + const { account } = this.props; const { isHovered } = this.state; return ( @@ -74,20 +65,17 @@ class Avatar extends ImmutablePureComponent { } -@connect(makeMapStateToProps) @injectIntl export default class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, - me: PropTypes.string.isRequired, onFollow: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - autoPlayGif: PropTypes.bool.isRequired, }; render () { - const { account, me, intl } = this.props; + const { account, intl } = this.props; if (!account) { return null; @@ -127,7 +115,7 @@ export default class Header extends ImmutablePureComponent { return ( <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> <div> - <Avatar account={account} autoPlayGif={this.props.autoPlayGif} /> + <Avatar account={account} /> <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} /> <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 2a88addc4..a40722417 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -12,14 +12,13 @@ import { getAccountGallery } from '../../selectors'; import MediaItem from './components/media_item'; import HeaderContainer from '../account_timeline/containers/header_container'; import { FormattedMessage } from 'react-intl'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import LoadMore from '../../components/load_more'; const mapStateToProps = (state, props) => ({ medias: getAccountGallery(state, props.params.accountId), isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); @connect(mapStateToProps) @@ -31,7 +30,6 @@ export default class AccountGallery extends ImmutablePureComponent { medias: ImmutablePropTypes.list.isRequired, isLoading: PropTypes.bool, hasMore: PropTypes.bool, - autoPlayGif: PropTypes.bool, }; componentDidMount () { @@ -67,7 +65,7 @@ export default class AccountGallery extends ImmutablePureComponent { } render () { - const { medias, autoPlayGif, isLoading, hasMore } = this.props; + const { medias, isLoading, hasMore } = this.props; let loadMore = null; @@ -100,7 +98,6 @@ export default class AccountGallery extends ImmutablePureComponent { <MediaItem key={media.get('id')} media={media} - autoPlayGif={autoPlayGif} /> )} {loadMore} diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index b33df282f..9a087e922 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -10,7 +10,6 @@ export default class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, - me: PropTypes.string.isRequired, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, @@ -66,7 +65,7 @@ export default class Header extends ImmutablePureComponent { } render () { - const { account, me } = this.props; + const { account } = this.props; if (account === null) { return <MissingIndicator />; @@ -76,13 +75,11 @@ export default class Header extends ImmutablePureComponent { <div className='account-timeline__header'> <InnerHeader account={account} - me={me} onFollow={this.handleFollow} /> <ActionBar account={account} - me={me} onBlock={this.handleBlock} onMention={this.handleMention} onReblogToggle={this.handleReblogToggle} diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index 68c037e9b..b41eb19d4 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -15,6 +15,7 @@ import { initReport } from '../../../actions/reports'; import { openModal } from '../../../actions/modal'; import { blockDomain, unblockDomain } from '../../../actions/domain_blocks'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { unfollowModal } from '../../../initial_state'; const messages = defineMessages({ unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, @@ -27,8 +28,6 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, { accountId }) => ({ account: getAccount(state, accountId), - me: state.getIn(['meta', 'me']), - unfollowModal: state.getIn(['meta', 'unfollow_modal']), }); return mapStateToProps; @@ -38,7 +37,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow (account) { if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { - if (this.unfollowModal) { + if (unfollowModal) { dispatch(openModal('CONFIRM', { message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, confirm: intl.formatMessage(messages.unfollowConfirm), diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index e3b864aee..3ad370e32 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -16,7 +16,6 @@ const mapStateToProps = (state, props) => ({ statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()), isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']), hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']), - me: state.getIn(['meta', 'me']), }); @connect(mapStateToProps) @@ -28,7 +27,6 @@ export default class AccountTimeline extends ImmutablePureComponent { statusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, hasMore: PropTypes.bool, - me: PropTypes.string.isRequired, }; componentWillMount () { @@ -50,7 +48,7 @@ export default class AccountTimeline extends ImmutablePureComponent { } render () { - const { statusIds, isLoading, hasMore, me } = this.props; + const { statusIds, isLoading, hasMore } = this.props; if (!statusIds && isLoading) { return ( @@ -70,7 +68,6 @@ export default class AccountTimeline extends ImmutablePureComponent { statusIds={statusIds} isLoading={isLoading} hasMore={hasMore} - me={me} onScrollToBottom={this.handleScrollToBottom} /> </Column> diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js index e73d984a9..9199529dd 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import LoadingIndicator from '../../components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountContainer from '../../containers/account_container'; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 2da656fc0..13a41f23f 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -45,7 +45,6 @@ export default class ComposeForm extends ImmutablePureComponent { preselectDate: PropTypes.instanceOf(Date), is_submitting: PropTypes.bool, is_uploading: PropTypes.bool, - me: PropTypes.string, onChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, onClearSuggestions: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index dffa04ff0..dc8fc02ba 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -157,7 +157,6 @@ class EmojiPickerMenu extends React.PureComponent { intl: PropTypes.object.isRequired, skinTone: PropTypes.number.isRequired, onSkinTone: PropTypes.func.isRequired, - autoPlay: PropTypes.bool, }; static defaultProps = { @@ -235,7 +234,7 @@ class EmojiPickerMenu extends React.PureComponent { } render () { - const { loading, style, intl, custom_emojis, autoPlay, skinTone, frequentlyUsedEmojis } = this.props; + const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props; if (loading) { return <div style={{ width: 299 }} />; @@ -250,7 +249,7 @@ class EmojiPickerMenu extends React.PureComponent { perLine={8} emojiSize={22} sheetSize={32} - custom={buildCustomEmojis(custom_emojis, autoPlay)} + custom={buildCustomEmojis(custom_emojis)} color='' emoji='' set='twitter' @@ -284,7 +283,6 @@ export default class EmojiPickerDropdown extends React.PureComponent { static propTypes = { custom_emojis: ImmutablePropTypes.list, frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string), - autoPlay: PropTypes.bool, intl: PropTypes.object.isRequired, onPickEmoji: PropTypes.func.isRequired, onSkinTone: PropTypes.func.isRequired, @@ -346,7 +344,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { } render () { - const { intl, onPickEmoji, autoPlay, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props; + const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props; const title = intl.formatMessage(messages.emoji); const { active, loading } = this.state; @@ -366,7 +364,6 @@ export default class EmojiPickerDropdown extends React.PureComponent { loading={loading} onClose={this.onHideDropdown} onPick={onPickEmoji} - autoPlay={autoPlay} onSkinTone={onSkinTone} skinTone={skinTone} frequentlyUsedEmojis={frequentlyUsedEmojis} diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index ffa0a3442..dfe8241c6 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -23,7 +23,6 @@ const mapStateToProps = state => ({ preselectDate: state.getIn(['compose', 'preselectDate']), is_submitting: state.getIn(['compose', 'is_submitting']), is_uploading: state.getIn(['compose', 'is_uploading']), - me: state.getIn(['compose', 'me']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), settings: state.get('local_settings'), filesAttached: state.getIn(['compose', 'media_attachments']).size > 0, diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js index 699687c69..e6a535a5d 100644 --- a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js +++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js @@ -61,7 +61,6 @@ const getCustomEmojis = createSelector([ const mapStateToProps = state => ({ custom_emojis: getCustomEmojis(state), - autoPlay: state.getIn(['meta', 'auto_play_gif']), skinTone: state.getIn(['settings', 'skinTone']), frequentlyUsedEmojis: getFrequentlyUsedEmojis(state), }); diff --git a/app/javascript/mastodon/features/compose/containers/navigation_container.js b/app/javascript/mastodon/features/compose/containers/navigation_container.js index 8cc53c087..eb9f3ea45 100644 --- a/app/javascript/mastodon/features/compose/containers/navigation_container.js +++ b/app/javascript/mastodon/features/compose/containers/navigation_container.js @@ -1,9 +1,10 @@ import { connect } from 'react-redux'; import NavigationBar from '../components/navigation_bar'; +import { me } from '../../../initial_state'; const mapStateToProps = state => { return { - account: state.getIn(['accounts', state.getIn(['meta', 'me'])]), + account: state.getIn(['accounts', me]), }; }; diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js index 35eab5976..d34471a3e 100644 --- a/app/javascript/mastodon/features/compose/containers/warning_container.js +++ b/app/javascript/mastodon/features/compose/containers/warning_container.js @@ -3,9 +3,10 @@ import { connect } from 'react-redux'; import Warning from '../components/warning'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import { me } from '../../../initial_state'; const mapStateToProps = state => ({ - needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', state.getIn(['meta', 'me']), 'locked']), + needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']), }); const WarningWrapper = ({ needsLockWarning }) => { diff --git a/app/javascript/mastodon/features/compose/util/counter.js b/app/javascript/mastodon/features/compose/util/counter.js index e6d2487c5..700ba2163 100644 --- a/app/javascript/mastodon/features/compose/util/counter.js +++ b/app/javascript/mastodon/features/compose/util/counter.js @@ -5,5 +5,5 @@ const urlPlaceholder = 'xxxxxxxxxxxxxxxxxxxxxxx'; export function countableText(inputText) { return inputText .replace(urlRegex, urlPlaceholder) - .replace(/(?:^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '@$2'); + .replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3'); }; diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js index 636402172..372459c78 100644 --- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js +++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js @@ -57,5 +57,21 @@ describe('emoji', () => { it('does an emoji whose filename is irregular', () => { expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />'); }); + + it('avoid emojifying on invisible text', () => { + expect(emojify('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>')) + .toEqual('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>'); + expect(emojify('<span class="invisible">:luigi:</span>', { ':luigi:': { static_url: 'luigi.exe' } })) + .toEqual('<span class="invisible">:luigi:</span>'); + }); + + it('avoid emojifying on invisible text with nested tags', () => { + expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇')) + .toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />'); + expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇')) + .toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />'); + expect(emojify('<span class="invisible">😄<br/>😴</span>😇')) + .toEqual('<span class="invisible">😄<br/>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />'); + }); }); }); diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index b70fc2b37..0f005dd50 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -1,3 +1,4 @@ +import { autoPlayGif } from '../../initial_state'; import unicodeMapping from './emoji_unicode_mapping_light'; import Trie from 'substring-trie'; @@ -5,13 +6,13 @@ const trie = new Trie(Object.keys(unicodeMapping)); const assetHost = process.env.CDN_HOST || ''; -let allowAnimations = false; - const emojify = (str, customEmojis = {}) => { - let rtn = ''; + const tagCharsWithoutEmojis = '<&'; + const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&'; + let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0; for (;;) { let match, i = 0, tag; - while (i < str.length && (tag = '<&:'.indexOf(str[i])) === -1 && !(match = trie.search(str.slice(i)))) { + while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) { i += str.codePointAt(i) < 65536 ? 1 : 2; } let rend, replacement = ''; @@ -27,7 +28,7 @@ const emojify = (str, customEmojis = {}) => { // now got a replacee as ':shortname:' // if you want additional emoji handler, add statements below which set replacement and return true. if (shortname in customEmojis) { - const filename = allowAnimations ? customEmojis[shortname].url : customEmojis[shortname].static_url; + const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`; return true; } @@ -35,7 +36,26 @@ const emojify = (str, customEmojis = {}) => { })()) rend = ++i; } else if (tag >= 0) { // <, & rend = str.indexOf('>;'[tag], i + 1) + 1; - if (!rend) break; + if (!rend) { + break; + } + if (tag === 0) { + if (invisible) { + if (str[i + 1] === '/') { // closing tag + if (!--invisible) { + tagChars = tagCharsWithEmojis; + } + } else if (str[rend - 2] !== '/') { // opening tag + invisible++; + } + } else { + if (str.startsWith('<span class="invisible">', i)) { + // avoid emojifying on invisible text + invisible = 1; + tagChars = tagCharsWithoutEmojis; + } + } + } i = rend; } else { // matched to unicode emoji const { filename, shortCode } = unicodeMapping[match]; @@ -51,14 +71,12 @@ const emojify = (str, customEmojis = {}) => { export default emojify; -export const buildCustomEmojis = (customEmojis, overrideAllowAnimations = false) => { +export const buildCustomEmojis = (customEmojis) => { const emojis = []; - allowAnimations = overrideAllowAnimations; - customEmojis.forEach(emoji => { const shortcode = emoji.get('shortcode'); - const url = allowAnimations ? emoji.get('url') : emoji.get('static_url'); + const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url'); const name = shortcode.replace(':', ''); emojis.push({ diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js index c0cba952a..e5b834a74 100644 --- a/app/javascript/mastodon/features/emoji/emoji_compressed.js +++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js @@ -64,14 +64,14 @@ Object.keys(emojiMap).forEach(key => { Object.keys(emojiIndex.emojis).forEach(key => { const { native } = emojiIndex.emojis[key]; - const { short_names, search, unified } = emojiMartData.emojis[key]; + let { short_names, search, unified } = emojiMartData.emojis[key]; if (short_names[0] !== key) { throw new Error('The compresser expects the first short_code to be the ' + 'key. It may need to be rewritten if the emoji change such that this ' + 'is no longer the case.'); } - short_names.splice(0, 1); // first short name can be inferred from the key + short_names = short_names.slice(1); // first short name can be inferred from the key const searchData = [native, short_names, search]; if (unicodeToUnifiedName(native) !== unified) { diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js index 4dbfefd87..6f113beb4 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { fetchFavourites } from '../../actions/interactions'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 94109b151..1fa52d511 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountAuthorizeContainer from './containers/account_authorize_container'; diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js index 89445559f..f64ed7948 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.js @@ -8,7 +8,7 @@ import { fetchFollowers, expandFollowers, } from '../../actions/accounts'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index c34830276..a0c0fac05 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -8,7 +8,7 @@ import { fetchFollowing, expandFollowing, } from '../../actions/accounts'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 9b94b9830..2f7d9281e 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -8,6 +8,7 @@ import { openModal } from '../../actions/modal'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { me } from '../../initial_state'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -31,7 +32,7 @@ const messages = defineMessages({ }); const mapStateToProps = state => ({ - me: state.getIn(['accounts', state.getIn(['meta', 'me'])]), + myAccount: state.getIn(['accounts', me]), columns: state.getIn(['settings', 'columns']), }); @@ -41,7 +42,7 @@ export default class GettingStarted extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, - me: ImmutablePropTypes.map.isRequired, + myAccount: ImmutablePropTypes.map.isRequired, columns: ImmutablePropTypes.list, multiColumn: PropTypes.bool, dispatch: PropTypes.func.isRequired, @@ -57,7 +58,7 @@ export default class GettingStarted extends ImmutablePureComponent { } render () { - const { intl, me, columns, multiColumn } = this.props; + const { intl, myAccount, columns, multiColumn } = this.props; let navItems = []; @@ -88,7 +89,7 @@ export default class GettingStarted extends ImmutablePureComponent { <ColumnLink key='6' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />, ]); - if (me.get('locked')) { + if (myAccount.get('locked')) { navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); } diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js index 0f3b8e710..ae6ec343f 100644 --- a/app/javascript/mastodon/features/mutes/index.js +++ b/app/javascript/mastodon/features/mutes/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountContainer from '../../containers/account_container'; diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js index f1904786a..579d6aaa0 100644 --- a/app/javascript/mastodon/features/reblogs/index.js +++ b/app/javascript/mastodon/features/reblogs/index.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { fetchReblogs } from '../../actions/interactions'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 3e94f7446..8c6994a07 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -4,6 +4,7 @@ import IconButton from '../../../components/icon_button'; import ImmutablePropTypes from 'react-immutable-proptypes'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; +import { me } from '../../../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -36,7 +37,6 @@ export default class ActionBar extends React.PureComponent { onReport: PropTypes.func, onPin: PropTypes.func, onEmbed: PropTypes.func, - me: PropTypes.string.isRequired, intl: PropTypes.object.isRequired, }; @@ -80,7 +80,7 @@ export default class ActionBar extends React.PureComponent { } render () { - const { status, me, intl } = this.props; + const { status, intl } = this.props; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index d8547db36..85a030ea8 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -25,7 +25,6 @@ export default class DetailedStatus extends ImmutablePureComponent { settings: ImmutablePropTypes.map.isRequired, onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, - autoPlayGif: PropTypes.bool, }; handleAccountClick = (e) => { @@ -76,7 +75,6 @@ export default class DetailedStatus extends ImmutablePureComponent { fullwidth={settings.getIn(['media', 'fullwidth'])} height={250} onOpenMedia={this.props.onOpenMedia} - autoPlayGif={this.props.autoPlayGif} /> ); mediaIcon = 'picture-o'; diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index c40630a0a..e7ea046dd 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -1,6 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { fetchStatus } from '../../actions/statuses'; import MissingIndicator from '../../components/missing_indicator'; @@ -22,13 +23,15 @@ import { import { deleteStatus } from '../../actions/statuses'; import { initReport } from '../../actions/reports'; import { makeGetStatus } from '../../selectors'; -import { ScrollContainer } from 'react-router-scroll'; +import { ScrollContainer } from 'react-router-scroll-4'; import ColumnBackButton from '../../components/column_back_button'; import StatusContainer from '../../../glitch/components/status/container'; import { openModal } from '../../actions/modal'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; +import { boostModal, deleteModal } from '../../initial_state'; +import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -43,10 +46,6 @@ const makeMapStateToProps = () => { settings: state.get('local_settings'), ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]), descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]), - me: state.getIn(['meta', 'me']), - boostModal: state.getIn(['meta', 'boost_modal']), - deleteModal: state.getIn(['meta', 'delete_modal']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); return mapStateToProps; @@ -67,17 +66,21 @@ export default class Status extends ImmutablePureComponent { settings: ImmutablePropTypes.map.isRequired, ancestorsIds: ImmutablePropTypes.list, descendantsIds: ImmutablePropTypes.list, - me: PropTypes.string, - boostModal: PropTypes.bool, - deleteModal: PropTypes.bool, - autoPlayGif: PropTypes.bool, intl: PropTypes.object.isRequired, }; + state = { + fullscreen: false, + }; + componentWillMount () { this.props.dispatch(fetchStatus(this.props.params.statusId)); } + componentDidMount () { + attachFullscreenListener(this.onFullScreenChange); + } + componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this._scrolledIntoView = false; @@ -113,7 +116,7 @@ export default class Status extends ImmutablePureComponent { if (status.get('reblogged')) { this.props.dispatch(unreblog(status)); } else { - if (e.shiftKey || !this.props.boostModal) { + if (e.shiftKey || !boostModal) { this.handleModalReblog(status); } else { this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); @@ -124,7 +127,7 @@ export default class Status extends ImmutablePureComponent { handleDeleteClick = (status) => { const { dispatch, intl } = this.props; - if (!this.props.deleteModal) { + if (!deleteModal) { dispatch(deleteStatus(status.get('id'))); } else { dispatch(openModal('CONFIRM', { @@ -259,9 +262,18 @@ export default class Status extends ImmutablePureComponent { } } + componentWillUnmount () { + detachFullscreenListener(this.onFullScreenChange); + } + + onFullScreenChange = () => { + this.setState({ fullscreen: isFullscreen() }); + } + render () { let ancestors, descendants; - const { status, settings, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props; + const { status, settings, ancestorsIds, descendantsIds } = this.props; + const { fullscreen } = this.state; if (status === null) { return ( @@ -295,7 +307,7 @@ export default class Status extends ImmutablePureComponent { <ColumnBackButton /> <ScrollContainer scrollKey='thread'> - <div className='scrollable detailed-status__wrapper' ref={this.setRef}> + <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}> {ancestors} <HotKeys handlers={handlers}> @@ -303,15 +315,12 @@ export default class Status extends ImmutablePureComponent { <DetailedStatus status={status} settings={settings} - autoPlayGif={autoPlayGif} - me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} /> <ActionBar status={status} - me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.js index b5e83bb71..73e48cf09 100644 --- a/app/javascript/mastodon/features/ui/components/mute_modal.js +++ b/app/javascript/mastodon/features/ui/components/mute_modal.js @@ -2,6 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { injectIntl, FormattedMessage } from 'react-intl'; +import Toggle from 'react-toggle'; import Button from '../../../components/button'; import { closeModal } from '../../../actions/modal'; import { muteAccount } from '../../../actions/accounts'; @@ -80,12 +81,13 @@ export default class MuteModal extends React.PureComponent { values={{ name: <strong>@{account.get('acct')}</strong> }} /> </p> - <p> + <div> <label htmlFor='mute-modal__hide-notifications-checkbox'> <FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' /> - <input id='mute-modal__hide-notifications-checkbox' type='checkbox' checked={notifications} onChange={this.toggleNotifications} /> + {' '} + <Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} /> </label> - </p> + </div> </div> <div className='mute-modal__action-bar'> diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js index daf6b485c..1f9f0cd03 100644 --- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js +++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js @@ -14,6 +14,7 @@ import { List as ImmutableList, Map as ImmutableMap, } from 'immutable'; +import { me } from '../../../initial_state'; const noop = () => { }; @@ -43,11 +44,11 @@ PageOne.propTypes = { domain: PropTypes.string.isRequired, }; -const PageTwo = ({ me }) => ( +const PageTwo = ({ myAccount }) => ( <div className='onboarding-modal__page onboarding-modal__page-two'> <div className='figure non-interactive'> <div className='pseudo-drawer'> - <NavigationBar onClose={noop} account={me} /> + <NavigationBar onClose={noop} account={myAccount} /> </div> <ComposeForm text='Awoo! #introductions' @@ -73,10 +74,10 @@ const PageTwo = ({ me }) => ( ); PageTwo.propTypes = { - me: ImmutablePropTypes.map.isRequired, + myAccount: ImmutablePropTypes.map.isRequired, }; -const PageThree = ({ me }) => ( +const PageThree = ({ myAccount }) => ( <div className='onboarding-modal__page onboarding-modal__page-three'> <div className='figure non-interactive'> <Search @@ -88,7 +89,7 @@ const PageThree = ({ me }) => ( /> <div className='pseudo-drawer'> - <NavigationBar onClose={noop} account={me} /> + <NavigationBar onClose={noop} account={myAccount} /> </div> </div> @@ -98,7 +99,7 @@ const PageThree = ({ me }) => ( ); PageThree.propTypes = { - me: ImmutablePropTypes.map.isRequired, + myAccount: ImmutablePropTypes.map.isRequired, }; const PageFour = ({ domain, intl }) => ( @@ -166,7 +167,7 @@ PageSix.propTypes = { }; const mapStateToProps = state => ({ - me: state.getIn(['accounts', state.getIn(['meta', 'me'])]), + myAccount: state.getIn(['accounts', me]), admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]), domain: state.getIn(['meta', 'domain']), }); @@ -178,7 +179,7 @@ export default class OnboardingModal extends React.PureComponent { static propTypes = { onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - me: ImmutablePropTypes.map.isRequired, + myAccount: ImmutablePropTypes.map.isRequired, domain: PropTypes.string.isRequired, admin: ImmutablePropTypes.map, }; @@ -188,11 +189,11 @@ export default class OnboardingModal extends React.PureComponent { }; componentWillMount() { - const { me, admin, domain, intl } = this.props; + const { myAccount, admin, domain, intl } = this.props; this.pages = [ - <PageOne acct={me.get('acct')} domain={domain} />, - <PageTwo me={me} />, - <PageThree me={me} />, + <PageOne acct={myAccount.get('acct')} domain={domain} />, + <PageTwo myAccount={myAccount} />, + <PageThree myAccount={myAccount} />, <PageFour domain={domain} intl={intl} />, <PageSix admin={admin} domain={domain} />, ]; diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index ff29bfdd4..a0aec4403 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -4,13 +4,13 @@ import { scrollTopTimeline } from '../../../actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { debounce } from 'lodash'; +import { me } from '../../../initial_state'; const makeGetStatusIds = () => createSelector([ (state, { type }) => state.getIn(['settings', type], ImmutableMap()), (state, { type }) => state.getIn(['timelines', type, 'items'], ImmutableList()), (state) => state.get('statuses'), - (state) => state.getIn(['meta', 'me']), -], (columnSettings, statusIds, statuses, me) => { +], (columnSettings, statusIds, statuses) => { const rawRegex = columnSettings.getIn(['regex', 'body'], '').trim(); let regex = null; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 9f77ab5aa..fc51df163 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -40,18 +40,20 @@ import { PinnedStatuses, } from './util/async-components'; import { HotKeys } from 'react-hotkeys'; +import { me } from '../../initial_state'; +import { defineMessages, injectIntl } from 'react-intl'; // Dummy import, to make sure that <Status /> ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. import '../../../glitch/components/status'; +const messages = defineMessages({ + beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, +}); + const mapStateToProps = state => ({ - systemFontUi: state.getIn(['meta', 'system_font_ui']), - layout: state.getIn(['local_settings', 'layout']), - isWide: state.getIn(['local_settings', 'stretch']), - navbarUnder: state.getIn(['local_settings', 'navbar_under']), - me: state.getIn(['meta', 'me']), isComposing: state.getIn(['compose', 'is_composing']), + hasComposingText: state.getIn(['compose', 'text']) !== '', }); const keyMap = { @@ -82,6 +84,7 @@ const keyMap = { }; @connect(mapStateToProps) +@injectIntl @withRouter export default class UI extends React.Component { @@ -97,8 +100,9 @@ export default class UI extends React.Component { systemFontUi: PropTypes.bool, navbarUnder: PropTypes.bool, isComposing: PropTypes.bool, - me: PropTypes.string, + hasComposingText: PropTypes.bool, location: PropTypes.object, + intl: PropTypes.object.isRequired, }; state = { @@ -106,6 +110,17 @@ export default class UI extends React.Component { draggingOver: false, }; + handleBeforeUnload = (e) => { + const { intl, isComposing, hasComposingText } = this.props; + + if (isComposing && hasComposingText) { + // Setting returnValue to any string causes confirmation dialog. + // Many browsers no longer display this text to users, + // but we set user-friendly message for other browsers, e.g. Edge. + e.returnValue = intl.formatMessage(messages.beforeUnload); + } + } + handleResize = debounce(() => { // The cached heights are no longer accurate, invalidate this.props.dispatch(clearHeight()); @@ -180,6 +195,7 @@ export default class UI extends React.Component { } componentWillMount () { + window.addEventListener('beforeunload', this.handleBeforeUnload, false); window.addEventListener('resize', this.handleResize, { passive: true }); document.addEventListener('dragenter', this.handleDragEnter, false); document.addEventListener('dragover', this.handleDragOver, false); @@ -222,6 +238,7 @@ export default class UI extends React.Component { } componentWillUnmount () { + window.removeEventListener('beforeunload', this.handleBeforeUnload); window.removeEventListener('resize', this.handleResize); document.removeEventListener('dragenter', this.handleDragEnter); document.removeEventListener('dragover', this.handleDragOver); @@ -321,7 +338,7 @@ export default class UI extends React.Component { } handleHotkeyGoToProfile = () => { - this.context.router.history.push(`/accounts/${this.props.me}`); + this.context.router.history.push(`/accounts/${me}`); } handleHotkeyGoToBlocked = () => { diff --git a/app/javascript/mastodon/features/ui/util/optional_motion.js b/app/javascript/mastodon/features/ui/util/optional_motion.js index af6368738..df3a8b54a 100644 --- a/app/javascript/mastodon/features/ui/util/optional_motion.js +++ b/app/javascript/mastodon/features/ui/util/optional_motion.js @@ -1,56 +1,5 @@ -// Like react-motion's Motion, but checks to see if the user prefers -// reduced motion and uses a cross-fade in those cases. - -import React from 'react'; +import { reduceMotion } from '../../../initial_state'; +import ReducedMotion from './reduced_motion'; import Motion from 'react-motion/lib/Motion'; -import PropTypes from 'prop-types'; - -const stylesToKeep = ['opacity', 'backgroundOpacity']; - -let reduceMotion; - -const extractValue = (value) => { - // This is either an object with a "val" property or it's a number - return (typeof value === 'object' && value && 'val' in value) ? value.val : value; -}; - -class OptionalMotion extends React.Component { - - static propTypes = { - defaultStyle: PropTypes.object, - style: PropTypes.object, - children: PropTypes.func, - } - - render() { - - const { style, defaultStyle, children } = this.props; - - if (typeof reduceMotion !== 'boolean') { - // This never changes without a page reload, so we can just grab it - // once from the body classes as opposed to using Redux's connect(), - // which would unnecessarily update every state change - reduceMotion = document.body.classList.contains('reduce-motion'); - } - if (reduceMotion) { - Object.keys(style).forEach(key => { - if (stylesToKeep.includes(key)) { - return; - } - // If it's setting an x or height or scale or some other value, we need - // to preserve the end-state value without actually animating it - style[key] = defaultStyle[key] = extractValue(style[key]); - }); - } - - return ( - <Motion style={style} defaultStyle={defaultStyle}> - {children} - </Motion> - ); - } - -} - -export default OptionalMotion; +export default reduceMotion ? ReducedMotion : Motion; diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js index 86b30d488..43007ddc3 100644 --- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js +++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js @@ -7,11 +7,19 @@ import BundleColumnError from '../components/bundle_column_error'; import BundleContainer from '../containers/bundle_container'; // Small wrapper to pass multiColumn to the route components -export const WrappedSwitch = ({ multiColumn, children }) => ( - <Switch> - {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))} - </Switch> -); +export class WrappedSwitch extends React.PureComponent { + + render () { + const { multiColumn, children } = this.props; + + return ( + <Switch> + {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))} + </Switch> + ); + } + +} WrappedSwitch.propTypes = { multiColumn: PropTypes.bool, diff --git a/app/javascript/mastodon/features/ui/util/reduced_motion.js b/app/javascript/mastodon/features/ui/util/reduced_motion.js new file mode 100644 index 000000000..95519042b --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/reduced_motion.js @@ -0,0 +1,44 @@ +// Like react-motion's Motion, but reduces all animations to cross-fades +// for the benefit of users with motion sickness. +import React from 'react'; +import Motion from 'react-motion/lib/Motion'; +import PropTypes from 'prop-types'; + +const stylesToKeep = ['opacity', 'backgroundOpacity']; + +const extractValue = (value) => { + // This is either an object with a "val" property or it's a number + return (typeof value === 'object' && value && 'val' in value) ? value.val : value; +}; + +class ReducedMotion extends React.Component { + + static propTypes = { + defaultStyle: PropTypes.object, + style: PropTypes.object, + children: PropTypes.func, + } + + render() { + + const { style, defaultStyle, children } = this.props; + + Object.keys(style).forEach(key => { + if (stylesToKeep.includes(key)) { + return; + } + // If it's setting an x or height or scale or some other value, we need + // to preserve the end-state value without actually animating it + style[key] = defaultStyle[key] = extractValue(style[key]); + }); + + return ( + <Motion style={style} defaultStyle={defaultStyle}> + {children} + </Motion> + ); + } + +} + +export default ReducedMotion; diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js new file mode 100644 index 000000000..ef5d8b0ef --- /dev/null +++ b/app/javascript/mastodon/initial_state.js @@ -0,0 +1,21 @@ +const element = document.getElementById('initial-state'); +const initialState = element && function () { + const result = JSON.parse(element.textContent); + try { + result.local_settings = JSON.parse(localStorage.getItem('mastodon-settings')); + } catch (e) { + result.local_settings = {}; + } + return result; +}(); + +const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop]; + +export const reduceMotion = getMeta('reduce_motion'); +export const autoPlayGif = getMeta('auto_play_gif'); +export const unfollowModal = getMeta('unfollow_modal'); +export const boostModal = getMeta('boost_modal'); +export const deleteModal = getMeta('delete_modal'); +export const me = getMeta('me'); + +export default initialState; diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index d99dacd59..2919928af 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -63,7 +63,7 @@ "confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?", "confirmations.unfollow.confirm": "Unfollow", "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 퍼가세요.", + "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.", "embed.preview": "다음과 같이 표시됩니다:", "emoji_button.activity": "활동", "emoji_button.custom": "Custom", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 1e0849d95..d826423b5 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -31,7 +31,7 @@ "column.favourites": "Favorits", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", - "column.mutes": "Personas en silenci", + "column.mutes": "Personas rescondudas", "column.notifications": "Notificacions", "column.pins": "Tuts penjats", "column.public": "Flux public global", @@ -55,12 +55,12 @@ "confirmation_modal.cancel": "Anullar", "confirmations.block.confirm": "Blocar", "confirmations.block.message": "Sètz segur de voler blocar {name} ?", - "confirmations.delete.confirm": "Suprimir", - "confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?", + "confirmations.delete.confirm": "Escafar", + "confirmations.delete.message": "Sètz segur de voler escafar l’estatut ?", "confirmations.domain_block.confirm": "Amagar tot lo domeni", "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", - "confirmations.mute.confirm": "Metre en silenci", - "confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?", + "confirmations.mute.confirm": "Rescondre", + "confirmations.mute.message": "Sètz segur de voler rescondre {name} ?", "confirmations.unfollow.confirm": "Quitar de sègre", "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?", "embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.", @@ -135,7 +135,7 @@ "onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra instància, aquí {domain}. Lo flux federat mòstra los estatuts publics de la gent que los de {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.", "onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.", "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos", - "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum mai larg. Òm los apèla instàncias.", + "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per construire un malhum mai larg. Òm los apèla instàncias.", "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}", "onboarding.page_one.welcome": "Benvengut a Mastodon !", "onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.", @@ -159,11 +159,11 @@ "privacy.public.short": "Public", "privacy.unlisted.long": "Mostrar pas dins los fluxes publics", "privacy.unlisted.short": "Pas-listat", - "relative_time.days": "fa {number}j", - "relative_time.hours": "fa {number}h", + "relative_time.days": "fa {number} d", + "relative_time.hours": "fa {number} h", "relative_time.just_now": "ara", - "relative_time.minutes": "fa {number} minutas", - "relative_time.seconds": "fa {number} segondas", + "relative_time.minutes": "fa {number} min", + "relative_time.seconds": "fa {number} s", "reply_indicator.cancel": "Anullar", "report.placeholder": "Comentaris addicionals", "report.submit": "Mandar", @@ -197,7 +197,7 @@ "status.share": "Partejar", "status.show_less": "Tornar plegar", "status.show_more": "Desplegar", - "status.unmute_conversation": "Conversacions amb silenci levat", + "status.unmute_conversation": "Tornar mostrar la conversacion", "status.unpin": "Tirar del perfil", "tabs_bar.compose": "Compausar", "tabs_bar.federated_timeline": "Flux public global", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index cf76f1b1f..b23a5e69f 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -89,7 +89,7 @@ "follow_request.reject": "Odrzuć", "getting_started.appsshort": "Aplikacje", "getting_started.faq": "FAQ", - "getting_started.heading": "Naucz się korzystać", + "getting_started.heading": "Rozpocznij", "getting_started.open_source_notice": "Mastodon jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitHubie tutaj: {github}.", "getting_started.userguide": "Podręcznik użytkownika", "home.column_settings.advanced": "Zaawansowane", @@ -174,7 +174,7 @@ "search_popout.tips.status": "wpis", "search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów", "search_popout.tips.user": "użytkownik", - "search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}", + "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}", "standalone.public_title": "Spojrzenie w głąb…", "status.cannot_reblog": "Ten wpis nie może zostać podbity", "status.delete": "Usuń", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index ddb8b83f5..a04d1cc31 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -161,7 +161,7 @@ "privacy.unlisted.short": "Não listada", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", - "relative_time.just_now": "now", + "relative_time.just_now": "agora", "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "reply_indicator.cancel": "Cancelar", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 827c815cf..0f4bbb7c1 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -1,25 +1,25 @@ { "account.block": "屏蔽 @{name}", "account.block_domain": "隐藏一切来自 {domain} 的嘟文", - "account.disclaimer_full": "下列资料不一定完整。", + "account.disclaimer_full": "此处显示的信息可能不是全部内容。", "account.edit_profile": "修改个人资料", "account.follow": "关注", "account.followers": "关注者", - "account.follows": "正关注", - "account.follows_you": "关注你", + "account.follows": "正在关注", + "account.follows_you": "关注了你", "account.media": "媒体", "account.mention": "提及 @{name}", - "account.mute": "将 @{name} 静音", + "account.mute": "静音 @{name}", "account.posts": "嘟文", "account.report": "举报 @{name}", - "account.requested": "等待审批", - "account.share": "分享 @{name}的个人资料", - "account.unblock": "解除对 @{name} 的屏蔽", + "account.requested": "正在等待对方同意。点击以取消发送关注请求", + "account.share": "分享 @{name} 的个人资料", + "account.unblock": "不再屏蔽 @{name}", "account.unblock_domain": "不再隐藏 {domain}", "account.unfollow": "取消关注", - "account.unmute": "取消 @{name} 的静音", + "account.unmute": "不再静音 @{name}", "account.view_full_profile": "查看完整资料", - "boost_modal.combo": "如你想在下次路过时显示,请按{combo},", + "boost_modal.combo": "下次按住 {combo} 即可跳过此提示", "bundle_column_error.body": "载入组件出错。", "bundle_column_error.retry": "重试", "bundle_column_error.title": "网络错误", @@ -37,72 +37,72 @@ "column.public": "跨站公共时间轴", "column_back_button.label": "返回", "column_header.hide_settings": "隐藏设置", - "column_header.moveLeft_settings": "将栏左移", - "column_header.moveRight_settings": "将栏右移", + "column_header.moveLeft_settings": "将此栏左移", + "column_header.moveRight_settings": "将此栏右移", "column_header.pin": "固定", "column_header.show_settings": "显示设置", - "column_header.unpin": "取下", + "column_header.unpin": "取消固定", "column_subheading.navigation": "导航", "column_subheading.settings": "设置", - "compose_form.lock_disclaimer": "你的帐户没 {locked}. 任何人可以通过关注你来查看只有关注者可见的嘟文.", + "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以通过关注你来查看仅关注者可见的嘟文。", "compose_form.lock_disclaimer.lock": "被保护", "compose_form.placeholder": "在想啥?", "compose_form.publish": "嘟嘟", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive": "将媒体文件标示为“敏感内容”", - "compose_form.spoiler": "将部分文本藏于警告消息之后", - "compose_form.spoiler_placeholder": "敏感内容的警告消息", + "compose_form.sensitive": "将媒体文件标记为敏感内容", + "compose_form.spoiler": "折叠嘟文内容", + "compose_form.spoiler_placeholder": "折叠部分的警告消息", "confirmation_modal.cancel": "取消", "confirmations.block.confirm": "屏蔽", - "confirmations.block.message": "想好了,真的要屏蔽 {name}?", + "confirmations.block.message": "想好了,真的要屏蔽 {name}?", "confirmations.delete.confirm": "删除", - "confirmations.delete.message": "想好了,真的要删除这条嘟文?", + "confirmations.delete.message": "想好了,真的要删除这条嘟文?", "confirmations.domain_block.confirm": "隐藏整个网站", - "confirmations.domain_block.message": "你真的真的确定要隐藏整个 {domain} ?多数情况下,封锁或静音几个特定目标就好。", + "confirmations.domain_block.message": "你真的真的确定要隐藏整个 {domain}?多数情况下,屏蔽或静音几个特定的用户就应该能满足你的需要了。", "confirmations.mute.confirm": "静音", - "confirmations.mute.message": "想好了,真的要静音 {name}?", + "confirmations.mute.message": "想好了,真的要静音 {name}?", "confirmations.unfollow.confirm": "取消关注", - "confirmations.unfollow.message": "确定要取消关注 {name}吗?", - "embed.instructions": "要内嵌此嘟文,请将以下代码贴进你的网站。", - "embed.preview": "到时大概长这样:", + "confirmations.unfollow.message": "确定要取消关注 {name} 吗?", + "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。", + "embed.preview": "它会像这样显示出来:", "emoji_button.activity": "活动", - "emoji_button.custom": "Custom", + "emoji_button.custom": "自定义", "emoji_button.flags": "旗帜", "emoji_button.food": "食物和饮料", "emoji_button.label": "加入表情符号", "emoji_button.nature": "自然", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "木有这个表情符号!(╯°□°)╯︵ ┻━┻", "emoji_button.objects": "物体", "emoji_button.people": "人物", - "emoji_button.recent": "Frequently used", + "emoji_button.recent": "常用", "emoji_button.search": "搜索…", - "emoji_button.search_results": "Search results", + "emoji_button.search_results": "搜索结果", "emoji_button.symbols": "符号", - "emoji_button.travel": "旅途和地点", - "empty_column.community": "本站时间轴暂时未有内容,快嘟几个来抢头香啊!", - "empty_column.hashtag": "这个标签暂时未有内容。", + "emoji_button.travel": "旅行和地点", + "empty_column.community": "本站时间轴暂时没有内容,快嘟几个来抢头香啊!", + "empty_column.hashtag": "这个话题标签下暂时没有内容。", "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", "empty_column.home.public_timeline": "公共时间轴", - "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。", - "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务器实例的用户吧!你和本站、友站的交流,将决定这里出现的内容。", - "follow_request.authorize": "批准", + "empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。", + "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!", + "follow_request.authorize": "同意", "follow_request.reject": "拒绝", - "getting_started.appsshort": "Apps", - "getting_started.faq": "FAQ", + "getting_started.appsshort": "应用", + "getting_started.faq": "常见问题", "getting_started.heading": "开始使用", - "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。", + "getting_started.open_source_notice": "Mastodon 是一个开源软件。欢迎前往 GitHub({github})贡献代码或反馈问题。", "getting_started.userguide": "用户指南", - "home.column_settings.advanced": "高端", - "home.column_settings.basic": "基本", - "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤", - "home.column_settings.show_reblogs": "显示被转的嘟文", - "home.column_settings.show_replies": "显示回应嘟文", - "home.settings": "字段设置", + "home.column_settings.advanced": "高级设置", + "home.column_settings.basic": "基本设置", + "home.column_settings.filter_regex": "使用正则表达式(regex)过滤", + "home.column_settings.show_reblogs": "显示转嘟", + "home.column_settings.show_replies": "显示回复", + "home.settings": "栏目设置", "lightbox.close": "关闭", "lightbox.next": "下一步", "lightbox.previous": "上一步", "loading_indicator.label": "加载中……", - "media_gallery.toggle_visible": "打开或关上", + "media_gallery.toggle_visible": "切换显示/隐藏", "missing_indicator.label": "找不到内容", "navigation_bar.blocks": "被屏蔽的用户", "navigation_bar.community_timeline": "本站时间轴", @@ -119,9 +119,9 @@ "notification.follow": "{name} 开始关注你", "notification.mention": "{name} 提及你", "notification.reblog": "{name} 转嘟了你的嘟文", - "notifications.clear": "清空通知纪录", - "notifications.clear_confirmation": "你确定要清空通知纪录吗?", - "notifications.column_settings.alert": "显示桌面通知", + "notifications.clear": "清空通知列表", + "notifications.clear_confirmation": "你确定要清空通知列表吗?", + "notifications.column_settings.alert": "桌面通知", "notifications.column_settings.favourite": "你的嘟文被收藏:", "notifications.column_settings.follow": "关注你:", "notifications.column_settings.mention": "提及你:", @@ -132,90 +132,91 @@ "notifications.column_settings.sound": "播放音效", "onboarding.done": "出发!", "onboarding.next": "下一步", - "onboarding.page_five.public_timelines": "本站时间轴显示来自 {domain} 的所有人的公共嘟文。 跨站公共时间轴显示 {domain} 上的各位关注的来自所有Mastodon服务器实例上的人发表的公共嘟文。这些就是寻人好去处的公共时间轴啦。", - "onboarding.page_four.home": "你的主时间轴上是你关注的用户的嘟文.", - "onboarding.page_four.notifications": "如果你和他人产生了互动,便会出现在通知列上啦~", - "onboarding.page_one.federation": "Mastodon是由一系列独立的服务器共同打造的强大的社交网络,我们将这些独立但又相互连接的服务器叫做服务器实例。", - "onboarding.page_one.handle": "你在 {domain}, {handle} 就是你的完整帐户名称。", - "onboarding.page_one.welcome": "欢迎来到 Mastodon!", + "onboarding.page_five.public_timelines": "本站时间轴显示的是由本站({domain})用户发布的所有公开嘟文。跨站公共时间轴显示的的是由本站用户关注对象所发布的所有公开嘟文。这些就是寻人好去处的公共时间轴啦。", + "onboarding.page_four.home": "你的主页上的时间轴上显示的是你关注对象的嘟文。", + "onboarding.page_four.notifications": "如果有人与你互动,便会出现在通知栏中哦~", + "onboarding.page_one.federation": "Mastodon 是由一系列独立的服务器共同打造的强大的社交网络,我们将这些各自独立但又相互连接的服务器叫做实例。", + "onboarding.page_one.handle": "你在 {domain},{handle} 就是你的完整帐户名称。", + "onboarding.page_one.welcome": "欢迎来到 Mastodon!", "onboarding.page_six.admin": "{admin} 是你所在服务器实例的管理员.", - "onboarding.page_six.almost_done": "差不多了…", - "onboarding.page_six.appetoot": "嗷呜~", - "onboarding.page_six.apps_available": "也有适用于 iOS, Android 和其它平台的 {apps} 咯~", - "onboarding.page_six.github": "Mastodon 是自由的开放源代码软件。欢迎来 {github} 报告问题,提交功能请求,或者贡献代码 :-)", + "onboarding.page_six.almost_done": "差不多了……", + "onboarding.page_six.appetoot": "嗷呜~", + "onboarding.page_six.apps_available": "我们还有适用于 iOS、Android 和其它平台的{apps}哦~", + "onboarding.page_six.github": "Mastodon 是自由的开源软件。欢迎前往 {github} 反馈问题、提出对新功能的建议或贡献代码 :-)", "onboarding.page_six.guidelines": "社区指南", - "onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的 {guidelines}!", - "onboarding.page_six.various_app": "移动应用程序", - "onboarding.page_three.profile": "修改你的个人资料,比如头像、简介、和昵称等等。在那还可以找到其它首选项。", - "onboarding.page_three.search": "用搜索来找人和标签吧,比如 {illustration} 或者 {introductions}。想找其它服务器实例上的人,用完整帐户名称(用户名@域名)啦。", - "onboarding.page_two.compose": "从这里开始嘟!上面的按钮提供了上传图片,修改隐私设置和提示敏感内容等多种功能。.", - "onboarding.skip": "好啦好啦我知道啦", - "privacy.change": "调整隐私设置", - "privacy.direct.long": "只有提及的用户能看到", - "privacy.direct.short": "私人消息", - "privacy.private.long": "只有关注你用户能看到", - "privacy.private.short": "关注者", - "privacy.public.long": "在公共时间轴显示", - "privacy.public.short": "公共", - "privacy.unlisted.long": "公开,但不在公共时间轴显示", - "privacy.unlisted.short": "公开", - "relative_time.days": "{number}d", - "relative_time.hours": "{number}h", - "relative_time.just_now": "now", - "relative_time.minutes": "{number}m", - "relative_time.seconds": "{number}s", + "onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的{guidelines}!", + "onboarding.page_six.various_app": "移动设备应用", + "onboarding.page_three.profile": "你可以修改你的个人资料,比如头像、简介和昵称等偏好设置。", + "onboarding.page_three.search": "你可以通过搜索功能寻找用户和话题标签,比如{illustration}或者{introductions}。如果你想搜索其他实例上的用户,就需要输入完整帐户名称(用户名@域名)哦。", + "onboarding.page_two.compose": "在撰写栏中开始嘟嘟吧!下方的按钮分别用来上传图片,修改嘟文可见范围,以及添加警告信息。", + "onboarding.skip": "跳过", + "privacy.change": "设置嘟文可见范围", + "privacy.direct.long": "只有被提及的用户能看到", + "privacy.direct.short": "私信", + "privacy.private.long": "只有关注你的用户能看到", + "privacy.private.short": "仅关注者", + "privacy.public.long": "所有人可见,并会出现在公共时间轴上", + "privacy.public.short": "公开", + "privacy.unlisted.long": "所有人可见,但不会出现在公共时间轴上", + "privacy.unlisted.short": "不公开", + "relative_time.days": "{number} 天", + "relative_time.hours": "{number} 时", + "relative_time.just_now": "刚刚", + "relative_time.minutes": "{number} 分", + "relative_time.seconds": "{number} 秒", "reply_indicator.cancel": "取消", - "report.placeholder": "额外消息", + "report.placeholder": "附言", "report.submit": "提交", - "report.target": "Reporting", + "report.target": "举报 {target}", "search.placeholder": "搜索", - "search_popout.search_format": "Advanced search format", - "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", - "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.total": "{count, number} {count, plural, one {result} other {results}}", + "search_popout.search_format": "高级搜索格式", + "search_popout.tips.hashtag": "话题标签", + "search_popout.tips.status": "嘟文", + "search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签", + "search_popout.tips.user": "用户", + "search_results.total": "共 {count, number} 个结果", "standalone.public_title": "大家都在干啥?", - "status.cannot_reblog": "没法转嘟这条嘟文啦……", + "status.cannot_reblog": "无法转嘟这条嘟文", "status.delete": "删除", "status.embed": "嵌入", "status.favourite": "收藏", "status.load_more": "加载更多", "status.media_hidden": "隐藏媒体内容", "status.mention": "提及 @{name}", - "status.more": "More", - "status.mute_conversation": "静音对话", + "status.more": "更多", + "status.mute_conversation": "静音此对话", "status.open": "展开嘟文", - "status.pin": "置顶到资料", + "status.pin": "在个人资料页面置顶", "status.reblog": "转嘟", - "status.reblogged_by": "{name} 转嘟", - "status.reply": "回应", - "status.replyAll": "回应整串", + "status.reblogged_by": "{name} 转嘟了", + "status.reply": "回复", + "status.replyAll": "回复所有人", "status.report": "举报 @{name}", "status.sensitive_toggle": "点击显示", "status.sensitive_warning": "敏感内容", - "status.share": "Share", - "status.show_less": "减少显示", - "status.show_more": "显示更多", - "status.unmute_conversation": "解禁对话", - "status.unpin": "解除置顶", + "status.share": "分享", + "status.show_less": "隐藏内容", + "status.show_more": "显示内容", + "status.unmute_conversation": "不再静音此对话", + "status.unpin": "在个人资料页面取消置顶", "tabs_bar.compose": "撰写", "tabs_bar.federated_timeline": "跨站", "tabs_bar.home": "主页", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", - "upload_area.title": "将文件拖放至此上传", + "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会被丢弃。", + "upload_area.title": "将文件拖放到此处开始上传", "upload_button.label": "上传媒体文件", - "upload_form.description": "Describe for the visually impaired", - "upload_form.undo": "还原", - "upload_progress.label": "上传中……", - "video.close": "关闭影片", + "upload_form.description": "为视觉障碍人士添加文字说明", + "upload_form.undo": "取消上传", + "upload_progress.label": "上传中…", + "video.close": "关闭视频", "video.exit_fullscreen": "退出全屏", - "video.expand": "展开影片", + "video.expand": "展开视频", "video.fullscreen": "全屏", - "video.hide": "隐藏影片", + "video.hide": "隐藏视频", "video.mute": "静音", "video.pause": "暂停", "video.play": "播放", - "video.unmute": "解除静音" + "video.unmute": "取消静音" } diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 251a40144..5d0acbd60 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -33,6 +33,7 @@ import { TIMELINE_DELETE } from '../actions/timelines'; import { STORE_HYDRATE } from '../actions/store'; import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; import uuid from '../uuid'; +import { me } from '../initial_state'; const initialState = ImmutableMap({ mounted: false, @@ -54,7 +55,6 @@ const initialState = ImmutableMap({ media_attachments: ImmutableList(), suggestion_token: null, suggestions: ImmutableList(), - me: null, default_advanced_options: ImmutableMap({ do_not_federate: false, }), @@ -77,7 +77,6 @@ const initialState = ImmutableMap({ function statusToTextMentions(state, status) { let set = ImmutableOrderedSet([]); - let me = state.get('me'); if (status.getIn(['account', 'id']) !== me) { set = set.add(`@${status.getIn(['account', 'acct'])} `); diff --git a/app/javascript/mastodon/reducers/custom_emojis.js b/app/javascript/mastodon/reducers/custom_emojis.js index f2a8ca5d2..307bcc7dc 100644 --- a/app/javascript/mastodon/reducers/custom_emojis.js +++ b/app/javascript/mastodon/reducers/custom_emojis.js @@ -8,7 +8,7 @@ const initialState = ImmutableList(); export default function custom_emojis(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - emojiSearch('', { custom: buildCustomEmojis(action.state.get('custom_emojis', []), action.state.getIn(['meta', 'auto_play_gif'], false)) }); + emojiSearch('', { custom: buildCustomEmojis(action.state.get('custom_emojis', [])) }); return action.state.get('custom_emojis'); default: return state; diff --git a/app/javascript/mastodon/reducers/meta.js b/app/javascript/mastodon/reducers/meta.js index 119ef9d8f..36a5a1c35 100644 --- a/app/javascript/mastodon/reducers/meta.js +++ b/app/javascript/mastodon/reducers/meta.js @@ -4,7 +4,6 @@ import { Map as ImmutableMap } from 'immutable'; const initialState = ImmutableMap({ streaming_api_base_url: null, access_token: null, - me: null, }); export default function meta(state = initialState, action) { diff --git a/app/javascript/mastodon/reducers/mutes.js b/app/javascript/mastodon/reducers/mutes.js index 496e6846a..a96232dbd 100644 --- a/app/javascript/mastodon/reducers/mutes.js +++ b/app/javascript/mastodon/reducers/mutes.js @@ -22,7 +22,7 @@ export default function mutes(state = initialState, action) { state.setIn(['new', 'notifications'], true); }); case MUTES_TOGGLE_HIDE_NOTIFICATIONS: - return state.setIn(['new', 'notifications'], !state.getIn(['new', 'notifications'])); + return state.updateIn(['new', 'notifications'], (old) => !old); default: return state; } diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 4b36082b2..36c68ffc5 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -1,5 +1,66 @@ import WebSocketClient from 'websocket.js'; +export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) { + return (dispatch, getState) => { + const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']); + const accessToken = getState().getIn(['meta', 'access_token']); + const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState); + let polling = null; + + const setupPolling = () => { + polling = setInterval(() => { + pollingRefresh(dispatch); + }, 20000); + }; + + const clearPolling = () => { + if (polling) { + clearInterval(polling); + polling = null; + } + }; + + const subscription = getStream(streamingAPIBaseURL, accessToken, path, { + connected () { + if (pollingRefresh) { + clearPolling(); + } + onConnect(); + }, + + disconnected () { + if (pollingRefresh) { + setupPolling(); + } + onDisconnect(); + }, + + received (data) { + onReceive(data); + }, + + reconnected () { + if (pollingRefresh) { + clearPolling(); + pollingRefresh(dispatch); + } + onConnect(); + }, + + }); + + const disconnect = () => { + if (subscription) { + subscription.close(); + } + clearPolling(); + }; + + return disconnect; + }; +} + + export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) { const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`); diff --git a/app/javascript/packs/custom.js b/app/javascript/packs/custom.js deleted file mode 100644 index 4db2964f6..000000000 --- a/app/javascript/packs/custom.js +++ /dev/null @@ -1 +0,0 @@ -require('../styles/custom.scss'); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 2506bbe62..6a6d1bdca 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -253,6 +253,15 @@ width: 0; height: 0; position: absolute; + + img, + svg { + margin: 0 !important; + border: 0 !important; + padding: 0 !important; + width: 0 !important; + height: 0 !important; + } } .ellipsis { @@ -555,6 +564,7 @@ font-weight: 400; overflow: visible; white-space: pre-wrap; + padding-top: 5px; &.status__content--with-spoiler { white-space: normal; @@ -565,8 +575,9 @@ } .emojione { - width: 18px; - height: 18px; + width: 20px; + height: 20px; + margin: -5px 0 0; } p { @@ -671,7 +682,7 @@ outline: 0; background: lighten($ui-base-color, 4%); - &.status-direct { + .status.status-direct { background: lighten($ui-base-color, 12%); } @@ -690,6 +701,12 @@ border-bottom: 1px solid lighten($ui-base-color, 8%); cursor: default; + @supports (-ms-overflow-style: -ms-autohiding-scrollbar) { + // Add margin to avoid Edge auto-hiding scrollbar appearing over content. + // On Edge 16 this is 16px and Edge <=15 it's 12px, so aim for 16px. + padding-right: 26px; // 10px + 16px + } + @keyframes fade { 0% { opacity: 0; } 100% { opacity: 1; } @@ -920,8 +937,9 @@ line-height: 24px; .emojione { - width: 22px; - height: 22px; + width: 24px; + height: 24px; + margin: -5px 0 0; } } @@ -2908,7 +2926,7 @@ button.icon-button.active i.fa-retweet { color: $primary-text-color; position: absolute; top: 10px; - right: 10px; + left: 10px; opacity: 0.7; display: inline-block; vertical-align: top; @@ -2923,7 +2941,7 @@ button.icon-button.active i.fa-retweet { .account--action-button { position: absolute; top: 10px; - left: 20px; + right: 20px; } .setting-toggle { @@ -3973,6 +3991,14 @@ button.icon-button.active i.fa-retweet { } } +.mute-modal { + line-height: 24px; +} + +.mute-modal .react-toggle { + vertical-align: middle; +} + .report-modal__statuses, .report-modal__comment { padding: 10px; diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss index 15ff84912..0bf9daafd 100644 --- a/app/javascript/styles/mastodon/landing_strip.scss +++ b/app/javascript/styles/mastodon/landing_strip.scss @@ -1,4 +1,5 @@ -.landing-strip { +.landing-strip, +.memoriam-strip { background: rgba(darken($ui-base-color, 7%), 0.8); color: $ui-primary-color; font-weight: 400; @@ -29,3 +30,7 @@ margin-bottom: 0; } } + +.memoriam-strip { + background: rgba($base-shadow-color, 0.7); +} diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 57f105da7..733a1c4b7 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -89,20 +89,28 @@ class Formatter end end + def count_tag_nesting(tag) + if tag[1] == '/' then -1 + elsif tag[-2] == '/' then 0 + else 1 + end + end + def encode_custom_emojis(html, emojis) return html if emojis.empty? emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url(:static))] }.to_h i = -1 - inside_tag = false + tag_open_index = nil inside_shortname = false shortname_start_index = -1 + invisible_depth = 0 while i + 1 < html.size i += 1 - if inside_shortname && html[i] == ':' + if invisible_depth.zero? && inside_shortname && html[i] == ':' shortcode = html[shortname_start_index + 1..i - 1] emoji = emoji_map[shortcode] @@ -116,12 +124,18 @@ class Formatter end inside_shortname = false - elsif inside_tag && html[i] == '>' - inside_tag = false + elsif tag_open_index && html[i] == '>' + tag = html[tag_open_index..i] + tag_open_index = nil + if invisible_depth.positive? + invisible_depth += count_tag_nesting(tag) + elsif tag == '<span class="invisible">' + invisible_depth = 1 + end elsif html[i] == '<' - inside_tag = true + tag_open_index = i inside_shortname = false - elsif !inside_tag && html[i] == ':' + elsif !tag_open_index && html[i] == ':' inside_shortname = true shortname_start_index = i end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 80c9d8ccf..d79f26366 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -7,6 +7,8 @@ class NotificationMailer < ApplicationMailer @me = recipient @status = notification.target_status + return if @me.user.disabled? + locale_for_account(@me) do thread_by_conversation(@status.conversation) mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct) @@ -17,6 +19,8 @@ class NotificationMailer < ApplicationMailer @me = recipient @account = notification.from_account + return if @me.user.disabled? + locale_for_account(@me) do mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct) end @@ -27,6 +31,8 @@ class NotificationMailer < ApplicationMailer @account = notification.from_account @status = notification.target_status + return if @me.user.disabled? + locale_for_account(@me) do thread_by_conversation(@status.conversation) mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) @@ -38,6 +44,8 @@ class NotificationMailer < ApplicationMailer @account = notification.from_account @status = notification.target_status + return if @me.user.disabled? + locale_for_account(@me) do thread_by_conversation(@status.conversation) mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct) @@ -48,6 +56,8 @@ class NotificationMailer < ApplicationMailer @me = recipient @account = notification.from_account + return if @me.user.disabled? + locale_for_account(@me) do mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct) end @@ -59,15 +69,11 @@ class NotificationMailer < ApplicationMailer @notifications = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since) @follows_since = Notification.where(account: @me, activity_type: 'Follow').where('created_at > ?', @since).count - return if @notifications.empty? + return if @me.user.disabled? || @notifications.empty? locale_for_account(@me) do mail to: @me.user.email, - subject: I18n.t( - :subject, - scope: [:notification_mailer, :digest], - count: @notifications.size - ) + subject: I18n.t(:subject, scope: [:notification_mailer, :digest], count: @notifications.size) end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index c475a9911..bdb29ebad 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -10,6 +10,8 @@ class UserMailer < Devise::Mailer @token = token @instance = Rails.configuration.x.local_domain + return if @resource.disabled? + I18n.with_locale(@resource.locale || I18n.default_locale) do mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance) end @@ -20,6 +22,8 @@ class UserMailer < Devise::Mailer @token = token @instance = Rails.configuration.x.local_domain + return if @resource.disabled? + I18n.with_locale(@resource.locale || I18n.default_locale) do mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject') end @@ -29,6 +33,8 @@ class UserMailer < Devise::Mailer @resource = user @instance = Rails.configuration.x.local_domain + return if @resource.disabled? + I18n.with_locale(@resource.locale || I18n.default_locale) do mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject') end diff --git a/app/models/account.rb b/app/models/account.rb index 85684c259..863bfaa25 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -3,7 +3,7 @@ # # Table name: accounts # -# id :integer not null, primary key +# id :bigint not null, primary key # username :string default(""), not null # domain :string # secret :string default(""), not null @@ -41,10 +41,11 @@ # shared_inbox_url :string default(""), not null # followers_url :string default(""), not null # protocol :integer default("ostatus"), not null +# memorial :boolean default(FALSE), not null # class Account < ApplicationRecord - MENTION_RE = /(?:^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i + MENTION_RE = /(?<=^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i include AccountAvatar include AccountFinderConcern @@ -152,6 +153,20 @@ class Account < ApplicationRecord ResolveRemoteAccountService.new.call(acct) end + def unsuspend! + transaction do + user&.enable! if local? + update!(suspended: false) + end + end + + def memorialize! + transaction do + user&.disable! if local? + update!(memorial: true) + end + end + def keypair @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key) end diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb index fb695e473..9c98ec2a6 100644 --- a/app/models/account_domain_block.rb +++ b/app/models/account_domain_block.rb @@ -6,8 +6,8 @@ # domain :string # created_at :datetime not null # updated_at :datetime not null -# account_id :integer -# id :integer not null, primary key +# account_id :bigint +# id :bigint not null, primary key # class AccountDomainBlock < ApplicationRecord diff --git a/app/models/account_moderation_note.rb b/app/models/account_moderation_note.rb index 3ac9b1ac1..06f464850 100644 --- a/app/models/account_moderation_note.rb +++ b/app/models/account_moderation_note.rb @@ -3,10 +3,10 @@ # # Table name: account_moderation_notes # -# id :integer not null, primary key +# id :bigint not null, primary key # content :text not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint not null +# target_account_id :bigint not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/block.rb b/app/models/block.rb index a913782ed..5778f7e90 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -5,9 +5,9 @@ # # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# id :integer not null, primary key -# target_account_id :integer not null +# account_id :bigint not null +# id :bigint not null, primary key +# target_account_id :bigint not null # class Block < ApplicationRecord diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index a68f7c3d8..c41f92581 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -23,7 +23,7 @@ module AccountInteractions def muting_map(target_account_ids, account_id) Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping| mapping[mute.target_account_id] = { - notifications: mute.hide_notifications? + notifications: mute.hide_notifications?, } end end @@ -91,8 +91,7 @@ module AccountInteractions mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account) # When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't. if mute.hide_notifications? != notifications - mute.hide_notifications = notifications - mute.save! + mute.update!(hide_notifications: notifications) end end diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 08c1ce945..e08532522 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -3,7 +3,7 @@ # # Table name: conversations # -# id :integer not null, primary key +# id :bigint not null, primary key # uri :string # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/conversation_mute.rb b/app/models/conversation_mute.rb index 8d2399adf..316865bd2 100644 --- a/app/models/conversation_mute.rb +++ b/app/models/conversation_mute.rb @@ -3,9 +3,9 @@ # # Table name: conversation_mutes # -# conversation_id :integer not null -# account_id :integer not null -# id :integer not null, primary key +# conversation_id :bigint not null +# account_id :bigint not null +# id :bigint not null, primary key # class ConversationMute < ApplicationRecord diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 28b6a2b0b..5723ebd5d 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -3,7 +3,7 @@ # # Table name: custom_emojis # -# id :integer not null, primary key +# id :bigint not null, primary key # shortcode :string default(""), not null # domain :string # image_file_name :string @@ -25,6 +25,8 @@ class CustomEmoji < ApplicationRecord :(#{SHORTCODE_RE_FRAGMENT}): (?=[^[:alnum:]:]|$)/x + has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode + has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce -strip' } } validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes } diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 1268290bc..557d0a19c 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -8,7 +8,7 @@ # updated_at :datetime not null # severity :integer default("silence") # reject_media :boolean default(FALSE), not null -# id :integer not null, primary key +# id :bigint not null, primary key # class DomainBlock < ApplicationRecord diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb index 839038bea..2c348197c 100644 --- a/app/models/email_domain_block.rb +++ b/app/models/email_domain_block.rb @@ -3,15 +3,34 @@ # # Table name: email_domain_blocks # -# id :integer not null, primary key -# domain :string not null +# id :bigint not null, primary key +# domain :string default(""), not null # created_at :datetime not null # updated_at :datetime not null # class EmailDomainBlock < ApplicationRecord + before_validation :normalize_domain + + validates :domain, presence: true, uniqueness: true + def self.block?(email) - domain = email.gsub(/.+@([^.]+)/, '\1') + _, domain = email.split('@', 2) + + return true if domain.nil? + + begin + domain = TagManager.instance.normalize_domain(domain) + rescue Addressable::URI::InvalidURIError + return true + end + where(domain: domain).exists? end + + private + + def normalize_domain + self.domain = TagManager.instance.normalize_domain(domain) + end end diff --git a/app/models/favourite.rb b/app/models/favourite.rb index d28d5c05b..f611aa6a9 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -5,9 +5,9 @@ # # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# id :integer not null, primary key -# status_id :integer not null +# account_id :bigint not null +# id :bigint not null, primary key +# status_id :bigint not null # class Favourite < ApplicationRecord diff --git a/app/models/follow.rb b/app/models/follow.rb index a8ddcb7f0..ea00a377a 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -5,9 +5,9 @@ # # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# id :integer not null, primary key -# target_account_id :integer not null +# account_id :bigint not null +# id :bigint not null, primary key +# target_account_id :bigint not null # show_reblogs :boolean default(TRUE), not null # diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 1a1c52382..962b61411 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -5,9 +5,9 @@ # # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# id :integer not null, primary key -# target_account_id :integer not null +# account_id :bigint not null +# id :bigint not null, primary key +# target_account_id :bigint not null # show_reblogs :boolean default(TRUE), not null # @@ -28,7 +28,5 @@ class FollowRequest < ApplicationRecord destroy! end - def reject! - destroy! - end + alias reject! destroy! end diff --git a/app/models/import.rb b/app/models/import.rb index 8ae7e3a46..6f1278556 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -11,8 +11,8 @@ # data_content_type :string # data_file_size :integer # data_updated_at :datetime -# account_id :integer not null -# id :integer not null, primary key +# account_id :bigint not null +# id :bigint not null, primary key # class Import < ApplicationRecord diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index f6c8879c5..7a553f703 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -3,14 +3,14 @@ # # Table name: media_attachments # -# id :integer not null, primary key -# status_id :integer +# id :bigint not null, primary key +# status_id :bigint # file_file_name :string # file_content_type :string # file_file_size :integer # file_updated_at :datetime # remote_url :string default(""), not null -# account_id :integer +# account_id :bigint # created_at :datetime not null # updated_at :datetime not null # shortcode :string diff --git a/app/models/mention.rb b/app/models/mention.rb index 3700c781c..fc089d365 100644 --- a/app/models/mention.rb +++ b/app/models/mention.rb @@ -3,11 +3,11 @@ # # Table name: mentions # -# status_id :integer +# status_id :bigint # created_at :datetime not null # updated_at :datetime not null -# account_id :integer -# id :integer not null, primary key +# account_id :bigint +# id :bigint not null, primary key # class Mention < ApplicationRecord diff --git a/app/models/mute.rb b/app/models/mute.rb index bcd3d247c..74b445c0b 100644 --- a/app/models/mute.rb +++ b/app/models/mute.rb @@ -3,11 +3,11 @@ # # Table name: mutes # +# id :bigint not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# id :integer not null, primary key -# target_account_id :integer not null +# account_id :bigint not null +# target_account_id :bigint not null # hide_notifications :boolean default(TRUE), not null # diff --git a/app/models/notification.rb b/app/models/notification.rb index 0a5d987cf..c88af9021 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -3,13 +3,13 @@ # # Table name: notifications # -# id :integer not null, primary key -# account_id :integer -# activity_id :integer +# id :bigint not null, primary key +# account_id :bigint +# activity_id :bigint # activity_type :string # created_at :datetime not null # updated_at :datetime not null -# from_account_id :integer +# from_account_id :bigint # class Notification < ApplicationRecord diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index e2bf65d94..63c04b410 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -3,7 +3,7 @@ # # Table name: preview_cards # -# id :integer not null, primary key +# id :bigint not null, primary key # url :string default(""), not null # title :string default(""), not null # description :string default(""), not null diff --git a/app/models/report.rb b/app/models/report.rb index bffb42b48..99c90b7dd 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -8,10 +8,10 @@ # action_taken :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# action_taken_by_account_id :integer -# id :integer not null, primary key -# target_account_id :integer not null +# account_id :bigint not null +# action_taken_by_account_id :bigint +# id :bigint not null, primary key +# target_account_id :bigint not null # class Report < ApplicationRecord diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index c1645223b..59565f877 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -3,25 +3,25 @@ # # Table name: session_activations # -# id :integer not null, primary key -# user_id :integer not null +# id :bigint not null, primary key +# user_id :bigint not null # session_id :string not null # created_at :datetime not null # updated_at :datetime not null # user_agent :string default(""), not null # ip :inet -# access_token_id :integer -# web_push_subscription_id :integer +# access_token_id :bigint +# web_push_subscription_id :bigint # -# id :integer not null, primary key -# user_id :integer not null +# id :bigint not null, primary key +# user_id :bigint not null # session_id :string not null # created_at :datetime not null # updated_at :datetime not null # user_agent :string default(""), not null # ip :inet -# access_token_id :integer +# access_token_id :bigint # class SessionActivation < ApplicationRecord diff --git a/app/models/setting.rb b/app/models/setting.rb index a14f156a1..be68d3123 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -8,8 +8,8 @@ # thing_type :string # created_at :datetime # updated_at :datetime -# id :integer not null, primary key -# thing_id :integer +# id :bigint not null, primary key +# thing_id :bigint # class Setting < RailsSettings::Base diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 8ffdc8313..ba2ca777b 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -3,7 +3,7 @@ # # Table name: site_uploads # -# id :integer not null, primary key +# id :bigint not null, primary key # var :string default(""), not null # file_file_name :string # file_content_type :string diff --git a/app/models/status.rb b/app/models/status.rb index d78a921b5..1dda6594e 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -3,25 +3,25 @@ # # Table name: statuses # -# id :integer not null, primary key +# id :bigint not null, primary key # uri :string -# account_id :integer not null +# account_id :bigint not null # text :text default(""), not null # created_at :datetime not null # updated_at :datetime not null -# in_reply_to_id :integer -# reblog_of_id :integer +# in_reply_to_id :bigint +# reblog_of_id :bigint # url :string # sensitive :boolean default(FALSE), not null # visibility :integer default("public"), not null -# in_reply_to_account_id :integer -# application_id :integer +# in_reply_to_account_id :bigint +# application_id :bigint # spoiler_text :text default(""), not null # reply :boolean default(FALSE), not null # favourites_count :integer default(0), not null # reblogs_count :integer default(0), not null # language :string -# conversation_id :integer +# conversation_id :bigint # local :boolean # diff --git a/app/models/status_pin.rb b/app/models/status_pin.rb index a72c19750..5795d07bf 100644 --- a/app/models/status_pin.rb +++ b/app/models/status_pin.rb @@ -3,9 +3,9 @@ # # Table name: status_pins # -# id :integer not null, primary key -# account_id :integer not null -# status_id :integer not null +# id :bigint not null, primary key +# account_id :bigint not null +# status_id :bigint not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index 720cd518c..2b812d826 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -3,13 +3,13 @@ # # Table name: stream_entries # -# activity_id :integer +# activity_id :bigint # activity_type :string # created_at :datetime not null # updated_at :datetime not null # hidden :boolean default(FALSE), not null -# account_id :integer -# id :integer not null, primary key +# account_id :bigint +# id :bigint not null, primary key # class StreamEntry < ApplicationRecord diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 39860196b..bc50c5317 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -11,8 +11,8 @@ # updated_at :datetime not null # last_successful_delivery_at :datetime # domain :string -# account_id :integer not null -# id :integer not null, primary key +# account_id :bigint not null +# id :bigint not null, primary key # class Subscription < ApplicationRecord diff --git a/app/models/tag.rb b/app/models/tag.rb index 0fa08e157..6ebaf1145 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -3,7 +3,7 @@ # # Table name: tags # -# id :integer not null, primary key +# id :bigint not null, primary key # name :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/user.rb b/app/models/user.rb index 325e27f44..326b871a1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,9 +3,8 @@ # # Table name: users # -# id :integer not null, primary key +# id :bigint not null, primary key # email :string default(""), not null -# account_id :integer not null # created_at :datetime not null # updated_at :datetime not null # encrypted_password :string default(""), not null @@ -31,10 +30,14 @@ # last_emailed_at :datetime # otp_backup_codes :string is an Array # filtered_languages :string default([]), not null, is an Array +# account_id :bigint not null +# disabled :boolean default(FALSE), not null +# moderator :boolean default(FALSE), not null # class User < ApplicationRecord include Settings::Extend + ACTIVE_DURATION = 14.days devise :registerable, :recoverable, @@ -51,8 +54,10 @@ class User < ApplicationRecord validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale? validates_with BlacklistedEmailValidator, if: :email_changed? - scope :recent, -> { order(id: :desc) } - scope :admins, -> { where(admin: true) } + scope :recent, -> { order(id: :desc) } + scope :admins, -> { where(admin: true) } + scope :moderators, -> { where(moderator: true) } + scope :staff, -> { admins.or(moderators) } scope :confirmed, -> { where.not(confirmed_at: nil) } scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) } @@ -68,54 +73,71 @@ class User < ApplicationRecord has_many :session_activations, dependent: :destroy + delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal, + :reduce_motion, :system_font_ui, :noindex, :theme, + to: :settings, prefix: :setting, allow_nil: false + def confirmed? confirmed_at.present? end - def disable_two_factor! - self.otp_required_for_login = false - otp_backup_codes&.clear - save! - end - - def setting_default_privacy - settings.default_privacy || (account.locked? ? 'private' : 'public') + def staff? + admin? || moderator? end - def setting_default_sensitive - settings.default_sensitive + def role + if admin? + 'admin' + elsif moderator? + 'moderator' + else + 'user' + end end - def setting_unfollow_modal - settings.unfollow_modal + def disable! + update!(disabled: true, + last_sign_in_at: current_sign_in_at, + current_sign_in_at: nil) end - def setting_boost_modal - settings.boost_modal + def enable! + update!(disabled: false) end - def setting_delete_modal - settings.delete_modal + def confirm! + skip_confirmation! + save! end - def setting_auto_play_gif - settings.auto_play_gif + def promote! + if moderator? + update!(moderator: false, admin: true) + elsif !admin? + update!(moderator: true) + end end - def setting_reduce_motion - settings.reduce_motion + def demote! + if admin? + update!(admin: false, moderator: true) + elsif moderator? + update!(moderator: false) + end end - def setting_system_font_ui - settings.system_font_ui + def disable_two_factor! + self.otp_required_for_login = false + otp_backup_codes&.clear + save! end - def setting_noindex - settings.noindex + def active_for_authentication? + super && !disabled? end - def setting_theme - settings.theme + def setting_default_privacy + settings.default_privacy || (account.locked? ? 'private' : 'public') end def token_for_app(a) diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index cb15dfa37..da67e7665 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -3,7 +3,7 @@ # # Table name: web_push_subscriptions # -# id :integer not null, primary key +# id :bigint not null, primary key # endpoint :string not null # key_p256dh :string not null # key_auth :string not null diff --git a/app/models/web/setting.rb b/app/models/web/setting.rb index 1b0bfb2b7..6d08c4d35 100644 --- a/app/models/web/setting.rb +++ b/app/models/web/setting.rb @@ -6,8 +6,8 @@ # data :json # created_at :datetime not null # updated_at :datetime not null -# id :integer not null, primary key -# user_id :integer +# id :bigint not null, primary key +# user_id :bigint # class Web::Setting < ApplicationRecord diff --git a/app/policies/account_moderation_note_policy.rb b/app/policies/account_moderation_note_policy.rb new file mode 100644 index 000000000..885411a5b --- /dev/null +++ b/app/policies/account_moderation_note_policy.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AccountModerationNotePolicy < ApplicationPolicy + def create? + staff? + end + + def destroy? + admin? || owner? + end + + private + + def owner? + record.account_id == current_account&.id + end +end diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb new file mode 100644 index 000000000..85e2c8419 --- /dev/null +++ b/app/policies/account_policy.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class AccountPolicy < ApplicationPolicy + def index? + staff? + end + + def show? + staff? + end + + def suspend? + staff? && !record.user&.staff? + end + + def unsuspend? + staff? + end + + def silence? + staff? && !record.user&.staff? + end + + def unsilence? + staff? + end + + def redownload? + admin? + end + + def subscribe? + admin? + end + + def unsubscribe? + admin? + end + + def memorialize? + admin? && !record.user&.admin? + end +end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 000000000..3e617001f --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ApplicationPolicy + attr_reader :current_account, :record + + def initialize(current_account, record) + @current_account = current_account + @record = record + end + + delegate :admin?, :moderator?, :staff?, to: :current_user, allow_nil: true + + private + + def current_user + current_account&.user + end +end diff --git a/app/policies/custom_emoji_policy.rb b/app/policies/custom_emoji_policy.rb new file mode 100644 index 000000000..a8c3cbc73 --- /dev/null +++ b/app/policies/custom_emoji_policy.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class CustomEmojiPolicy < ApplicationPolicy + def index? + staff? + end + + def create? + admin? + end + + def update? + admin? + end + + def copy? + admin? + end + + def enable? + staff? + end + + def disable? + staff? + end + + def destroy? + admin? + end +end diff --git a/app/policies/domain_block_policy.rb b/app/policies/domain_block_policy.rb new file mode 100644 index 000000000..47c0a81af --- /dev/null +++ b/app/policies/domain_block_policy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class DomainBlockPolicy < ApplicationPolicy + def index? + admin? + end + + def show? + admin? + end + + def create? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/policies/email_domain_block_policy.rb b/app/policies/email_domain_block_policy.rb new file mode 100644 index 000000000..5a75ee183 --- /dev/null +++ b/app/policies/email_domain_block_policy.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class EmailDomainBlockPolicy < ApplicationPolicy + def index? + admin? + end + + def create? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/policies/instance_policy.rb b/app/policies/instance_policy.rb new file mode 100644 index 000000000..d1956e2de --- /dev/null +++ b/app/policies/instance_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class InstancePolicy < ApplicationPolicy + def index? + admin? + end + + def resubscribe? + admin? + end +end diff --git a/app/policies/report_policy.rb b/app/policies/report_policy.rb new file mode 100644 index 000000000..95b5c30c8 --- /dev/null +++ b/app/policies/report_policy.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ReportPolicy < ApplicationPolicy + def update? + staff? + end + + def index? + staff? + end + + def show? + staff? + end +end diff --git a/app/policies/settings_policy.rb b/app/policies/settings_policy.rb new file mode 100644 index 000000000..2dcb79f51 --- /dev/null +++ b/app/policies/settings_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SettingsPolicy < ApplicationPolicy + def update? + admin? + end + + def show? + admin? + end +end diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index f4a5e7c6c..8e0c1eef1 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -1,22 +1,19 @@ # frozen_string_literal: true -class StatusPolicy - attr_reader :account, :status - - def initialize(account, status) - @account = account - @status = status +class StatusPolicy < ApplicationPolicy + def index? + staff? end def show? return false if local_only? && account.nil? if direct? - owned? || status.mentions.where(account: account).exists? + owned? || record.mentions.where(account: current_account).exists? elsif private? - owned? || account&.following?(status.account) || status.mentions.where(account: account).exists? + owned? || current_account&.following?(author) || record.mentions.where(account: current_account).exists? else - account.nil? || !status.account.blocking?(account) + current_account.nil? || !author.blocking?(current_account) end end @@ -25,30 +22,34 @@ class StatusPolicy end def destroy? - admin? || owned? + staff? || owned? end alias unreblog? destroy? - private - - def admin? - account&.user&.admin? + def update? + staff? end + private + def direct? - status.direct_visibility? + record.direct_visibility? end def owned? - status.account.id == account&.id + author.id == current_account&.id end def private? - status.private_visibility? + record.private_visibility? + end + + def author + record.account end def local_only? - status.local_only? + record.local_only? end end diff --git a/app/policies/subscription_policy.rb b/app/policies/subscription_policy.rb new file mode 100644 index 000000000..ac9a8a6c4 --- /dev/null +++ b/app/policies/subscription_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class SubscriptionPolicy < ApplicationPolicy + def index? + admin? + end +end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 000000000..aae207d06 --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class UserPolicy < ApplicationPolicy + def reset_password? + staff? && !record.staff? + end + + def disable_2fa? + admin? && !record.staff? + end + + def confirm? + staff? && !record.confirmed? + end + + def enable? + admin? + end + + def disable? + admin? && !record.admin? + end + + def promote? + admin? && promoteable? + end + + def demote? + admin? && !record.admin? && demoteable? + end + + private + + def promoteable? + !record.staff? || !record.admin? + end + + def demoteable? + record.staff? + end +end diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 1f5ee789a..787c5add3 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -7,7 +7,7 @@ class InitialStateSerializer < ActiveModel::Serializer has_many :custom_emojis, serializer: REST::CustomEmojiSerializer def custom_emojis - CustomEmoji.local + CustomEmoji.local.where(disabled: false) end def meta diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index e2a89a87c..8d7b7a17c 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -16,7 +16,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService return if actor_id.nil? || !trustworthy_attribution?(@json['id'], actor_id) actor = ActivityPub::TagManager.instance.uri_to_resource(actor_id, Account) - actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil? + actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil? || needs_update(actor) return if actor.suspended? @@ -44,4 +44,8 @@ class ActivityPub::FetchRemoteStatusService < BaseService def expected_type? %w(Note Article).include? @json['type'] end + + def needs_update(actor) + actor.possibly_stale? + end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 3fa3f152c..d5960c3ad 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -36,17 +36,58 @@ class NotifyService < BaseService false end + def following_sender? + return @following_sender if defined?(@following_sender) + @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account) + end + + def optional_non_follower? + @recipient.user.settings.interactions['must_be_follower'] && !@notification.from_account.following?(@recipient) + end + + def optional_non_following? + @recipient.user.settings.interactions['must_be_following'] && !following_sender? + end + + def direct_message? + @notification.type == :mention && @notification.target_status.direct_visibility? + end + + def response_to_recipient? + @notification.target_status.in_reply_to_account_id == @recipient.id + end + + def optional_non_following_and_direct? + direct_message? && + @recipient.user.settings.interactions['must_be_following_dm'] && + !following_sender? && + !response_to_recipient? + end + + def hellbanned? + @notification.from_account.silenced? && !following_sender? + end + + def from_self? + @recipient.id == @notification.from_account.id + end + + def domain_blocking? + @recipient.domain_blocking?(@notification.from_account.domain) && !following_sender? + end + def blocked? - blocked = @recipient.suspended? # Skip if the recipient account is suspended anyway - blocked ||= @recipient.id == @notification.from_account.id # Skip for interactions with self - blocked ||= @recipient.domain_blocking?(@notification.from_account.domain) && !@recipient.following?(@notification.from_account) # Skip for domain blocked accounts - blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts + blocked = @recipient.suspended? # Skip if the recipient account is suspended anyway + blocked ||= from_self? # Skip for interactions with self + blocked ||= domain_blocking? # Skip for domain blocked accounts + blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts blocked ||= @recipient.muting_notifications?(@notification.from_account) - blocked ||= (@notification.from_account.silenced? && !@recipient.following?(@notification.from_account)) # Hellban - blocked ||= (@recipient.user.settings.interactions['must_be_follower'] && !@notification.from_account.following?(@recipient)) # Options - blocked ||= (@recipient.user.settings.interactions['must_be_following'] && !@recipient.following?(@notification.from_account)) # Options + blocked ||= hellbanned? # Hellban + blocked ||= optional_non_follower? # Options + blocked ||= optional_non_following? # Options + blocked ||= optional_non_following_and_direct? # Options blocked ||= conversation_muted? - blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters + blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters blocked end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index d1b8f42c7..974c586f2 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -74,11 +74,11 @@ class PostStatusService < BaseService end def process_mentions_service - @process_mentions_service ||= ProcessMentionsService.new + ProcessMentionsService.new end def process_hashtags_service - @process_hashtags_service ||= ProcessHashtagsService.new + ProcessHashtagsService.new end def redis diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 1c3eea369..a229d4ff8 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -10,23 +10,26 @@ class ProcessMentionsService < BaseService def call(status) return unless status.local? - status.text.scan(Account::MENTION_RE).each do |match| - username, domain = match.first.split('@') - mentioned_account = Account.find_remote(username, domain) - - if mentioned_account.nil? && !domain.nil? - begin - mentioned_account = follow_remote_account_service.call(match.first.to_s) - rescue Goldfinger::Error, HTTP::Error - mentioned_account = nil - end + status.text = status.text.gsub(Account::MENTION_RE) do |match| + begin + mentioned_account = resolve_remote_account_service.call($1) + rescue Goldfinger::Error, HTTP::Error + mentioned_account = nil end - next if mentioned_account.nil? + if mentioned_account.nil? + username, domain = match.first.split('@') + mentioned_account = Account.find_remote(username, domain) + end + + next match if mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus? && status.stream_entry.hidden?) mentioned_account.mentions.where(status: status).first_or_create(status: status) + "@#{mentioned_account.acct}" end + status.save! + status.mentions.includes(:account).each do |mention| create_notification(status, mention) end @@ -54,7 +57,7 @@ class ProcessMentionsService < BaseService ).as_json).sign!(status.account)) end - def follow_remote_account_service - @follow_remote_account_service ||= ResolveRemoteAccountService.new + def resolve_remote_account_service + ResolveRemoteAccountService.new end end diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 983c5495b..5b37ba9ba 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -1,22 +1,27 @@ # frozen_string_literal: true class SuspendAccountService < BaseService - def call(account, remove_user = false) + def call(account, options = {}) @account = account + @options = options - purge_user if remove_user - purge_profile - purge_content - unsubscribe_push_subscribers + purge_user! + purge_profile! + purge_content! + unsubscribe_push_subscribers! end private - def purge_user - @account.user.destroy + def purge_user! + if @options[:remove_user] + @account.user&.destroy + else + @account.user&.disable! + end end - def purge_content + def purge_content! @account.statuses.reorder(nil).find_in_batches do |statuses| BatchedRemoveStatusService.new.call(statuses) end @@ -33,7 +38,7 @@ class SuspendAccountService < BaseService end end - def purge_profile + def purge_profile! @account.suspended = true @account.display_name = '' @account.note = '' @@ -42,7 +47,7 @@ class SuspendAccountService < BaseService @account.save! end - def unsubscribe_push_subscribers + def unsubscribe_push_subscribers! destroy_all(@account.subscriptions) end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 5b504912d..47ee02cf0 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,22 +1,23 @@ - processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format account .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } .card__illustration - - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) - .controls - - if current_account.following?(account) - = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do - = fa_icon 'user-times' - = t('accounts.unfollow') - - else - = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do - = fa_icon 'user-plus' - = t('accounts.follow') - - elsif !user_signed_in? - .controls - .remote-follow - = link_to account_remote_follow_path(account), class: 'icon-button' do - = fa_icon 'user-plus' - = t('accounts.remote_follow') + - unless account.memorial? + - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) + .controls + - if current_account.following?(account) + = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-times' + = t('accounts.unfollow') + - else + = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.follow') + - elsif !user_signed_in? + .controls + .remote-follow + = link_to account_remote_follow_path(account), class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.remote_follow') .avatar= image_tag account.avatar.url(:original), class: 'u-photo' diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 6c90b2c04..fd8ad5530 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -12,7 +12,9 @@ = opengraph 'og:type', 'profile' = render 'og', account: @account, url: short_account_url(@account, only_path: false) -- if show_landing_strip? +- if @account.memorial? + .memoriam-strip= t('in_memoriam_html') +- elsif show_landing_strip? = render partial: 'shared/landing_strip', locals: { account: @account } .h-feed diff --git a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml index 4651630e9..6761a4319 100644 --- a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml +++ b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml @@ -7,4 +7,4 @@ %time.formatted{ datetime: account_moderation_note.created_at.iso8601, title: l(account_moderation_note.created_at) } = l account_moderation_note.created_at %td - = link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete + = link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note) diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 1b56a3a31..27a0682d8 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -42,7 +42,7 @@ - if params[key].present? = hidden_field_tag key, params[key] - - %i(username display_name email ip).each do |key| + - %i(username by_domain display_name email ip).each do |key| .input.string.optional = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}") diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 1f5c8fcf5..f49594828 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -17,7 +17,20 @@ - if @account.local? %tr %th= t('admin.accounts.email') - %td= @account.user_email + %td + = @account.user_email + + - if @account.user_confirmed? + = fa_icon('check') + %tr + %th= t('admin.accounts.login_status') + %td + - if @account.user&.disabled? + = t('admin.accounts.disabled') + = table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user) + - else + = t('admin.accounts.enabled') + = table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post if can?(:disable, @account.user) %tr %th= t('admin.accounts.most_recent_ip') %td= @account.user_current_sign_in_ip @@ -62,26 +75,28 @@ %div{ style: 'overflow: hidden' } %div{ style: 'float: right' } - if @account.local? - = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' + = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) - if @account.user&.otp_required_for_login? - = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' + = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) + - unless @account.memorial? + = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:memorialize, @account) - else - = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' + = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) %div{ style: 'float: left' } - if @account.silenced? - = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' + = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' if can?(:unsilence, @account) - else - = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button' + = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button' if can?(:silence, @account) - if @account.local? - unless @account.user_confirmed? - = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' + = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user) - if @account.suspended? - = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' + = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' if can?(:unsuspend, @account) - else - = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' + = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account) - unless @account.local? %hr @@ -107,9 +122,9 @@ %div{ style: 'overflow: hidden' } %div{ style: 'float: right' } - = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' + = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' if can?(:subscribe, @account) - if @account.subscribed? - = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' + = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account) %hr %h3 ActivityPub @@ -130,6 +145,20 @@ %th= t('admin.accounts.followers_url') %td= link_to @account.followers_url, @account.followers_url +- else + %hr + + .table-wrapper + %table.table + %tbody + %tr + %th= t('admin.accounts.role') + %td + = t("admin.accounts.roles.#{@account.user&.role}") + %td< + = table_link_to 'angle-double-up', t('admin.accounts.promote'), promote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:promote, @account.user) + = table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user) + %hr %h3= t('admin.accounts.moderation_notes') diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml index 399d13bbd..bab34bc8d 100644 --- a/app/views/admin/custom_emojis/_custom_emoji.html.haml +++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml @@ -1,6 +1,6 @@ %tr %td - = image_tag custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:" + = custom_emoji_tag(custom_emoji) %td %samp= ":#{custom_emoji.shortcode}:" %td @@ -15,7 +15,10 @@ - else = table_link_to 'eye-slash', t('admin.custom_emojis.unlisted'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: true }), method: :patch - else - = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post + - if custom_emoji.local_counterpart.present? + = link_to safe_join([custom_emoji_tag(custom_emoji.local_counterpart), t('admin.custom_emojis.overwrite')]), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post, class: 'table-action-link' + - else + = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post %td - if custom_emoji.disabled? = table_link_to 'power-off', t('admin.custom_emojis.enable'), enable_admin_custom_emoji_path(custom_emoji), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index cba2bbbd4..878ffb032 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -3,9 +3,6 @@ %link{ href: asset_pack_path('features/compose.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %link{ href: asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %link{ href: asset_pack_path('features/notifications.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/community_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/public_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/direct_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index b1fd9ef40..24b74c787 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -29,7 +29,6 @@ = yield :header_tags - body_classes ||= @body_classes || '' - - body_classes += ' reduce-motion' if current_account&.user&.setting_reduce_motion - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui %body{ class: add_rtl_body_class(body_classes) } diff --git a/app/views/settings/notifications/show.html.haml b/app/views/settings/notifications/show.html.haml index 80cd615c7..b718b62df 100644 --- a/app/views/settings/notifications/show.html.haml +++ b/app/views/settings/notifications/show.html.haml @@ -11,7 +11,7 @@ = ff.input :reblog, as: :boolean, wrapper: :with_label = ff.input :favourite, as: :boolean, wrapper: :with_label = ff.input :mention, as: :boolean, wrapper: :with_label - + .fields-group = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff| = ff.input :digest, as: :boolean, wrapper: :with_label @@ -20,6 +20,7 @@ = f.simple_fields_for :interactions, hash_to_object(current_user.settings.interactions) do |ff| = ff.input :must_be_follower, as: :boolean, wrapper: :with_label = ff.input :must_be_following, as: :boolean, wrapper: :with_label + = ff.input :must_be_following_dm, as: :boolean, wrapper: :with_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb index 80edcfda7..0be16d994 100644 --- a/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb +++ b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb @@ -1,6 +1,6 @@ <p>Boas vindas, <%= @resource.email %>!</p> -<p>Você acabou de criar uma conta no <%= @instance %>.</p> +<p>Você acabou de criar uma conta na instância <%= @instance %>.</p> <p>Para confirmar o seu cadastro, por favor clique no link a seguir: <br> <%= link_to 'Confirmar cadastro', confirmation_url(@resource, confirmation_token: @token) %> @@ -9,4 +9,4 @@ <p>Atenciosamente,<p> -<p>A equipe do <%= @instance %></p> +<p>A equipe da instância <%= @instance %></p> diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb index 95efb3436..578f7acb5 100644 --- a/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb +++ b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb @@ -1,6 +1,6 @@ Boas vindas, <%= @resource.email %>! -Você acabou de criar uma conta no <%= @instance %>. +Você acabou de criar uma conta na instância <%= @instance %>. Para confirmar o seu cadastro, por favor clique no link a seguir: <%= confirmation_url(@resource, confirmation_token: @token) %> @@ -9,4 +9,4 @@ Por favor, leia também os nossos termos e condições de uso <%= terms_url %> Atenciosamente, -A equipe do <%= @instance %> +A equipe da instância <%= @instance %> diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb index de2f8b6e0..8a676498a 100644 --- a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb +++ b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb @@ -1,10 +1,13 @@ -<p><%= @resource.email %> ,嗨呀!</p> +<p><%= @resource.email %>,你好呀!</p> -<p>你刚刚在 <%= @instance %> 创建了帐号。</p> +<p>你刚刚在 <%= @instance %> 创建了一个帐户呢。</p> -<p>点击下面的链接来完成注册啦 : <br> +<p>点击下面的链接来完成注册啦:<br> <%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %> -<p>别忘了看看 <%= link_to '使用条款', terms_url %>。</p> +<p>上面的链接按不动?把下面的链接复制到地址栏再试试:<br> +<span><%= confirmation_url(@resource, confirmation_token: @token) %></span> -<p> <%= @instance %> 敬上</p> \ No newline at end of file +<p>记得读一读我们的<%= link_to '使用条款', terms_url %>哦。</p> + +<p>来自 <%= @instance %> 管理团队</p> diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb index d7d4b4b23..25d901f16 100644 --- a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb +++ b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb @@ -1,10 +1,10 @@ -<%= @resource.email %> ,嗨呀! +<%= @resource.email %>,你好呀! -你刚刚在 <%= @instance %> 创建了帐号。 +你刚刚在 <%= @instance %> 创建了一个帐户呢。 -点击下面的链接来完成注册啦 : <br> -<%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %> +点击下面的链接来完成注册啦: +<%= confirmation_url(@resource, confirmation_token: @token) %> -别忘了看看 <%= link_to 'terms and conditions', terms_url %>。 +记得读一读我们的使用条款哦:<%= terms_url %> -<%= @instance %> 敬上 \ No newline at end of file +来自 <%= @instance %> 管理团队 \ No newline at end of file diff --git a/app/views/user_mailer/password_change.pt-BR.html.erb b/app/views/user_mailer/password_change.pt-BR.html.erb index 5f707ba09..a1aaa265e 100644 --- a/app/views/user_mailer/password_change.pt-BR.html.erb +++ b/app/views/user_mailer/password_change.pt-BR.html.erb @@ -1,3 +1,3 @@ <p>Olá, <%= @resource.email %>!</p> -<p>Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada.</p> +<p>Estamos te contatando para te notificar que a sua senha na instância <%= @instance %> foi modificada.</p> diff --git a/app/views/user_mailer/password_change.pt-BR.text.erb b/app/views/user_mailer/password_change.pt-BR.text.erb index d8b76648c..eb7368ba9 100644 --- a/app/views/user_mailer/password_change.pt-BR.text.erb +++ b/app/views/user_mailer/password_change.pt-BR.text.erb @@ -1,3 +1,3 @@ Olá, <%= @resource.email %>! -Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada. +Estamos te contatando para te notificar que a sua senha na instância <%= @instance %> foi modificada. diff --git a/app/views/user_mailer/password_change.zh-cn.html.erb b/app/views/user_mailer/password_change.zh-cn.html.erb index 115030af4..64e8b6b2f 100644 --- a/app/views/user_mailer/password_change.zh-cn.html.erb +++ b/app/views/user_mailer/password_change.zh-cn.html.erb @@ -1,3 +1,3 @@ -<p><%= @resource.email %>,嗨呀!</p> +<p><%= @resource.email %>,你好呀!</p> -<p>这只是一封用来通知你的密码已经被修改的邮件。_(:3」∠)_</p> +<p>提醒一下,你在 <%= @instance %> 上的密码被更改了哦。</p> diff --git a/app/views/user_mailer/password_change.zh-cn.text.erb b/app/views/user_mailer/password_change.zh-cn.text.erb index 5a989d324..dbc065173 100644 --- a/app/views/user_mailer/password_change.zh-cn.text.erb +++ b/app/views/user_mailer/password_change.zh-cn.text.erb @@ -1,3 +1,3 @@ -<%= @resource.email %>,嗨呀! +<%= @resource.email %>,你好呀! -这只是一封用来通知你的密码已经被修改的邮件。_(:3」∠)_ +提醒一下,你在 <%= @instance %> 上的密码被更改了哦。 diff --git a/app/views/user_mailer/reset_password_instructions.oc.html.erb b/app/views/user_mailer/reset_password_instructions.oc.html.erb index 6c775b3a1..92e4b8f8b 100644 --- a/app/views/user_mailer/reset_password_instructions.oc.html.erb +++ b/app/views/user_mailer/reset_password_instructions.oc.html.erb @@ -1,6 +1,6 @@ <p>Bonjorn <%= @resource.email %> !</p> -<p>Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p> +<p>Qualqu’un a demandat la reïnicializacion de vòstre senhal per Mastodon. Podètz realizar la reïnicializacion en clicant sul ligam çai-jos.</p> <p><%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %></p> diff --git a/app/views/user_mailer/reset_password_instructions.oc.text.erb b/app/views/user_mailer/reset_password_instructions.oc.text.erb index 26432d2df..5a5219589 100644 --- a/app/views/user_mailer/reset_password_instructions.oc.text.erb +++ b/app/views/user_mailer/reset_password_instructions.oc.text.erb @@ -1,6 +1,6 @@ Bonjorn <%= @resource.email %> ! -Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p> +Qualqu’un a demandat la reïnicializacion de vòstre senhal per Mastodon. Podètz realizar la reïnicializacion en clicant sul ligam çai-jos.</p> <%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %> diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb index 940438b7c..9b21aae92 100644 --- a/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb +++ b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb @@ -1,8 +1,8 @@ <p>Olá, <%= @resource.email %>!</p> -<p>Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo:</p> +<p>Alguém solicitou um link para mudar a sua senha na instância <%= @instance %>. Você pode fazer isso através do link abaixo:</p> <p><%= link_to 'Mudar a minha senha', edit_password_url(@resource, reset_password_token: @token) %></p> <p>Se você não solicitou isso, por favor ignore este e-mail.</p> -<p>A senha senha não será modificada até que você acesse o link acima e crie uma nova.</p> +<p>A senha não será modificada até que você acesse o link acima e crie uma nova.</p> diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb index f574fe08f..2abff0c0d 100644 --- a/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb +++ b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb @@ -1,8 +1,8 @@ Olá, <%= @resource.email %>! -Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo: +Alguém solicitou um link para mudar a sua senha na instância <%= @instance %>. Você pode fazer isso através do link abaixo: <%= edit_password_url(@resource, reset_password_token: @token) %> Se você não solicitou isso, por favor ignore este e-mail. -A senha senha não será modificada até que você acesse o link acima e crie uma nova. +A senha não será modificada até que você acesse o link acima e crie uma nova. diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb index 51e3073f1..124305675 100644 --- a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb +++ b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb @@ -1,7 +1,8 @@ -<p><%= @resource.email %> ,嗨呀!!</p> +<p><%= @resource.email %>,你好呀!</p> -<p>有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接:</p> +<p>有人想修改你在 <%= @instance %> 上的密码呢。如果你确实想修改密码的话,点击下面的链接吧:</p> -<p><%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %></p> +<p><%= link_to '修改密码', edit_password_url(@resource, reset_password_token: @token) %></p> -<p>如果不是的话,忘了它吧。只有你本人通过上面的链接设置新的密码以后你的新密码才会生效。</p> +<p>如果你不想修改密码的话,还请忽略这封邮件哦。</p> +<p>在你点击上面的链接并修改密码前,你的密码是不会改变的。</p> diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb index 7df590f78..f7cd88847 100644 --- a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb +++ b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb @@ -1,7 +1,8 @@ -<%= @resource.email %> ,嗨呀!! +<%= @resource.email %>,你好呀! -有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接: +有人想修改你在 <%= @instance %> 上的密码呢。如果你确实想修改密码的话,点击下面的链接吧: -<%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %> +<%= edit_password_url(@resource, reset_password_token: @token) %> -如果不是的话,忘了它吧。只有你本人通过上面的链接设置新的密码以后你的新密码才会生效。 +如果你不想修改密码的话,还请忽略这封邮件哦。 +在你点击上面的链接并修改密码前,你的密码是不会改变的。 diff --git a/app/workers/admin/suspension_worker.rb b/app/workers/admin/suspension_worker.rb index 6338b1130..e41465ccc 100644 --- a/app/workers/admin/suspension_worker.rb +++ b/app/workers/admin/suspension_worker.rb @@ -6,6 +6,6 @@ class Admin::SuspensionWorker sidekiq_options queue: 'pull' def perform(account_id, remove_user = false) - SuspendAccountService.new.call(Account.find(account_id), remove_user) + SuspendAccountService.new.call(Account.find(account_id), remove_user: remove_user) end end diff --git a/app/workers/thread_resolve_worker.rb b/app/workers/thread_resolve_worker.rb index 38287e8e6..c18a778d5 100644 --- a/app/workers/thread_resolve_worker.rb +++ b/app/workers/thread_resolve_worker.rb @@ -3,7 +3,11 @@ class ThreadResolveWorker include Sidekiq::Worker - sidekiq_options queue: 'pull', retry: false + sidekiq_options queue: 'pull', retry: 3 + + sidekiq_retry_in do |count| + 15 + 10 * (count**4) + rand(10 * (count**4)) + end def perform(child_status_id, parent_url) child_status = Status.find(child_status_id) diff --git a/bin/webpack b/bin/webpack index 528233a78..9d3800c74 100755 --- a/bin/webpack +++ b/bin/webpack @@ -1,27 +1,17 @@ #!/usr/bin/env ruby -$stdout.sync = true +# frozen_string_literal: true +# +# This file was generated by Bundler. +# +# The application 'webpack' is installed as part of a gem, and +# this file is here to facilitate running it. +# -require "shellwords" +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) -ENV["RAILS_ENV"] ||= "development" -RAILS_ENV = ENV["RAILS_ENV"] +require "rubygems" +require "bundler/setup" -ENV["NODE_ENV"] ||= RAILS_ENV -NODE_ENV = ENV["NODE_ENV"] - -APP_PATH = File.expand_path("../", __dir__) -NODE_MODULES_PATH = File.join(APP_PATH, "node_modules") -WEBPACK_CONFIG = File.join(APP_PATH, "config/webpack/#{NODE_ENV}.js") - -unless File.exist?(WEBPACK_CONFIG) - puts "Webpack configuration not found." - puts "Please run bundle exec rails webpacker:install to install webpacker" - exit! -end - -env = { "NODE_PATH" => NODE_MODULES_PATH.shellescape } -cmd = [ "#{NODE_MODULES_PATH}/.bin/webpack", "--config", WEBPACK_CONFIG ] + ARGV - -Dir.chdir(APP_PATH) do - exec env, *cmd -end +load Gem.bin_path("webpacker", "webpack") diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server index c9672f663..cf701102a 100755 --- a/bin/webpack-dev-server +++ b/bin/webpack-dev-server @@ -1,68 +1,17 @@ #!/usr/bin/env ruby -$stdout.sync = true - -require "shellwords" -require "yaml" -require "socket" - -ENV["RAILS_ENV"] ||= "development" -RAILS_ENV = ENV["RAILS_ENV"] - -ENV["NODE_ENV"] ||= RAILS_ENV -NODE_ENV = ENV["NODE_ENV"] - -APP_PATH = File.expand_path("../", __dir__) -CONFIG_FILE = File.join(APP_PATH, "config/webpacker.yml") -NODE_MODULES_PATH = File.join(APP_PATH, "node_modules") -WEBPACK_CONFIG = File.join(APP_PATH, "config/webpack/#{NODE_ENV}.js") - -DEFAULT_LISTEN_HOST_ADDR = NODE_ENV == 'development' ? 'localhost' : '0.0.0.0' - -def args(key) - index = ARGV.index(key) - index ? ARGV[index + 1] : nil -end - -begin - dev_server = YAML.load_file(CONFIG_FILE)[RAILS_ENV]["dev_server"] - - HOSTNAME = args('--host') || dev_server["host"] - PORT = args('--port') || dev_server["port"] - HTTPS = ARGV.include?('--https') || dev_server["https"] - DEV_SERVER_ADDR = "http#{"s" if HTTPS}://#{HOSTNAME}:#{PORT}" - LISTEN_HOST_ADDR = args('--listen-host') || DEFAULT_LISTEN_HOST_ADDR - -rescue Errno::ENOENT, NoMethodError - $stdout.puts "Webpack dev_server configuration not found in #{CONFIG_FILE}." - $stdout.puts "Please run bundle exec rails webpacker:install to install webpacker" - exit! -end - -begin - server = TCPServer.new(LISTEN_HOST_ADDR, PORT) - server.close - -rescue Errno::EADDRINUSE - $stdout.puts "Another program is running on port #{PORT}. Set a new port in #{CONFIG_FILE} for dev_server" - exit! -end - -# Delete supplied host, port and listen-host CLI arguments -["--host", "--port", "--listen-host"].each do |arg| - ARGV.delete(args(arg)) - ARGV.delete(arg) -end - -env = { "NODE_PATH" => NODE_MODULES_PATH.shellescape } - -cmd = [ - "#{NODE_MODULES_PATH}/.bin/webpack-dev-server", "--progress", "--color", - "--config", WEBPACK_CONFIG, - "--host", LISTEN_HOST_ADDR, - "--public", "#{HOSTNAME}:#{PORT}", - "--port", PORT.to_s -] + ARGV - -Dir.chdir(APP_PATH) do - exec env, *cmd -end +# frozen_string_literal: true +# +# This file was generated by Bundler. +# +# The application 'webpack-dev-server' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("webpacker", "webpack-dev-server") diff --git a/boxfile.yml b/boxfile.yml index 59a66d87b..6b904e07d 100644 --- a/boxfile.yml +++ b/boxfile.yml @@ -42,6 +42,7 @@ run.config: fs_watch: true + deploy.config: extra_steps: - NODE_ENV=production bundle exec rake assets:precompile @@ -60,6 +61,7 @@ deploy.config: web.web: - bundle exec rake db:migrate:setup + web.web: start: nginx: nginx -c /app/nanobox/nginx-web.conf @@ -78,6 +80,7 @@ web.web: data.storage: - public/system + web.stream: start: nginx: nginx -c /app/nanobox/nginx-stream.conf @@ -91,8 +94,13 @@ web.stream: writable_dirs: - tmp + worker.sidekiq: - start: bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push -L /app/log/sidekiq.log + start: + default: bundle exec sidekiq -c 5 -q default -L /app/log/sidekiq.log + mailers: bundle exec sidekiq -c 5 -q mailers -L /app/log/sidekiq.log + pull: bundle exec sidekiq -c 5 -q pull -L /app/log/sidekiq.log + push: bundle exec sidekiq -c 5 -q push -L /app/log/sidekiq.log writable_dirs: - tmp @@ -105,50 +113,78 @@ worker.sidekiq: data.storage: - public/system - cron: - - id: generate_static_gifs - schedule: '*/15 * * * *' - command: 'bundle exec rake mastodon:maintenance:add_static_avatars' - - id: update_counter_caches - schedule: '50 * * * *' - command: 'bundle exec rake mastodon:maintenance:update_counter_caches' +worker.cron_only: + start: sleep 365d + + writable_dirs: + - tmp + + log_watch: + rake: 'log/production.log' - # runs feeds:clear, media:clear, users:clear, and push:refresh - - id: do_daily_tasks - schedule: '00 00 * * *' - command: 'bundle exec rake mastodon:daily' + network_dirs: + data.storage: + - public/system - - id: clear_silenced_media - schedule: '10 00 * * *' - command: 'bundle exec rake mastodon:media:remove_silenced' + cron: + # 20:00 (8 pm), server time: send out the daily digest emails to everyone + # who opted to receive one + - id: send_digest_emails + schedule: '00 20 * * *' + command: 'bundle exec rake mastodon:emails:digest' + # 00:10 (ten past midnight), server time: remove local copies of remote + # users' media once they are older than a certain age (use NUM_DAYS evar to + # change this from the default of 7 days) - id: clear_remote_media - schedule: '20 00 * * *' + schedule: '10 00 * * *' command: 'bundle exec rake mastodon:media:remove_remote' + # 00:20 (twenty past midnight), server time: remove subscriptions to remote + # users that nobody follows locally (anymore) - id: clear_unfollowed_subs - schedule: '30 00 * * *' + schedule: '20 00 * * *' command: 'bundle exec rake mastodon:push:clear' - - id: send_digest_emails - schedule: '00 20 * * *' - command: 'bundle exec rake mastodon:emails:digest' - + # 00:30 (half past midnight), server time: update local copies of remote + # users' avatars to match whatever they currently have set on their profile + - id: update_remote_avatars + schedule: '30 00 * * *' + command: 'bundle exec rake mastodon:media:redownload_avatars' + + ############################################################################ + # This task is one you might want to enable, or might not. It keeps disk + # usage low, but makes "shadow bans" (scenarios where the user is silenced, + # but not intended to be made aware that the silencing has occurred) much + # more difficult to put in place, as users would then notice their media is + # vanishing on a regular basis. Enable it if you aren't worried about users + # knowing they've been silenced (on the instance level), and want to save + # disk space. Leave it disabled otherwise. + ############################################################################ + # # 00:00 (midnight), server time: remove media posted by silenced users + # - id: clear_silenced_media + # schedule: '00 00 * * *' + # command: 'bundle exec rake mastodon:media:remove_silenced' + + ############################################################################ # The following two tasks can be uncommented to automatically open and close # registrations on a schedule. The format of 'schedule' is a standard cron # time expression: minute hour day month day-of-week; search for "cron # time expressions" for more info on how to set these up. The examples here # open registration only from 8 am to 4 pm, server time. - # + ############################################################################ + # # 08:00 (8 am), server time: open registrations so new users can join # - id: open_registrations # schedule: '00 08 * * *' # command: 'bundle exec rake mastodon:settings:open_registrations' # + # # 16:00 (4 pm), server time: close registrations so new users *can't* join # - id: close_registrations # schedule: '00 16 * * *' # command: 'bundle exec rake mastodon:settings:close_registrations' + data.db: image: nanobox/postgresql:9.5 @@ -170,6 +206,7 @@ data.db: curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE done + data.redis: image: nanobox/redis:3.0 @@ -189,6 +226,7 @@ data.redis: curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE done + data.storage: image: nanobox/unfs:0.9 diff --git a/config/brakeman.ignore b/config/brakeman.ignore index f198eebac..f7cf89dff 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -10,7 +10,7 @@ "line": 122, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -29,7 +29,7 @@ "line": 128, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -48,7 +48,7 @@ "line": 35, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -60,25 +60,6 @@ { "warning_type": "Dynamic Render Path", "warning_code": 15, - "fingerprint": "3b0a20b08aef13cf8cf865384fae0cfd3324d8200a83262bf4abbc8091b5fec5", - "check_name": "Render", - "message": "Render path contains parameter value", - "file": "app/views/admin/custom_emojis/index.html.haml", - "line": 31, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", - "code": "render(action => filtered_custom_emojis.page(params[:page]), {})", - "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}], - "location": { - "type": "template", - "template": "admin/custom_emojis/index" - }, - "user_input": "params[:page]", - "confidence": "Weak", - "note": "" - }, - { - "warning_type": "Dynamic Render Path", - "warning_code": 15, "fingerprint": "44d3f14e05d8fbb5b23e13ac02f15aa38b2a2f0f03b9ba76bab7f98e155a4a4e", "check_name": "Render", "message": "Render path contains parameter value", @@ -105,7 +86,7 @@ "line": 131, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -124,7 +105,7 @@ "line": 106, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -134,13 +115,32 @@ "note": "" }, { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "8d843713d99e8403f7992f3e72251b633817cf9076ffcbbad5613859d2bbc127", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/admin/custom_emojis/index.html.haml", + "line": 31, + "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]), {})", + "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}], + "location": { + "type": "template", + "template": "admin/custom_emojis/index" + }, + "user_input": "params[:page]", + "confidence": "Weak", + "note": "" + }, + { "warning_type": "SQL Injection", "warning_code": 0, "fingerprint": "9ccb9ba6a6947400e187d515e0bf719d22993d37cfc123c824d7fafa6caa9ac3", "check_name": "SQL", "message": "Possible SQL injection", "file": "lib/mastodon/snowflake.rb", - "line": 86, + "line": 87, "link": "http://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "connection.execute(\" CREATE OR REPLACE FUNCTION timestamp_id(table_name text)\\n RETURNS bigint AS\\n $$\\n DECLARE\\n time_part bigint;\\n sequence_base bigint;\\n tail bigint;\\n BEGIN\\n time_part := (\\n -- Get the time in milliseconds\\n ((date_part('epoch', now()) * 1000))::bigint\\n -- And shift it over two bytes\\n << 16);\\n\\n sequence_base := (\\n 'x' ||\\n -- Take the first two bytes (four hex characters)\\n substr(\\n -- Of the MD5 hash of the data we documented\\n md5(table_name ||\\n '#{SecureRandom.hex(16)}' ||\\n time_part::text\\n ),\\n 1, 4\\n )\\n -- And turn it into a bigint\\n )::bit(16)::bigint;\\n\\n -- Finally, add our sequence number to our base, and chop\\n -- it to the last two bytes\\n tail := (\\n (sequence_base + nextval(table_name || '_id_seq'))\\n & 65535);\\n\\n -- Return the time part and the sequence part. OR appears\\n -- faster here than addition, but they're equivalent:\\n -- time_part has no trailing two bytes, and tail is only\\n -- the last two bytes.\\n RETURN time_part | tail;\\n END\\n $$ LANGUAGE plpgsql VOLATILE;\\n\")", "render_path": null, @@ -182,7 +182,7 @@ "line": 95, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -240,7 +240,7 @@ "line": 125, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -269,6 +269,6 @@ "note": "" } ], - "updated": "2017-10-07 19:24:02 +0200", + "updated": "2017-10-20 00:00:54 +0900", "brakeman_version": "4.0.1" } diff --git a/config/deploy.rb b/config/deploy.rb index 33b88b109..3fd149f21 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -lock '3.8.2' +lock '3.10.0' set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git') set :branch, ENV.fetch('BRANCH', 'master') diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index b35e5c09a..08a96f727 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -46,6 +46,7 @@ ignore_missing: - 'terms.body_html' - 'application_mailer.salutation' - 'errors.500' + ignore_unused: - 'activemodel.errors.*' - 'activerecord.attributes.*' @@ -58,3 +59,4 @@ ignore_unused: - 'errors.messages.*' - 'activerecord.errors.models.doorkeeper/*' - 'errors.429' + - 'admin.accounts.roles.*' diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index 2c82a91db..14bd034e6 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -7,60 +7,76 @@ Paperclip.interpolates :filename do |attachment, style| [basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.') end -Paperclip::Attachment.default_options[:use_timestamp] = false +Paperclip::Attachment.default_options.merge!( + use_timestamp: false, + path: ':class/:attachment/:id_partition/:style/:filename', + storage: :fog +) if ENV['S3_ENABLED'] == 'true' - Aws.eager_autoload!(services: %w(S3)) + require 'fog/aws' - Paperclip::Attachment.default_options[:storage] = :s3 - Paperclip::Attachment.default_options[:s3_protocol] = ENV.fetch('S3_PROTOCOL') { 'https' } - Paperclip::Attachment.default_options[:url] = ':s3_domain_url' - Paperclip::Attachment.default_options[:s3_host_name] = ENV.fetch('S3_HOSTNAME') { "s3-#{ENV.fetch('S3_REGION')}.amazonaws.com" } - Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename' - Paperclip::Attachment.default_options[:s3_headers] = { 'Cache-Control' => 'max-age=315576000' } - Paperclip::Attachment.default_options[:s3_permissions] = ENV.fetch('S3_PERMISSION') { 'public-read' } - Paperclip::Attachment.default_options[:s3_region] = ENV.fetch('S3_REGION') { 'us-east-1' } + s3_protocol = ENV.fetch('S3_PROTOCOL') { 'https' } + s3_hostname = ENV.fetch('S3_HOSTNAME') { "s3-#{ENV['S3_REGION']}.amazonaws.com" } + aws_signature_version = ENV['S3_SIGNATURE_VERSION'] == 's3' ? 2 : ENV['S3_SIGNATURE_VERSION'].to_i + aws_signature_version = 4 if aws_signature_version.zero? - Paperclip::Attachment.default_options[:s3_credentials] = { - bucket: ENV.fetch('S3_BUCKET'), - access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'), - secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'), - } - - unless ENV['S3_ENDPOINT'].blank? - Paperclip::Attachment.default_options[:s3_options] = { - endpoint: ENV['S3_ENDPOINT'], - signature_version: ENV['S3_SIGNATURE_VERSION'] || 'v4', - force_path_style: true, + Paperclip::Attachment.default_options.merge!( + fog_credentials: { + provider: 'AWS', + aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], + aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], + aws_signature_version: aws_signature_version, + region: ENV.fetch('S3_REGION') { 'us-east-1' }, + scheme: s3_protocol, + host: s3_hostname + }, + fog_directory: ENV['S3_BUCKET'], + fog_options: { + acl: ENV.fetch('S3_PERMISSION') { 'public-read' }, + cache_control: 'max-age=315576000', } + ) - Paperclip::Attachment.default_options[:url] = ':s3_path_url' + if ENV.has_key?('S3_ENDPOINT') + Paperclip::Attachment.default_options[:fog_credentials].merge!( + endpoint: ENV['S3_ENDPOINT'], + path_style: true + ) + Paperclip::Attachment.default_options[:fog_host] = "#{s3_protocol}://#{s3_hostname}/#{ENV['S3_BUCKET']}" end - unless ENV['S3_CLOUDFRONT_HOST'].blank? - Paperclip::Attachment.default_options[:url] = ':s3_alias_url' - Paperclip::Attachment.default_options[:s3_host_alias] = ENV['S3_CLOUDFRONT_HOST'] + if ENV.has_key?('S3_CLOUDFRONT_HOST') + Paperclip::Attachment.default_options[:fog_host] = "#{s3_protocol}://#{ENV['S3_CLOUDFRONT_HOST']}" end elsif ENV['SWIFT_ENABLED'] == 'true' + require 'fog/openstack' + Paperclip::Attachment.default_options.merge!( - path: ':class/:attachment/:id_partition/:style/:filename', - storage: :fog, fog_credentials: { provider: 'OpenStack', - openstack_username: ENV.fetch('SWIFT_USERNAME'), - openstack_project_name: ENV.fetch('SWIFT_TENANT'), - openstack_tenant: ENV.fetch('SWIFT_TENANT'), # Some OpenStack-v2 ignores project_name but needs tenant - openstack_api_key: ENV.fetch('SWIFT_PASSWORD'), - openstack_auth_url: ENV.fetch('SWIFT_AUTH_URL'), - openstack_domain_name: ENV['SWIFT_DOMAIN_NAME'] || 'default', + openstack_username: ENV['SWIFT_USERNAME'], + openstack_project_name: ENV['SWIFT_TENANT'], + openstack_tenant: ENV['SWIFT_TENANT'], # Some OpenStack-v2 ignores project_name but needs tenant + openstack_api_key: ENV['SWIFT_PASSWORD'], + openstack_auth_url: ENV['SWIFT_AUTH_URL'], + openstack_domain_name: ENV.fetch('SWIFT_DOMAIN_NAME') { 'default' }, openstack_region: ENV['SWIFT_REGION'], - openstack_cache_ttl: ENV['SWIFT_CACHE_TTL'] || 60, + openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 }, }, - fog_directory: ENV.fetch('SWIFT_CONTAINER'), + fog_directory: ENV['SWIFT_CONTAINER'], fog_host: ENV['SWIFT_OBJECT_URL'], fog_public: true ) else - Paperclip::Attachment.default_options[:path] = (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename' - Paperclip::Attachment.default_options[:url] = (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename' + require 'fog/local' + + Paperclip::Attachment.default_options.merge!( + fog_credentials: { + provider: 'Local', + local_root: ENV.fetch('PAPERCLIP_ROOT_PATH') { Rails.root.join('public', 'system') }, + }, + fog_directory: '', + fog_host: ENV.fetch('PAPERCLIP_ROOT_URL') { '/system' } + ) end diff --git a/config/initializers/statsd.rb b/config/initializers/statsd.rb index 17a176174..45702ac94 100644 --- a/config/initializers/statsd.rb +++ b/config/initializers/statsd.rb @@ -4,7 +4,7 @@ if ENV['STATSD_ADDR'].present? host, port = ENV['STATSD_ADDR'].split(':') statsd = ::Statsd.new(host, port) - statsd.namespace = ['Mastodon', Rails.env].join('.') + statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') } ::NSA.inform_statsd(statsd) do |informant| informant.collect(:action_controller, :web) diff --git a/config/locales/activerecord.zh-CN.yml b/config/locales/activerecord.zh-CN.yml new file mode 100644 index 000000000..8628d6677 --- /dev/null +++ b/config/locales/activerecord.zh-CN.yml @@ -0,0 +1,13 @@ +--- +zh-CN: + activerecord: + errors: + models: + account: + attributes: + username: + invalid: 只能使用字母、数字和下划线 + status: + attributes: + reblog: + taken: 已经被转嘟过 diff --git a/config/locales/devise.oc.yml b/config/locales/devise.oc.yml index 5cccb48ff..266f87373 100644 --- a/config/locales/devise.oc.yml +++ b/config/locales/devise.oc.yml @@ -9,7 +9,7 @@ oc: already_authenticated: Sètz ja connectat. inactive: Vòstre compte es pas encara activat. invalid: Corrièl o senhal invalid. - last_attempt: Vos demòra un ensag abans que vòstre compte siasgue blocat. + last_attempt: Vos demòra un ensag abans que vòstre compte siasque blocat. locked: Vòstre compte es blocat. not_found_in_database: Corrièl o senhal invalid. timeout: Vòstra session s’a acabat. Mercés de vos tornar connectar per contunhar. diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml index 8cc814224..0e40fcc90 100644 --- a/config/locales/devise.zh-CN.yml +++ b/config/locales/devise.zh-CN.yml @@ -2,24 +2,24 @@ zh-CN: devise: confirmations: - confirmed: 成功验证您的邮箱地址。 - send_instructions: 您的电子邮箱将在几分钟后收到一封邮箱确认邮件。 - send_paranoid_instructions: 如果您的邮箱存在于我们的数据库中,您将收到一封确认帐号的邮件。 + confirmed: 成功验证你的邮箱地址。 + send_instructions: 你的电子邮箱将在几分钟后收到一封确认邮件。如果没有,请检查你的垃圾邮箱。 + send_paranoid_instructions: 如果你的邮箱存在于我们的数据库中,你将收到一封确认注册的邮件。如果没有,请检查你的垃圾邮箱。 failure: - already_authenticated: 您已经登录。 - inactive: 您还没有激活帐户。 + already_authenticated: 你已经登录。 + inactive: 你还没有激活帐户。 invalid: " %{authentication_keys} 或密码错误。" - last_attempt: 您还有最后一次尝试机会,再次失败您的帐号将被锁定。 - locked: 您的帐号已被锁定。 + last_attempt: 你还有最后一次尝试机会,再次失败你的帐户将被锁定。 + locked: 你的帐户已被锁定。 not_found_in_database: "%{authentication_keys}或密码错误。" - timeout: 您已登录超时,请重新登录。 + timeout: 你已登录超时,请重新登录。 unauthenticated: 继续操作前请注册或者登录。 - unconfirmed: 继续操作前请先确认您的帐号。 + unconfirmed: 继续操作前请先确认你的帐户。 mailer: confirmation_instructions: subject: Mastodon 帐户确认信息 email_changed: - subject: Mastodon 电邮已被修改 + subject: Mastodon 电子邮件地址已被修改 password_change: subject: Mastodon 密码已被重置 reset_password_instructions: @@ -30,33 +30,33 @@ zh-CN: failure: 由于%{reason},无法从%{kind}获得授权。 success: 成功地从%{kind}获得授权。 passwords: - no_token: 无重置邮件不可访问密码重置页面。如果您是从重置邮件来到了这个页面,请确保您输入的URL完整的。 - send_instructions: 几分钟后,您将收到重置密码的电子邮件。 - send_paranoid_instructions: 如果您的邮箱存在于我们的数据库中,您将收到一封找回密码的邮件。 - updated: 您的密码已修改成功,您现在已登录。 - updated_not_active: 您的密码已修改成功。 + no_token: 你必须通过密码重置邮件才能访问这个页面。如果你确实如此,请确保你输入的 URL 是完整的。 + send_instructions: 几分钟后,你将收到重置密码的电子邮件。如果没有,请检查你的垃圾邮箱。 + send_paranoid_instructions: 如果你的邮箱存在于我们的数据库中,你将收到一封找回密码的邮件。如果没有,请检查你的垃圾邮箱。 + updated: 你的密码已修改成功,你现在已登录。 + updated_not_active: 你的密码已修改成功。 registrations: - destroyed: 再见!您的帐户已成功注销。我们希望很快可以再见到您。 - signed_up: 欢迎!您已注册成功。 - signed_up_but_inactive: 您已注册,但尚未激活帐号。 - signed_up_but_locked: 您已注册,但帐号被锁定了。 - signed_up_but_unconfirmed: 一封带有确认链接的邮件已经发送至您的邮箱,请检查邮箱(包括垃圾邮箱),并点击该链接激活您的帐号。 - update_needs_confirmation: 信息更新成功,但我们需要验证您的新电子邮件地址,请检查邮箱(包括垃圾邮箱),并点击该链接激活您的帐号。 - updated: 帐号资料更新成功。 + destroyed: 再见!你的帐户已成功注销。我们希望很快可以再见到你。 + signed_up: 欢迎!你已注册成功。 + signed_up_but_inactive: 你已注册,但尚未激活帐户。 + signed_up_but_locked: 你已注册,但帐户被锁定了。 + signed_up_but_unconfirmed: 一封带有确认链接的邮件已经发送至你的邮箱,请点击邮件中的链接以激活你的帐户。如果没有,请检查你的垃圾邮箱。 + update_needs_confirmation: 信息更新成功,但我们需要验证你的新电子邮件地址,请点击邮件中的链接以确认。如果没有,请检查你的垃圾邮箱。 + updated: 帐户资料更新成功。 sessions: - already_signed_out: 已经退出成功。 + already_signed_out: 已成功登出。 signed_in: 登录成功。 - signed_out: 退出成功。 + signed_out: 登出成功。 unlocks: - send_instructions: 几分钟后,您将收到一封解锁帐号的邮件。 - send_paranoid_instructions: 如果您的邮箱存在于我们的数据库中,您将收到一封解锁帐号的邮件。 - unlocked: 您的帐号已成功解锁,您现在已登录。 + send_instructions: 几分钟后,你将收到一封解锁帐户的邮件。如果没有,请检查你的垃圾邮箱。 + send_paranoid_instructions: 如果你的邮箱存在于我们的数据库中,你将收到一封解锁帐户的邮件。如果没有,请检查你的垃圾邮箱。 + unlocked: 你的帐户已成功解锁。登录以继续。 errors: messages: already_confirmed: 已经确认,请重新登录。 - confirmation_period_expired: 注册帐号后须在%{period}以内确认。请重新注册。 + confirmation_period_expired: 注册帐户后须在 %{period}以内确认。请重新注册。 expired: 邮件确认已过期,请重新注册。 not_found: 找不到。 not_locked: 未锁定。 not_saved: - other: 发生%{count}个错误,导致%{resource}保存失败: + other: 发生 %{count} 个错误,导致%{resource}保存失败: diff --git a/config/locales/doorkeeper.pl.yml b/config/locales/doorkeeper.pl.yml index fa4324e4d..33f133c06 100644 --- a/config/locales/doorkeeper.pl.yml +++ b/config/locales/doorkeeper.pl.yml @@ -59,7 +59,7 @@ pl: error: title: Wystapił błąd new: - able_to: Będzie w stanie + able_to: Uzyska prompt: Aplikacja %{client_name} prosi o dostęp do Twojego konta title: Wymagana jest autoryzacja show: @@ -114,6 +114,6 @@ pl: application: title: Uwierzytelnienie OAuth jest wymagane scopes: - follow: śledzenie, blokowanie, usuwanie blokady, anulowanie śledzenia kont + follow: możliwość śledzenia, blokowania, usuwania blokad, anulowania śledzenia kont read: dostęp do odczytu danych konta - write: publikowanie wpisów w Twoim imieniu + write: możliwość publikowania wpisów w Twoim imieniu diff --git a/config/locales/doorkeeper.zh-CN.yml b/config/locales/doorkeeper.zh-CN.yml index 12b38b81f..c7e8f368d 100644 --- a/config/locales/doorkeeper.zh-CN.yml +++ b/config/locales/doorkeeper.zh-CN.yml @@ -3,15 +3,16 @@ zh-CN: activerecord: attributes: doorkeeper/application: - name: 名称 - redirect_uri: 登录回调地址 + name: 应用名称 + redirect_uri: 重定向 URI scopes: 权限范围 + website: 应用网站 errors: models: doorkeeper/application: attributes: redirect_uri: - fragment_present: 不能包含片段(#) + fragment_present: 不能包含网址片段(#) invalid_uri: 必须是有效的 URL 格式 relative_uri: 必须是绝对的 URL 地址 secured_uri: 必须是 HTTPS/SSL 的 URL 地址 @@ -30,60 +31,65 @@ zh-CN: form: error: 抱歉! 提交信息的时候遇到了下面的错误 help: - native_redirect_uri: 使用 %{native_redirect_uri} 作为本地测试 + native_redirect_uri: 本地测试请使用 %{native_redirect_uri} redirect_uri: 每行只能有一个 URL - scopes: 用空格隔开权限范围,留空则使用默认设置 + scopes: 用空格分割权限范围,留空则使用默认设置 index: - callback_url: 登录回调地址 + application: 应用 + callback_url: 回调 URL + delete: 删除 name: 名称 new: 创建新应用 + scopes: 权限范围 + show: 显示 title: 你的应用 new: title: 创建新应用 show: actions: 操作 application_id: 应用 ID - callback_urls: 登录回调地址 + callback_urls: 回调 URL scopes: 权限范围 - secret: 私钥 + secret: 应用密钥 title: 应用:%{name} authorizations: buttons: - authorize: 授权 - deny: 拒绝 + authorize: 同意授权 + deny: 拒绝授权 error: - title: 存在错误 + title: 发生错误 new: - able_to: 此应用将会 - prompt: 授权 %{client_name} 使用你的帐号? - title: 需要你授权 + able_to: 此应用将能够 + prompt: 授权 %{client_name} 使用你的帐户? + title: 需要授权 show: - title: Copy this authorization code and paste it to the application. + title: 接下来请复制此处的授权代码并粘贴到应用中。 authorized_applications: buttons: - revoke: 注销 + revoke: 撤销授权 confirmations: - revoke: 确定要注销此应用的认证信息吗? + revoke: 确定要撤销对此应用的授权吗? index: application: 应用 created_at: 授权时间 date_format: "%Y-%m-%d %H:%M:%S" - title: 你授权的应用列表 + scopes: 权限范围 + title: 已授权的应用列表 errors: messages: access_denied: 用户或服务器拒绝了请求 - credential_flow_not_configured: Resource Owner Password Credentials flow failed,原因是 Doorkeeper.configure.resource_owner_from_credentials 尚未设置。 - invalid_client: 由于未知、不支持或没有客户端,认证失败 + credential_flow_not_configured: 由于 Doorkeeper.configure.resource_owner_from_credentials 尚未配置,应用验证授权流程失败。 + invalid_client: 由于应用信息未知、未提交认证信息或使用了不支持的认证方式,认证失败 invalid_grant: 授权方式无效,或者登录回调地址无效、过期或已被撤销 invalid_redirect_uri: 无效的登录回调地址 - invalid_request: 这个请求缺少必要的参数,或者参数值、格式不正确 - invalid_resource_owner: 资源所有者认证无效或没有所有者 - invalid_scope: 请求范围无效、未知或格式不正确 + invalid_request: 请求缺少必要的参数,或者参数值、格式不正确 + invalid_resource_owner: 资源所有者认证无效,或找不到所有者 + invalid_scope: 请求的权限范围无效、未知或格式不正确 invalid_token: expired: 访问令牌已过期 revoked: 访问令牌已被吊销 unknown: 访问令牌无效 - resource_owner_authenticator_not_configured: Resource Owner find failed,原因是 Doorkeeper.configure.resource_owner_authenticator 尚未设置。 + resource_owner_authenticator_not_configured: 由于 Doorkeeper.configure.resource_owner_authenticator 尚未配置,查找资源所有者失败。 server_error: 服务器异常,无法处理请求 temporarily_unavailable: 服务器维护中或负载过高,暂时无法处理请求 unauthorized_client: 未授权的应用,请求无法执行 @@ -99,16 +105,15 @@ zh-CN: notice: 应用修改成功 authorized_applications: destroy: - notice: 已成功注销了应用的认证信息 + notice: 已成功撤销对此应用的授权 layouts: admin: nav: applications: 应用 - home: 首页 oauth2_provider: OAuth2 提供商 application: - title: OAuth 认证 + title: 需要 OAuth 认证 scopes: - follow: 关注(或取消关注),屏蔽(或取消屏蔽)用户 - read: 读取你的账户数据 + follow: 关注(或取消关注)、屏蔽(或取消屏蔽)用户 + read: 读取你的帐户数据 write: 为你发表嘟文 diff --git a/config/locales/en.yml b/config/locales/en.yml index d5c46470c..a590b189f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -43,7 +43,7 @@ en: people_followed_by: People whom %{name} follows people_who_follow: People who follow %{name} posts: Toots - posts_with_replies: Toots with replies + posts_with_replies: Toots and replies remote_follow: Remote follow reserved_username: The username is reserved roles: @@ -59,13 +59,19 @@ en: destroyed_msg: Moderation note successfully destroyed! accounts: are_you_sure: Are you sure? + by_domain: Domain confirm: Confirm confirmed: Confirmed + demote: Demote + disable: Disable disable_two_factor_authentication: Disable 2FA + disabled: Disabled display_name: Display name domain: Domain edit: Edit email: E-mail + enable: Enable + enabled: Enabled feed_url: Feed URL followers: Followers followers_url: Followers URL @@ -77,7 +83,9 @@ en: local: Local remote: Remote title: Location + login_status: Login status media_attachments: Media attachments + memorialize: Turn into memoriam moderation: all: All silenced: Silenced @@ -94,6 +102,7 @@ en: outbox_url: Outbox URL perform_full_suspension: Perform full suspension profile_url: Profile URL + promote: Promote protocol: Protocol public: Public push_subscription_expires: PuSH subscription expires @@ -101,6 +110,11 @@ en: reset: Reset reset_password: Reset password resubscribe: Resubscribe + role: Permissions + roles: + admin: Administrator + moderator: Moderator + user: User salmon_url: Salmon URL search: Search shared_inbox_url: Shared Inbox URL @@ -133,6 +147,7 @@ en: listed: Listed new: title: Add new custom emoji + overwrite: Overwrite shortcode: Shortcode shortcode_hint: At least 2 characters, only alphanumeric characters and underscores title: Custom emojis @@ -377,6 +392,7 @@ en: following: Following list muting: Muting list upload: Upload + in_memoriam_html: In Memoriam. keyword_mutes: add_keyword: Add keyword edit: Edit diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0d9c227f3..55588d111 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -437,7 +437,7 @@ fr: action_favourite: Ajouter aux favoris title: "%{name} vous a mentionné·e" reblog: - title: "%{name} a partagé⋅e votre statut" + title: "%{name} a partagé votre statut" remote_follow: acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre ce⋅tte utilisateur⋅rice missing_resource: L’URL de redirection n’a pas pu être trouvée diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 391556040..82b642b5b 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -59,13 +59,19 @@ ja: destroyed_msg: モデレーションメモを削除しました accounts: are_you_sure: 本当に実行しますか? + by_domain: ドメイン confirm: 確認 confirmed: 確認済み + demote: 降格 + disable: 無効化 disable_two_factor_authentication: 二段階認証を無効にする + disabled: 無効 display_name: 表示名 domain: ドメイン edit: 編集 email: E-mail + enable: 有効化 + enabled: 有効 feed_url: フィードURL followers: フォロワー数 followers_url: Followers URL @@ -77,7 +83,9 @@ ja: local: ローカル remote: リモート title: ロケーション + login_status: ログイン media_attachments: 添付されたメディア + memorialize: 追悼アカウント化 moderation: all: すべて silenced: サイレンス中 @@ -94,6 +102,7 @@ ja: outbox_url: Outbox URL perform_full_suspension: 完全に活動停止させる profile_url: プロフィールURL + promote: 昇格 protocol: プロトコル public: パブリック push_subscription_expires: PuSH購読期限 @@ -101,6 +110,11 @@ ja: reset: リセット reset_password: パスワード再設定 resubscribe: 再講読 + role: 役割 + roles: + admin: 管理者 + moderator: モデレーター + user: ユーザー salmon_url: Salmon URL search: 検索 shared_inbox_url: Shared Inbox URL @@ -130,11 +144,16 @@ ja: enable: 有効化 enabled_msg: 絵文字を有効化しました image_hint: 50KBまでのPNG画像を利用できます。 + listed: 収載 new: title: 新規カスタム絵文字の追加 + overwrite: 上書き shortcode: ショートコード shortcode_hint: 2文字以上の半角英数字とアンダーバーのみ利用できます。 title: カスタム絵文字 + unlisted: 未収載 + update_failed_msg: 絵文字を更新できませんでした + updated_msg: 絵文字の更新に成功しました upload: アップロード domain_blocks: add_new: 新規追加 @@ -373,6 +392,7 @@ ja: following: フォロー中のアカウントリスト muting: ミュートしたアカウントリスト upload: アップロード + in_memoriam_html: 故人を偲んで landing_strip_html: "<strong>%{name}</strong> さんはインスタンス %{link_to_root_path} のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。" landing_strip_signup_html: もしお持ちでないなら <a href="%{sign_up_path}">こちら</a> からサインアップできます。 media_attachments: @@ -390,8 +410,8 @@ ja: one: "新しい1件の通知 \U0001F418" other: "新しい%{count}件の通知 \U0001F418" favourite: - body: 'あなたのトゥートが %{name} さんにお気に入り登録されました:' - subject: "%{name} さんがあなたのトゥートをお気に入りに登録しました" + body: "%{name} さんにお気に入り登録された、あなたのトゥートがあります:" + subject: "%{name} さんにお気に入りに登録されました" follow: body: "%{name} さんにフォローされています" subject: "%{name} さんにフォローされています" @@ -402,8 +422,8 @@ ja: body: "%{name} さんから返信がありました:" subject: "%{name} さんに返信されました" reblog: - body: 'あなたのトゥートが %{name} さんにブーストされました:' - subject: あなたのトゥートが %{name} さんにブーストされました + body: "%{name} さんにブーストされた、あなたのトゥートがあります:" + subject: "%{name} さんにブーストされました" number: human: decimal_units: diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 0d2a8c2f6..914cc7e9d 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -59,13 +59,18 @@ oc: destroyed_msg: Nòta de moderacion ben suprimida ! accounts: are_you_sure: Sètz segur ? + by_domain: Domeni confirm: Confirmar confirmed: Confirmat + disable: Desactivar disable_two_factor_authentication: Desactivar 2FA + disabled: Desactivat display_name: Escais-nom domain: Domeni edit: Modificar email: Corrièl + enable: Activar + enabled: Activat feed_url: Flux URL followers: Seguidors followers_url: URL dels seguidors @@ -77,7 +82,9 @@ oc: local: Locals remote: Alonhats title: Emplaçament + login_status: Estat formulari de connexion media_attachments: Mèdias ajustats + memorialize: Passar en memorial moderation: all: Tot silenced: Rescondut @@ -130,11 +137,16 @@ oc: enable: Activar enabled_msg: Aqueste emoji es ben activat image_hint: PNG cap a 50Ko + listed: Listat new: title: Ajustar un nòu emoji personal + overwrite: Remplaçar shortcode: Acorchi shortcode_hint: Almens 2 caractèrs, solament alfanumerics e jonhent bas title: Emojis personals + unlisted: Pas listat + update_failed_msg: Mesa a jorn de l’emoji fracasada + updated_msg: Emoji ben mes a jorn ! upload: Enviar domain_blocks: add_new: N’ajustar un nòu @@ -451,6 +463,7 @@ oc: following: Lista de mond que seguètz muting: Lista de mond que volètz pas legir upload: Importar + in_memoriam_html: En Memòria. landing_strip_html: "<strong>%{name}</strong> utiliza %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse." landing_strip_signup_html: S’es pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>. media_attachments: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index c58c1c2f8..49dace354 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -9,7 +9,7 @@ pl: contact_missing: Nie ustawiono contact_unavailable: Nie dotyczy description_headline: Czym jest %{domain}? - domain_count_after: instancji + domain_count_after: instancjami domain_count_before: Serwer połączony z extended_description_html: | <h3>Dobre miejsce na zasady użytkowania</h3> @@ -59,13 +59,19 @@ pl: destroyed_msg: Pomyślnie usunięto notatkę moderacyjną! accounts: are_you_sure: Jesteś tego pewien? + by_domain: Domena confirm: Potwierdź confirmed: Potwierdzono + demote: Degraduj + disable: Dezaktywuj disable_two_factor_authentication: Wyłącz uwierzytelnianie dwuetapowe + disabled: Dezaktywowano display_name: Wyświetlana nazwa domain: Domena edit: Edytuj email: Adres e-mail + enable: Aktywuj + enabled: Aktywowano feed_url: Adres kanału followers: Śledzący followers_url: Adres śledzących @@ -77,7 +83,9 @@ pl: local: Lokalne remote: Zdalne title: Położenie + login_status: Stan logowania media_attachments: Załączniki multimedialne + memorialize: Przełącz na „In Memoriam” moderation: all: Wszystkie silenced: Wyciszone @@ -94,6 +102,7 @@ pl: outbox_url: Adres skrzynki nadawczej perform_full_suspension: Całkowicie zawieś profile_url: Adres profilu + promote: Podnieś uprawnienia protocol: Protokół public: Publiczne push_subscription_expires: Subskrypcja PuSH wygasa @@ -101,6 +110,11 @@ pl: reset: Resetuj reset_password: Resetuj hasło resubscribe: Ponów subskrypcję + role: Uprawnienia + roles: + admin: Administrator + moderator: Moderator + user: Użytkownik salmon_url: Adres Salmon search: Szukaj shared_inbox_url: Adres udostępnianej skrzynki @@ -109,7 +123,7 @@ pl: report: zgłoszeń targeted_reports: Zgłoszenia dotyczące tego użytkownika silence: Wycisz - statuses: Statusy + statuses: Wpisy subscribe: Subskrybuj title: Konta undo_silenced: Cofnij wyciszenie @@ -130,12 +144,16 @@ pl: enable: Włącz enabled_msg: Pomyślnie przywrócono emoji image_hint: Plik PNG ważący do 50KB + listed: Widoczne new: title: Dodaj nowe niestandardowe emoji shortcode: Shortcode shortcode_hint: Co najmniej 2 znaki, tylko znaki alfanumeryczne i podkreślniki title: Niestandardowe emoji - upload: Wyślij + unlisted: Niewidoczne + update_failed_msg: Nie udało się zaktualizować emoji + updated_msg: Pomyślnie zaktualizowano emoji + upload: Dodaj domain_blocks: add_new: Dodaj nową created_msg: Blokada domen jest przetwarzana @@ -203,7 +221,7 @@ pl: reported_by: Zgłaszający resolved: Rozwiązane silence_account: Wycisz konto - status: Status + status: Stan suspend_account: Zawieś konto target: Cel title: Zgłoszenia @@ -212,7 +230,7 @@ pl: settings: bootstrap_timeline_accounts: desc_html: Oddzielaj nazwy użytkowników przecinkami. Działa tylko dla niezablokowanych kont w obrębie instancji. Jeżeli puste, zostaną użyte konta administratorów instancji. - title: Domyślne obserwacje nowych użytkowników + title: Domyślnie obserwowani użytkownicy contact_information: email: Służbowy adres e-mail username: Nazwa użytkownika do kontaktu @@ -256,7 +274,7 @@ pl: show: Pokaż zawartość multimedialną title: Media no_media: Bez zawartości multimedialnej - title: Statusy konta + title: Wpisy konta with_media: Z zawartością multimedialną subscriptions: callback_url: URL zwrotny @@ -332,7 +350,7 @@ pl: errors: '403': Nie masz uprawnień, aby wyświetlić tę stronę. '404': Strona, którą próbujesz odwiedzić, nie istnieje. - '410': Strona, którą próbujesz odwiedzić, już nie istnieje. + '410': Strona, którą próbujesz odwiedzić, przestała istnieć. '422': content: Sprawdzanie bezpieczeństwa nie powiodło się. Czy blokujesz pliki cookie? title: Sprawdzanie bezpieczeństwa nie powiodło się @@ -349,7 +367,7 @@ pl: storage: Urządzenie przechowujące dane followers: domain: Domena - explanation_html: Jeżeli chcesz mieć pewność, kto może przeczytać Twoje statusy, musisz kontrolować, kto śledzi Twój profil. <strong>Twoje prywatne statusy są dostarczane na te instancje, na których jesteś śledzony</strong>. Możesz sprawdzać, kto Cię śledzi i blokować ich, jeśli nie ufasz właścicielom lub oprogramowaniu danej instancji. + explanation_html: Jeżeli chcesz mieć pewność, kto może przeczytać Twoje wpisy, musisz kontrolować, kto śledzi Twój profil. <strong>Twoje prywatne wpisy są dostarczane na te instancje, na których jesteś śledzony</strong>. Możesz sprawdzać, kto Cię śledzi i blokować ich, jeśli nie ufasz właścicielom lub oprogramowaniu danej instancji. followers_count: Liczba śledzących lock_link: Zablokuj swoje konto purge: Przestań śledzić @@ -357,7 +375,7 @@ pl: one: W trakcie usuwania śledzących z jednej domeny… other: W trakcie usuwania śledzących z %{count} domen… true_privacy_html: Pamiętaj, że <strong>rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end</strong>. - unlocked_warning_html: Każdy może Cię śledzić, aby natychmiastowo zobaczyć twoje statusy. %{lock_link} aby móc kontrolować, kto Cię śledzi. + unlocked_warning_html: Każdy może Cię śledzić, aby natychmiastowo zobaczyć twoje wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi. unlocked_warning_title: Twoje konto nie jest zablokowane generic: changes_saved_msg: Ustawienia zapisane! @@ -374,11 +392,12 @@ pl: following: Lista śledzonych muting: Lista wyciszonych upload: Załaduj + in_memoriam_html: Ku pamięci. landing_strip_html: "<strong>%{name}</strong> ma konto na %{link_to_root_path}. Możesz je śledzić i wejść z nim w interakcję jeśli masz konto gdziekolwiek w Fediwersum." landing_strip_signup_html: Jeśli jeszcze go nie masz, możesz <a href="%{sign_up_path}">stworzyć konto</a>. media_attachments: validations: - images_and_video: Nie możesz załączyć pliku wideo do statusu, który zawiera już zdjęcia + images_and_video: Nie możesz załączyć pliku wideo do wpisu, który zawiera już zdjęcia too_many: Nie możesz załączyć więcej niż 4 plików notification_mailer: digest: @@ -431,7 +450,7 @@ pl: web: Sieć push_notifications: favourite: - title: "%{name} dodał Twój status do ulubionych" + title: "%{name} dodał Twój wpis do ulubionych" follow: title: "%{name} zaczął Cię śledzić" group: @@ -442,7 +461,7 @@ pl: action_favourite: Dodaj do ulubionych title: "%{name} wspomniał o Tobie" reblog: - title: "%{name} podbił Twój status" + title: "%{name} podbił Twój wpis" remote_follow: acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny @@ -610,7 +629,7 @@ pl: manual_instructions: 'Jeżeli nie możesz zeskanować kodu QR, musisz wprowadzić ten kod ręcznie:' recovery_codes: Przywróć kody zapasowe recovery_codes_regenerated: Pomyślnie wygenerowano ponownie kody zapasowe - recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. <strong>Trzymaj je w bezpiecznym miejscu</strong>. Na przykład, wydrukuj je i przechowuj z ważnymu dokumentami. + recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. <strong>Trzymaj je w bezpiecznym miejscu</strong>. Na przykład, wydrukuj je i przechowuj z ważnymi dokumentami. setup: Skonfiguruj wrong_code: Wprowadzony kod jest niepoprawny! Czy czas serwera i urządzenia jest poprawny? users: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 4b9ea152f..f5c61c01c 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -43,7 +43,7 @@ pt-BR: people_followed_by: Pessoas que %{name} segue people_who_follow: Pessoas que seguem %{name} posts: Toots - posts_with_replies: Toots com respostas + posts_with_replies: Toots e respostas remote_follow: Siga remotamente reserved_username: Este usuário está reservado roles: @@ -59,13 +59,19 @@ pt-BR: destroyed_msg: Nota de moderação excluída com sucesso! accounts: are_you_sure: Você tem certeza? + by_domain: Domínio confirm: Confirmar confirmed: Confirmado + demote: Rebaixar + disable: Desativar disable_two_factor_authentication: Desativar 2FA + disabled: Desativado display_name: Nome de exibição domain: Domínio edit: Editar email: E-mail + enable: Ativar + enabled: Ativado feed_url: URL do feed followers: Seguidores followers_url: URL de seguidores @@ -77,7 +83,9 @@ pt-BR: local: Local remote: Remoto title: Localização + login_status: Status de login media_attachments: Mídia(s) anexada(s) + memorialize: Tornar um memorial moderation: all: Todos silenced: Silenciados @@ -94,6 +102,7 @@ pt-BR: outbox_url: URL da Outbox perform_full_suspension: Efetue suspensão total profile_url: URL do perfil + promote: Promover protocol: Protocolo public: Público push_subscription_expires: Inscrição PuSH expira @@ -101,6 +110,11 @@ pt-BR: reset: Anular reset_password: Modificar senha resubscribe: Reinscrever-se + role: Permissões + roles: + admin: Administrador + moderator: Moderador + user: Usuário salmon_url: Salmon URL search: Pesquisar shared_inbox_url: URL da Inbox Compartilhada @@ -130,11 +144,16 @@ pt-BR: enable: Habilitar enabled_msg: Emoji habilitado com sucesso! image_hint: PNG de até 50KB + listed: Listado new: title: Adicionar novo emoji customizado + overwrite: Sobrescrever shortcode: Atalho shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underscores title: Emojis customizados + unlisted: Não listado + update_failed_msg: Não foi possível atualizar esse emoji + updated_msg: Emoji atualizado com sucesso! upload: Enviar domain_blocks: add_new: Adicionar novo @@ -373,6 +392,7 @@ pt-BR: following: Pessoas que você segue muting: Lista de silêncio upload: Enviar + in_memoriam_html: Em memória de landing_strip_html: "<strong>%{name}</strong> é um usuário no %{link_to_root_path}. Você pode segui-lo ou interagir com ele se você tiver uma conta em qualquer lugar no fediverso." landing_strip_signup_html: Se não, você pode <a href="%{sign_up_path}">se cadastrar aqui</a>. media_attachments: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index aafae48ce..faf41f316 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -54,6 +54,7 @@ en: interactions: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow + must_be_following_dm: Block direct messages from people you don't follow notification_emails: digest: Send digest e-mails favourite: Send e-mail when someone favourites your status diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 993eae706..48eaf79b2 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -50,6 +50,7 @@ ja: interactions: must_be_follower: フォロワー以外からの通知をブロック must_be_following: フォローしていないユーザーからの通知をブロック + must_be_following_dm: フォローしていないユーザーからのダイレクトメッセージをブロック notification_emails: digest: タイムラインからピックアップしてメールで通知する favourite: お気に入りに登録された時にメールで通知する diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml index d43c0d7eb..f178d1857 100644 --- a/config/locales/simple_form.oc.yml +++ b/config/locales/simple_form.oc.yml @@ -13,7 +13,7 @@ oc: one: Demòra encara <span class="name-counter">1</span> caractèr other: Demòran encara <span class="name-counter">%{count}</span> caractèrs setting_noindex: Aquò es destinat a vòstre perfil public e vòstra pagina d’estatuts - setting_theme: Aquò càmbia lo tèma grafic de Mastodon quand sètz connectat qualque siaque lo periferic. + setting_theme: Aquò càmbia lo tèma grafic de Mastodon quand sètz connectat qual que siasque lo periferic. imports: data: Fichièr CSV exportat d’una autra instància Mastodon sessions: diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 68f84d109..8b539662c 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -58,6 +58,7 @@ pl: interactions: must_be_follower: Nie wyświetlaj powiadomień od osób, które Cię nie śledzą must_be_following: Nie wyświetlaj powiadomień od osób, których nie śledzisz + must_be_following_dm: Nie wyświetlaj wiadomości bezpośrednich od osób, których nie śledzisz notification_emails: digest: Wysyłaj podsumowania e-mailem favourite: Powiadamiaj mnie e-mailem, gdy ktoś polubi mój wpis diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index eafaa972e..f8c9461ad 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -3,49 +3,60 @@ zh-CN: simple_form: hints: defaults: - avatar: 最大 2MB,限 PNG, GIF 或 JPG 格式,将缩到 120x120px - display_name: 不起过 30 个字符 - header: 最大 2MB,限 PNG, GIF 或 JPG 格式,将缩到 700x335px - locked: 默认仅向粉丝公开嘟文,需要手工批准粉丝关注请求。 - note: 最多 160 个字符 + avatar: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 120×120px + digest: 在你长时间未登录的情况下,我们会向你发送一份含有提及你的嘟文的摘要邮件 + display_name: 还能输入 <span class="name-counter">%{count}</span> 个字符 + header: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 700×335px + locked: 你需要手动审核所有关注请求 + note: 还能输入 <span class="note-counter">%{count}</span> 个字符 + setting_noindex: 此设置会影响到你的公开个人资料以及嘟文页面 + setting_theme: 此设置会影响到你从任意设备登录时 Mastodon 的显示样式 imports: - data: 从其他服务器节点导出的 CSV 文件 + data: 请上传从其他 Mastodon 实例导出的 CSV 文件 sessions: - otp: 输入你手机生成的两步验证码,或者恢复代码。 + otp: 输入你手机上生成的双重认证码,或者任意一个恢复代码。 user: - filtered_languages: 下列被选择的语言的嘟文将不会出现在你的公共时间轴上。 + filtered_languages: 勾选语言的嘟文将不会出现在你的公共时间轴上 labels: defaults: avatar: 头像 confirm_new_password: 确认新密码 confirm_password: 确认密码 current_password: 当前密码 - data: 数据 - display_name: 显示名 - email: 邮箱 - filtered_languages: 屏蔽下列语言的嘟文 - header: 个人页面顶部 + data: 数据文件 + display_name: 昵称 + email: 电子邮件地址 + filtered_languages: 语言过滤 + header: 个人资料页横幅图片 locale: 语言 - locked: 隐私模式(锁嘟) + locked: 保护你的帐户(锁嘟) new_password: 新密码 note: 简介 - otp_attempt: 两步认证码 + otp_attempt: 双重认证代码 password: 密码 + setting_auto_play_gif: 自动播放 GIF 动画 setting_boost_modal: 在转嘟前询问我 - setting_default_privacy: 嘟文默认隐私度 - severity: 等级 + setting_default_privacy: 嘟文默认可见范围 + setting_default_sensitive: 总是将我发送的媒体文件标记为敏感内容 + setting_delete_modal: 在删除嘟文前询问我 + setting_noindex: 禁止搜索引擎建立索引 + setting_reduce_motion: 降低过渡动画效果 + setting_system_font_ui: 使用系统默认字体 + setting_theme: 站点主题 + setting_unfollow_modal: 在取消关注前询问我 + severity: 级别 type: 导入数据类型 username: 用户名 interactions: - must_be_follower: 隐藏没有关注你的用户的通知 - must_be_following: 隐藏你不关注的用户的通知 + must_be_follower: 屏蔽来自未关注你的用户的通知 + must_be_following: 屏蔽来自你未关注的用户的通知 notification_emails: digest: 发送摘要邮件 - favourite: 当有用户赞了你的嘟文时,发电邮通知 - follow: 当有用户关注你时,发电邮通知 - follow_request: 当有用户要求关注你时,发电邮通知 - mention: 当有用户在嘟文中提及你时,发电邮通知 - reblog: 当有用户转嘟了你的嘟文时,发电邮通知 + favourite: 当有用户收藏了你的嘟文时,发送电子邮件提醒我 + follow: 当有用户关注你时,发送电子邮件提醒我 + follow_request: 当有用户向你发送关注请求时,发送电子邮件提醒我 + mention: 当有用户在嘟文中提及你时,发送电子邮件提醒我 + reblog: 当有用户转嘟了你的嘟文时,发送电子邮件提醒我 'no': 否 required: mark: "*" diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 44d0f3803..b50c34fd0 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1,196 +1,350 @@ --- zh-CN: about: - about_mastodon_html: Mastodon(长毛象)是一个<em>自由、开放源码</em>的社交网站。它是一个分布式的服务,避免你的通信被单一商业机构垄断操控。请你选择一家你信任的 Mastodon 实例,在上面创建帐号,然后你就可以和任一 Mastodon 实例上的用户互通,享受无缝的<em>社交</em>交流。 + about_hashtag_html: 这里展示的是带有话题标签 <strong>#%{hashtag}</strong> 的公开嘟文。如果你在象毛世界中拥有一个帐户,就可以加入讨论。 + about_mastodon_html: Mastodon(长毛象)是一个基于开放式网络协议和自由、开源软件建立的社交网络,有着类似于电子邮件的分布式设计。 about_this: 关于本实例 - closed_registrations: 这个实例目前不开放注册 _(:3」∠)_ + closed_registrations: 这个实例目前没有开放注册。不过,你可以前往其他实例注册一个帐户,同样可以加入到这个网络中哦! contact: 联络 + contact_missing: 未设定 + contact_unavailable: 未公开 description_headline: 关于 %{domain} domain_count_after: 个其它实例 domain_count_before: 现已接入 - other_instances: 其它实例 + extended_description_html: | + <h3>这里可以写一些规定</h3> + <p>本站尚未设置详细介绍。</p> + features: + humane_approach_body: Mastodon 从其他网络的失败经验中汲取了教训,致力于在与错误的社交媒体使用方式的斗争中做出符合伦理化设计的选择。 + humane_approach_title: 更加以人为本 + not_a_product_body: Mastodon 绝非一个商业网络。这里既没有广告,也没有数据挖掘,更没有围墙花园。中心机构在这里不复存在。 + not_a_product_title: 作为用户,你并非一件商品 + real_conversation_body: Mastodon 有着高达 500 字的字数限制,以及对内容的细化控制和媒体警告提示的支持,只为让你能够畅所欲言。 + real_conversation_title: 为真正的交流而生 + within_reach_body: 通过一个面向开发者友好的 API 生态系统,Mastodon 让你可以随时随地通过众多 iOS、Android 以及其他平台的应用与朋友们保持联系。 + within_reach_title: 始终触手可及 + find_another_instance: 寻找另一个实例 + generic_description: "%{domain} 是这个庞大网络中的一台服务器" + hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例 + learn_more: 详细了解 + other_instances: 其他实例 source_code: 源码 status_count_after: 条嘟文 status_count_before: 他们共嘟出了 user_count_after: 位用户 user_count_before: 这里共注册有 + what_is_mastodon: Mastodon 是什么? accounts: follow: 关注 - followers: 粉丝 - following: 关注 - nothing_here: 神马都没有! + followers: 关注者 + following: 正在关注 + media: 媒体 + nothing_here: 这里神马都没有! people_followed_by: 正关注 people_who_follow: 粉丝 posts: 嘟文 + posts_with_replies: 嘟文和回复 remote_follow: 跨站关注 + reserved_username: 此用户名已保留 + roles: + admin: 管理员 unfollow: 取消关注 admin: + account_moderation_notes: + account: 管理员 + create: 新建 + created_at: 日期 + created_msg: 管理记录建立成功! + delete: 删除 + destroyed_msg: 管理记录删除成功! accounts: are_you_sure: 你确定吗? + by_domain: 域名 confirm: 确认 confirmed: 已确认 - disable_two_factor_authentication: 两步认证无效 - display_name: 显示名称 + demote: 降任 + disable: 停用 + disable_two_factor_authentication: 停用双重认证 + disabled: 已停用 + display_name: 昵称 domain: 域名 edit: 编辑 - email: 电邮地址 + email: 电子邮件地址 + enable: 启用 + enabled: 已启用 feed_url: 订阅 URL followers: 关注者 + followers_url: 关注者(Followers)URL follows: 正在关注 - ip: IP地址 + inbox_url: 收件箱(Inbox)URL + ip: IP 地址 location: all: 全部 local: 本地 remote: 远程 - title: 地点 + title: 位置 + login_status: 登录状态 media_attachments: 媒体文件 + memorialize: 设置为追悼帐户 moderation: all: 全部 - silenced: 被静音的 - suspended: 被停权的 - title: 管理操作 - most_recent_activity: 最新活动 - most_recent_ip: 最新 IP 地址 + silenced: 已静音 + suspended: 已封禁 + title: 帐户状态 + moderation_notes: 管理记录 + most_recent_activity: 最后一次活跃的时间 + most_recent_ip: 最后一次活跃的 IP 地址 not_subscribed: 未订阅 order: alphabetic: 按字母 most_recent: 按时间 title: 排序 - perform_full_suspension: 实行完全暂停 - profile_url: 个人文件 URL - public: 公共 - push_subscription_expires: 推送订阅过期 + outbox_url: 发件箱(Outbox)URL + perform_full_suspension: 永久封禁 + profile_url: 个人资料页面 URL + promote: 升任 + protocol: 协议 + public: 公开页面 + push_subscription_expires: PuSH 订阅过期时间 + redownload: 刷新头像 reset: 重置 reset_password: 重置密码 - salmon_url: Salmon 反馈 URL + resubscribe: 重新订阅 + role: 用户组 + roles: + admin: 管理员 + moderator: 协管 + user: 用户 + salmon_url: Salmon URL search: 搜索 + shared_inbox_url: 公用收件箱(Shared Inbox)URL show: - created_reports: 这个帐户创建的报告 - report: 报告 - targeted_reports: 关于这个帐户的报告 + created_reports: 这个帐户提交的举报 + report: 个举报 + targeted_reports: 针对这个帐户的举报 silence: 静音 statuses: 嘟文 + subscribe: 订阅 title: 用户 undo_silenced: 解除静音 - undo_suspension: 解除停权 - username: 用户名称 - web: 用户页面 + undo_suspension: 解除封禁 + unsubscribe: 取消订阅 + username: 用户名 + web: 站内页面 + custom_emojis: + copied_msg: 成功将表情复制到本地 + copy: 复制 + copy_failed_msg: 无法将表情复制到本地 + created_msg: 表情添加成功! + delete: 删除 + destroyed_msg: 表情删除成功! + disable: 停用 + disabled_msg: 表情停用成功 + emoji: 表情 + enable: 启用 + enabled_msg: 表情启用成功 + image_hint: PNG 格式,最大 50KB + listed: 已显示 + new: + title: 添加新的自定义表情 + overwrite: 覆盖 + shortcode: 短代码 + shortcode_hint: 至少 2 个字符,只能使用字母、数字和下划线 + title: 自定义表情 + unlisted: 已隐藏 + update_failed_msg: 表情更新失败! + updated_msg: 表情更新成功! + upload: 上传 domain_blocks: - add_new: 添加 - created_msg: 正处理域名阻隔 - destroyed_msg: 已撤销域名阻隔 - domain: 域名阻隔 + add_new: 添加新条目 + created_msg: 正在进行域名屏蔽 + destroyed_msg: 域名屏蔽已撤销 + domain: 域名 new: - create: 添加域名阻隔 - hint: "「域名阻隔」不会隔绝该域名用户的嘟帐户入本站数据库,但会嘟文抵达后,自动套用特定的审批操作。" + create: 添加域名屏蔽 + hint: 域名屏蔽不会阻止该域名下的帐户进入本站的数据库,但是会对来自这个域名的帐户自动进行预先设置的管理操作。 severity: - desc_html: "「<strong>自动静音</strong>」令该域名用户的嘟文,设为只对关注者显示,没有关注的人会看不到。 「<strong>自动除名</strong>」会自动将该域名用户的嘟文、媒体文件、个人资料从本服务器实例删除。" + desc_html: 选择<strong>自动静音</strong>会将该域名下帐户发送的嘟文设置为仅关注者可见;选择<strong>自动封禁</strong>会将该域名下帐户发送的嘟文、媒体文件以及个人资料数据从本实例上删除;如果你只是想拒绝接收来自该域名的任何媒体文件,请选择<strong>无</strong>。 + noop: 无 silence: 自动静音 - suspend: 自动除名 - title: 添加域名阻隔 - reject_media: 拒绝媒体文件 - reject_media_hint: 删除本地缓存的媒体文件,再也不在未来下载这个站点的文件。和自动除名无关。 + suspend: 自动封禁 + title: 添加域名屏蔽 + reject_media: 拒绝接收媒体文件 + reject_media_hint: 删除本地已缓存的媒体文件,并且不再接收来自该域名的任何媒体文件。此选项不影响封禁 severities: + noop: 无 silence: 自动静音 - suspend: 自动除名 - severity: 阻隔程度 + suspend: 自动封禁 + severity: 屏蔽级别 show: - affected_accounts: - one: 数据库中有1个帐户受影响 - other: 数据库中有%{count}个帐户受影响 + affected_accounts: 将会影响到数据库中的 %{count} 个帐户 retroactive: silence: 对此域名的所有帐户取消静音 - suspend: 对此域名的所有帐户取消除名 - title: 撤销 %{domain} 的域名阻隔 + suspend: 对此域名的所有帐户取消封禁 + title: 撤销对 %{domain} 的域名屏蔽 undo: 撤销 - title: 域名阻隔 + title: 域名屏蔽 undo: 撤销 + email_domain_blocks: + add_new: 添加新条目 + created_msg: 电子邮件域名屏蔽添加成功 + delete: 删除 + destroyed_msg: 电子邮件域名屏蔽删除成功 + domain: 域名 + new: + create: 添加屏蔽 + title: 添加电子邮件域名屏蔽 + title: 电子邮件域名屏蔽 instances: - account_count: 已知帐号 + account_count: 已知帐户 domain_name: 域名 + reset: 重置 + search: 搜索 title: 已知实例 reports: - are_you_sure: 你确定吗? + action_taken_by: 操作执行者 + are_you_sure: 你确定吗? comment: label: 备注 none: 没有 delete: 删除 id: ID - mark_as_resolved: 标示为「已处理」 + mark_as_resolved: 标记为“已处理” nsfw: - 'false': NSFW无效 - 'true': NSFW有效 + 'false': 取消 NSFW 标记 + 'true': 添加 NSFW 标记 report: '举报 #%{id}' + report_contents: 内容 reported_account: 举报用户 - reported_by: 举报者 + reported_by: 举报人 resolved: 已处理 - silence_account: 将用户静音 + silence_account: 静音用户 status: 状态 - suspend_account: 将用户停权 - target: 对象 + suspend_account: 封禁用户 + target: 被举报人 title: 举报 unresolved: 未处理 view: 查看 settings: + bootstrap_timeline_accounts: + desc_html: 用半角逗号分隔多个用户名。只能添加来自本站且未开启保护的帐户。如果留空,则默认关注本站所有的管理员。 + title: 新用户默认关注 contact_information: - email: 输入一个公开的电邮地址 - username: 输入用户名称 + email: 输入一个公开的电子邮件地址 + username: 输入用户名 registrations: closed_message: - desc_html: 当本站暂停接受注册时,会显示这个消息。<br/> 可使用 HTML - title: 暂停注册消息 + desc_html: 本站关闭注册期间的提示信息。可以使用 HTML 标签 + title: 关闭注册时的提示消息 + deletion: + desc_html: 允许所有人删除自己的帐户 + title: 开放删除帐户权限 open: + desc_html: 允许任何人建立一个帐户 title: 开放注册 site_description: - desc_html: 在首页显示,及在 meta 标签中用作网站介绍。<br>你可以在此使用 HTML 标签,尤其是<code><a></code> 和 <code><em></code>。 - title: 本站介绍 + desc_html: 展示在首页以及 meta 标签中的网站简介。可以使用 HTML 标签,包括 <code><a></code> 和 <code><em></code>。 + title: 本站简介 site_description_extended: - desc_html: 本站详细信息页的內容<br/>你可在此使用 HTML - title: 本站详细信息 + desc_html: 可以填写行为守则、规定、指南或其他本站特有的内容。可以使用 HTML 标签 + title: 本站详细介绍 + site_terms: + desc_html: 可以填写自己的隐私权政策、使用条款或其他法律文本。可以使用 HTML 标签 + title: 自定义使用条款 site_title: 本站名称 + thumbnail: + desc_html: 用于在 OpenGraph 和 API 中显示预览图。推荐分辨率 1200×630px + title: 本站缩略图 + timeline_preview: + desc_html: 在主页显示公开时间线 + title: 时间线预览 title: 网站设置 + statuses: + back_to_account: 返回帐户信息页 + batch: + delete: 删除 + nsfw_off: 取消 NSFW 标记 + nsfw_on: 添加 NSFW 标记 + execute: 执行 + failed_to_execute: 执行失败 + media: + hide: 隐藏媒体文件 + show: 显示媒体文件 + title: 媒体文件 + no_media: 不含媒体文件 + title: 帐户嘟文 + with_media: 含有媒体文件 subscriptions: callback_url: 回调 URL - confirmed: 确定 - expires_in: 期限 - last_delivery: 数据最后送抵时间 - title: WebSub 订阅 - topic: 所订阅资源 + confirmed: 已确认 + expires_in: 失效时间 + last_delivery: 最后一次接收数据的时间 + title: WebSub + topic: Topic title: 管理 + admin_mailer: + new_report: + body: "%{reporter} 举报了 %{target}" + subject: 来自 %{instance} 的新举报(#%{id}) application_mailer: - settings: 更改电邮设置︰%{link} + salutation: "%{name}," + settings: 更改电子邮件首选项:%{link} signature: 来自 %{instance} 的 Mastodon 通知 view: 查看: applications: + created: 应用创建成功 + destroyed: 应用删除成功 invalid_url: URL 无效 + regenerate_token: 重置访问令牌 + token_regenerated: 访问令牌重置成功 + warning: 一定小心,千万不要把它分享给任何人! + your_token: 你的访问令牌 auth: - change_password: 登录凭据 + agreement_html: 注册即表示你同意<a href="%{rules_path}">我们的使用条款</a>和<a href="%{terms_path}">隐私权政策</a>。 + change_password: 帐户安全 + delete_account: 删除帐户 + delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。 didnt_get_confirmation: 没有收到确认邮件? forgot_password: 忘记密码? + invalid_reset_password_token: 密码重置令牌无效或已过期。请重新发起重置密码请求。 login: 登录 logout: 登出 register: 注册 - resend_confirmation: 重发确认邮件 + resend_confirmation: 重新发送确认邮件 reset_password: 重置密码 set_new_password: 设置新密码 authorize_follow: error: 对不起,寻找这个跨站用户时出错 follow: 关注 + follow_request: 关注请求已发送给: + following: 成功!你正在关注: + post_follow: + close: 你也可以直接关闭这个窗口。 + return: 返回至个人资料页 + web: 返回本站 title: 关注 %{acct} datetime: distance_in_words: - about_x_hours: "%{count} 小时" + about_x_hours: "%{count} 时" about_x_months: "%{count} 个月" about_x_years: "%{count} 年" - almost_x_years: 接近 %{count} 年 + almost_x_years: "%{count} 年" half_a_minute: 刚刚 - less_than_x_minutes: "%{count} 分不到" + less_than_x_minutes: "%{count} 分" less_than_x_seconds: 刚刚 - over_x_years: 超过 %{count} 年 + over_x_years: "%{count} 年" x_days: "%{count} 天" x_minutes: "%{count} 分" x_months: "%{count} 个月" x_seconds: "%{count} 秒" + deletes: + bad_password_msg: 想得美,黑客!密码输入错误 + confirm_password: 输入你当前的密码来验证身份 + description_html: 继续操作将会<strong>永久地、不可撤销地</strong>删除你帐户中的内容,并冻结你的帐户。你的用户名将会被保留,以防有人冒用你的身份。 + proceed: 删除帐户 + success_msg: 你的帐户已经成功删除 + warning_html: 我们只能保证本实例上的内容已经被彻底删除。对于已经被广泛传播的内容,它们在本实例以外的某些地方可能仍然可见。此外,失去连接的服务器以及停止接收订阅的服务器上的数据亦无法删除。 + warning_title: 关于已传播的内容的警告 errors: '403': 无权查看 '404': 找不到页面 @@ -199,68 +353,71 @@ zh-CN: content: 无法确认登录信息。你是不是屏蔽了 Cookie? title: 无法确认登录信息 '429': 被限制 + '500': + content: 抱歉,我们这里出错了。 + title: 这个页面不正确 + noscript_html: 请启用 JavaScript 以便使用 Mastodon 网页版应用。你也可以选择适用于你的平台的 <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">Mastodon 应用</a>。 exports: - blocks: 被你封锁的用户 + blocks: 屏蔽的用户 csv: CSV - follows: 你所关注的用户 - mutes: 你所静音的用户 - storage: 媒体容量大小 + follows: 关注的用户 + mutes: 静音的用户 + storage: 媒体文件存储 followers: domain: 域名 - explanation_html: 想要保护你的嘟文的话,请慎重考虑关注你的人。<strong>你的受保护的嘟文会发送到有你的关注者的所有实例上</strong>。你也许想要复查一下关注者列表来移除那些你无法信任的关注者。 + explanation_html: 为保证你的嘟文的隐私安全,你应当时刻留意你的关注者列表。<strong>受保护的嘟文将会发送到所有关注者所在的实例上</strong>。有些实例使用的软件代码或其管理员可能不会尊重你的隐私设置,因此你应当复查一下关注者列表,并移除那些你无法信任的关注者。 followers_count: 关注者数量 - lock_link: 保护你的帐户 + lock_link: 为你的帐户开启保护 purge: 从关注者中移除 - success: 从 %{count} 个域名中移除了关注者。 - true_privacy_html: "<strong>真正的隐私只能靠端到端加密来实现</strong>!" - unlocked_warning_html: 任何人都可以关注你然后查看被保护的嘟文, %{lock_link} 可以复核和拒绝关注请求。 - unlocked_warning_title: 你的帐户没被保护 + success: 正在从 %{count} 个域名中移除关注者…… + true_privacy_html: 请始终铭记:<strong>真正的隐私只能靠端到端加密来实现</strong>! + unlocked_warning_html: 任何人都可以通过关注你来立即查看被保护的嘟文。%{lock_link},即可审核并拒绝关注请求。 + unlocked_warning_title: 你的帐户未受到保护 generic: - changes_saved_msg: 更改已被保存。 + changes_saved_msg: 更改保存成功! powered_by: 基于 %{link} 构建 - save_changes: 保存 + save_changes: 保存更改 validation_errors: - one: 出错啦!请确认以下出错的地方,修改之后再来一次: - other: 出错啦!请确认以下 %{count} 处出错的地方,修改之后再来一次: + one: 出错啦!检查一下下面出错的地方吧 + other: 出错啦!检查一下下面 %{count} 处出错的地方吧 imports: - preface: 你可以在此导入你在其他服务器实例所导出的数据文件,包括︰你所关注、封锁的用户。 - success: 你已成功上载数据文件,我们正将数据导入,请稍候 + preface: 你可以在此导入你在其他实例导出的数据,比如你所关注或屏蔽的用户列表。 + success: 数据上传成功,正在处理中 types: - blocking: 封锁名单 - following: 关注名单 - muting: 静音名单 - upload: 上载 - landing_strip_html: "<strong>%{name}</strong> 是一个在 %{link_to_root_path} 的用户。只要你是象毛世界里(Mastodon、GNU social)任一服务器实例的用户,便可以跨站关注此站用户并与其沟通。" - landing_strip_signup_html: 如果你没有这类帐户,欢迎在<a href="%{sign_up_path}">此处登记</a>。 + blocking: 屏蔽列表 + following: 关注列表 + muting: 静音列表 + upload: 上传 + in_memoriam_html: 谨此悼念。 + landing_strip_html: "<strong>%{name}</strong> 是一位来自 %{link_to_root_path} 的用户。如果你想关注这个人或者与这个人互动,你需要在任意一个 Mastodon 实例或与其兼容的网站上拥有一个帐户。" + landing_strip_signup_html: 还没有这种帐户?你可以<a href="%{sign_up_path}">在本站注册一个</a>。 media_attachments: validations: - images_and_video: 无法添加视频到一个已经包含图片的嘟文中 - too_many: 最多只能添加4张图片 + images_and_video: 无法在嘟文中同时插入视频和图片 + too_many: 最多只能添加 4 张图片 notification_mailer: digest: - body: 自从你在%{since}使用%{instance}以后,错过了这些嘟嘟滴滴: - mention: "%{name} 在此提及了你︰" + body: 自从你最后一次(时间是%{since})登录 %{instance} 以来,你错过了这些嘟嘟滴滴: + mention: "%{name} 在嘟文中提到了你:" new_followers_summary: - one: 有人关注你了!耶! - other: 有 %{count} 个人关注了你!别激动! - subject: - one: "你有一个新通知 \U0001F418" - other: "%{count} 个通知太多,赶快去看看 \U0001F418" + one: 有个人关注了你!耶! + other: 有 %{count} 个人关注了你!好棒! + subject: "自从你最后一次登录以来,你错过了 %{count} 条新通知 \U0001F418" favourite: - body: "%{name} 收藏了你" - subject: "%{name} 给你点了收藏" + body: 你的嘟文被 %{name} 收藏了: + subject: "%{name} 收藏了你的嘟文" follow: - body: "%{name} 关注了你" + body: "%{name} 关注了你!" subject: "%{name} 关注了你" follow_request: - body: "%{name} 要求关注你" - subject: 等候关注你的用户︰ %{name} + body: "%{name} 请求关注你" + subject: 待审核的关注者:%{name} mention: - body: "%{name} 在文章中提及你︰" - subject: "%{name} 在文章中提及你" + body: "%{name} 在嘟文中提到了你:" + subject: "%{name} 提到了你" reblog: - body: 你的嘟文得到 %{name} 的转嘟 - subject: "%{name} 转嘟(嘟嘟滴)了你的嘟文" + body: 你的嘟文被 %{name} 转嘟了: + subject: "%{name} 转嘟了你的嘟文" number: human: decimal_units: @@ -275,54 +432,197 @@ zh-CN: pagination: next: 下一页 prev: 上一页 - truncate: "……" + truncate: "…" + preferences: + languages: 语言 + other: 其他 + publishing: 发布 + web: 站内 + push_notifications: + favourite: + title: "%{name} 收藏了你的嘟文" + follow: + title: "%{name} 关注了你" + group: + title: "%{count} 条新通知" + mention: + action_boost: 转嘟 + action_expand: 显示更多 + action_favourite: 收藏 + title: "%{name} 提到了你" + reblog: + title: "%{name} 转嘟了你的嘟文" remote_follow: - acct: 请输入你的︰用户名称@实例域名 - missing_resource: 无法找到您的帐户转接网址 - proceed: 下一步 - prompt: 你正准备关注︰ + acct: 请输入你的“用户名@实例域名” + missing_resource: 无法确定你的帐户的跳转 URL + proceed: 确认关注 + prompt: 你正准备关注: + sessions: + activity: 最后一次活跃的时间 + browser: 浏览器 + browsers: + alipay: 支付宝 + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + firefox: Firefox + generic: 未知浏览器 + ie: Internet Explorer + micro_messenger: 微信 + nokia: Nokia S40 Ovi 浏览器 + opera: Opera + phantom_js: PhantomJS + qq: QQ浏览器 + safari: Safari + uc_browser: UC浏览器 + weibo: 新浪微博 + current_session: 当前会话 + description: "%{platform} 上的 %{browser}" + explanation: 你的 Mastodon 帐户目前已在这些浏览器上登录。 + ip: IP 地址 + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: Mac + other: 未知平台 + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + revoke: 注销 + revoke_success: 会话注销成功 + title: 会话 settings: authorized_apps: 已授权的应用 back: 回到 Mastodon + delete: 删除帐户 + development: 开发 edit_profile: 更改个人信息 export: 导出 followers: 授权的关注者 import: 导入 + notifications: 通知 preferences: 首选项 settings: 设置 - two_factor_authentication: 两步认证 + two_factor_authentication: 双重认证 + your_apps: 你的应用 statuses: - open_in_web: 打开网页 + open_in_web: 在站内打开 over_character_limit: 超过了 %{max} 字的限制 + pin_errors: + limit: 置顶的嘟文条数超出限制 + ownership: 不能置顶他人的嘟文 + private: 不能置顶非公开的嘟文 + reblog: 不能置顶转嘟 show_more: 显示更多 visibilities: - private: 限关注者 - private_long: 仅向关注者公开 + private: 仅关注者 + private_long: 只有关注你的用户能看到 public: 公开 - public_long: 向所有人公开 - unlisted: 于公共时间线中隐藏 - unlisted_long: 公开,但不显示在公共时间线中 + public_long: 所有人可见,并会出现在公共时间轴上 + unlisted: 不公开 + unlisted_long: 所有人可见,但不会出现在公共时间轴上 stream_entries: - click_to_show: 显示 + click_to_show: 点击显示 + pinned: 置顶嘟文 reblogged: 转嘟 sensitive_content: 敏感内容 + terms: + body_html: | + <h2>隐私权政策</h2> + + <h3 id="collect">我们收集什么信息?</h3> + + <p>我们从你在我们站点注册开始从你那开始收集信息,并收集关于你在论坛的阅读和写作的数据,并评估分享的内容。</p> + + <p>当在我们站点注册时,你可能被要求输入你的名字和邮件地址。然而你可以在不用注册的情况下访问站点。你的邮件地将通过一个独一无二的链接验证。如果链接被访问了,我们就知道你控制了该邮件地址。</p> + + <p>当已注册和发帖时,我们记录发布帖子时的 IP 地址。我们也可能保留服务器日志,其中包括了每一个向我们服务器的请求。</p> + + <h3 id="use">我们如何使用你的信息?</h3> + + <p>从你那收集的任何数据将以以下方式使用:</p> + + <ul> + <li>改进你的个人体验 — 你的信息帮助我们更好地满足你的个人需求。</li> + <li>改进我们的站点 — 我们基于信息和我们从你那收到的反馈不断地试图改进我们的站点。</li> + <li>改善我们的客户服务 — 你的信息帮助我们更有效地回应用户服务请求和支持。</li> + <li>用于发送阶段性的邮件 — 你提供的邮件地址可能用于接受信息、你想看到的通知或与你用户名有关的回复和询问,或是其他的请求和问题。</li> + </ul> + + <h3 id="protect">我们如何保护你的信息?</h3> + + <p>我们实现了一系列的安全措施保证你输入、提交或者访问你个人信息的数据安全。</p> + + <h3 id="data-retention">数据保存政策是什么?</h3> + + <p>我们将善意地:</p> + + <ul> + <li>保存 90 天内的所有向服务器的包含 IP 地址的请求。</li> + <li>保存 5 年内已注册用户和与他们的帖子有关的 IP 地址。</li> + </ul> + + <h3 id="cookies">我们使用 Cookie 吗?</h3> + + <p>是的。Cookie 是网站或它的服务商通过网页浏览器存储在你电脑硬盘上的小文件(如果你同意)。这些 Cookie 使站点能分辨你的浏览器,并且,如果你注册了一个账户,与你的注册账户关联。</p> + + <p>我们使用 Cookie 为之后的访问和编译一小段关于站点流量和交互的数据来判断并保存你的个人设置,这样我们可以在之后提供更好的站点体验和工具。我们可能使用第三方服务商来帮助我们更好地理解我们的站点访客。这些服务商是不允许代表我们使用收集的信息,除非是在帮助我们执行和改进我们的站点。</p> + + <h3 id="disclose">我们会在站外提供任何信息吗?</h3> + + <p>我们绝不销售、交易或任何向外转移你个人信息的行为。这不包括帮助我们管理站点、改进站点或给你提供服务的第三方团体,这些团体需要保证对这些信息保密。当我们认为提交你的信息符合法律、我们的站点政策或保护我们或其他人的权利、知识产权或安全时,我们也可能提交你的信息。然而,非个人访问信息可能供其他团体使用,用于市场、广告或其他用途。</p> + + <h3 id="third-party">第三方链接</h3> + + <p>偶尔地,根据我们的判断,我们可能在我们的站点上包括或提供第三方团体的产品或服务。这些第三方站点用于独立和不同的隐私政策。因此我们对链接到的站点或活动没有责任和权利。尽管如此,我们寻求保护我们的整个站点并且欢迎你给这些站点反馈。</p> + + <h3 id="coppa">儿童在线隐私保护法案合规</h3> + + <p>我们的站点、产品和服务提供给 13 岁以上的人们。如果服务器位于美国,并且你小于 13 岁,根据<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">儿童在线隐私保护法案合规</a>,不要使用这个站点。</p> + + <h3 id="online">仅用于在线隐私政策</h3> + + <p>这个线上隐私政策只适用于通过我们站点收集到的信息,并不包括线下收集的信息。</p> + + <h3 id="consent">你的同意</h3> + + <p>你使用站点的同时,代表你同意了我们网站的隐私政策。</p> + + <h3 id="changes">隐私政策的更改</h3> + + <p>如果我们决定更改我们的隐私政策,我们将在此页更新这些改变。</p> + + <p>文档以 CC-BY-SA 发布。最后更新时间为2013年5月31日。</p> + + <p>原文出自 <a href="https://github.com/discourse/discourse">Discourse 隐私权政策</a>。</p> + title: "%{instance} 使用条款和隐私权政策" + themes: + default: Mastodon time: formats: default: "%Y年%-m月%d日 %H:%M" two_factor_authentication: - code_hint: 请输入你认证器产生的代码,以确认设置 - description_html: 当你启用<strong>两步认证</strong>后,你登录时将额外需要使用手机或其他认证器生成的代码。 + code_hint: 输入你的认证器生成的代码以确认 + description_html: 启用<strong>双重认证</strong>后,你需要输入手机认证器生成的代码才能登录 disable: 停用 enable: 启用 - enabled_success: 已成功启用两步认证 + enabled: 双重认证已启用 + enabled_success: 双重认证启用成功 generate_recovery_codes: 生成恢复代码 - instructions_html: "<strong>请用你手机的认证器应用(如 Google Authenticator、Authy),扫描这里的 QR 二维码</strong>。在两步认证启用后,你登录时将需要使用此应用程序产生的认证码。" - lost_recovery_codes: 如果你丢了手机,你可以用恢复代码重新访问你的帐户。如果你丢了恢复代码,也可以在这里重新生成一个,不过以前的恢复代码就失效了。<del>(废话)</del> - manual_instructions: 如果你无法扫描 QR 二维码,请手动输入这个文本密码︰ - recovery_codes_regenerated: 已成功重新生成恢复代码 - recovery_instructions_html: 如果你的手机无法使用,你可以使用下面的任何恢复代码来恢复你的帐号。请保管好你的恢复代码以防泄漏(例如你可以打印好它们并和重要文档一起保存)。 + instructions_html: "<strong>请使用 Google 身份验证器或其他 TOTP 双重认证手机应用扫描此处的二维码</strong>。启用双重认证后,你需要输入该应用生成的代码来登录你的帐户。" + lost_recovery_codes: 如果你的手机不慎丢失,你可以使用恢复代码来重新获得对帐户的访问权。如果你遗失了恢复代码,可以在此处重新生成。之前使用的恢复代码将会失效。 + manual_instructions: 如果你无法扫描二维码,请手动输入下列文本: + recovery_codes: 备份恢复代码 + recovery_codes_regenerated: 恢复代码重新生成成功 + recovery_instructions_html: 如果你的手机无法使用,你可以使用下列任意一个恢复代码来重新获得对帐户的访问权。<strong>请妥善保管好你的恢复代码</strong>(例如,你可以将它们打印出来,然后和其他重要的文件放在一起)。 setup: 设置 - wrong_code: 你输入的认证码并不正确!可能服务器时间和你手机不一致,请检查你手机的时钟,或与本站管理员联系。 + wrong_code: 输入的认证码无效!请检查你设备上显示的时间是否正确,如果正确,你可能需要联系管理员以检查服务器的时间是否正确。 users: - invalid_email: 邮箱格式有误 - invalid_otp_token: 两步认证码有误 + invalid_email: 输入的电子邮件地址无效 + invalid_otp_token: 输入的双重认证代码无效 + signed_in_as: 当前登录的帐户: diff --git a/config/navigation.rb b/config/navigation.rb index 9fa029b72..16c48849b 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -21,16 +21,16 @@ SimpleNavigation::Configuration.run do |navigation| development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications} end - primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.admin? } do |admin| + primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.staff? } do |admin| admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports} admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts} - admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances} - admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url - admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks} - admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks} - admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' } - admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' } - admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url + admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? } + admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? } + admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? } + admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? } + admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } + admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? } + admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? } admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis} end diff --git a/config/routes.rb b/config/routes.rb index aca613ed2..78b96b0e4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -133,7 +133,10 @@ Rails.application.routes.draw do member do post :subscribe post :unsubscribe + post :enable + post :disable post :redownload + post :memorialize end resource :reset, only: [:create] @@ -141,6 +144,13 @@ Rails.application.routes.draw do resource :suspension, only: [:create, :destroy] resource :confirmation, only: [:create] resources :statuses, only: [:index, :create, :update, :destroy] + + resource :role do + member do + post :promote + post :demote + end + end end resources :users, only: [] do diff --git a/config/settings.yml b/config/settings.yml index c03d0b766..1b8a6673e 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -36,6 +36,7 @@ defaults: &defaults interactions: must_be_follower: false must_be_following: false + must_be_following_dm: false reserved_usernames: - admin - support diff --git a/config/webpack/production.js b/config/webpack/production.js index cd1dd91dc..e2d7f11dc 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -9,6 +9,16 @@ const OfflinePlugin = require('offline-plugin'); const { publicPath } = require('./configuration.js'); const path = require('path'); +let compressionAlgorithm; +try { + const zopfli = require('node-zopfli'); + compressionAlgorithm = (content, options, fn) => { + zopfli.gzip(content, options, fn); + }; +} catch (error) { + compressionAlgorithm = 'gzip'; +} + module.exports = merge(sharedConfig, { output: { filename: '[name]-[chunkhash].js', @@ -33,7 +43,7 @@ module.exports = merge(sharedConfig, { }), new CompressionPlugin({ asset: '[path].gz[query]', - algorithm: 'gzip', + algorithm: compressionAlgorithm, test: /\.(js|css|html|json|ico|svg|eot|otf|ttf)$/, }), new BundleAnalyzerPlugin({ // generates report.html and stats.json @@ -48,7 +58,37 @@ module.exports = merge(sharedConfig, { }), new OfflinePlugin({ publicPath: publicPath, // sw.js must be served from the root to avoid scope issues - caches: { }, // do not cache things, we only use it for push notifications for now + caches: { + main: [':rest:'], + additional: [':externals:'], + optional: [ + '**/locale_*.js', // don't fetch every locale; the user only needs one + '**/*_polyfills-*.js', // the user may not need polyfills + '**/*.woff2', // the user may have system-fonts enabled + // images/audio can be cached on-demand + '**/*.png', + '**/*.jpg', + '**/*.jpeg', + '**/*.svg', + '**/*.mp3', + '**/*.ogg', + ], + }, + externals: [ + '/emoji/1f602.svg', // used for emoji picker dropdown + '/emoji/sheet.png', // used in emoji-mart + ], + excludes: [ + '**/*.gz', + '**/*.map', + 'stats.json', + 'report.html', + // any browser that supports ServiceWorker will support woff2 + '**/*.eot', + '**/*.ttf', + '**/*-webfont-*.svg', + '**/*.woff', + ], ServiceWorker: { entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'), cacheName: 'mastodon', diff --git a/config/webpack/shared.js b/config/webpack/shared.js index e3a1fc379..5d176db4e 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -12,29 +12,27 @@ const localePackPaths = require('./generateLocalePacks'); const extensionGlob = `**/*{${settings.extensions.join(',')}}*`; const entryPath = join(settings.source_path, settings.source_entry_path); const packPaths = sync(join(entryPath, extensionGlob)); -const entryPacks = [...packPaths, ...localePackPaths].filter(path => path !== join(entryPath, 'custom.js')); - -const themePaths = Object.keys(themes).reduce( - (themePaths, name) => { - const themeData = themes[name]; - themePaths[`themes/${name}`] = resolve(themeData.pack_directory, themeData.pack); - return themePaths; - }, {} -); module.exports = { entry: Object.assign( - entryPacks.reduce( - (map, entry) => { - const localMap = map; - let namespace = relative(join(entryPath), dirname(entry)); - if (namespace === join('..', '..', '..', 'tmp', 'packs')) { - namespace = ''; // generated by generateLocalePacks.js - } - localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry); - return localMap; + packPaths.reduce((map, entry) => { + const localMap = map; + const namespace = relative(join(entryPath), dirname(entry)); + localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry); + return localMap; + }, {}), + localePackPaths.reduce((map, entry) => { + const localMap = map; + localMap[basename(entry, extname(entry, extname(entry)))] = resolve(entry); + return localMap; + }, {}), + Object.keys(themes).reduce( + (themePaths, name) => { + const themeData = themes[name]; + themePaths[`themes/${name}`] = resolve(themeData.pack_directory, themeData.pack); + return themePaths; }, {} - ), themePaths + ) ), output: { @@ -58,7 +56,7 @@ module.exports = { } ), new ExtractTextPlugin({ - filename: env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css', + filename: env.NODE_ENV === 'production' ? '[name]-[contenthash].css' : '[name].css', allChunks: true, }), new ManifestPlugin({ diff --git a/db/migrate/20171107143332_add_memorial_to_accounts.rb b/db/migrate/20171107143332_add_memorial_to_accounts.rb new file mode 100644 index 000000000..f3e012ce8 --- /dev/null +++ b/db/migrate/20171107143332_add_memorial_to_accounts.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddMemorialToAccounts < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :accounts, :memorial, :bool, default: false } + end + + def down + remove_column :accounts, :memorial + end +end diff --git a/db/migrate/20171107143624_add_disabled_to_users.rb b/db/migrate/20171107143624_add_disabled_to_users.rb new file mode 100644 index 000000000..a71cac1c6 --- /dev/null +++ b/db/migrate/20171107143624_add_disabled_to_users.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddDisabledToUsers < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :users, :disabled, :bool, default: false } + end + + def down + remove_column :users, :disabled + end +end diff --git a/db/migrate/20171109012327_add_moderator_to_accounts.rb b/db/migrate/20171109012327_add_moderator_to_accounts.rb new file mode 100644 index 000000000..ddd87583a --- /dev/null +++ b/db/migrate/20171109012327_add_moderator_to_accounts.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddModeratorToAccounts < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :users, :moderator, :bool, default: false } + end + + def down + remove_column :users, :moderator + end +end diff --git a/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb b/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb new file mode 100644 index 000000000..84a341510 --- /dev/null +++ b/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb @@ -0,0 +1,8 @@ +class AddIndexDomainToEmailDomainBlocks < ActiveRecord::Migration[5.1] + disable_ddl_transaction! + + def change + add_index :email_domain_blocks, :domain, algorithm: :concurrently, unique: true + change_column_default :email_domain_blocks, :domain, from: nil, to: '' + end +end diff --git a/db/schema.rb b/db/schema.rb index 93505f9a0..0691c4220 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171028221157) do +ActiveRecord::Schema.define(version: 20171114080328) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -71,6 +71,7 @@ ActiveRecord::Schema.define(version: 20171028221157) do t.string "shared_inbox_url", default: "", null: false t.string "followers_url", default: "", null: false t.integer "protocol", default: 0, null: false + t.boolean "memorial", default: false, null: false t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower" t.index ["uri"], name: "index_accounts_on_uri" @@ -125,9 +126,10 @@ ActiveRecord::Schema.define(version: 20171028221157) do end create_table "email_domain_blocks", force: :cascade do |t| - t.string "domain", null: false + t.string "domain", default: "", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true end create_table "favourites", force: :cascade do |t| @@ -213,6 +215,7 @@ ActiveRecord::Schema.define(version: 20171028221157) do t.boolean "hide_notifications", default: true, null: false t.bigint "account_id", null: false t.bigint "target_account_id", null: false + t.boolean "hide_notifications", default: true, null: false t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true end @@ -447,6 +450,8 @@ ActiveRecord::Schema.define(version: 20171028221157) do t.string "otp_backup_codes", array: true t.string "filtered_languages", default: [], null: false, array: true t.bigint "account_id", null: false + t.boolean "disabled", default: false, null: false + t.boolean "moderator", default: false, null: false t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 5614ddf48..995cf0d6f 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -10,14 +10,41 @@ namespace :mastodon do desc 'Turn a user into an admin, identified by the USERNAME environment variable' task make_admin: :environment do include RoutingHelper + account_username = ENV.fetch('USERNAME') - user = User.joins(:account).where(accounts: { username: account_username }) + user = User.joins(:account).where(accounts: { username: account_username }) if user.present? user.update(admin: true) puts "Congrats! #{account_username} is now an admin. \\o/\nNavigate to #{edit_admin_settings_url} to get started" else - puts "User could not be found; please make sure an Account with the `#{account_username}` username exists." + puts "User could not be found; please make sure an account with the `#{account_username}` username exists." + end + end + + desc 'Turn a user into a moderator, identified by the USERNAME environment variable' + task make_mod: :environment do + account_username = ENV.fetch('USERNAME') + user = User.joins(:account).where(accounts: { username: account_username }) + + if user.present? + user.update(moderator: true) + puts "Congrats! #{account_username} is now a moderator \\o/" + else + puts "User could not be found; please make sure an account with the `#{account_username}` username exists." + end + end + + desc 'Remove admin and moderator privileges from user identified by the USERNAME environment variable' + task revoke_staff: :environment do + account_username = ENV.fetch('USERNAME') + user = User.joins(:account).where(accounts: { username: account_username }) + + if user.present? + user.update(moderator: false, admin: false) + puts "#{account_username} is no longer admin or moderator." + else + puts "User could not be found; please make sure an account with the `#{account_username}` username exists." end end @@ -81,7 +108,7 @@ namespace :mastodon do task remove_remote: :environment do time_ago = ENV.fetch('NUM_DAYS') { 7 }.to_i.days.ago - MediaAttachment.where.not(remote_url: '').where('created_at < ?', time_ago).find_each do |media| + MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).find_each do |media| media.file.destroy media.save end diff --git a/nanobox/nginx-web.conf.erb b/nanobox/nginx-web.conf.erb index 24cd17cff..a839f3036 100644 --- a/nanobox/nginx-web.conf.erb +++ b/nanobox/nginx-web.conf.erb @@ -42,7 +42,12 @@ http { try_files $uri @rails; } - location ~ ^/(assets|system/media_attachments/files|system/accounts/avatars) { + location /sw.js { + add_header Cache-Control "public, max-age=0"; + try_files $uri @rails; + } + + location ~ ^/(emoji|packs|system/media_attachments/files|system/accounts/avatars) { add_header Cache-Control "public, max-age=31536000, immutable"; try_files $uri @rails; } diff --git a/package.json b/package.json index 8d24c7850..159181030 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,13 @@ "dependencies": { "array-includes": "^3.0.3", "atrament": "^0.2.3", - "autoprefixer": "^7.1.2", - "axios": "^0.16.2", + "autoprefixer": "^7.1.6", + "axios": "~0.16.2", "babel-core": "^6.25.0", "babel-loader": "^7.1.1", "babel-plugin-lodash": "^3.2.11", - "babel-plugin-preval": "^1.3.2", + "babel-plugin-preval": "^1.6.1", "babel-plugin-react-intl": "^2.3.1", - "babel-plugin-react-transform": "^2.0.2", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-decorators-legacy": "^1.3.4", @@ -36,30 +35,30 @@ "babel-plugin-transform-react-inline-elements": "^6.22.0", "babel-plugin-transform-react-jsx-self": "^6.22.0", "babel-plugin-transform-react-jsx-source": "^6.22.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.6", + "babel-plugin-transform-react-remove-prop-types": "^0.4.10", "babel-plugin-transform-runtime": "^6.23.0", - "babel-preset-env": "^1.6.0", + "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.24.1", "classnames": "^2.2.5", - "compression-webpack-plugin": "^0.4.0", - "cross-env": "^5.0.1", + "compression-webpack-plugin": "^1.0.1", + "cross-env": "^5.1.1", "css-loader": "^0.28.4", "detect-passive-events": "^1.0.2", "dotenv": "^4.0.0", "emoji-mart": "Gargron/emoji-mart#build", "es6-symbol": "^3.1.1", "escape-html": "^1.0.3", - "express": "^4.15.2", - "extract-text-webpack-plugin": "^2.1.2", + "express": "^4.16.2", + "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^0.11.2", "font-awesome": "^4.7.0", "glob": "^7.1.1", "http-link-header": "^0.8.0", - "immutable": "^3.8.1", + "immutable": "^3.8.2", "intersection-observer": "^0.4.0", "intl": "^1.2.5", "intl-messageformat": "^2.1.0", - "intl-relativeformat": "^2.0.0", + "intl-relativeformat": "^2.1.0", "is-nan": "^1.2.1", "js-yaml": "^3.9.0", "lodash": "^4.17.4", @@ -73,7 +72,7 @@ "offline-plugin": "^4.8.3", "path-complete-extname": "^0.1.0", "pg": "^6.4.0", - "postcss-loader": "^2.0.6", + "postcss-loader": "^2.0.8", "postcss-object-fit-images": "^1.1.2", "postcss-smart-import": "^0.7.5", "precss": "^2.0.0", @@ -84,15 +83,15 @@ "react-dom": "^16.0.0", "react-hotkeys": "^0.10.0", "react-immutable-proptypes": "^2.1.0", - "react-immutable-pure-component": "^1.0.0", + "react-immutable-pure-component": "^1.1.1", "react-intl": "^2.4.0", - "react-motion": "^0.5.0", - "react-notification": "^6.7.1", - "react-overlays": "^0.8.1", + "react-motion": "^0.5.2", + "react-notification": "^6.8.2", + "react-overlays": "^0.8.3", "react-redux": "^5.0.4", - "react-redux-loading-bar": "^2.9.2", + "react-redux-loading-bar": "^2.9.3", "react-router-dom": "^4.1.1", - "react-router-scroll": "Gargron/react-router-scroll#build", + "react-router-scroll-4": "^1.0.0-beta.1", "react-swipeable-views": "^0.12.3", "react-textarea-autosize": "^5.0.7", "react-toggle": "^4.0.1", @@ -102,17 +101,17 @@ "redux-thunk": "^2.2.0", "requestidlecallback": "^0.3.0", "reselect": "^3.0.1", - "resolve-url-loader": "^2.1.0", + "resolve-url-loader": "^2.2.0", "rimraf": "^2.6.1", "sass-loader": "^6.0.6", "stringz": "^0.2.2", - "style-loader": "^0.18.2", + "style-loader": "^0.19.0", "substring-trie": "^1.0.2", "throng": "^4.0.0", "tiny-queue": "^0.2.1", "uuid": "^3.1.0", "uws": "^8.14.0", - "webpack": "^3.4.1", + "webpack": "^3.8.1", "webpack-bundle-analyzer": "^2.8.3", "webpack-manifest-plugin": "^1.2.1", "webpack-merge": "^4.1.0", @@ -121,19 +120,20 @@ "devDependencies": { "babel-eslint": "^7.2.3", "enzyme": "^3.0.0", - "enzyme-adapter-react-16": "^1.0.0", + "enzyme-adapter-react-16": "^1.0.2", "eslint": "^3.19.0", - "eslint-plugin-import": "^2.7.0", + "eslint-plugin-import": "^2.8.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.3", "jest": "^21.2.1", "raf": "^3.4.0", "react-intl-translations-manager": "^5.0.0", "react-test-renderer": "^16.0.0", - "webpack-dev-server": "^2.6.1", + "webpack-dev-server": "^2.9.3", "yargs": "^8.0.2" }, "optionalDependencies": { - "fsevents": "*" + "fsevents": "*", + "node-zopfli": "^2.0.2" } } diff --git a/public/sounds/boop.mp3 b/public/sounds/boop.mp3 index 02a035d91..bf9c3c1aa 100644 --- a/public/sounds/boop.mp3 +++ b/public/sounds/boop.mp3 Binary files differdiff --git a/public/sounds/boop.ogg b/public/sounds/boop.ogg index a2124e116..a6551c9fd 100644 --- a/public/sounds/boop.ogg +++ b/public/sounds/boop.ogg Binary files differdiff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index 92f888590..a8ade790c 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -4,6 +4,7 @@ RSpec.describe AccountsController, type: :controller do render_views let(:alice) { Fabricate(:account, username: 'alice') } + let(:eve) { Fabricate(:user) } describe 'GET #show' do let!(:status1) { Status.create!(account: alice, text: 'Hello world') } @@ -19,93 +20,123 @@ RSpec.describe AccountsController, type: :controller do let!(:status_pin3) { StatusPin.create!(account: alice, status: status7, created_at: 10.minutes.ago) } before do + alice.block!(eve.account) status3.media_attachments.create!(account: alice, file: fixture_file_upload('files/attachment.jpg', 'image/jpeg')) end - context 'atom' do + shared_examples 'responses' do before do - get :show, params: { username: alice.username, max_id: status4.stream_entry.id, since_id: status1.stream_entry.id }, format: 'atom' + sign_in(current_user) if defined? current_user + get :show, params: { + username: alice.username, + max_id: (max_id if defined? max_id), + since_id: (since_id if defined? since_id), + current_user: (current_user if defined? current_user), + }, format: format end it 'assigns @account' do expect(assigns(:account)).to eq alice end - it 'assigns @entries' do - entries = assigns(:entries).to_a - expect(entries.size).to eq 2 - expect(entries[0].status).to eq status3 - expect(entries[1].status).to eq status2 + it 'returns http success' do + expect(response).to have_http_status(:success) end - it 'returns http success with Atom' do - expect(response).to have_http_status(:success) + it 'returns correct format' do + expect(response.content_type).to eq content_type end end - context 'activitystreams2' do - before do - get :show, params: { username: alice.username }, format: 'json' - end + context 'atom' do + let(:format) { 'atom' } + let(:content_type) { 'application/atom+xml' } - it 'assigns @account' do - expect(assigns(:account)).to eq alice + shared_examples 'responsed streams' do + it 'assigns @entries' do + entries = assigns(:entries).to_a + expect(entries.size).to eq expected_statuses.size + entries.each.zip(expected_statuses.each) do |entry, expected_status| + expect(entry.status).to eq expected_status + end + end end - it 'returns http success with Activity Streams 2.0' do - expect(response).to have_http_status(:success) - end + include_examples 'responses' - it 'returns application/activity+json' do - expect(response.content_type).to eq 'application/activity+json' - end - end + context 'without max_id nor since_id' do + let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] } - context 'html without since_id nor max_id' do - before do - get :show, params: { username: alice.username } + include_examples 'responsed streams' end - it 'assigns @account' do - expect(assigns(:account)).to eq alice - end + context 'with max_id and since_id' do + let(:max_id) { status4.stream_entry.id } + let(:since_id) { status1.stream_entry.id } + let(:expected_statuses) { [status3, status2] } - it 'assigns @pinned_statuses' do - pinned_statuses = assigns(:pinned_statuses).to_a - expect(pinned_statuses.size).to eq 3 - expect(pinned_statuses[0]).to eq status7 - expect(pinned_statuses[1]).to eq status5 - expect(pinned_statuses[2]).to eq status6 + include_examples 'responsed streams' end + end - it 'returns http success' do - expect(response).to have_http_status(:success) - end + context 'activitystreams2' do + let(:format) { 'json' } + let(:content_type) { 'application/activity+json' } + + include_examples 'responses' end - context 'html with since_id and max_id' do - before do - get :show, params: { username: alice.username, max_id: status4.id, since_id: status1.id } - end + context 'html' do + let(:format) { nil } + let(:content_type) { 'text/html' } - it 'assigns @account' do - expect(assigns(:account)).to eq alice - end + shared_examples 'responsed statuses' do + it 'assigns @pinned_statuses' do + pinned_statuses = assigns(:pinned_statuses).to_a + expect(pinned_statuses.size).to eq expected_pinned_statuses.size + pinned_statuses.each.zip(expected_pinned_statuses.each) do |pinned_status, expected_pinned_status| + expect(pinned_status).to eq expected_pinned_status + end + end - it 'assigns @statuses' do - statuses = assigns(:statuses).to_a - expect(statuses.size).to eq 2 - expect(statuses[0]).to eq status3 - expect(statuses[1]).to eq status2 + it 'assigns @statuses' do + statuses = assigns(:statuses).to_a + expect(statuses.size).to eq expected_statuses.size + statuses.each.zip(expected_statuses.each) do |status, expected_status| + expect(status).to eq expected_status + end + end end - it 'assigns an empty array to @pinned_statuses' do - pinned_statuses = assigns(:pinned_statuses).to_a - expect(pinned_statuses.size).to eq 0 + include_examples 'responses' + + context 'with anonymous visitor' do + context 'without since_id nor max_id' do + let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] } + let(:expected_pinned_statuses) { [status7, status5, status6] } + + include_examples 'responsed statuses' + end + + context 'with since_id nor max_id' do + let(:max_id) { status4.id } + let(:since_id) { status1.id } + let(:expected_statuses) { [status3, status2] } + let(:expected_pinned_statuses) { [] } + + include_examples 'responsed statuses' + end end - it 'returns http success' do - expect(response).to have_http_status(:success) + context 'with blocked visitor' do + let(:current_user) { eve } + + context 'without since_id nor max_id' do + let(:expected_statuses) { [] } + let(:expected_pinned_statuses) { [] } + + include_examples 'responsed statuses' + end end end end diff --git a/spec/fabricators/session_activation_fabricator.rb b/spec/fabricators/session_activation_fabricator.rb index 46050bdab..526faaec2 100644 --- a/spec/fabricators/session_activation_fabricator.rb +++ b/spec/fabricators/session_activation_fabricator.rb @@ -1,4 +1,4 @@ Fabricator(:session_activation) do - user_id 1 + user session_id "MyString" end diff --git a/spec/fabricators/setting_fabricator.rb b/spec/fabricators/setting_fabricator.rb new file mode 100644 index 000000000..336d7c355 --- /dev/null +++ b/spec/fabricators/setting_fabricator.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +Fabricator(:setting) do +end diff --git a/spec/lib/settings/scoped_settings_spec.rb b/spec/lib/settings/scoped_settings_spec.rb new file mode 100644 index 000000000..7566685b4 --- /dev/null +++ b/spec/lib/settings/scoped_settings_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Settings::ScopedSettings do + let(:object) { Fabricate(:user) } + let(:scoped_setting) { described_class.new(object) } + let(:val) { 'whatever' } + let(:methods) { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) } + + describe '.initialize' do + it 'sets @object' do + scoped_setting = described_class.new(object) + expect(scoped_setting.instance_variable_get(:@object)).to be object + end + end + + describe '#method_missing' do + it 'sets scoped_setting.method_name = val' do + methods.each do |key| + scoped_setting.send("#{key}=", val) + expect(scoped_setting.send(key)).to eq val + end + end + end + + describe '#[]= and #[]' do + it 'sets [key] = val' do + methods.each do |key| + scoped_setting[key] = val + expect(scoped_setting[key]).to eq val + end + end + end +end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 361577eff..7501c498c 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -93,21 +93,44 @@ RSpec.describe Account, type: :model do end describe '#save_with_optional_media!' do - it 'sets default avatar, header, avatar_remote_url, and header_remote_url if some of them are invalid' do + before do stub_request(:get, 'https://remote/valid_avatar').to_return(request_fixture('avatar.txt')) stub_request(:get, 'https://remote/invalid_avatar').to_return(request_fixture('feed.txt')) - account = Fabricate(:account, - avatar_remote_url: 'https://remote/valid_avatar', - header_remote_url: 'https://remote/valid_avatar') + end + + let(:account) do + Fabricate(:account, + avatar_remote_url: 'https://remote/valid_avatar', + header_remote_url: 'https://remote/valid_avatar') + end + + let!(:expectation) { account.dup } + + context 'with valid properties' do + before do + account.save_with_optional_media! + end + + it 'unchanges avatar, header, avatar_remote_url, and header_remote_url' do + expect(account.avatar_remote_url).to eq expectation.avatar_remote_url + expect(account.header_remote_url).to eq expectation.header_remote_url + expect(account.avatar_file_name).to eq expectation.avatar_file_name + expect(account.header_file_name).to eq expectation.header_file_name + end + end - account.avatar_remote_url = 'https://remote/invalid_avatar' - account.save_with_optional_media! + context 'with invalid properties' do + before do + account.avatar_remote_url = 'https://remote/invalid_avatar' + account.save_with_optional_media! + end - account.reload - expect(account.avatar_remote_url).to eq '' - expect(account.header_remote_url).to eq '' - expect(account.avatar_file_name).to eq nil - expect(account.header_file_name).to eq nil + it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do + expect(account.avatar_remote_url).to eq '' + expect(account.header_remote_url).to eq '' + expect(account.avatar_file_name).to eq nil + expect(account.header_file_name).to eq nil + end end end @@ -123,6 +146,61 @@ RSpec.describe Account, type: :model do end end + describe '#possibly_stale?' do + let(:account) { Fabricate(:account, last_webfingered_at: last_webfingered_at) } + + context 'last_webfingered_at is nil' do + let(:last_webfingered_at) { nil } + + it 'returns true' do + expect(account.possibly_stale?).to be true + end + end + + context 'last_webfingered_at is more than 24 hours before' do + let(:last_webfingered_at) { 25.hours.ago } + + it 'returns true' do + expect(account.possibly_stale?).to be true + end + end + + context 'last_webfingered_at is less than 24 hours before' do + let(:last_webfingered_at) { 23.hours.ago } + + it 'returns false' do + expect(account.possibly_stale?).to be false + end + end + end + + describe '#refresh!' do + let(:account) { Fabricate(:account, domain: domain) } + let(:acct) { account.acct } + + context 'domain is nil' do + let(:domain) { nil } + + it 'returns nil' do + expect(account.refresh!).to be_nil + end + + it 'calls not ResolveRemoteAccountService#call' do + expect_any_instance_of(ResolveRemoteAccountService).not_to receive(:call).with(acct) + account.refresh! + end + end + + context 'domain is present' do + let(:domain) { 'example.com' } + + it 'calls ResolveRemoteAccountService#call' do + expect_any_instance_of(ResolveRemoteAccountService).to receive(:call).with(acct).once + account.refresh! + end + end + end + describe '#to_param' do it 'returns username' do account = Fabricate(:account, username: 'alice') diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb index f47d9d057..1e238e27c 100644 --- a/spec/models/concerns/account_interactions_spec.rb +++ b/spec/models/concerns/account_interactions_spec.rb @@ -2,38 +2,36 @@ require 'rails_helper' describe AccountInteractions do describe 'muting an account' do - before do - @me = Fabricate(:account, username: 'Me') - @you = Fabricate(:account, username: 'You') - end + let(:me) { Fabricate(:account, username: 'Me') } + let(:you) { Fabricate(:account, username: 'You') } context 'with the notifications option unspecified' do before do - @me.mute!(@you) + me.mute!(you) end it 'defaults to muting notifications' do - expect(@me.muting_notifications?(@you)).to be(true) + expect(me.muting_notifications?(you)).to be true end end context 'with the notifications option set to false' do before do - @me.mute!(@you, notifications: false) + me.mute!(you, notifications: false) end it 'does not mute notifications' do - expect(@me.muting_notifications?(@you)).to be(false) + expect(me.muting_notifications?(you)).to be false end end context 'with the notifications option set to true' do before do - @me.mute!(@you, notifications: true) + me.mute!(you, notifications: true) end it 'does mute notifications' do - expect(@me.muting_notifications?(@you)).to be(true) + expect(me.muting_notifications?(you)).to be true end end end diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb index cb51e9519..bb150b837 100644 --- a/spec/models/custom_emoji_spec.rb +++ b/spec/models/custom_emoji_spec.rb @@ -1,6 +1,35 @@ require 'rails_helper' RSpec.describe CustomEmoji, type: :model do + describe '#local?' do + let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) } + + subject { custom_emoji.local? } + + context 'domain is nil' do + let(:domain) { nil } + + it 'returns true' do + is_expected.to be true + end + end + + context 'domain is present' do + let(:domain) { 'example.com' } + + it 'returns false' do + is_expected.to be false + end + end + end + + describe '#object_type' do + it 'returns :emoji' do + custom_emoji = Fabricate(:custom_emoji) + expect(custom_emoji.object_type).to be :emoji + end + end + describe '.from_text' do let!(:emojo) { Fabricate(:custom_emoji) } diff --git a/spec/models/email_domain_block_spec.rb b/spec/models/email_domain_block_spec.rb index 5f5d189d9..efd2853a9 100644 --- a/spec/models/email_domain_block_spec.rb +++ b/spec/models/email_domain_block_spec.rb @@ -13,9 +13,10 @@ RSpec.describe EmailDomainBlock, type: :model do Fabricate(:email_domain_block, domain: 'example.com') expect(EmailDomainBlock.block?('nyarn@example.com')).to eq true end + it 'returns true if the domain is not registed' do - Fabricate(:email_domain_block, domain: 'domain') - expect(EmailDomainBlock.block?('example')).to eq false + Fabricate(:email_domain_block, domain: 'example.com') + expect(EmailDomainBlock.block?('nyarn@example.net')).to eq false end end end diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb index 62bd724d7..7bc93a2aa 100644 --- a/spec/models/follow_request_spec.rb +++ b/spec/models/follow_request_spec.rb @@ -2,6 +2,17 @@ require 'rails_helper' RSpec.describe FollowRequest, type: :model do describe '#authorize!' do + let(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) } + let(:account) { Fabricate(:account) } + let(:target_account) { Fabricate(:account) } + + it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do + expect(account).to receive(:follow!).with(target_account, reblogs: true) + expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id) + expect(follow_request).to receive(:destroy!) + follow_request.authorize! + end + it 'generates a Follow' do follow_request = Fabricate.create(:follow_request) follow_request.authorize! @@ -23,25 +34,4 @@ RSpec.describe FollowRequest, type: :model do expect(follow_request.account.muting_reblogs?(target)).to be true end end - - describe '#reject!' - - describe 'validations' do - it 'has a valid fabricator' do - follow_request = Fabricate.build(:follow_request) - expect(follow_request).to be_valid - end - - it 'is invalid without an account' do - follow_request = Fabricate.build(:follow_request, account: nil) - follow_request.valid? - expect(follow_request).to model_have_error_on_field(:account) - end - - it 'is invalid without a target account' do - follow_request = Fabricate.build(:follow_request, target_account: nil) - follow_request.valid? - expect(follow_request).to model_have_error_on_field(:target_account) - end - end end diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index 435b4f326..b40a641f7 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -1,6 +1,83 @@ require 'rails_helper' RSpec.describe MediaAttachment, type: :model do + describe 'local?' do + let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url) } + + subject { media_attachment.local? } + + context 'remote_url is blank' do + let(:remote_url) { '' } + + it 'returns true' do + is_expected.to be true + end + end + + context 'remote_url is present' do + let(:remote_url) { 'remote_url' } + + it 'returns false' do + is_expected.to be false + end + end + end + + describe 'needs_redownload?' do + let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url, file: file) } + + subject { media_attachment.needs_redownload? } + + context 'file is blank' do + let(:file) { nil } + + context 'remote_url is blank' do + let(:remote_url) { '' } + + it 'returns false' do + is_expected.to be false + end + end + + context 'remote_url is present' do + let(:remote_url) { 'remote_url' } + + it 'returns true' do + is_expected.to be true + end + end + end + + context 'file is present' do + let(:file) { attachment_fixture('avatar.gif') } + + context 'remote_url is blank' do + let(:remote_url) { '' } + + it 'returns false' do + is_expected.to be false + end + end + + context 'remote_url is present' do + let(:remote_url) { 'remote_url' } + + it 'returns true' do + is_expected.to be false + end + end + end + end + + describe '#to_param' do + let(:media_attachment) { Fabricate(:media_attachment) } + let(:shortcode) { media_attachment.shortcode } + + it 'returns shortcode' do + expect(media_attachment.to_param).to eq shortcode + end + end + describe 'animated gif conversion' do let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('avatar.gif')) } diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 97e8095cd..763b1523f 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -5,6 +5,74 @@ RSpec.describe Notification, type: :model do pending end + describe '#target_status' do + before do + allow(notification).to receive(:type).and_return(type) + allow(notification).to receive(:activity).and_return(activity) + end + + let(:notification) { Fabricate(:notification) } + let(:status) { instance_double('Status') } + let(:favourite) { instance_double('Favourite') } + let(:mention) { instance_double('Mention') } + + context 'type is :reblog' do + let(:type) { :reblog } + let(:activity) { status } + + it 'calls activity.reblog' do + expect(activity).to receive(:reblog) + notification.target_status + end + end + + context 'type is :favourite' do + let(:type) { :favourite } + let(:activity) { favourite } + + it 'calls activity.status' do + expect(activity).to receive(:status) + notification.target_status + end + end + + context 'type is :mention' do + let(:type) { :mention } + let(:activity) { mention } + + it 'calls activity.status' do + expect(activity).to receive(:status) + notification.target_status + end + end + end + + describe '#browserable?' do + let(:notification) { Fabricate(:notification) } + + subject { notification.browserable? } + + context 'type is :follow_request' do + before do + allow(notification).to receive(:type).and_return(:follow_request) + end + + it 'returns false' do + is_expected.to be false + end + end + + context 'type is not :follow_request' do + before do + allow(notification).to receive(:type).and_return(:else) + end + + it 'returns true' do + is_expected.to be true + end + end + end + describe '#type' do it 'returns :reblog for a Status' do notification = Notification.new(activity: Status.new) @@ -26,4 +94,49 @@ RSpec.describe Notification, type: :model do expect(notification.type).to eq :follow end end + + describe '.reload_stale_associations!' do + context 'account_ids are empty' do + let(:cached_items) { [] } + + subject { described_class.reload_stale_associations!(cached_items) } + + it 'returns nil' do + is_expected.to be nil + end + end + + context 'account_ids are present' do + before do + allow(accounts_with_ids).to receive(:[]).with(stale_account1.id).and_return(account1) + allow(accounts_with_ids).to receive(:[]).with(stale_account2.id).and_return(account2) + allow(Account).to receive_message_chain(:where, :map, :to_h).and_return(accounts_with_ids) + end + + let(:cached_items) do + [ + Fabricate(:notification, activity: Fabricate(:status)), + Fabricate(:notification, activity: Fabricate(:follow)), + ] + end + + let(:stale_account1) { cached_items[0].from_account } + let(:stale_account2) { cached_items[1].from_account } + + let(:account1) { Fabricate(:account) } + let(:account2) { Fabricate(:account) } + + let(:accounts_with_ids) { { account1.id => account1, account2.id => account2 } } + + it 'reloads associations' do + expect(cached_items[0].from_account).to be stale_account1 + expect(cached_items[1].from_account).to be stale_account2 + + described_class.reload_stale_associations!(cached_items) + + expect(cached_items[0].from_account).to be account1 + expect(cached_items[1].from_account).to be account2 + end + end + end end diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb new file mode 100644 index 000000000..7a4597ee7 --- /dev/null +++ b/spec/models/remote_follow_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe RemoteFollow do + before do + stub_request(:get, 'https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no').to_return(request_fixture('webfinger.txt')) + end + + let(:attrs) { nil } + let(:remote_follow) { described_class.new(attrs) } + + describe '.initialize' do + subject { remote_follow.acct } + + context 'attrs with acct' do + let(:attrs) { { acct: 'gargron@quitter.no' } } + + it 'returns acct' do + is_expected.to eq 'gargron@quitter.no' + end + end + + context 'attrs without acct' do + let(:attrs) { {} } + + it do + is_expected.to be_nil + end + end + end + + describe '#valid?' do + subject { remote_follow.valid? } + + context 'attrs with acct' do + let(:attrs) { { acct: 'gargron@quitter.no' }} + + it do + is_expected.to be true + end + end + + context 'attrs without acct' do + let(:attrs) { { } } + + it do + is_expected.to be false + end + end + end + + describe '#subscribe_address_for' do + before do + remote_follow.valid? + end + + let(:attrs) { { acct: 'gargron@quitter.no' } } + let(:account) { Fabricate(:account, username: 'alice') } + + subject { remote_follow.subscribe_address_for(account) } + + it 'returns subscribe address' do + is_expected.to eq 'https://quitter.no/main/ostatussub?profile=alice%40cb6e6126.ngrok.io' + end + end +end diff --git a/spec/models/remote_profile_spec.rb b/spec/models/remote_profile_spec.rb new file mode 100644 index 000000000..da5048f0a --- /dev/null +++ b/spec/models/remote_profile_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe RemoteProfile do + let(:remote_profile) { RemoteProfile.new(body) } + let(:body) do + <<-XML + <feed xmlns="http://www.w3.org/2005/Atom"> + <author>John</author> + XML + end + + describe '.initialize' do + it 'calls Nokogiri::XML.parse' do + expect(Nokogiri::XML).to receive(:parse).with(body, nil, 'utf-8') + RemoteProfile.new(body) + end + + it 'sets document' do + remote_profile = RemoteProfile.new(body) + expect(remote_profile).not_to be nil + end + end + + describe '#root' do + let(:document) { remote_profile.document } + + it 'callse document.at_xpath' do + expect(document).to receive(:at_xpath).with( + '/atom:feed|/atom:entry', + atom: OStatus::TagManager::XMLNS + ) + + remote_profile.root + end + end + + describe '#author' do + let(:root) { remote_profile.root } + + it 'calls root.at_xpath' do + expect(root).to receive(:at_xpath).with( + './atom:author|./dfrn:owner', + atom: OStatus::TagManager::XMLNS, + dfrn: OStatus::TagManager::DFRN_XMLNS + ) + + remote_profile.author + end + end + + describe '#hub_link' do + let(:root) { remote_profile.root } + + it 'calls #link_href_from_xml' do + expect(remote_profile).to receive(:link_href_from_xml).with(root, 'hub') + remote_profile.hub_link + end + end + + describe '#display_name' do + let(:author) { remote_profile.author } + + it 'calls author.at_xpath.content' do + expect(author).to receive_message_chain(:at_xpath, :content).with( + './poco:displayName', + poco: OStatus::TagManager::POCO_XMLNS + ).with(no_args) + + remote_profile.display_name + end + end + + describe '#note' do + let(:author) { remote_profile.author } + + it 'calls author.at_xpath.content' do + expect(author).to receive_message_chain(:at_xpath, :content).with( + './atom:summary|./poco:note', + atom: OStatus::TagManager::XMLNS, + poco: OStatus::TagManager::POCO_XMLNS + ).with(no_args) + + remote_profile.note + end + end + + describe '#scope' do + let(:author) { remote_profile.author } + + it 'calls author.at_xpath.content' do + expect(author).to receive_message_chain(:at_xpath, :content).with( + './mastodon:scope', + mastodon: OStatus::TagManager::MTDN_XMLNS + ).with(no_args) + + remote_profile.scope + end + end + + describe '#avatar' do + let(:author) { remote_profile.author } + + it 'calls #link_href_from_xml' do + expect(remote_profile).to receive(:link_href_from_xml).with(author, 'avatar') + remote_profile.avatar + end + end + + describe '#header' do + let(:author) { remote_profile.author } + + it 'calls #link_href_from_xml' do + expect(remote_profile).to receive(:link_href_from_xml).with(author, 'header') + remote_profile.header + end + end + + describe '#locked?' do + before do + allow(remote_profile).to receive(:scope).and_return(scope) + end + + subject { remote_profile.locked? } + + context 'scope is private' do + let(:scope) { 'private' } + + it 'returns true' do + is_expected.to be true + end + end + + context 'scope is not private' do + let(:scope) { 'public' } + + it 'returns false' do + is_expected.to be false + end + end + end +end diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb index 49c72fbd4..2aa695037 100644 --- a/spec/models/session_activation_spec.rb +++ b/spec/models/session_activation_spec.rb @@ -1,5 +1,127 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe SessionActivation, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe '#detection' do + let(:session_activation) { Fabricate(:session_activation, user_agent: 'Chrome/62.0.3202.89') } + + it 'sets a Browser instance as detection' do + expect(session_activation.detection).to be_kind_of Browser::Chrome + end + end + + describe '#browser' do + before do + allow(session_activation).to receive(:detection).and_return(detection) + end + + let(:detection) { double(id: 1) } + let(:session_activation) { Fabricate(:session_activation) } + + it 'returns detection.id' do + expect(session_activation.browser).to be 1 + end + end + + describe '#platform' do + before do + allow(session_activation).to receive(:detection).and_return(detection) + end + + let(:session_activation) { Fabricate(:session_activation) } + let(:detection) { double(platform: double(id: 1)) } + + it 'returns detection.platform.id' do + expect(session_activation.platform).to be 1 + end + end + + describe '.active?' do + subject { described_class.active?(id) } + + context 'id is absent' do + let(:id) { nil } + + it 'returns nil' do + is_expected.to be nil + end + end + + context 'id is present' do + let(:id) { '1' } + let!(:session_activation) { Fabricate(:session_activation, session_id: id) } + + context 'id exists as session_id' do + it 'returns true' do + is_expected.to be true + end + end + + context 'id does not exist as session_id' do + before do + session_activation.update!(session_id: '2') + end + + it 'returns false' do + is_expected.to be false + end + end + end + end + + describe '.activate' do + let(:options) { { user: Fabricate(:user), session_id: '1' } } + + it 'calls create! and purge_old' do + expect(described_class).to receive(:create!).with(options) + expect(described_class).to receive(:purge_old) + described_class.activate(options) + end + + it 'returns an instance of SessionActivation' do + expect(described_class.activate(options)).to be_kind_of SessionActivation + end + end + + describe '.deactivate' do + context 'id is absent' do + let(:id) { nil } + + it 'returns nil' do + expect(described_class.deactivate(id)).to be nil + end + end + + context 'id exists' do + let(:id) { '1' } + + it 'calls where.destroy_all' do + expect(described_class).to receive_message_chain(:where, :destroy_all) + .with(session_id: id).with(no_args) + + described_class.deactivate(id) + end + end + end + + describe '.purge_old' do + it 'calls order.offset.destroy_all' do + expect(described_class).to receive_message_chain(:order, :offset, :destroy_all) + .with('created_at desc').with(Rails.configuration.x.max_session_activations).with(no_args) + + described_class.purge_old + end + end + + describe '.exclusive' do + let(:id) { '1' } + + it 'calls where.destroy_all' do + expect(described_class).to receive_message_chain(:where, :destroy_all) + .with('session_id != ?', id).with(no_args) + + described_class.exclusive(id) + end + end end diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb new file mode 100644 index 000000000..e99dfc0d7 --- /dev/null +++ b/spec/models/setting_spec.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Setting, type: :model do + describe '#to_param' do + let(:setting) { Fabricate(:setting, var: var) } + let(:var) { 'var' } + + it 'returns setting.var' do + expect(setting.to_param).to eq var + end + end + + describe '.[]' do + before do + allow(described_class).to receive(:rails_initialized?).and_return(rails_initialized) + end + + let(:key) { 'key' } + + context 'rails_initialized? is falsey' do + let(:rails_initialized) { false } + + it 'calls RailsSettings::Base#[]' do + expect(RailsSettings::Base).to receive(:[]).with(key) + described_class[key] + end + end + + context 'rails_initialized? is truthy' do + before do + allow(RailsSettings::Base).to receive(:cache_key).with(key, nil).and_return(cache_key) + end + + let(:rails_initialized) { true } + let(:cache_key) { 'cache-key' } + let(:cache_value) { 'cache-value' } + + it 'calls not RailsSettings::Base#[]' do + expect(RailsSettings::Base).not_to receive(:[]).with(key) + described_class[key] + end + + it 'calls Rails.cache.fetch' do + expect(Rails).to receive_message_chain(:cache, :fetch).with(cache_key) + described_class[key] + end + + context 'Rails.cache does not exists' do + before do + allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object) + allow(described_class).to receive(:default_settings).and_return(default_settings) + allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records) + Rails.cache.clear(cache_key) + end + + let(:object) { nil } + let(:default_value) { 'default_value' } + let(:default_settings) { { key => default_value } } + let(:records) { [Fabricate(:setting, var: key, value: nil)] } + + it 'calls RailsSettings::Settings.object' do + expect(RailsSettings::Settings).to receive(:object).with(key) + described_class[key] + end + + context 'RailsSettings::Settings.object returns truthy' do + let(:object) { db_val } + let(:db_val) { double(value: 'db_val') } + + context 'default_value is a Hash' do + let(:default_value) { { default_value: 'default_value' } } + + it 'calls default_value.with_indifferent_access.merge!' do + expect(default_value).to receive_message_chain(:with_indifferent_access, :merge!) + .with(db_val.value) + + described_class[key] + end + end + + context 'default_value is not a Hash' do + let(:default_value) { 'default_value' } + + it 'returns db_val.value' do + expect(described_class[key]).to be db_val.value + end + end + end + + context 'RailsSettings::Settings.object returns falsey' do + let(:object) { nil } + + it 'returns default_settings[key]' do + expect(described_class[key]).to be default_settings[key] + end + end + end + + context 'Rails.cache exists' do + before do + Rails.cache.write(cache_key, cache_value) + end + + it 'returns the cached value' do + expect(described_class[key]).to eq cache_value + end + end + end + end + + describe '.all_as_records' do + before do + allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records) + allow(described_class).to receive(:default_settings).and_return(default_settings) + end + + let(:key) { 'key' } + let(:default_value) { 'default_value' } + let(:default_settings) { { key => default_value } } + let(:original_setting) { Fabricate(:setting, var: key, value: nil) } + let(:records) { [original_setting] } + + it 'returns a Hash' do + expect(described_class.all_as_records).to be_kind_of Hash + end + + context 'records includes Setting with var as the key' do + let(:records) { [original_setting] } + + it 'includes the original Setting' do + setting = described_class.all_as_records[key] + expect(setting).to eq original_setting + end + end + + context 'records includes nothing' do + let(:records) { [] } + + context 'default_value is not a Hash' do + it 'includes Setting with value of default_value' do + setting = described_class.all_as_records[key] + + expect(setting).to be_kind_of Setting + expect(setting).to have_attributes(var: key) + expect(setting).to have_attributes(value: 'default_value') + end + end + + context 'default_value is a Hash' do + let(:default_value) { { 'foo' => 'fuga' } } + + it 'returns {}' do + expect(described_class.all_as_records).to eq({}) + end + end + end + end + + describe '.default_settings' do + before do + allow(RailsSettings::Default).to receive(:enabled?).and_return(enabled) + end + + subject { described_class.default_settings } + + context 'RailsSettings::Default.enabled? is false' do + let(:enabled) { false } + + it 'returns {}' do + is_expected.to eq({}) + end + end + + context 'RailsSettings::Settings.enabled? is true' do + let(:enabled) { true } + + it 'returns instance of RailsSettings::Default' do + is_expected.to be_kind_of RailsSettings::Default + end + end + end +end diff --git a/spec/models/site_upload_spec.rb b/spec/models/site_upload_spec.rb index 8745d54b8..f7ea06921 100644 --- a/spec/models/site_upload_spec.rb +++ b/spec/models/site_upload_spec.rb @@ -1,5 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe SiteUpload, type: :model do + describe '#cache_key' do + let(:site_upload) { SiteUpload.new(var: 'var') } + it 'returns cache_key' do + expect(site_upload.cache_key).to eq 'site_uploads/var' + end + end end diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 12e857169..91fd13c94 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -69,6 +69,36 @@ RSpec.describe Status, type: :model do end end + describe '#hidden?' do + context 'if private_visibility?' do + it 'returns true' do + subject.visibility = :private + expect(subject.hidden?).to be true + end + end + + context 'if direct_visibility?' do + it 'returns true' do + subject.visibility = :direct + expect(subject.hidden?).to be true + end + end + + context 'if public_visibility?' do + it 'returns false' do + subject.visibility = :public + expect(subject.hidden?).to be false + end + end + + context 'if unlisted_visibility?' do + it 'returns false' do + subject.visibility = :unlisted + expect(subject.hidden?).to be false + end + end + end + describe '#content' do it 'returns the text of the status if it is not a reblog' do expect(subject.content).to eql subject.text diff --git a/spec/models/stream_entry_spec.rb b/spec/models/stream_entry_spec.rb index 3b7ff5143..8f8bfbd58 100644 --- a/spec/models/stream_entry_spec.rb +++ b/spec/models/stream_entry_spec.rb @@ -6,6 +6,121 @@ RSpec.describe StreamEntry, type: :model do let(:status) { Fabricate(:status, account: alice) } let(:reblog) { Fabricate(:status, account: bob, reblog: status) } let(:reply) { Fabricate(:status, account: bob, thread: status) } + let(:stream_entry) { Fabricate(:stream_entry, activity: activity) } + let(:activity) { reblog } + + describe '#object_type' do + before do + allow(stream_entry).to receive(:orphaned?).and_return(orphaned) + allow(stream_entry).to receive(:targeted?).and_return(targeted) + end + + subject { stream_entry.object_type } + + context 'orphaned? is true' do + let(:orphaned) { true } + let(:targeted) { false } + + it 'returns :activity' do + is_expected.to be :activity + end + end + + context 'targeted? is true' do + let(:orphaned) { false } + let(:targeted) { true } + + it 'returns :activity' do + is_expected.to be :activity + end + end + + context 'orphaned? and targeted? are false' do + let(:orphaned) { false } + let(:targeted) { false } + + context 'activity is reblog' do + let(:activity) { reblog } + + it 'returns :note' do + is_expected.to be :note + end + end + + context 'activity is reply' do + let(:activity) { reply } + + it 'returns :comment' do + is_expected.to be :comment + end + end + end + end + + describe '#verb' do + before do + allow(stream_entry).to receive(:orphaned?).and_return(orphaned) + end + + subject { stream_entry.verb } + + context 'orphaned? is true' do + let(:orphaned) { true } + + it 'returns :delete' do + is_expected.to be :delete + end + end + + context 'orphaned? is false' do + let(:orphaned) { false } + + context 'activity is reblog' do + let(:activity) { reblog } + + it 'returns :share' do + is_expected.to be :share + end + end + + context 'activity is reply' do + let(:activity) { reply } + + it 'returns :post' do + is_expected.to be :post + end + end + end + end + + describe '#mentions' do + before do + allow(stream_entry).to receive(:orphaned?).and_return(orphaned) + end + + subject { stream_entry.mentions } + + context 'orphaned? is true' do + let(:orphaned) { true } + + it 'returns []' do + is_expected.to eq [] + end + end + + context 'orphaned? is false' do + before do + reblog.mentions << Fabricate(:mention, account: alice) + reblog.mentions << Fabricate(:mention, account: bob) + end + + let(:orphaned) { false } + + it 'returns [Account] includes alice and bob' do + is_expected.to eq [alice, bob] + end + end + end describe '#targeted?' do it 'returns true for a reblog' do diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index f727fa1dd..1ca50cc29 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -35,6 +35,13 @@ RSpec.describe Tag, type: :model do end end + describe '#to_param' do + it 'returns name' do + tag = Fabricate(:tag, name: 'foo') + expect(tag.to_param).to eq 'foo' + end + end + describe '.search_for' do it 'finds tag records with matching names' do tag = Fabricate(:tag, name: "match") diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 99aeca01b..77a12c26d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -177,27 +177,10 @@ RSpec.describe User, type: :model do end end - describe '#setting_auto_play_gif' do - it 'returns auto-play gif setting' do + describe 'settings' do + it 'is instance of Settings::ScopedSettings' do user = Fabricate(:user) - user.settings[:auto_play_gif] = false - expect(user.setting_auto_play_gif).to eq false - end - end - - describe '#setting_system_font_ui' do - it 'returns system font ui setting' do - user = Fabricate(:user) - user.settings[:system_font_ui] = false - expect(user.setting_system_font_ui).to eq false - end - end - - describe '#setting_boost_modal' do - it 'returns boost modal setting' do - user = Fabricate(:user) - user.settings[:boost_modal] = false - expect(user.setting_boost_modal).to eq false + expect(user.settings).to be_kind_of Settings::ScopedSettings end end @@ -219,22 +202,6 @@ RSpec.describe User, type: :model do end end - describe '#setting_unfollow_modal' do - it 'returns unfollow modal setting' do - user = Fabricate(:user) - user.settings[:unfollow_modal] = true - expect(user.setting_unfollow_modal).to eq true - end - end - - describe '#setting_delete_modal' do - it 'returns delete modal setting' do - user = Fabricate(:user) - user.settings[:delete_modal] = false - expect(user.setting_delete_modal).to eq false - end - end - describe 'whitelist' do around(:each) do |example| old_whitelist = Rails.configuration.x.email_whitelist diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 250a880a2..a8ebc16b8 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -67,6 +67,39 @@ RSpec.describe NotifyService do is_expected.to_not change(Notification, :count) end end + + context 'for direct messages' do + let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) } + + before do + user.settings.interactions = user.settings.interactions.merge('must_be_following_dm' => enabled) + end + + context 'if recipient is supposed to be following sender' do + let(:enabled) { true } + + it 'does not notify' do + is_expected.to_not change(Notification, :count) + end + + context 'if the message chain initiated by recipient' do + let(:reply_to) { Fabricate(:status, account: recipient) } + let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } + + it 'does notify' do + is_expected.to change(Notification, :count) + end + end + end + + context 'if recipient is NOT supposed to be following sender' do + let(:enabled) { false } + + it 'does notify' do + is_expected.to change(Notification, :count) + end + end + end context do let(:asshole) { Fabricate(:account, username: 'asshole') } diff --git a/streaming/index.js b/streaming/index.js index f0492784a..8adc5174a 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -467,7 +467,7 @@ const startWorker = (workerId) => { }); }, 30000); - server.listen(process.env.PORT || 4000, process.env.HOST || '127.0.0.1', () => { + server.listen(process.env.PORT || 4000, () => { log.info(`Worker ${workerId} now listening on ${server.address().address}:${server.address().port}`); }); diff --git a/yarn.lock b/yarn.lock index aceff463b..0805e8af3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,23 +3,16 @@ "@types/node@^6.0.46": - version "6.0.89" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.89.tgz#154be0e6a823760cd6083aa8c48f952e2e63e0b0" + version "6.0.90" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.90.tgz#0ed74833fa1b73dcdb9409dcb1c97ec0a8b13b02" abab@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" + version "1.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" abbrev@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" - -accepts@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" - dependencies: - mime-types "~2.1.11" - negotiator "0.6.1" + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" accepts@~1.3.4: version "1.3.4" @@ -54,9 +47,9 @@ acorn@^4.0.3, acorn@^4.0.4: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" -acorn@^5.0.0, acorn@^5.0.1, acorn@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75" +acorn@^5.0.0, acorn@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" adjust-sourcemap-loader@^1.1.0: version "1.1.0" @@ -75,8 +68,8 @@ ajv-keywords@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" ajv-keywords@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" @@ -85,14 +78,14 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.0.0, ajv@^5.1.5: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" +ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda" dependencies: co "^4.6.0" fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" - json-stable-stringify "^1.0.1" align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" @@ -134,13 +127,7 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" - dependencies: - color-convert "^1.0.0" - -ansi-styles@^3.2.0: +ansi-styles@^3.1.0, ansi-styles@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" dependencies: @@ -151,15 +138,11 @@ any-promise@^0.1.0: resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-0.1.0.tgz#830b680aa7e56f33451d4b049f3bd8044498ee27" anymatch@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" dependencies: - arrify "^1.0.0" micromatch "^2.1.5" - -ap@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" + normalize-path "^2.0.0" append-transform@^0.4.0: version "0.4.0" @@ -168,8 +151,8 @@ append-transform@^0.4.0: default-require-extensions "^1.0.0" aproba@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" are-we-there-yet@~1.1.2: version "1.1.4" @@ -294,15 +277,17 @@ async-foreach@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" -async@0.2.x: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" +async@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" + dependencies: + lodash "^4.14.0" async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2, async@^2.1.4, async@^2.1.5: +async@^2.1.2, async@^2.1.4, async@^2.1.5, async@^2.4.1: version "2.5.0" resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" dependencies: @@ -331,41 +316,45 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" -autoprefixer@^7.1.2: - version "7.1.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.4.tgz#960847dbaa4016bc8e8e52ec891cbf8f1257a748" +autoprefixer@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.6.tgz#fb933039f74af74a83e71225ce78d9fd58ba84d7" dependencies: - browserslist "^2.4.0" - caniuse-lite "^1.0.30000726" + browserslist "^2.5.1" + caniuse-lite "^1.0.30000748" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^6.0.11" + postcss "^6.0.13" postcss-value-parser "^3.2.3" aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" -aws4@^1.2.1: +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -axios@^0.16.2: +axios@~0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" dependencies: follow-redirects "^1.2.3" is-buffer "^1.1.5" -babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" +babel-code-frame@7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-7.0.0-beta.0.tgz#418a7b5f3f7dc9a4670e61b1158b4c5661bec98d" dependencies: - chalk "^1.1.0" + chalk "^2.0.0" esutils "^2.0.2" js-tokens "^3.0.0" -babel-code-frame@^6.26.0: +babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -406,6 +395,15 @@ babel-eslint@^7.2.3: babel-types "^6.23.0" babylon "^6.17.0" +babel-eslint@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.0.1.tgz#5d718be7a328625d006022eb293ed3008cbd6346" + dependencies: + babel-code-frame "7.0.0-beta.0" + babel-traverse "7.0.0-beta.0" + babel-types "7.0.0-beta.0" + babylon "7.0.0-beta.22" + babel-generator@^6.18.0, babel-generator@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" @@ -428,12 +426,12 @@ babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: babel-types "^6.24.1" babel-helper-builder-react-jsx@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.24.1.tgz#0ad7917e33c8d751e646daca4e77cc19377d2cbc" + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - esutils "^2.0.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + esutils "^2.0.2" babel-helper-call-delegate@^6.24.1: version "6.24.1" @@ -445,13 +443,13 @@ babel-helper-call-delegate@^6.24.1: babel-types "^6.24.1" babel-helper-define-map@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz#7a9747f258d8947d32d515f6aa1c7bd02204a080" + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" dependencies: babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - lodash "^4.2.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" babel-helper-explode-assignable-expression@^6.24.1: version "6.24.1" @@ -461,6 +459,15 @@ babel-helper-explode-assignable-expression@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" +babel-helper-function-name@7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-7.0.0-beta.0.tgz#d1b6779b647e5c5c31ebeb05e13b998e4d352d56" + dependencies: + babel-helper-get-function-arity "7.0.0-beta.0" + babel-template "7.0.0-beta.0" + babel-traverse "7.0.0-beta.0" + babel-types "7.0.0-beta.0" + babel-helper-function-name@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" @@ -471,6 +478,12 @@ babel-helper-function-name@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" +babel-helper-get-function-arity@7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-7.0.0-beta.0.tgz#9d1ab7213bb5efe1ef1638a8ea1489969b5a8b6e" + dependencies: + babel-types "7.0.0-beta.0" + babel-helper-get-function-arity@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" @@ -493,12 +506,12 @@ babel-helper-optimise-call-expression@^6.24.1: babel-types "^6.24.1" babel-helper-regex@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz#d36e22fab1008d79d88648e32116868128456ce8" + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - lodash "^4.2.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" babel-helper-remap-async-to-generator@^6.24.1: version "6.24.1" @@ -543,9 +556,15 @@ babel-loader@^7.1.1: loader-utils "^1.0.2" mkdirp "^0.5.1" -babel-macros@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/babel-macros/-/babel-macros-1.0.2.tgz#04475889990243cc58a0afb5ea3308ec6b89e797" +babel-macros@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-macros/-/babel-macros-1.2.0.tgz#39e47ed6d286d4a98f1948d8bab45dac17e4e2d4" + dependencies: + cosmiconfig "3.1.0" + +babel-messages@7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-7.0.0-beta.0.tgz#6df01296e49fc8fbd0637394326a167f36da817b" babel-messages@^6.23.0: version "6.23.0" @@ -578,15 +597,15 @@ babel-plugin-lodash@^3.2.11: glob "^7.1.1" lodash "^4.17.2" -babel-plugin-preval@^1.3.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-preval/-/babel-plugin-preval-1.5.0.tgz#be4e3353ce6ec4fd0c6b199701193306033bf54b" +babel-plugin-preval@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/babel-plugin-preval/-/babel-plugin-preval-1.6.1.tgz#c33fd124f9cb9281550cae35ff0ea6065e32261d" dependencies: babel-core "^6.26.0" - babel-macros "^1.0.0" + babel-macros "^1.1.1" babel-register "^6.26.0" babylon "^6.18.0" - require-from-string "^1.2.1" + require-from-string "^2.0.1" babel-plugin-react-intl@^2.3.1: version "2.3.1" @@ -596,12 +615,6 @@ babel-plugin-react-intl@^2.3.1: intl-messageformat-parser "^1.2.0" mkdirp "^0.5.1" -babel-plugin-react-transform@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/babel-plugin-react-transform/-/babel-plugin-react-transform-2.0.2.tgz#515bbfa996893981142d90b1f9b1635de2995109" - dependencies: - lodash "^4.6.1" - babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" @@ -676,14 +689,14 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: babel-runtime "^6.22.0" babel-plugin-transform-es2015-block-scoping@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576" + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - lodash "^4.2.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" babel-plugin-transform-es2015-classes@^6.23.0: version "6.24.1" @@ -747,16 +760,7 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015 babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe" - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.24.1: +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" dependencies: @@ -896,17 +900,15 @@ babel-plugin-transform-react-jsx@^6.24.1: babel-plugin-syntax-jsx "^6.8.0" babel-runtime "^6.22.0" -babel-plugin-transform-react-remove-prop-types@^0.4.6: - version "0.4.8" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.8.tgz#0aff04bc1d6564ec49cf23bcffb99c11881958db" - dependencies: - babel-traverse "^6.25.0" +babel-plugin-transform-react-remove-prop-types@^0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.10.tgz#3c7f3a03ad8aa6bb8c00e93fd13a433910444545" babel-plugin-transform-regenerator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz#b8da305ad43c3c99b4848e4fe4037b770d23c418" + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" dependencies: - regenerator-transform "0.9.11" + regenerator-transform "^0.10.0" babel-plugin-transform-runtime@^6.23.0: version "6.23.0" @@ -921,9 +923,9 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-preset-env@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4" +babel-preset-env@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48" dependencies: babel-plugin-check-es2015-constants "^6.22.0" babel-plugin-syntax-trailing-function-commas "^6.22.0" @@ -992,21 +994,23 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.10.0" - -babel-runtime@^6.26.0: +babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.16.0, babel-template@^6.26.0: +babel-template@7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-7.0.0-beta.0.tgz#85083cf9e4395d5e48bf5154d7a8d6991cafecfb" + dependencies: + babel-traverse "7.0.0-beta.0" + babel-types "7.0.0-beta.0" + babylon "7.0.0-beta.22" + lodash "^4.2.0" + +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: @@ -1016,17 +1020,21 @@ babel-template@^6.16.0, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-template@^6.24.1, babel-template@^6.3.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.25.0" - babel-types "^6.25.0" - babylon "^6.17.2" +babel-traverse@7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-7.0.0-beta.0.tgz#da14be9b762f62a2f060db464eaafdd8cd072a41" + dependencies: + babel-code-frame "7.0.0-beta.0" + babel-helper-function-name "7.0.0-beta.0" + babel-messages "7.0.0-beta.0" + babel-types "7.0.0-beta.0" + babylon "7.0.0-beta.22" + debug "^3.0.1" + globals "^10.0.0" + invariant "^2.2.0" lodash "^4.2.0" -babel-traverse@^6.18.0, babel-traverse@^6.26.0: +babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -1040,21 +1048,15 @@ babel-traverse@^6.18.0, babel-traverse@^6.26.0: invariant "^2.2.2" lodash "^4.17.4" -babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" +babel-types@7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-7.0.0-beta.0.tgz#eb8b6e556470e6dcc4aef982d79ad229469b5169" dependencies: - babel-code-frame "^6.22.0" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-types "^6.25.0" - babylon "^6.17.2" - debug "^2.2.0" - globals "^9.0.0" - invariant "^2.2.0" + esutils "^2.0.2" lodash "^4.2.0" + to-fast-properties "^2.0.0" -babel-types@^6.18.0, babel-types@^6.26.0: +babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: @@ -1063,20 +1065,11 @@ babel-types@^6.18.0, babel-types@^6.26.0: lodash "^4.17.4" to-fast-properties "^1.0.3" -babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" - dependencies: - babel-runtime "^6.22.0" - esutils "^2.0.2" - lodash "^4.2.0" - to-fast-properties "^1.0.1" - -babylon@^6.17.0, babylon@^6.17.2: - version "6.17.4" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" +babylon@7.0.0-beta.22: + version "7.0.0-beta.22" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.22.tgz#74f0ad82ed7c7c3cfeab74cf684f815104161b65" -babylon@^6.18.0: +babylon@^6.17.0, babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" @@ -1113,12 +1106,12 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" big.js@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" + version "3.2.0" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" binary-extensions@^1.0.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" + version "1.10.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" block-stream@*: version "0.0.9" @@ -1127,8 +1120,8 @@ block-stream@*: inherits "~2.0.0" bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.7" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" body-parser@1.18.2: version "1.18.2" @@ -1166,6 +1159,18 @@ boom@2.x.x: dependencies: hoek "2.x.x" +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + dependencies: + hoek "4.x.x" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -1192,14 +1197,15 @@ browser-resolve@^1.11.2: resolve "1.1.7" browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a" + version "1.1.1" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" dependencies: - buffer-xor "^1.0.2" + buffer-xor "^1.0.3" cipher-base "^1.0.0" create-hash "^1.1.0" - evp_bytestokey "^1.0.0" + evp_bytestokey "^1.0.3" inherits "^2.0.1" + safe-buffer "^5.0.1" browserify-cipher@^1.0.0: version "1.0.0" @@ -1249,19 +1255,12 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^2.1.2: - version "2.1.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.1.5.tgz#e882550df3d1cd6d481c1a3e0038f2baf13a4711" - dependencies: - caniuse-lite "^1.0.30000684" - electron-to-chromium "^1.3.14" - -browserslist@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.4.0.tgz#693ee93d01e66468a6348da5498e011f578f87f8" +browserslist@^2.1.2, browserslist@^2.5.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.6.1.tgz#cc65a05ad6131ebda26f076f2822ba1bc826376b" dependencies: - caniuse-lite "^1.0.30000718" - electron-to-chromium "^1.3.18" + caniuse-lite "^1.0.30000755" + electron-to-chromium "^1.3.27" bser@^2.0.0: version "2.0.0" @@ -1270,14 +1269,14 @@ bser@^2.0.0: node-int64 "^0.4.0" buffer-indexof@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" buffer-writer@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" -buffer-xor@^1.0.2: +buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1297,10 +1296,6 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" -bytes@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -1356,16 +1351,12 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000700" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000700.tgz#97cfc483865eea8577dc7a3674929b9abf553095" + version "1.0.30000755" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000755.tgz#a08c547c39dbe4ad07dcca9763fcbbff0c891de0" -caniuse-lite@^1.0.30000684: - version "1.0.30000700" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000700.tgz#6084871ec75c6fa62327de97622514f95d9db26a" - -caniuse-lite@^1.0.30000718, caniuse-lite@^1.0.30000726: - version "1.0.30000740" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000740.tgz#f2c4c04d6564eb812e61006841700ad557f6f973" +caniuse-lite@^1.0.30000748, caniuse-lite@^1.0.30000755: + version "1.0.30000755" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000755.tgz#9ce5f6e06bd75ec8209abe8853c3beef02248d65" caseless@~0.12.0: version "0.12.0" @@ -1382,7 +1373,7 @@ chain-function@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -1392,17 +1383,9 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d" - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - -chalk@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" dependencies: ansi-styles "^3.1.0" escape-string-regexp "^1.0.5" @@ -1446,12 +1429,12 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: safe-buffer "^5.0.1" circular-json@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" clap@^1.0.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.0.tgz#59c90fe3e137104746ff19469a27a634ff68c857" + version "1.2.3" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" dependencies: chalk "^1.1.3" @@ -1466,8 +1449,8 @@ cli-cursor@^1.0.1: restore-cursor "^1.0.1" cli-width@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" cliui@^2.1.0: version "2.1.0" @@ -1512,15 +1495,15 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -color-convert@^1.0.0, color-convert@^1.3.0, color-convert@^1.9.0: +color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" dependencies: color-name "^1.1.1" color-name@^1.0.0, color-name@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" color-string@^0.3.0: version "0.3.0" @@ -1570,32 +1553,30 @@ complex.js@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.4.tgz#d8e7cfb9652d1e853e723386421c1a0ca7a48373" -compressible@~2.0.10: - version "2.0.10" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" +compressible@~2.0.11: + version "2.0.12" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" dependencies: - mime-db ">= 1.27.0 < 2" + mime-db ">= 1.30.0 < 2" -compression-webpack-plugin@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-0.4.0.tgz#811de04215f811ea6a12d4d8aed8457d758f13ac" +compression-webpack-plugin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.0.1.tgz#7f0a2af9f642b4f87b5989516a3b9e9b41bb4b3f" dependencies: - async "0.2.x" - webpack-sources "^0.1.0" - optionalDependencies: - node-zopfli "^2.0.0" + async "2.4.1" + webpack-sources "^1.0.1" compression@^1.5.2: - version "1.7.0" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.0.tgz#030c9f198f1643a057d776a738e922da4373012d" + version "1.7.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db" dependencies: - accepts "~1.3.3" - bytes "2.5.0" - compressible "~2.0.10" - debug "2.6.8" + accepts "~1.3.4" + bytes "3.0.0" + compressible "~2.0.11" + debug "2.6.9" on-headers "~1.0.1" safe-buffer "5.1.1" - vary "~1.1.1" + vary "~1.1.2" concat-map@0.0.1: version "0.0.1" @@ -1610,8 +1591,8 @@ concat-stream@^1.5.2: typedarray "^0.0.6" connect-history-api-fallback@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169" + version "1.4.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.4.0.tgz#3db24f973f4b923b0e82f619ce0df02411ca623d" console-browserify@^1.1.0: version "1.1.0" @@ -1636,12 +1617,8 @@ content-disposition@0.5.2: resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" content-type-parser@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94" - -content-type@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" + resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" content-type@~1.0.4: version "1.0.4" @@ -1667,21 +1644,26 @@ core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" -core-js@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" - -core-js@^2.5.0: +core-js@^2.4.0, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" -core-util-is@~1.0.0: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +cosmiconfig@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.1.0.tgz#640a94bf9847f321800403cd273af60665c73397" + dependencies: + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^3.0.0" + require-from-string "^2.0.1" + cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.3.tgz#952771eb0dddc1cb3fa2f6fbe51a522e93b3ee0a" + version "2.2.2" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892" dependencies: is-directory "^0.3.1" js-yaml "^3.4.3" @@ -1698,7 +1680,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2: +create-hash@^1.1.0, create-hash@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" dependencies: @@ -1726,9 +1708,9 @@ create-react-class@^15.5.2: loose-envify "^1.3.1" object-assign "^4.1.1" -cross-env@^5.0.1: - version "5.0.5" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.0.5.tgz#4383d364d9660873dd185b398af3bfef5efffef3" +cross-env@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.1.tgz#b6d8ab97f304c0f71dae7277b75fe424c08dfa74" dependencies: cross-spawn "^5.1.0" is-windows "^1.0.0" @@ -1740,14 +1722,7 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - -cross-spawn@^5.1.0: +cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" dependencies: @@ -1761,6 +1736,12 @@ cryptiles@2.x.x: dependencies: boom "2.x.x" +cryptiles@3.x.x: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" + dependencies: + boom "5.x.x" + crypto-browserify@^3.11.0: version "3.11.1" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f" @@ -1777,12 +1758,12 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" css-color-function@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.0.tgz#72c767baf978f01b8a8a94f42f17ba5d22a776fc" + version "1.3.3" + resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.3.tgz#8ed24c2c0205073339fafa004bc8c141fccb282e" dependencies: balanced-match "0.1.0" color "^0.11.0" - debug "~0.7.4" + debug "^3.1.0" rgb "~0.1.0" css-color-names@0.0.4: @@ -1952,27 +1933,17 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" - dependencies: - ms "2.0.0" - -debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.6, debug@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" - dependencies: - ms "2.0.0" - -debug@2.6.9, debug@^2.6.3: +debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: ms "2.0.0" -debug@~0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" +debug@^3.0.1, debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" @@ -2048,10 +2019,6 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@1.1.0, depd@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" - depd@1.1.1, depd@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" @@ -2102,8 +2069,8 @@ dns-equal@^1.0.0: resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" dns-packet@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c" + version "1.2.2" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.2.2.tgz#a8a26bec7646438963fc86e06f8f8b16d6c8bf7a" dependencies: ip "^1.1.0" safe-buffer "^5.0.1" @@ -2128,7 +2095,7 @@ doctrine@^2.0.0: esutils "^2.0.2" isarray "^1.0.0" -dom-helpers@^3.0.0, dom-helpers@^3.2.0, dom-helpers@^3.2.1: +dom-helpers@^3.2.0, dom-helpers@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" @@ -2194,16 +2161,12 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" ejs@^2.3.4, ejs@^2.5.6: - version "2.5.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88" - -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.14: - version "1.3.15" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.15.tgz#08397934891cbcfaebbd18b82a95b5a481138369" + version "2.5.7" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -electron-to-chromium@^1.3.18: - version "1.3.24" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6" +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.27: + version "1.3.27" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" elliptic@^6.0.0: version "6.4.0" @@ -2222,8 +2185,8 @@ emoji-mart@Gargron/emoji-mart#build: resolved "https://codeload.github.com/Gargron/emoji-mart/tar.gz/a5e1afe5ebcf2841e611d20d261b029581cbe051" emoji-regex@^6.1.0: - version "6.4.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.4.3.tgz#6ac2ac58d4b78def5e39b33fcbf395688af3076c" + version "6.5.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" emojis-list@^2.0.0: version "2.1.0" @@ -2252,15 +2215,16 @@ entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -enzyme-adapter-react-16@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.1.tgz#066cb1735e65d8d95841a023f94dab3ce6109e17" +enzyme-adapter-react-16@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.2.tgz#8c6f431f17c69e1e9eeb25ca4bd92f31971eb2dd" dependencies: enzyme-adapter-utils "^1.0.0" lodash "^4.17.4" object.assign "^4.0.4" object.values "^1.0.4" prop-types "^15.5.10" + react-test-renderer "^16.0.0-0" enzyme-adapter-utils@^1.0.0: version "1.0.1" @@ -2291,13 +2255,13 @@ errno@^0.1.3, errno@^0.1.4: dependencies: prr "~0.0.0" -error-ex@^1.2.0: +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" dependencies: is-arrayish "^0.2.1" -es-abstract@^1.6.1: +es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227" dependencies: @@ -2307,15 +2271,6 @@ es-abstract@^1.6.1: is-callable "^1.1.3" is-regex "^1.0.4" -es-abstract@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" - dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.0" - is-callable "^1.1.3" - is-regex "^1.0.3" - es-to-primitive@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" @@ -2324,20 +2279,20 @@ es-to-primitive@^1.1.1: is-date-object "^1.0.1" is-symbol "^1.0.1" -es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.24" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14" +es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.35" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.35.tgz#18ee858ce6a3c45c7d79e91c15fcca9ec568494f" dependencies: - es6-iterator "2" - es6-symbol "~3.1" + es6-iterator "~2.0.1" + es6-symbol "~3.1.1" -es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" +es6-iterator@^2.0.1, es6-iterator@~2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" dependencies: d "1" - es5-ext "^0.10.14" - es6-symbol "^3.1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" es6-map@^0.1.3: version "0.1.5" @@ -2360,7 +2315,7 @@ es6-set@~0.1.5: es6-symbol "3.1.1" event-emitter "~0.3.5" -es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1: +es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" dependencies: @@ -2385,15 +2340,15 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" escodegen@^1.6.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + version "1.9.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" + esprima "^3.1.3" + estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: - source-map "~0.2.0" + source-map "~0.5.6" escope@^3.6.0: version "3.6.0" @@ -2418,9 +2373,9 @@ eslint-module-utils@^2.1.1: debug "^2.6.8" pkg-dir "^1.0.0" -eslint-plugin-import@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz#21de33380b9efb55f5ef6d2e210ec0e07e7fa69f" +eslint-plugin-import@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz#fa1b6ef31fcb3c501c09859c1b86f1fc5b986894" dependencies: builtin-modules "^1.1.1" contains-path "^0.1.0" @@ -2495,16 +2450,20 @@ eslint@^3.19.0: user-home "^2.0.0" espree@^3.4.0: - version "3.4.3" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.3.tgz#2910b5ccd49ce893c2ffffaab4fd8b3a31b82374" + version "3.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e" dependencies: - acorn "^5.0.1" + acorn "^5.1.1" acorn-jsx "^3.0.0" -esprima@^2.6.0, esprima@^2.7.1: +esprima@^2.6.0: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + esprima@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" @@ -2522,22 +2481,14 @@ esrecurse@^4.1.0: estraverse "^4.1.0" object-assign "^4.0.1" -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" - estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" -esutils@^2.0.0, esutils@^2.0.2: +esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" -etag@~1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" - etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -2563,11 +2514,12 @@ eventsource@0.1.6: dependencies: original ">=0.0.5" -evp_bytestokey@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53" +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" dependencies: - create-hash "^1.1.1" + md5.js "^1.3.4" + safe-buffer "^5.1.1" exec-sh@^0.2.0: version "0.2.1" @@ -2575,12 +2527,12 @@ exec-sh@^0.2.0: dependencies: merge "^1.1.3" -execa@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" dependencies: - cross-spawn "^4.0.0" - get-stream "^2.2.0" + cross-spawn "^5.0.1" + get-stream "^3.0.0" is-stream "^1.1.0" npm-run-path "^2.0.0" p-finally "^1.0.0" @@ -2614,42 +2566,9 @@ expect@^21.2.1: jest-message-util "^21.2.1" jest-regex-util "^21.2.0" -express@^4.13.3: - version "4.15.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662" - dependencies: - accepts "~1.3.3" - array-flatten "1.1.1" - content-disposition "0.5.2" - content-type "~1.0.2" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.7" - depd "~1.1.0" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.0" - finalhandler "~1.0.3" - fresh "0.5.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.1" - path-to-regexp "0.1.7" - proxy-addr "~1.1.4" - qs "6.4.0" - range-parser "~1.2.0" - send "0.15.3" - serve-static "1.12.3" - setprototypeof "1.0.3" - statuses "~1.3.1" - type-is "~1.6.15" - utils-merge "1.0.0" - vary "~1.1.1" - -express@^4.15.2: - version "4.16.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.1.tgz#6b33b560183c9b253b7b62144df33a4654ac9ed0" +express@^4.13.3, express@^4.15.2, express@^4.16.2: + version "4.16.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: accepts "~1.3.4" array-flatten "1.1.1" @@ -2682,7 +2601,7 @@ express@^4.15.2: utils-merge "1.0.1" vary "~1.1.2" -extend@~3.0.0: +extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -2692,23 +2611,27 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extract-text-webpack-plugin@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.2.tgz#756ef4efa8155c3681833fbc34da53b941746d6c" +extract-text-webpack-plugin@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7" dependencies: - async "^2.1.2" - loader-utils "^1.0.2" + async "^2.4.1" + loader-utils "^1.1.0" schema-utils "^0.3.0" webpack-sources "^1.0.1" -extsprintf@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -2735,7 +2658,7 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.14, fbjs@^0.8.16: +fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.9: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" dependencies: @@ -2747,18 +2670,6 @@ fbjs@^0.8.14, fbjs@^0.8.16: setimmediate "^1.0.5" ua-parser-js "^0.7.9" -fbjs@^0.8.4, fbjs@^0.8.9: - version "0.8.12" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.9" - figures@^1.3.5: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -2791,8 +2702,8 @@ fileset@^2.0.2: minimatch "^3.0.3" filesize@^3.5.9: - version "3.5.10" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.10.tgz#fc8fa23ddb4ef9e5e0ab6e1e64f679a24a56761f" + version "3.5.11" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" fill-range@^2.1.0: version "2.2.3" @@ -2816,18 +2727,6 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" -finalhandler@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" - dependencies: - debug "2.6.7" - encodeurl "~1.0.1" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.1" - statuses "~1.3.1" - unpipe "~1.0.0" - find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" @@ -2850,8 +2749,8 @@ find-up@^2.0.0, find-up@^2.1.0: locate-path "^2.0.0" flat-cache@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + version "1.3.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" dependencies: circular-json "^0.3.1" del "^2.0.2" @@ -2863,10 +2762,10 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" follow-redirects@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea" + version "1.2.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.5.tgz#ffd3e14cbdd5eaa72f61b6368c1f68516c2a26cc" dependencies: - debug "^2.4.5" + debug "^2.6.9" font-awesome@^4.7.0: version "4.7.0" @@ -2908,9 +2807,13 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -forwarded@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" +form-data@~2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" forwarded@~0.1.2: version "0.1.2" @@ -2920,10 +2823,6 @@ fraction.js@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.2.tgz#0eae896626f334b1bde763371347a83b5575d7f0" -fresh@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" - fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -3019,12 +2918,9 @@ get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" getpass@^0.1.1: version "0.1.7" @@ -3056,7 +2952,11 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^9.0.0, globals@^9.14.0, globals@^9.18.0: +globals@^10.0.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-10.2.0.tgz#69490789091fcaa7f7d512c668c8eb73894a4ef2" + +globals@^9.14.0, globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -3090,8 +2990,8 @@ globule@^1.0.0: minimatch "~3.0.2" gonzales-pe@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.0.3.tgz#36148e18e267184fbfdc929af28f29ad9fbf9746" + version "4.2.2" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.2.tgz#f50a8c17842f13a9007909b7cb32188266e4d74c" dependencies: minimist "1.1.x" @@ -3114,8 +3014,8 @@ handle-thing@^1.2.5: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" handlebars@^4.0.3: - version "4.0.10" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" dependencies: async "^1.4.0" optimist "^0.6.1" @@ -3127,6 +3027,10 @@ har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + har-validator@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" @@ -3134,6 +3038,13 @@ har-validator@~4.2.1: ajv "^4.9.1" har-schema "^1.0.5" +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3164,6 +3075,13 @@ hash-base@^2.0.0: dependencies: inherits "^2.0.1" +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.3" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" @@ -3171,7 +3089,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.0" -hawk@~3.1.3: +hawk@3.1.3, hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" dependencies: @@ -3180,6 +3098,15 @@ hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" @@ -3202,6 +3129,10 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +hoek@4.x.x: + version "4.2.0" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" @@ -3231,8 +3162,8 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" html-encoding-sniffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da" + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" dependencies: whatwg-encoding "^1.0.1" @@ -3264,19 +3195,14 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" -http-errors@~1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" - dependencies: - depd "1.1.0" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - http-link-header@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-0.8.0.tgz#a22b41a0c9b1e2d8fac1bf1b697c6bd532d5f5e4" +http-parser-js@>=0.4.0: + version "0.4.9" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" + http-proxy-middleware@~0.17.4: version "0.17.4" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" @@ -3301,22 +3227,22 @@ http-signature@~1.1.0: jsprim "^1.2.2" sshpk "^1.7.0" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" -iconv-lite@0.4.13: - version "0.4.13" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" - -iconv-lite@0.4.19: +iconv-lite@0.4.19, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -iconv-lite@~0.4.13: - version "0.4.18" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" - icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -3332,12 +3258,19 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" ignore@^3.2.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" + version "3.3.7" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" -immutable@^3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" +immutable@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" + +import-local@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" imurmurhash@^0.1.4: version "0.1.4" @@ -3405,8 +3338,8 @@ internal-ip@1.2.0: meow "^3.3.0" interpret@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" + version "1.0.4" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0" intersection-observer@^0.4.0: version "0.4.2" @@ -3421,24 +3354,18 @@ intl-messageformat-parser@1.2.0: resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.2.0.tgz#5906b7f953ab7470e0dc8549097b648b991892ff" intl-messageformat-parser@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.3.0.tgz#c5d26ffb894c7d9c2b9fa444c67f417ab2594268" - -intl-messageformat@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.0.0.tgz#3d56982583425aee23b76c8b985fb9b0aae5be3c" - dependencies: - intl-messageformat-parser "1.2.0" + version "1.4.0" + resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz#b43d45a97468cadbe44331d74bb1e8dea44fc075" -intl-messageformat@^2.1.0: +intl-messageformat@^2.0.0, intl-messageformat@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.1.0.tgz#1c51da76f02a3f7b360654cdc51bbc4d3fa6c72c" dependencies: intl-messageformat-parser "1.2.0" -intl-relativeformat@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.0.0.tgz#d6ba9dc6c625819bc0abdb1d4e238138b7488f26" +intl-relativeformat@^2.0.0, intl-relativeformat@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.1.0.tgz#010f1105802251f40ac47d0e3e1a201348a255df" dependencies: intl-messageformat "^2.0.0" @@ -3460,10 +3387,6 @@ ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" -ipaddr.js@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" - ipaddr.js@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" @@ -3483,8 +3406,8 @@ is-binary-path@^1.0.0: binary-extensions "^1.0.0" is-buffer@^1.0.2, is-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" is-builtin-module@^1.0.0: version "1.0.0" @@ -3561,8 +3484,8 @@ is-glob@^3.1.0: is-extglob "^2.1.0" is-my-json-valid@^2.10.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" @@ -3625,7 +3548,7 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" -is-regex@^1.0.3, is-regex@^1.0.4: +is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" dependencies: @@ -3705,17 +3628,17 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" istanbul-api@^1.1.1: - version "1.1.14" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.14.tgz#25bc5701f7c680c0ffff913de46e3619a3a6e680" + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" dependencies: async "^2.1.4" fileset "^2.0.2" istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.0.7" - istanbul-lib-instrument "^1.8.0" - istanbul-lib-report "^1.1.1" - istanbul-lib-source-maps "^1.2.1" - istanbul-reports "^1.1.2" + istanbul-lib-hook "^1.1.0" + istanbul-lib-instrument "^1.9.1" + istanbul-lib-report "^1.1.2" + istanbul-lib-source-maps "^1.2.2" + istanbul-reports "^1.1.3" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" @@ -3724,15 +3647,15 @@ istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" -istanbul-lib-hook@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" +istanbul-lib-hook@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532" +istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" @@ -3742,28 +3665,28 @@ istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-ins istanbul-lib-coverage "^1.1.1" semver "^5.3.0" -istanbul-lib-report@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" +istanbul-lib-report@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" dependencies: istanbul-lib-coverage "^1.1.1" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" dependencies: - debug "^2.6.3" + debug "^3.1.0" istanbul-lib-coverage "^1.1.1" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f" +istanbul-reports@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" dependencies: handlebars "^4.0.3" @@ -3997,8 +3920,8 @@ jest@^21.2.1: jest-cli "^21.2.1" js-base64@^2.1.8, js-base64@^2.1.9: - version "2.1.9" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" + version "2.3.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" js-string-escape@1.0.1: version "1.0.1" @@ -4008,14 +3931,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.4.3, js-yaml@^3.5.1: - version "3.9.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^3.7.0, js-yaml@^3.9.0: +js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -4066,8 +3982,8 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" json-loader@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" json-schema-traverse@^0.3.0: version "0.3.1" @@ -4110,13 +4026,13 @@ jsonpointer@^4.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" jsprim@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" dependencies: assert-plus "1.0.0" - extsprintf "1.0.2" + extsprintf "1.3.0" json-schema "0.2.3" - verror "1.3.6" + verror "1.10.0" jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.4: version "1.4.1" @@ -4332,13 +4248,13 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.4: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" loglevel@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" + version "1.5.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.5.1.tgz#189078c94ab9053ee215a0acdbf24244ea0f6502" longest@^1.0.1: version "1.0.1" @@ -4369,10 +4285,10 @@ macaddress@^0.2.8: resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" make-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + version "1.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" dependencies: - pify "^2.3.0" + pify "^3.0.0" makeerror@1.0.x: version "1.0.11" @@ -4397,8 +4313,8 @@ math-expression-evaluator@^1.2.14: resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" mathjs@^3.11.5: - version "3.14.2" - resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-3.14.2.tgz#bb79b7dc878b7f586ce408ab067a9a42db2e7a2d" + version "3.16.5" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-3.16.5.tgz#d75a5265435d2824b067b37a478771deebf6aacc" dependencies: complex.js "2.0.4" decimal.js "7.2.3" @@ -4408,6 +4324,13 @@ mathjs@^3.11.5: tiny-emitter "2.0.0" typed-function "0.10.5" +md5.js@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4471,48 +4394,30 @@ micromatch@^2.1.5, micromatch@^2.3.11: regex-cache "^0.4.2" miller-rabin@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d" + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" dependencies: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.27.0 < 2": - version "1.29.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" - -mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" +"mime-db@>= 1.30.0 < 2": + version "1.31.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.31.0.tgz#a49cd8f3ebf3ed1a482b60561d9105ad40ca74cb" mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: - version "2.1.15" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" - dependencies: - mime-db "~1.27.0" - -mime-types@~2.1.16: +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: mime-db "~1.30.0" -mime@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" - -mime@1.4.1: +mime@1.4.1, mime@^1.3.4: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" -mime@^1.3.4: - version "1.3.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" - mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -4584,8 +4489,8 @@ mute-stream@0.0.5: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" nan@^2.0.0, nan@^2.3.0, nan@^2.3.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + version "2.7.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" natural-compare@^1.4.0: version "1.4.0" @@ -4604,8 +4509,8 @@ negotiator@0.6.1: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" node-fetch@^1.0.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" dependencies: encoding "^0.1.11" is-stream "^1.0.1" @@ -4674,14 +4579,15 @@ node-notifier@^5.0.2: which "^1.2.12" node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.4: - version "0.6.36" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" + version "0.6.38" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d" dependencies: + hawk "3.1.3" mkdirp "^0.5.1" nopt "^4.0.1" npmlog "^4.0.2" rc "^1.1.7" - request "^2.81.0" + request "2.81.0" rimraf "^2.6.1" semver "^5.3.0" tar "^2.2.1" @@ -4710,7 +4616,7 @@ node-sass@^4.5.2: sass-graph "^2.1.1" stdout-stream "^1.4.0" -node-zopfli@^2.0.0: +node-zopfli@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node-zopfli/-/node-zopfli-2.0.2.tgz#a7a473ae92aaea85d4c68d45bbf2c944c46116b8" dependencies: @@ -4748,7 +4654,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -4800,7 +4706,7 @@ number-is-nan@^1.0.0: version "1.4.3" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" -oauth-sign@~0.8.1: +oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -4944,10 +4850,10 @@ os-locale@^1.4.0: lcid "^1.0.0" os-locale@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.0.0.tgz#15918ded510522b81ee7ae5a309d54f639fc39a4" + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" dependencies: - execa "^0.5.0" + execa "^0.7.0" lcid "^1.0.0" mem "^1.1.0" @@ -4981,8 +4887,8 @@ p-locate@^2.0.0: p-limit "^1.1.0" p-map@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" packet-reader@0.3.1: version "0.3.1" @@ -5031,6 +4937,12 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-json@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-3.0.0.tgz#fa6f47b18e23826ead32f263e744d0e1e847fb13" + dependencies: + error-ex "^1.3.1" + parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" @@ -5041,10 +4953,6 @@ parse5@^3.0.1: dependencies: "@types/node" "^6.0.46" -parseurl@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" - parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -5108,8 +5016,8 @@ path-type@^2.0.0: pify "^2.0.0" pbkdf2@^3.0.3: - version "3.0.12" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2" + version "3.0.14" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -5137,10 +5045,9 @@ pg-pool@1.*: object-assign "4.1.0" pg-types@1.*: - version "1.12.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.0.tgz#8ad3b7b897e3fd463e62de241ad5fc640b4a66f0" + version "1.12.1" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.1.tgz#d64087e3903b58ffaad279e7595c52208a14c3d2" dependencies: - ap "~0.2.0" postgres-array "~1.0.0" postgres-bytea "~1.0.0" postgres-date "~1.0.0" @@ -5258,11 +5165,11 @@ postcss-custom-media@^6.0.0: postcss "^6.0.1" postcss-custom-properties@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.1.0.tgz#9caf1151ac41b1e9e64d3a2ff9ece996ca18977d" + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.2.0.tgz#5d929a7f06e9b84e0f11334194c0ba9a30acfbe9" dependencies: balanced-match "^1.0.0" - postcss "^6.0.3" + postcss "^6.0.13" postcss-custom-selectors@^4.0.1: version "4.0.1" @@ -5325,12 +5232,12 @@ postcss-import@^10.0.0: read-cache "^1.0.0" resolve "^1.1.7" -postcss-js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-1.0.0.tgz#ccee5aa3b1970dd457008e79438165f66919ba30" +postcss-js@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-1.0.1.tgz#ffaf29226e399ea74b5dce02cab1729d7addbc7b" dependencies: camelcase-css "^1.0.1" - postcss "^6.0.1" + postcss "^6.0.11" postcss-load-config@^1.2.0: version "1.2.0" @@ -5355,12 +5262,12 @@ postcss-load-plugins@^2.3.0: cosmiconfig "^2.1.1" object-assign "^4.1.0" -postcss-loader@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.6.tgz#8c7e0055a3df1889abc6bad52dd45b2f41bbc6fc" +postcss-loader@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.8.tgz#8c67ddb029407dfafe684a406cfc16bad2ce0814" dependencies: loader-utils "^1.1.0" - postcss "^6.0.2" + postcss "^6.0.0" postcss-load-config "^1.2.0" schema-utils "^0.3.0" @@ -5432,13 +5339,13 @@ postcss-minify-selectors@^2.0.4: postcss-selector-parser "^2.0.0" postcss-mixins@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-6.0.1.tgz#f5c9726259a6103733b43daa6a8b67dd0ed7aa47" + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-6.2.0.tgz#fa9d2c2166b2ae7745956c727ab9dd2de4b96a40" dependencies: globby "^6.1.0" - postcss "^6.0.3" - postcss-js "^1.0.0" - postcss-simple-vars "^4.0.0" + postcss "^6.0.13" + postcss-js "^1.0.1" + postcss-simple-vars "^4.1.0" sugarss "^1.0.0" postcss-modules-extract-imports@^1.0.0: @@ -5469,16 +5376,17 @@ postcss-modules-values@^1.1.0: postcss "^6.0.1" postcss-nested@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-2.0.2.tgz#f38fad547f5c3747160aec3bb34745819252974a" + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-2.1.2.tgz#04057281f9631fef684857fb0119bae04ede03c6" dependencies: - postcss "^6.0.1" + postcss "^6.0.9" + postcss-selector-parser "^2.2.3" postcss-nesting@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.0.1.tgz#8fc2ce40cbfcfab7ee24e7b68fb6ebe84b641469" + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.2.1.tgz#0483bce338b3f0828ced90ff530b29b98b00300d" dependencies: - postcss "^6.0.1" + postcss "^6.0.11" postcss-normalize-charset@^1.1.0: version "1.1.1" @@ -5574,7 +5482,7 @@ postcss-selector-not@^3.0.1: balanced-match "^0.4.2" postcss "^6.0.1" -postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2, postcss-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" dependencies: @@ -5582,11 +5490,11 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-simple-vars@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-4.0.0.tgz#d49e082897d9a4824f2268fa91d969d943e2ea76" +postcss-simple-vars@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-4.1.0.tgz#043248cfef8d3f51b3486a28c09f8375dbf1b2f9" dependencies: - postcss "^6.0.1" + postcss "^6.0.9" postcss-smart-import@^0.7.5: version "0.7.5" @@ -5634,28 +5542,20 @@ postcss-zindex@^2.0.1: uniqs "^2.0.0" postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16, postcss@^5.2.6: - version "5.2.17" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b" + version "5.2.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" dependencies: chalk "^1.1.3" js-base64 "^2.1.9" source-map "^0.5.6" supports-color "^3.2.3" -postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.2, postcss@^6.0.3, postcss@^6.0.6: - version "6.0.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.6.tgz#bba4d58e884fc78c840d1539e10eddaabb8f73bd" - dependencies: - chalk "^2.0.1" - source-map "^0.5.6" - supports-color "^4.1.0" - -postcss@^6.0.11: - version "6.0.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.12.tgz#6b0155089d2d212f7bd6a0cecd4c58c007403535" +postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.3, postcss@^6.0.6, postcss@^6.0.9: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f" dependencies: chalk "^2.1.0" - source-map "^0.5.7" + source-map "^0.6.1" supports-color "^4.4.0" postgres-array@~1.0.0: @@ -5671,8 +5571,8 @@ postgres-date@~1.0.0: resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" postgres-interval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.0.tgz#1031e7bac34564132862adc9eb6c6d2f3aa75bb4" + version "1.1.1" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.1.tgz#acdb0f897b4b1c6e496d9d4e0a853e1c428f06f0" dependencies: xtend "^4.0.0" @@ -5721,8 +5621,8 @@ pretty-format@^21.2.1: ansi-styles "^3.2.0" private@^0.1.6, private@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" process-nextick-args@~1.0.6: version "1.0.7" @@ -5754,7 +5654,7 @@ prop-types-extra@^1.0.1: dependencies: warning "^3.0.0" -prop-types@^15.5.10, prop-types@^15.6.0: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -5762,20 +5662,6 @@ prop-types@^15.5.10, prop-types@^15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" -prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8: - version "15.5.10" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - -proxy-addr@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" - dependencies: - forwarded "~0.1.0" - ipaddr.js "1.3.0" - proxy-addr@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" @@ -5814,17 +5700,17 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" q@^1.1.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" - -qs@6.4.0, qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" -qs@6.5.1: +qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -5852,13 +5738,7 @@ quote@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/quote/-/quote-0.4.0.tgz#10839217f6c1362b89194044d29b233fd7f32f01" -raf@^3.1.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.3.2.tgz#0c13be0b5b49b46f76d6669248d527cf2b02fe27" - dependencies: - performance-now "^2.1.0" - -raf@^3.3.2, raf@^3.4.0: +raf@^3.1.0, raf@^3.3.2, raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" dependencies: @@ -5906,8 +5786,8 @@ raw-body@2.3.2: unpipe "1.0.0" rc@^1.1.7: - version "1.2.1" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + version "1.2.2" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -5924,12 +5804,12 @@ react-dom@^16.0.0: prop-types "^15.6.0" react-event-listener@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.5.0.tgz#d82105135573e187e3d900d18150a5882304b8d1" + version "0.5.1" + resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.5.1.tgz#ba36076e47bc37c5a67ff5ccd4a9ff0f15621040" dependencies: babel-runtime "^6.26.0" - fbjs "^0.8.14" - prop-types "^15.5.10" + fbjs "^0.8.16" + prop-types "^15.6.0" warning "^3.0.0" react-hotkeys@^0.10.0: @@ -5945,9 +5825,9 @@ react-immutable-proptypes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4" -react-immutable-pure-component@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-1.0.1.tgz#c36b11546822a17fbc115c43278fc1698147687f" +react-immutable-pure-component@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-1.1.1.tgz#0ff69c6d4eaecd886db16805f3bbc1d2a2c654d8" react-intl-translations-manager@^5.0.0: version "5.0.1" @@ -5967,34 +5847,34 @@ react-intl@^2.4.0: intl-relativeformat "^2.0.0" invariant "^2.1.1" -react-motion@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.1.tgz#b90631408175ab1668e173caccd66d41a44f4592" +react-motion@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" dependencies: performance-now "^0.2.0" prop-types "^15.5.8" raf "^3.1.0" -react-notification@^6.7.1: - version "6.8.0" - resolved "https://registry.yarnpkg.com/react-notification/-/react-notification-6.8.0.tgz#9cb7aa06c8e5085b4c0dc2e8d9aa1da1fbc61d93" +react-notification@^6.8.2: + version "6.8.2" + resolved "https://registry.yarnpkg.com/react-notification/-/react-notification-6.8.2.tgz#5d9910cc2993bd51a234d3809e94a98dc490cfb0" dependencies: prop-types "^15.5.10" -react-overlays@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.8.1.tgz#26e480003c2fd6f581a4a66c0c86cb3dff17e626" +react-overlays@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.8.3.tgz#fad65eea5b24301cca192a169f5dddb0b20d3ac5" dependencies: classnames "^2.2.5" dom-helpers "^3.2.1" prop-types "^15.5.10" prop-types-extra "^1.0.1" - react-transition-group "^2.0.0-beta.0" + react-transition-group "^2.2.0" warning "^3.0.0" -react-redux-loading-bar@^2.9.2: - version "2.9.2" - resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.9.2.tgz#f0e604ee35af5ecb25addb10bf24ca3d478c95a8" +react-redux-loading-bar@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.9.3.tgz#65865dddcbf597169e787edec15eec7ebfb84149" dependencies: prop-types "^15.5.6" @@ -6020,11 +5900,11 @@ react-router-dom@^4.1.1: react-router "^4.2.0" warning "^3.0.0" -react-router-scroll@Gargron/react-router-scroll#build: - version "0.4.3" - resolved "https://codeload.github.com/Gargron/react-router-scroll/tar.gz/17a028e3c2db0e488c6dca6ab1639783fb54480a" +react-router-scroll-4@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/react-router-scroll-4/-/react-router-scroll-4-1.0.0-beta.1.tgz#b1838c6e67b9fe64db33176958e88836e10bc4ce" dependencies: - prop-types "^15.6.0" + babel-eslint "^8.0.1" scroll-behavior "^0.9.1" warning "^3.0.0" @@ -6069,7 +5949,7 @@ react-swipeable-views@^0.12.3: react-swipeable-views-utils "^0.12.8" warning "^3.0.0" -react-test-renderer@^16.0.0: +react-test-renderer@^16.0.0, react-test-renderer@^16.0.0-0: version "16.0.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.0.0.tgz#9fe7b8308f2f71f29fc356d4102086f131c9cb15" dependencies: @@ -6088,9 +5968,9 @@ react-toggle@^4.0.1: dependencies: classnames "^2.2.5" -react-transition-group@^2.0.0-beta.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.0.tgz#793bf8cb15bfe91b3101b24bce1c1d2891659575" +react-transition-group@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10" dependencies: chain-function "^1.0.0" classnames "^2.2.5" @@ -6234,35 +6114,30 @@ redux@^3.7.1: symbol-observable "^1.0.3" regenerate@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" - -regenerator-runtime@^0.10.0: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + version "1.3.3" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" regenerator-runtime@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" -regenerator-transform@0.9.11: - version "0.9.11" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283" +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" dependencies: babel-runtime "^6.18.0" babel-types "^6.19.0" private "^0.1.6" regex-cache@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" dependencies: is-equal-shallow "^0.1.3" - is-primitive "^2.0.0" regex-parser@^2.2.1: - version "2.2.7" - resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.7.tgz#bd090e09181849acc45457e765f7be2a63f50ef1" + version "2.2.8" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.8.tgz#da4c0cda5a828559094168930f455f532b6ffbac" regexpu-core@^1.0.0: version "1.0.0" @@ -6291,8 +6166,8 @@ regjsparser@^0.1.4: jsesc "~0.5.0" remove-trailing-separator@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" repeat-element@^1.1.2: version "1.1.2" @@ -6308,7 +6183,34 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@2, request@^2.79.0, request@^2.81.0: +request@2, request@^2.79.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: @@ -6343,10 +6245,14 @@ require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" -require-from-string@^1.1.0, require-from-string@^1.2.1: +require-from-string@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" +require-from-string@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.1.tgz#c545233e9d7da6616e9d59adfb39fc9f588676ff" + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -6366,17 +6272,27 @@ reselect@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" -resolve-url-loader@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.1.0.tgz#27c95cc16a4353923fdbdc2dbaf5eef22232c477" +resolve-url-loader@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.2.0.tgz#9662feaa11debf7cf8e3feb91dae9544aa7dee88" dependencies: adjust-sourcemap-loader "^1.1.0" camelcase "^4.0.0" @@ -6396,15 +6312,9 @@ resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" - dependencies: - path-parse "^1.0.5" - -resolve@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.3.3: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" dependencies: path-parse "^1.0.5" @@ -6440,13 +6350,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" - dependencies: - glob "^7.0.5" - -rimraf@^2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -6460,8 +6364,8 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rst-selector-parser@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.2.tgz#9927b619bd5af8dc23a76c64caef04edf90d2c65" + version "2.2.3" + resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" dependencies: lodash.flattendeep "^4.4.0" nearley "^2.7.10" @@ -6476,7 +6380,7 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -6528,11 +6432,11 @@ schema-utils@^0.3.0: ajv "^5.0.0" scroll-behavior@^0.9.1: - version "0.9.3" - resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.3.tgz#e48bcc8af364f3f07176e8dbca3968bd5e71557b" + version "0.9.4" + resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.4.tgz#73b4a0eae3e59c0b8f3b6fc1ff78f054a513e79c" dependencies: - dom-helpers "^3.0.0" - invariant "^2.2.1" + dom-helpers "^3.2.1" + invariant "^2.2.2" scss-tokenizer@^0.2.3: version "0.2.3" @@ -6550,36 +6454,22 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" selfsigned@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a" + version "1.10.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" dependencies: node-forge "0.6.33" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" +"semver@2 || 3 || 4 || 5", semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" semver@4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" -send@0.15.3: - version "0.15.3" - resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309" - dependencies: - debug "2.6.7" - depd "~1.1.0" - destroy "~1.0.4" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.0" - fresh "0.5.0" - http-errors "~1.6.1" - mime "1.3.4" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.3.1" +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" send@0.16.1: version "0.16.1" @@ -6600,25 +6490,16 @@ send@0.16.1: statuses "~1.3.1" serve-index@^1.7.2: - version "1.9.0" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.0.tgz#d2b280fc560d616ee81b48bf0fa82abed2485ce7" + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" dependencies: - accepts "~1.3.3" + accepts "~1.3.4" batch "0.6.1" - debug "2.6.8" - escape-html "~1.0.3" - http-errors "~1.6.1" - mime-types "~2.1.15" - parseurl "~1.3.1" - -serve-static@1.12.3: - version "1.12.3" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2" - dependencies: - encodeurl "~1.0.1" + debug "2.6.9" escape-html "~1.0.3" - parseurl "~1.3.1" - send "0.15.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" serve-static@1.13.1: version "1.13.1" @@ -6650,10 +6531,11 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.8" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" + version "2.4.9" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d" dependencies: inherits "^2.0.1" + safe-buffer "^5.0.1" shallow-clone@^0.1.2: version "0.1.2" @@ -6704,6 +6586,12 @@ sntp@1.x.x: dependencies: hoek "2.x.x" +sntp@2.x.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" + dependencies: + hoek "4.x.x" + sockjs-client@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" @@ -6732,10 +6620,6 @@ source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" -source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - source-map-resolve@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" @@ -6767,19 +6651,13 @@ source-map@^0.4.2, source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" - -source-map@^0.5.7: +source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3, source-map@~0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" - dependencies: - amdefine ">=0.0.4" +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" spdx-correct@~1.0.0: version "1.0.2" @@ -6819,8 +6697,8 @@ spdy@^3.4.1: spdy-transport "^2.0.18" split@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae" + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" dependencies: through "2" @@ -6842,7 +6720,11 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -"statuses@>= 1.3.1 < 2", statuses@~1.3.1: +"statuses@>= 1.3.1 < 2": + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + +statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -6889,8 +6771,8 @@ string-width@^1.0.1, string-width@^1.0.2: strip-ansi "^3.0.0" string-width@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0" + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" @@ -6905,7 +6787,7 @@ string_decoder@~1.0.3: dependencies: safe-buffer "~5.1.0" -stringstream@~0.0.4: +stringstream@~0.0.4, stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -6949,9 +6831,9 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -style-loader@^0.18.2: - version "0.18.2" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb" +style-loader@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.19.0.tgz#7258e788f0fee6a42d710eaf7d6c2412a4c50759" dependencies: loader-utils "^1.0.2" schema-utils "^0.3.0" @@ -6976,21 +6858,9 @@ supports-color@^3.1.2, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" -supports-color@^4.0.0, supports-color@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037" - dependencies: - has-flag "^2.0.0" - -supports-color@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" - dependencies: - has-flag "^2.0.0" - -supports-color@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" +supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" dependencies: has-flag "^2.0.0" @@ -7026,12 +6896,12 @@ table@^3.7.8: string-width "^2.0.0" tapable@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c" + version "0.2.8" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" tar-pack@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + version "3.4.1" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" dependencies: debug "^2.2.0" fstream "^1.0.10" @@ -7086,9 +6956,13 @@ thunky@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + timers-browserify@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" + version "2.0.4" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" dependencies: setimmediate "^1.0.4" @@ -7108,13 +6982,17 @@ to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" -to-fast-properties@^1.0.1, to-fast-properties@^1.0.3: +to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" -tough-cookie@^2.3.2, tough-cookie@~2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + +tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: punycode "^1.4.1" @@ -7170,8 +7048,8 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" ua-parser-js@^0.7.9: - version "0.7.13" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be" + version "0.7.17" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" @@ -7269,10 +7147,6 @@ util@0.10.3, util@^0.10.3: dependencies: inherits "2.0.1" -utils-merge@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" - utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -7300,10 +7174,6 @@ value-equal@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" -vary@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -7312,11 +7182,13 @@ vendors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" -verror@1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" dependencies: - extsprintf "1.0.2" + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" vm-browserify@0.0.4: version "0.0.4" @@ -7362,8 +7234,8 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" webidl-conversions@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0" + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" webpack-bundle-analyzer@^2.8.3: version "2.9.0" @@ -7382,17 +7254,18 @@ webpack-bundle-analyzer@^2.8.3: ws "^2.3.1" webpack-dev-middleware@^1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9" + version "1.12.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709" dependencies: memory-fs "~0.4.1" mime "^1.3.4" path-is-absolute "^1.0.0" range-parser "^1.0.3" + time-stamp "^2.0.0" -webpack-dev-server@^2.6.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.1.tgz#7ac9320b61b00eb65b2109f15c82747fc5b93585" +webpack-dev-server@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.3.tgz#f0554e88d129e87796a6f74a016b991743ca6f81" dependencies: ansi-html "0.0.7" array-includes "^3.0.3" @@ -7400,10 +7273,12 @@ webpack-dev-server@^2.6.1: chokidar "^1.6.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" + debug "^3.1.0" del "^3.0.0" express "^4.13.3" html-entities "^1.2.0" http-proxy-middleware "~0.17.4" + import-local "^0.1.1" internal-ip "1.2.0" ip "^1.1.5" loglevel "^1.4.1" @@ -7432,13 +7307,6 @@ webpack-merge@^4.1.0: dependencies: lodash "^4.17.4" -webpack-sources@^0.1.0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750" - dependencies: - source-list-map "~0.1.7" - source-map "~0.5.3" - webpack-sources@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" @@ -7446,9 +7314,9 @@ webpack-sources@^1.0.1: source-list-map "^2.0.0" source-map "~0.5.3" -webpack@^3.4.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.6.0.tgz#a89a929fbee205d35a4fa2cc487be9cbec8898bc" +webpack@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.8.1.tgz#b16968a81100abe61608b0153c9159ef8bb2bd83" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" @@ -7474,14 +7342,15 @@ webpack@^3.4.1: yargs "^8.0.2" websocket-driver@>=0.5.1: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" dependencies: + http-parser-js ">=0.4.0" websocket-extensions ">=0.1.1" websocket-extensions@>=0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7" + version "0.1.2" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.2.tgz#0e18781de629a18308ce1481650f67ffa2693a5d" websocket.js@^0.1.12: version "0.1.12" @@ -7490,10 +7359,10 @@ websocket.js@^0.1.12: backoff "^2.4.1" whatwg-encoding@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4" + version "1.0.3" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" dependencies: - iconv-lite "0.4.13" + iconv-lite "0.4.19" whatwg-fetch@>=0.10.0: version "2.0.3" @@ -7518,13 +7387,7 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@1, which@^1.2.9: - version "1.2.14" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" - dependencies: - isexe "^2.0.0" - -which@^1.2.12: +which@1, which@^1.2.12, which@^1.2.9: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: |