From a1ad2ad9519fda525858ba5aef86815a6f6385f2 Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Fri, 26 Jun 2020 14:31:13 +0200
Subject: [PATCH 01/29] Change sensitive preview cards to not blur text
 (#14143)

Also only require click-through for interactive embeds.
---
 .../mastodon/features/status/components/card.js           | 8 +++-----
 app/javascript/styles/mastodon/components.scss            | 5 -----
 2 files changed, 3 insertions(+), 10 deletions(-)

diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js
index e35b1fd5fd..0af7c54e4b 100644
--- a/app/javascript/mastodon/features/status/components/card.js
+++ b/app/javascript/mastodon/features/status/components/card.js
@@ -6,7 +6,6 @@ import { FormattedMessage } from 'react-intl';
 import punycode from 'punycode';
 import classnames from 'classnames';
 import Icon from 'mastodon/components/icon';
-import classNames from 'classnames';
 import { useBlurhash } from 'mastodon/initial_state';
 import { decode } from 'blurhash';
 import { debounce } from 'lodash';
@@ -231,7 +230,7 @@ export default class Card extends React.PureComponent {
     const height      = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
 
     const description = (
-      <div className={classNames('status-card__content', { 'status-card__content--blurred': !revealed })}>
+      <div className='status-card__content'>
         {title}
         {!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
         <span className='status-card__host'>{provider}</span>
@@ -239,7 +238,7 @@ export default class Card extends React.PureComponent {
     );
 
     let embed     = '';
-    let canvas = <canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('status-card__image-preview', { 'status-card__image-preview--hidden' : revealed && this.state.previewLoaded })} />;
+    let canvas = <canvas width={32} height={32} ref={this.setCanvasRef} className={classnames('status-card__image-preview', { 'status-card__image-preview--hidden' : revealed && this.state.previewLoaded })} />;
     let thumbnail = <img src={card.get('image')} alt='' style={{ width: horizontal ? width : null, height: horizontal ? height : null, visibility: revealed ? null : 'hidden' }} onLoad={this.handleImageLoad} className='status-card__image-image' />;
     let spoilerButton = (
       <button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
@@ -247,7 +246,7 @@ export default class Card extends React.PureComponent {
       </button>
     );
     spoilerButton = (
-      <div className={classNames('spoiler-button', { 'spoiler-button--minified': revealed })}>
+      <div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}>
         {spoilerButton}
       </div>
     );
@@ -305,7 +304,6 @@ export default class Card extends React.PureComponent {
       <a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
         {embed}
         {description}
-        {!revealed && spoilerButton}
       </a>
     );
   }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index e3da780ef0..7a22aeb04a 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3105,11 +3105,6 @@ a.status-card {
   flex: 1 1 auto;
   overflow: hidden;
   padding: 14px 14px 14px 8px;
-
-  &--blurred {
-    filter: blur(2px);
-    pointer-events: none;
-  }
 }
 
 .status-card__description {

From 4662afe0756f821ce5febb90dee96887b4c247bb Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Fri, 26 Jun 2020 21:28:40 +0200
Subject: [PATCH 02/29] Fix help text around `tootctl email_domain_blocks`
 (#14147)

---
 lib/cli.rb                              |  2 +-
 lib/mastodon/email_domain_blocks_cli.rb | 29 +++++++++++++++----------
 2 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/lib/cli.rb b/lib/cli.rb
index 7cab0d5a15..9162144cc4 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -54,7 +54,7 @@ module Mastodon
     desc 'upgrade SUBCOMMAND ...ARGS', 'Various version upgrade utilities'
     subcommand 'upgrade', Mastodon::UpgradeCLI
 
-    desc 'email-domain-blocks SUBCOMMAND ...ARGS', 'Manage E-mail domain blocks'
+    desc 'email_domain_blocks SUBCOMMAND ...ARGS', 'Manage e-mail domain blocks'
     subcommand 'email_domain_blocks', Mastodon::EmailDomainBlocksCLI
 
     option :dry_run, type: :boolean
diff --git a/lib/mastodon/email_domain_blocks_cli.rb b/lib/mastodon/email_domain_blocks_cli.rb
index 8b468ed15c..7fe1efaaa5 100644
--- a/lib/mastodon/email_domain_blocks_cli.rb
+++ b/lib/mastodon/email_domain_blocks_cli.rb
@@ -13,13 +13,11 @@ module Mastodon
       true
     end
 
-    desc 'list', 'list E-mail domain blocks'
-    long_desc <<-LONG_DESC
-      list up all E-mail domain blocks.
-    LONG_DESC
+    desc 'list', 'List blocked e-mail domains'
     def list
       EmailDomainBlock.where(parent_id: nil).order(id: 'DESC').find_each do |entry|
         say(entry.domain.to_s, :white)
+
         EmailDomainBlock.where(parent_id: entry.id).order(id: 'DESC').find_each do |child|
           say("  #{child.domain}", :cyan)
         end
@@ -27,13 +25,17 @@ module Mastodon
     end
 
     option :with_dns_records, type: :boolean
-    desc 'add [DOMAIN...]', 'add E-mail domain blocks'
+    desc 'add DOMAIN...', 'Block e-mail domain(s)'
     long_desc <<-LONG_DESC
-      add E-mail domain blocks from a given DOMAIN.
+      Blocking an e-mail domain prevents users from signing up
+      with e-mail addresses from that domain. You can provide one or
+      multiple domains to the command.
 
-      When the --with-dns-records option is given, An attempt to resolve the
-      given domain's DNS records will be made and the results will also be
-      blacklisted.
+      When the --with-dns-records option is given, an attempt to resolve the
+      given domains' DNS records will be made and the results (A, AAAA and MX) will
+      also be blocked. This can be helpful if you are blocking an e-mail server that
+      has many different domains pointing to it as it allows you to essentially block
+      it at the root.
     LONG_DESC
     def add(*domains)
       if domains.empty?
@@ -72,11 +74,13 @@ module Mastodon
 
         (hostnames + ips).uniq.each do |hostname|
           another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block)
+
           if EmailDomainBlock.where(domain: hostname).exists?
             say("#{hostname} is already blocked.", :yellow)
             skipped += 1
             next
           end
+
           another_email_domain_block.save!
           processed += 1
         end
@@ -85,7 +89,7 @@ module Mastodon
       say("Added #{processed}, skipped #{skipped}", color(processed, 0))
     end
 
-    desc 'remove [DOMAIN...]', 'remove E-mail domain blocks'
+    desc 'remove DOMAIN...', 'Remove e-mail domain blocks'
     def remove(*domains)
       if domains.empty?
         say('No domain(s) given', :red)
@@ -98,6 +102,7 @@ module Mastodon
 
       domains.each do |domain|
         entry = EmailDomainBlock.find_by(domain: domain)
+
         if entry.nil?
           say("#{domain} is not yet blocked.", :yellow)
           skipped += 1
@@ -105,12 +110,12 @@ module Mastodon
         end
 
         children_count = EmailDomainBlock.where(parent_id: entry.id).count
-
         result = entry.destroy
+
         if result
           processed += 1 + children_count
         else
-          say("#{domain} was not unblocked. 'destroy' returns false.", :red)
+          say("#{domain} could not be unblocked.", :red)
           failed += 1
         end
       end

From 8c04e37b033ff35174753fb82faa1cd7bed110da Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Sat, 27 Jun 2020 20:20:11 +0200
Subject: [PATCH 03/29] Remove the terms blacklist and whitelist from UX
 (#14149)

Localization strings:

- "Whitelist mode" -> "Limited federation mode"
- "Blacklist e-mail domain" -> "Block e-mail domain"
- "Whitelist domain" -> "Allow domain for federation"

...And so on

Environment variables (backwards-compatible):

- `WHITELIST_MODE` -> `LIMITED_FEDERATION_MODE`
- `EMAIL_DOMAIN_BLACKLIST` -> `EMAIL_DOMAIN_DENYLIST`
- `EMAIL_DOMAIN_WHITELIST` -> `EMAIL_DOMAIN_ALLOWLIST`

tootctl:

- `tootctl domains purge --whitelist-mode` -> `tootctl domains purge --limited-federation-mode`

Removed badly maintained and no longer relevant .env.production.sample file
---
 .env.production.sample                  | 262 ------------------------
 config/initializers/2_whitelist_mode.rb |   2 +-
 config/initializers/blacklists.rb       |   4 +-
 config/locales/en.yml                   |  34 +--
 lib/mastodon/domains_cli.rb             |   8 +-
 5 files changed, 24 insertions(+), 286 deletions(-)
 delete mode 100644 .env.production.sample

diff --git a/.env.production.sample b/.env.production.sample
deleted file mode 100644
index e041e0a04c..0000000000
--- a/.env.production.sample
+++ /dev/null
@@ -1,262 +0,0 @@
-# Service dependencies
-# You may set REDIS_URL instead for more advanced options
-# You may also set REDIS_NAMESPACE to share Redis between multiple Mastodon servers
-REDIS_HOST=redis
-REDIS_PORT=6379
-# You may set DATABASE_URL instead for more advanced options
-DB_HOST=db
-DB_USER=postgres
-DB_NAME=postgres
-DB_PASS=
-DB_PORT=5432
-# Optional ElasticSearch configuration
-# You may also set ES_PREFIX to share the same cluster between multiple Mastodon servers (falls back to REDIS_NAMESPACE if not set)
-# ES_ENABLED=true
-# ES_HOST=es
-# ES_PORT=9200
-
-# Federation
-# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
-# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
-LOCAL_DOMAIN=example.com
-
-# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
-
-# Use this only if you need to run mastodon on a different domain than the one used for federation.
-# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
-# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
-# WEB_DOMAIN=mastodon.example.com
-
-# Use this if you want to have several aliases handler@example1.com
-# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not
-# be added. Comma separated values
-# ALTERNATE_DOMAINS=example1.com,example2.com
-
-# Application secrets
-# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web bundle exec rake secret` if you use docker compose)
-SECRET_KEY_BASE=
-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 `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web bundle exec rake mastodon:webpush:generate_vapid_key` if you use docker compose)
-#
-# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
-VAPID_PRIVATE_KEY=
-VAPID_PUBLIC_KEY=
-
-# Registrations
-# Single user mode will disable registrations and redirect frontpage to the first profile
-# SINGLE_USER_MODE=true
-# Prevent registrations with following e-mail domains
-# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
-# Only allow registrations with the following e-mail domains
-# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
-
-# Optionally change default language
-# DEFAULT_LOCALE=de
-
-# E-mail configuration
-# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
-# If you want to use an SMTP server without authentication (e.g local Postfix relay)
-# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
-# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
-SMTP_SERVER=smtp.mailgun.org
-SMTP_PORT=587
-SMTP_LOGIN=
-SMTP_PASSWORD=
-SMTP_FROM_ADDRESS=notifications@example.com
-#SMTP_REPLY_TO=
-#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
-#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
-#SMTP_AUTH_METHOD=plain
-#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
-# PAPERCLIP_ROOT_URL=/system
-
-# Optional asset host for multi-server setups
-# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN
-# if WEB_DOMAIN is not set. For example, the server may have the
-# following header field:
-# Access-Control-Allow-Origin: https://example.com/
-# CDN_HOST=https://assets.example.com
-
-# S3 (optional)
-# The attachment host must allow cross origin request from WEB_DOMAIN or
-# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the
-# following header field:
-# Access-Control-Allow-Origin: https://192.168.1.123:9000/
-# S3_ENABLED=true
-# S3_BUCKET=
-# AWS_ACCESS_KEY_ID=
-# AWS_SECRET_ACCESS_KEY=
-# S3_REGION=
-# S3_PROTOCOL=http
-# S3_HOSTNAME=192.168.1.123:9000
-
-# S3 (Minio Config (optional) Please check Minio instance for details)
-# The attachment host must allow cross origin request - see the description
-# above.
-# S3_ENABLED=true
-# S3_BUCKET=
-# AWS_ACCESS_KEY_ID=
-# AWS_SECRET_ACCESS_KEY=
-# S3_REGION=
-# S3_PROTOCOL=https
-# S3_HOSTNAME=
-# S3_ENDPOINT=
-# S3_SIGNATURE_VERSION=
-
-# Google Cloud Storage (optional)
-# Use S3 compatible API. Since GCS does not support Multipart Upload,
-# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
-# The attachment host must allow cross origin request - see the description
-# above.
-# S3_ENABLED=true
-# AWS_ACCESS_KEY_ID=
-# AWS_SECRET_ACCESS_KEY=
-# S3_REGION=
-# S3_PROTOCOL=https
-# S3_HOSTNAME=storage.googleapis.com
-# S3_ENDPOINT=https://storage.googleapis.com
-# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
-
-# Swift (optional)
-# The attachment host must allow cross origin request - see the description
-# above.
-# SWIFT_ENABLED=true
-# SWIFT_USERNAME=
-# For Keystone V3, the value for SWIFT_TENANT should be the project name
-# SWIFT_TENANT=
-# SWIFT_PASSWORD=
-# Some OpenStack V3 providers require PROJECT_ID (optional)
-# SWIFT_PROJECT_ID=
-# 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 (e.g. to serve files on a custom domain, possibly using Cloudfront or Cloudflare)
-# S3_ALIAS_HOST=
-
-# Streaming API integration
-# STREAMING_API_BASE_URL=
-
-# Advanced settings
-# If you need to use pgBouncer, you need to disable prepared statements:
-# PREPARED_STATEMENTS=false
-
-# Cluster number setting for streaming API server.
-# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
-STREAMING_CLUSTER_NUM=1
-
-# Docker mastodon user
-# If you use Docker, you may want to assign UID/GID manually.
-# UID=1000
-# GID=1000
-
-# LDAP authentication (optional)
-# LDAP_ENABLED=true
-# LDAP_HOST=localhost
-# LDAP_PORT=389
-# LDAP_METHOD=simple_tls
-# LDAP_BASE=
-# LDAP_BIND_DN=
-# LDAP_PASSWORD=
-# LDAP_UID=cn
-# LDAP_MAIL=mail
-# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email}))
-# LDAP_UID_CONVERSION_ENABLED=true
-# LDAP_UID_CONVERSION_SEARCH=., -
-# LDAP_UID_CONVERSION_REPLACE=_
-
-# PAM authentication (optional)
-# PAM authentication uses for the email generation the "email" pam variable
-# and optional as fallback PAM_DEFAULT_SUFFIX
-# The pam environment variable "email" is provided by:
-# https://github.com/devkral/pam_email_extractor
-# PAM_ENABLED=true
-# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
-# PAM_EMAIL_DOMAIN=example.com
-# Name of the pam service (pam "auth" section is evaluated)
-# PAM_DEFAULT_SERVICE=rpam
-# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
-# PAM_CONTROLLED_SERVICE=rpam
-
-# Global OAuth settings (optional) :
-# If you have only one strategy, you may want to enable this
-# OAUTH_REDIRECT_AT_SIGN_IN=true
-
-# Optional CAS authentication (cf. omniauth-cas) :
-# CAS_ENABLED=true
-# CAS_URL=https://sso.myserver.com/
-# CAS_HOST=sso.myserver.com/
-# CAS_PORT=443
-# CAS_SSL=true
-# CAS_VALIDATE_URL=
-# CAS_CALLBACK_URL=
-# CAS_LOGOUT_URL=
-# CAS_LOGIN_URL=
-# CAS_UID_FIELD='user'
-# CAS_CA_PATH=
-# CAS_DISABLE_SSL_VERIFICATION=false
-# CAS_UID_KEY='user'
-# CAS_NAME_KEY='name'
-# CAS_EMAIL_KEY='email'
-# CAS_NICKNAME_KEY='nickname'
-# CAS_FIRST_NAME_KEY='firstname'
-# CAS_LAST_NAME_KEY='lastname'
-# CAS_LOCATION_KEY='location'
-# CAS_IMAGE_KEY='image'
-# CAS_PHONE_KEY='phone'
-
-# Optional SAML authentication (cf. omniauth-saml)
-# SAML_ENABLED=true
-# SAML_ACS_URL=http://localhost:3000/auth/auth/saml/callback
-# SAML_ISSUER=https://example.com
-# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
-# SAML_IDP_CERT=
-# SAML_IDP_CERT_FINGERPRINT=
-# SAML_NAME_IDENTIFIER_FORMAT=
-# SAML_CERT=
-# SAML_PRIVATE_KEY=
-# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
-# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
-# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
-# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
-# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
-# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
-# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
-# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
-# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
-# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
-# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
-
-# Use HTTP proxy for outgoing request (optional)
-# http_proxy=http://gateway.local:8118
-# Access control for hidden service.
-# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
-
-# Authorized fetch mode (optional)
-# Require remote servers to authentify when fetching toots, see
-# https://docs.joinmastodon.org/admin/config/#authorized_fetch
-# AUTHORIZED_FETCH=true
-
-# Whitelist mode (optional)
-# Only allow federation with whitelisted domains, see
-# https://docs.joinmastodon.org/admin/config/#whitelist_mode
-# WHITELIST_MODE=true
diff --git a/config/initializers/2_whitelist_mode.rb b/config/initializers/2_whitelist_mode.rb
index a17ad07a2c..1cc6a8e724 100644
--- a/config/initializers/2_whitelist_mode.rb
+++ b/config/initializers/2_whitelist_mode.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 Rails.application.configure do
-  config.x.whitelist_mode = ENV['WHITELIST_MODE'] == 'true'
+  config.x.whitelist_mode = (ENV['LIMITED_FEDERATION_MODE'] || ENV['WHITELIST_MODE']) == 'true'
 end
diff --git a/config/initializers/blacklists.rb b/config/initializers/blacklists.rb
index 020d84f569..0e3339c98e 100644
--- a/config/initializers/blacklists.rb
+++ b/config/initializers/blacklists.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
 Rails.application.configure do
-  config.x.email_domains_blacklist = ENV.fetch('EMAIL_DOMAIN_BLACKLIST') { 'mvrht.com' }
-  config.x.email_domains_whitelist = ENV.fetch('EMAIL_DOMAIN_WHITELIST') { '' }
+  config.x.email_domains_blacklist = (ENV['EMAIL_DOMAIN_DENYLIST']  || ENV['EMAIL_DOMAIN_BLACKLIST']) || ''
+  config.x.email_domains_whitelist = (ENV['EMAIL_DOMAIN_ALLOWLIST'] || ENV['EMAIL_DOMAIN_WHITELIST']) || ''
 end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 52692129e1..7fc58643fa 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -95,7 +95,7 @@ en:
       delete: Delete
       destroyed_msg: Moderation note successfully destroyed!
     accounts:
-      add_email_domain_block: Blacklist e-mail domain
+      add_email_domain_block: Block e-mail domain
       approve: Approve
       approve_all: Approve all
       are_you_sure: Are you sure?
@@ -196,7 +196,7 @@ en:
       username: Username
       warn: Warn
       web: Web
-      whitelisted: Whitelisted
+      whitelisted: Allowed for federation
     action_logs:
       action_types:
         assigned_to_self_report: Assign Report
@@ -241,15 +241,15 @@ en:
         create_account_warning: "%{name} sent a warning to %{target}"
         create_announcement: "%{name} created new announcement %{target}"
         create_custom_emoji: "%{name} uploaded new emoji %{target}"
-        create_domain_allow: "%{name} whitelisted domain %{target}"
+        create_domain_allow: "%{name} allowed federation with domain %{target}"
         create_domain_block: "%{name} blocked domain %{target}"
-        create_email_domain_block: "%{name} blacklisted e-mail domain %{target}"
+        create_email_domain_block: "%{name} blocked e-mail domain %{target}"
         demote_user: "%{name} demoted user %{target}"
         destroy_announcement: "%{name} deleted announcement %{target}"
         destroy_custom_emoji: "%{name} destroyed emoji %{target}"
-        destroy_domain_allow: "%{name} removed domain %{target} from whitelist"
+        destroy_domain_allow: "%{name} disallowed federation with domain %{target}"
         destroy_domain_block: "%{name} unblocked domain %{target}"
-        destroy_email_domain_block: "%{name} whitelisted e-mail domain %{target}"
+        destroy_email_domain_block: "%{name} unblocked e-mail domain %{target}"
         destroy_status: "%{name} removed status by %{target}"
         disable_2fa_user: "%{name} disabled two factor requirement for user %{target}"
         disable_custom_emoji: "%{name} disabled emoji %{target}"
@@ -350,12 +350,12 @@ en:
       week_interactions: interactions this week
       week_users_active: active this week
       week_users_new: users this week
-      whitelist_mode: Whitelist mode
+      whitelist_mode: Limited federation mode
     domain_allows:
-      add_new: Whitelist domain
-      created_msg: Domain has been successfully whitelisted
-      destroyed_msg: Domain has been removed from the whitelist
-      undo: Remove from whitelist
+      add_new: Allow federation with domain
+      created_msg: Domain has been successfully allowed for federation
+      destroyed_msg: Domain has been disallowed from federation
+      undo: Disallow federation with domain
     domain_blocks:
       add_new: Add new domain block
       created_msg: Domain block is now being processed
@@ -398,16 +398,16 @@ en:
       view: View domain block
     email_domain_blocks:
       add_new: Add new
-      created_msg: Successfully added e-mail domain to blacklist
+      created_msg: Successfully blocked e-mail domain
       delete: Delete
-      destroyed_msg: Successfully deleted e-mail domain from blacklist
+      destroyed_msg: Successfully unblocked e-mail domain
       domain: Domain
-      empty: No e-mail domains currently blacklisted.
+      empty: No e-mail domains currently blocked.
       from_html: from %{domain}
       new:
         create: Add domain
-        title: New e-mail blacklist entry
-      title: E-mail blacklist
+        title: Block new e-mail domain
+      title: Blocked e-mail domains
     instances:
       by_domain: Domain
       delivery_available: Delivery is available
@@ -451,7 +451,7 @@ en:
       pending: Waiting for relay's approval
       save_and_enable: Save and enable
       setup: Setup a relay connection
-      signatures_not_enabled: Relays will not work correctly while secure mode or whitelist mode is enabled
+      signatures_not_enabled: Relays will not work correctly while secure mode or limited federation mode is enabled
       status: Status
       title: Relays
     report_notes:
diff --git a/lib/mastodon/domains_cli.rb b/lib/mastodon/domains_cli.rb
index b5435bb5e5..558737c273 100644
--- a/lib/mastodon/domains_cli.rb
+++ b/lib/mastodon/domains_cli.rb
@@ -16,22 +16,22 @@ module Mastodon
     option :concurrency, type: :numeric, default: 5, aliases: [:c]
     option :verbose, type: :boolean, aliases: [:v]
     option :dry_run, type: :boolean
-    option :whitelist_mode, type: :boolean
+    option :limited_federation_mode, type: :boolean
     desc 'purge [DOMAIN...]', 'Remove accounts from a DOMAIN without a trace'
     long_desc <<-LONG_DESC
       Remove all accounts from a given DOMAIN without leaving behind any
       records. Unlike a suspension, if the DOMAIN still exists in the wild,
       it means the accounts could return if they are resolved again.
 
-      When the --whitelist-mode option is given, instead of purging accounts
-      from a single domain, all accounts from domains that are not whitelisted
+      When the --limited-federation-mode option is given, instead of purging accounts
+      from a single domain, all accounts from domains that have not been explicitly allowed
       are removed from the database.
     LONG_DESC
     def purge(*domains)
       dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
 
       scope = begin
-        if options[:whitelist_mode]
+        if options[:limited_federation_mode]
           Account.remote.where.not(domain: DomainAllow.pluck(:domain))
         elsif !domains.empty?
           Account.remote.where(domain: domains)

From fa4876a1b93d4bb62038cca75bd5017fe49b59ae Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Sun, 28 Jun 2020 20:55:32 +0200
Subject: [PATCH 04/29] Fix read marker state not being udpated internally
 (#14155)

Fixes #14151
---
 app/javascript/mastodon/reducers/markers.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/javascript/mastodon/reducers/markers.js b/app/javascript/mastodon/reducers/markers.js
index 2e67be82e7..fb1572ff59 100644
--- a/app/javascript/mastodon/reducers/markers.js
+++ b/app/javascript/mastodon/reducers/markers.js
@@ -1,6 +1,6 @@
 import {
   MARKERS_SUBMIT_SUCCESS,
-} from '../actions/notifications';
+} from '../actions/markers';
 
 const initialState = ImmutableMap({
   home: '0',

From 64aac3073340dbc92c33f5f1c6f76dcafa77a450 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 29 Jun 2020 13:56:55 +0200
Subject: [PATCH 05/29] Add customizable thumbnails for audio and video
 attachments (#14145)

- Change audio files to not be stripped of metadata
- Automatically extract cover art from audio if it exists
- Add `thumbnail` parameter to `POST /api/v1/media`, `POST /api/v2/media` and `PUT /api/v1/media/:id`
- Add `icon` to represent it in attachments in ActivityPub
- Fix `preview_url` containing URL of missing missing image when there is no thumbnail instead of null
- Fix duration of audio not being displayed on public pages until the file is loaded
---
 app/controllers/api/v1/media_controller.rb    |   2 +-
 app/controllers/media_proxy_controller.rb     |   4 +-
 .../settings/pictures_controller.rb           |  13 +--
 app/javascript/mastodon/components/status.js  |   3 +-
 .../mastodon/features/audio/index.js          |  42 ++++---
 .../status/components/detailed_status.js      |   3 +-
 app/lib/activitypub/activity/create.rb        |  12 +-
 app/models/concerns/remotable.rb              |  29 +++--
 app/models/media_attachment.rb                | 108 ++++++++++++------
 .../activitypub/note_serializer.rb            |  10 ++
 .../rest/media_attachment_serializer.rb       |   4 +-
 .../activitypub/process_account_service.rb    |   4 +-
 app/views/statuses/_detailed_status.html.haml |   2 +-
 app/views/statuses/_simple_status.html.haml   |   2 +-
 app/workers/post_process_media_worker.rb      |   2 +-
 app/workers/redownload_media_worker.rb        |   3 +-
 ..._thumbnail_columns_to_media_attachments.rb |  11 ++
 db/schema.rb                                  |   7 +-
 lib/mastodon/media_cli.rb                     |  10 +-
 lib/paperclip/attachment_extensions.rb        |   2 +-
 lib/paperclip/image_extractor.rb              |  49 ++++++++
 lib/paperclip/type_corrector.rb               |  10 +-
 spec/models/concerns/remotable_spec.rb        |  53 ++-------
 23 files changed, 247 insertions(+), 138 deletions(-)
 create mode 100644 db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb
 create mode 100644 lib/paperclip/image_extractor.rb

diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb
index 0bb3d0d27b..a2a919a3e6 100644
--- a/app/controllers/api/v1/media_controller.rb
+++ b/app/controllers/api/v1/media_controller.rb
@@ -39,7 +39,7 @@ class Api::V1::MediaController < Api::BaseController
   end
 
   def media_attachment_params
-    params.permit(:file, :description, :focus)
+    params.permit(:file, :thumbnail, :description, :focus)
   end
 
   def file_type_error
diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb
index 014b89de10..a8261ec2ba 100644
--- a/app/controllers/media_proxy_controller.rb
+++ b/app/controllers/media_proxy_controller.rb
@@ -28,8 +28,8 @@ class MediaProxyController < ApplicationController
   private
 
   def redownload!
-    @media_attachment.file_remote_url = @media_attachment.remote_url
-    @media_attachment.created_at      = Time.now.utc
+    @media_attachment.download_file!
+    @media_attachment.created_at = Time.now.utc
     @media_attachment.save!
   end
 
diff --git a/app/controllers/settings/pictures_controller.rb b/app/controllers/settings/pictures_controller.rb
index 73926707bd..df2a6eed3e 100644
--- a/app/controllers/settings/pictures_controller.rb
+++ b/app/controllers/settings/pictures_controller.rb
@@ -7,13 +7,8 @@ module Settings
     before_action :set_picture
 
     def destroy
-      if valid_picture
-        account_params = {
-          @picture => nil,
-          (@picture + '_remote_url') => nil,
-        }
-
-        msg = UpdateAccountService.new.call(@account, account_params) ? I18n.t('generic.changes_saved_msg') : nil
+      if valid_picture?
+        msg = I18n.t('generic.changes_saved_msg') if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' })
         redirect_to settings_profile_path, notice: msg, status: 303
       else
         bad_request
@@ -30,8 +25,8 @@ module Settings
       @picture = params[:id]
     end
 
-    def valid_picture
-      @picture == 'avatar' || @picture == 'header'
+    def valid_picture?
+      %w(avatar header).include?(@picture)
     end
   end
 end
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 2dc9619366..827b69500d 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -352,7 +352,8 @@ class Status extends ImmutablePureComponent {
               <Component
                 src={attachment.get('url')}
                 alt={attachment.get('description')}
-                poster={status.getIn(['account', 'avatar_static'])}
+                poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
+                blurhash={attachment.get('blurhash')}
                 duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
                 width={this.props.cachedMediaWidth}
                 height={110}
diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js
index 5f6132f12a..99926e52ab 100644
--- a/app/javascript/mastodon/features/audio/index.js
+++ b/app/javascript/mastodon/features/audio/index.js
@@ -157,6 +157,7 @@ class Audio extends React.PureComponent {
     fullscreen: PropTypes.bool,
     intl: PropTypes.object.isRequired,
     cacheWidth: PropTypes.func,
+    blurhash: PropTypes.string,
   };
 
   state = {
@@ -222,32 +223,42 @@ class Audio extends React.PureComponent {
     window.addEventListener('scroll', this.handleScroll);
     window.addEventListener('resize', this.handleResize, { passive: true });
 
-    const img = new Image();
-    img.crossOrigin = 'anonymous';
-    img.onload = () => this.handlePosterLoad(img);
-    img.src = this.props.poster;
+    if (!this.props.blurhash) {
+      const img = new Image();
+      img.crossOrigin = 'anonymous';
+      img.onload = () => this.handlePosterLoad(img);
+      img.src = this.props.poster;
+    } else {
+      this._setColorScheme();
+      this._decodeBlurhash();
+    }
   }
 
   componentDidUpdate (prevProps, prevState) {
-    if (prevProps.poster !== this.props.poster) {
+    if (prevProps.poster !== this.props.poster && !this.props.blurhash) {
       const img = new Image();
       img.crossOrigin = 'anonymous';
       img.onload = () => this.handlePosterLoad(img);
       img.src = this.props.poster;
     }
 
-    if (prevState.blurhash !== this.state.blurhash) {
-      const context = this.blurhashCanvas.getContext('2d');
-      const pixels = decode(this.state.blurhash, 32, 32);
-      const outputImageData = new ImageData(pixels, 32, 32);
-
-      context.putImageData(outputImageData, 0, 0);
+    if (prevState.blurhash !== this.state.blurhash || prevProps.blurhash !== this.props.blurhash) {
+      this._setColorScheme();
+      this._decodeBlurhash();
     }
 
     this._clear();
     this._draw();
   }
 
+  _decodeBlurhash () {
+    const context = this.blurhashCanvas.getContext('2d');
+    const pixels = decode(this.props.blurhash || this.state.blurhash, 32, 32);
+    const outputImageData = new ImageData(pixels, 32, 32);
+
+    context.putImageData(outputImageData, 0, 0);
+  }
+
   componentWillUnmount () {
     window.removeEventListener('scroll', this.handleScroll);
     window.removeEventListener('resize', this.handleResize);
@@ -415,7 +426,7 @@ class Audio extends React.PureComponent {
   }
 
   handlePosterLoad = image => {
-    const canvas = document.createElement('canvas');
+    const canvas  = document.createElement('canvas');
     const context = canvas.getContext('2d');
 
     canvas.width  = image.width;
@@ -425,10 +436,15 @@ class Audio extends React.PureComponent {
 
     const inputImageData = context.getImageData(0, 0, image.width, image.height);
     const blurhash = encode(inputImageData.data, image.width, image.height, 4, 4);
+
+    this.setState({ blurhash });
+  }
+
+  _setColorScheme () {
+    const blurhash     = this.props.blurhash || this.state.blurhash;
     const averageColor = decodeRGB(decode83(blurhash.slice(2, 6)));
 
     this.setState({
-      blurhash,
       color: adjustColor(averageColor),
       darkText: luma(averageColor) >= 165,
     });
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 935e4207e9..f7d0c9bd43 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -125,7 +125,8 @@ class DetailedStatus extends ImmutablePureComponent {
             src={attachment.get('url')}
             alt={attachment.get('description')}
             duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
-            poster={status.getIn(['account', 'avatar_static'])}
+            poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
+            blurhash={attachment.get('blurhash')}
             height={150}
           />
         );
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 3509a6c404..d3d4605516 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -238,12 +238,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
       begin
         href             = Addressable::URI.parse(attachment['url']).normalize.to_s
-        media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['summary'].presence || attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
+        media_attachment = MediaAttachment.create(account: @account, remote_url: href, thumbnail_remote_url: icon_url_from_attachment(attachment), description: attachment['summary'].presence || attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
         media_attachments << media_attachment
 
         next if unsupported_media_type?(attachment['mediaType']) || skip_download?
 
-        media_attachment.file_remote_url = href
+        media_attachment.download_file!
+        media_attachment.download_thumbnail!
         media_attachment.save
       rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
         RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
@@ -256,6 +257,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     media_attachments
   end
 
+  def icon_url_from_attachment(attachment)
+    url = attachment['icon'].is_a?(Hash) ? attachment['icon']['url'] : attachment['icon']
+    Addressable::URI.parse(url).normalize.to_s if url.present?
+  rescue Addressable::URI::InvalidURIError
+    nil
+  end
+
   def process_poll
     return unless @object['type'] == 'Question' && (@object['anyOf'].is_a?(Array) || @object['oneOf'].is_a?(Array))
 
diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index c728a460e5..6fc1dcc268 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -4,12 +4,12 @@ module Remotable
   extend ActiveSupport::Concern
 
   class_methods do
-    def remotable_attachment(attachment_name, limit, suppress_errors: true)
-      attribute_name  = "#{attachment_name}_remote_url".to_sym
-      method_name     = "#{attribute_name}=".to_sym
-      alt_method_name = "reset_#{attachment_name}!".to_sym
+    def remotable_attachment(attachment_name, limit, suppress_errors: true, download_on_assign: true, attribute_name: nil)
+      attribute_name ||= "#{attachment_name}_remote_url".to_sym
+
+      define_method("download_#{attachment_name}!") do
+        url = self[attribute_name]
 
-      define_method method_name do |url|
         return if url.blank?
 
         begin
@@ -18,7 +18,7 @@ module Remotable
           return
         end
 
-        return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank? || (self[attribute_name] == url && send("#{attachment_name}_file_name").present?)
+        return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank?
 
         begin
           Request.new(:get, url).perform do |response|
@@ -36,10 +36,8 @@ module Remotable
 
             basename = SecureRandom.hex(8)
 
-            send("#{attachment_name}_file_name=", basename + extname)
-            send("#{attachment_name}=", StringIO.new(response.body_with_limit(limit)))
-
-            self[attribute_name] = url if has_attribute?(attribute_name)
+            public_send("#{attachment_name}_file_name=", basename + extname)
+            public_send("#{attachment_name}=", StringIO.new(response.body_with_limit(limit)))
           end
         rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e
           Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
@@ -50,14 +48,15 @@ module Remotable
         end
       end
 
-      define_method alt_method_name do
-        url = self[attribute_name]
+      define_method("#{attribute_name}=") do |url|
+        return if self[attribute_name] == url && public_send("#{attachment_name}_file_name").present?
 
-        return if url.blank?
+        self[attribute_name] = url
 
-        self[attribute_name] = ''
-        send(method_name, url)
+        public_send("download_#{attachment_name}!") if download_on_assign
       end
+
+      alias_method("reset_#{attachment_name}!", "download_#{attachment_name}!")
     end
   end
 
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index d44467009f..f67566a187 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -21,6 +21,11 @@
 #  blurhash                    :string
 #  processing                  :integer
 #  file_storage_schema_version :integer
+#  thumbnail_file_name         :string
+#  thumbnail_content_type      :string
+#  thumbnail_file_size         :integer
+#  thumbnail_updated_at        :datetime
+#  thumbnail_remote_url        :string
 #
 
 class MediaAttachment < ApplicationRecord
@@ -49,13 +54,13 @@ class MediaAttachment < ApplicationRecord
     original: {
       pixels: 1_638_400, # 1280x1280px
       file_geometry_parser: FastGeometryParser,
-    },
+    }.freeze,
 
     small: {
       pixels: 160_000, # 400x400px
       file_geometry_parser: FastGeometryParser,
       blurhash: BLURHASH_OPTIONS,
-    },
+    }.freeze,
   }.freeze
 
   VIDEO_FORMAT = {
@@ -74,14 +79,14 @@ class MediaAttachment < ApplicationRecord
         'frames:v' => 60 * 60 * 3,
         'crf' => 18,
         'map_metadata' => '-1',
-      },
-    },
+      }.freeze,
+    }.freeze,
   }.freeze
 
   VIDEO_PASSTHROUGH_OPTIONS = {
-    video_codecs: ['h264'],
-    audio_codecs: ['aac', nil],
-    colorspaces: ['yuv420p'],
+    video_codecs: ['h264'].freeze,
+    audio_codecs: ['aac', nil].freeze,
+    colorspaces: ['yuv420p'].freeze,
     options: {
       format: 'mp4',
       convert_options: {
@@ -90,9 +95,9 @@ class MediaAttachment < ApplicationRecord
           'map_metadata' => '-1',
           'c:v' => 'copy',
           'c:a' => 'copy',
-        },
-      },
-    },
+        }.freeze,
+      }.freeze,
+    }.freeze,
   }.freeze
 
   VIDEO_STYLES = {
@@ -101,15 +106,15 @@ class MediaAttachment < ApplicationRecord
         output: {
           'loglevel' => 'fatal',
           vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
-        },
-      },
+        }.freeze,
+      }.freeze,
       format: 'png',
       time: 0,
       file_geometry_parser: FastGeometryParser,
       blurhash: BLURHASH_OPTIONS,
-    },
+    }.freeze,
 
-    original: VIDEO_FORMAT.merge(passthrough_options: VIDEO_PASSTHROUGH_OPTIONS),
+    original: VIDEO_FORMAT.merge(passthrough_options: VIDEO_PASSTHROUGH_OPTIONS).freeze,
   }.freeze
 
   AUDIO_STYLES = {
@@ -119,16 +124,23 @@ class MediaAttachment < ApplicationRecord
       convert_options: {
         output: {
           'loglevel' => 'fatal',
-          'map_metadata' => '-1',
           'q:a' => 2,
-        },
-      },
-    },
+        }.freeze,
+      }.freeze,
+    }.freeze,
   }.freeze
 
   VIDEO_CONVERTED_STYLES = {
-    small: VIDEO_STYLES[:small],
-    original: VIDEO_FORMAT,
+    small: VIDEO_STYLES[:small].freeze,
+    original: VIDEO_FORMAT.freeze,
+  }.freeze
+
+  THUMBNAIL_STYLES = {
+    original: IMAGE_STYLES[:small].freeze,
+  }.freeze
+
+  GLOBAL_CONVERT_OPTIONS = {
+    all: '-quality 90 -strip +set modify-date +set create-date',
   }.freeze
 
   IMAGE_LIMIT = 10.megabytes
@@ -144,18 +156,28 @@ class MediaAttachment < ApplicationRecord
   has_attached_file :file,
                     styles: ->(f) { file_styles f },
                     processors: ->(f) { file_processors f },
-                    convert_options: { all: '-quality 90 -strip +set modify-date +set create-date' }
+                    convert_options: GLOBAL_CONVERT_OPTIONS
 
   validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
   validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :larger_media_format?
   validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :larger_media_format?
-  remotable_attachment :file, VIDEO_LIMIT, suppress_errors: false
+  remotable_attachment :file, VIDEO_LIMIT, suppress_errors: false, download_on_assign: false, attribute_name: :remote_url
+
+  has_attached_file :thumbnail,
+                    styles: THUMBNAIL_STYLES,
+                    processors: [:lazy_thumbnail, :blurhash_transcoder],
+                    convert_options: GLOBAL_CONVERT_OPTIONS
+
+  validates_attachment_content_type :thumbnail, content_type: IMAGE_MIME_TYPES
+  validates_attachment_size :thumbnail, less_than: IMAGE_LIMIT
+  remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true, download_on_assign: false
 
   include Attachmentable
 
   validates :account, presence: true
   validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local?
   validates :file, presence: true, if: :local?
+  validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
 
   scope :attached,   -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
   scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
@@ -215,16 +237,21 @@ class MediaAttachment < ApplicationRecord
     @delay_processing
   end
 
+  def delay_processing_for_attachment?(attachment_name)
+    @delay_processing && attachment_name == :file
+  end
+
   after_commit :enqueue_processing, on: :create
   after_commit :reset_parent_cache, on: :update
 
   before_create :prepare_description, unless: :local?
   before_create :set_shortcode
   before_create :set_processing
-  before_create :set_meta
 
-  before_post_process :set_type_and_extension
-  before_post_process :check_video_dimensions
+  after_post_process :set_meta
+
+  before_file_post_process :set_type_and_extension
+  before_file_post_process :check_video_dimensions
 
   class << self
     def supported_mime_types
@@ -237,25 +264,25 @@ class MediaAttachment < ApplicationRecord
 
     private
 
-    def file_styles(f)
-      if f.instance.file_content_type == 'image/gif' || VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type)
+    def file_styles(attachment)
+      if attachment.instance.file_content_type == 'image/gif' || VIDEO_CONVERTIBLE_MIME_TYPES.include?(attachment.instance.file_content_type)
         VIDEO_CONVERTED_STYLES
-      elsif IMAGE_MIME_TYPES.include?(f.instance.file_content_type)
+      elsif IMAGE_MIME_TYPES.include?(attachment.instance.file_content_type)
         IMAGE_STYLES
-      elsif VIDEO_MIME_TYPES.include?(f.instance.file_content_type)
+      elsif VIDEO_MIME_TYPES.include?(attachment.instance.file_content_type)
         VIDEO_STYLES
       else
         AUDIO_STYLES
       end
     end
 
-    def file_processors(f)
-      if f.file_content_type == 'image/gif'
+    def file_processors(instance)
+      if instance.file_content_type == 'image/gif'
         [:gif_transcoder, :blurhash_transcoder]
-      elsif VIDEO_MIME_TYPES.include?(f.file_content_type)
+      elsif VIDEO_MIME_TYPES.include?(instance.file_content_type)
         [:video_transcoder, :blurhash_transcoder, :type_corrector]
-      elsif AUDIO_MIME_TYPES.include?(f.file_content_type)
-        [:transcoder, :type_corrector]
+      elsif AUDIO_MIME_TYPES.include?(instance.file_content_type)
+        [:image_extractor, :transcoder, :type_corrector]
       else
         [:lazy_thumbnail, :blurhash_transcoder, :type_corrector]
       end
@@ -298,7 +325,7 @@ class MediaAttachment < ApplicationRecord
   def check_video_dimensions
     return unless (video? || gifv?) && file.queued_for_write[:original].present?
 
-    movie = FFMPEG::Movie.new(file.queued_for_write[:original].path)
+    movie = ffmpeg_data(file.queued_for_write[:original].path)
 
     return unless movie.valid?
 
@@ -317,6 +344,8 @@ class MediaAttachment < ApplicationRecord
       meta[style] = style == :small || image? ? image_geometry(file) : video_metadata(file)
     end
 
+    meta[:small] = image_geometry(thumbnail.queued_for_write[:original]) if thumbnail.queued_for_write.key?(:original)
+
     meta
   end
 
@@ -334,7 +363,7 @@ class MediaAttachment < ApplicationRecord
   end
 
   def video_metadata(file)
-    movie = FFMPEG::Movie.new(file.path)
+    movie = ffmpeg_data(file.path)
 
     return {} unless movie.valid?
 
@@ -347,6 +376,13 @@ class MediaAttachment < ApplicationRecord
     }.compact
   end
 
+  # We call this method about 3 different times on potentially different
+  # paths but ultimately the same file, so it makes sense to memoize the
+  # result while disregarding the path
+  def ffmpeg_data(path = nil)
+    @ffmpeg_data ||= FFMPEG::Movie.new(path)
+  end
+
   def enqueue_processing
     PostProcessMediaWorker.perform_async(id) if delay_processing?
   end
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 110621a28b..f26fd93a42 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -167,6 +167,8 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
     attributes :type, :media_type, :url, :name, :blurhash
     attribute :focal_point, if: :focal_point?
 
+    has_one :icon, serializer: ActivityPub::ImageSerializer, if: :thumbnail?
+
     def type
       'Document'
     end
@@ -190,6 +192,14 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
     def focal_point
       [object.file.meta['focus']['x'], object.file.meta['focus']['y']]
     end
+
+    def icon
+      object.thumbnail
+    end
+
+    def thumbnail?
+      object.thumbnail.present?
+    end
   end
 
   class MentionSerializer < ActivityPub::Serializer
diff --git a/app/serializers/rest/media_attachment_serializer.rb b/app/serializers/rest/media_attachment_serializer.rb
index cc10e30015..e65f7acf1a 100644
--- a/app/serializers/rest/media_attachment_serializer.rb
+++ b/app/serializers/rest/media_attachment_serializer.rb
@@ -28,7 +28,9 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
   def preview_url
     if object.needs_redownload?
       media_proxy_url(object.id, :small)
-    else
+    elsif object.thumbnail.present?
+      full_asset_url(object.thumbnail.url(:original))
+    elsif object.file.styles.key?(:small)
       full_asset_url(object.file.url(:small))
     end
   end
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index f4276cece9..85b915ec67 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -89,8 +89,8 @@ class ActivityPub::ProcessAccountService < BaseService
   end
 
   def set_fetchable_attributes!
-    @account.avatar_remote_url = image_url('icon')  unless skip_download?
-    @account.header_remote_url = image_url('image') unless skip_download?
+    @account.avatar_remote_url = image_url('icon')  || '' unless skip_download?
+    @account.header_remote_url = image_url('image') || '' unless skip_download?
     @account.public_key        = public_key || ''
     @account.statuses_count    = outbox_total_items    if outbox_total_items.present?
     @account.following_count   = following_total_items if following_total_items.present?
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index 684dd08d16..d10017db90 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -33,7 +33,7 @@
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - elsif status.media_attachments.first.audio?
       - audio = status.media_attachments.first
-      = react_component :audio, src: audio.file.url(:original), poster: full_asset_url(status.account.avatar_static_url), width: 670, height: 380, alt: audio.description, duration: audio.file.meta.dig(:original, :duration) do
+      = react_component :audio, src: audio.file.url(:original), poster: audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url, blurhash: audio.blurhash, width: 670, height: 380, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - else
       = react_component :media_gallery, height: 380, sensitive: status.sensitive?, standalone: true, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index 06dc5ff937..ab09dfe458 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -39,7 +39,7 @@
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - elsif status.media_attachments.first.audio?
       - audio = status.media_attachments.first
-      = react_component :audio, src: audio.file.url(:original), poster: full_asset_url(status.account.avatar_static_url), width: 610, height: 343, alt: audio.description, duration: audio.file.meta.dig(:original, :duration) do
+      = react_component :audio, src: audio.file.url(:original), poster: audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url, blurhash: audio.blurhash, width: 610, height: 343, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - else
       = react_component :media_gallery, height: 343, sensitive: status.sensitive?, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
diff --git a/app/workers/post_process_media_worker.rb b/app/workers/post_process_media_worker.rb
index 73f9ae2bf6..a904f35b14 100644
--- a/app/workers/post_process_media_worker.rb
+++ b/app/workers/post_process_media_worker.rb
@@ -32,7 +32,7 @@ class PostProcessMediaWorker
 
     media_attachment.file.reprocess!(:original)
     media_attachment.processing = :complete
-    media_attachment.file_meta = previous_meta
+    media_attachment.file_meta = previous_meta.merge(media_attachment.file_meta).with_indifferent_access.slice(:focus, :original, :small)
     media_attachment.save
   rescue ActiveRecord::RecordNotFound
     true
diff --git a/app/workers/redownload_media_worker.rb b/app/workers/redownload_media_worker.rb
index 071501a491..0638cd0f09 100644
--- a/app/workers/redownload_media_worker.rb
+++ b/app/workers/redownload_media_worker.rb
@@ -11,7 +11,8 @@ class RedownloadMediaWorker
 
     return if media_attachment.remote_url.blank?
 
-    media_attachment.file_remote_url = media_attachment.remote_url
+    media_attachment.download_file!
+    media_attachment.download_thumbnail!
     media_attachment.save
   rescue ActiveRecord::RecordNotFound
     true
diff --git a/db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb b/db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb
new file mode 100644
index 0000000000..f9c87a53cc
--- /dev/null
+++ b/db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb
@@ -0,0 +1,11 @@
+class AddThumbnailColumnsToMediaAttachments < ActiveRecord::Migration[5.2]
+  def up
+    add_attachment :media_attachments, :thumbnail
+    add_column :media_attachments, :thumbnail_remote_url, :string
+  end
+
+  def down
+    remove_attachment :media_attachments, :thumbnail
+    remove_column :media_attachments, :thumbnail_remote_url
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d603d98d77..277eccba77 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: 2020_06_20_164023) do
+ActiveRecord::Schema.define(version: 2020_06_27_125810) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -489,6 +489,11 @@ ActiveRecord::Schema.define(version: 2020_06_20_164023) do
     t.string "blurhash"
     t.integer "processing"
     t.integer "file_storage_schema_version"
+    t.string "thumbnail_file_name"
+    t.string "thumbnail_content_type"
+    t.integer "thumbnail_file_size"
+    t.datetime "thumbnail_updated_at"
+    t.string "thumbnail_remote_url"
     t.index ["account_id"], name: "index_media_attachments_on_account_id"
     t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id"
     t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true
diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb
index c95f3410ae..2a4e3e379f 100644
--- a/lib/mastodon/media_cli.rb
+++ b/lib/mastodon/media_cli.rb
@@ -31,10 +31,11 @@ module Mastodon
       processed, aggregate = parallelize_with_progress(MediaAttachment.cached.where.not(remote_url: '').where('created_at < ?', time_ago)) do |media_attachment|
         next if media_attachment.file.blank?
 
-        size = media_attachment.file_file_size
+        size = media_attachment.file_file_size + (media_attachment.thumbnail_file_size || 0)
 
         unless options[:dry_run]
           media_attachment.file.destroy
+          media_attachment.thumbnail.destroy
           media_attachment.save
         end
 
@@ -227,11 +228,12 @@ module Mastodon
         next if media_attachment.remote_url.blank? || (!options[:force] && media_attachment.file_file_name.present?)
 
         unless options[:dry_run]
-          media_attachment.file_remote_url = media_attachment.remote_url
+          media_attachment.reset_file!
+          media_attachment.reset_thumbnail!
           media_attachment.save
         end
 
-        media_attachment.file_file_size
+        media_attachment.file_file_size + (media_attachment.thumbnail_file_size || 0)
       end
 
       say("Downloaded #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
@@ -239,7 +241,7 @@ module Mastodon
 
     desc 'usage', 'Calculate disk space consumed by Mastodon'
     def usage
-      say("Attachments:\t#{number_to_human_size(MediaAttachment.sum(:file_file_size))} (#{number_to_human_size(MediaAttachment.where(account: Account.local).sum(:file_file_size))} local)")
+      say("Attachments:\t#{number_to_human_size(MediaAttachment.sum(Arel.sql('COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)')))} (#{number_to_human_size(MediaAttachment.where(account: Account.local).sum(Arel.sql('COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)')))} local)")
       say("Custom emoji:\t#{number_to_human_size(CustomEmoji.sum(:image_file_size))} (#{number_to_human_size(CustomEmoji.local.sum(:image_file_size))} local)")
       say("Preview cards:\t#{number_to_human_size(PreviewCard.sum(:image_file_size))}")
       say("Avatars:\t#{number_to_human_size(Account.sum(:avatar_file_size))} (#{number_to_human_size(Account.local.sum(:avatar_file_size))} local)")
diff --git a/lib/paperclip/attachment_extensions.rb b/lib/paperclip/attachment_extensions.rb
index f3e51dbd38..93df0a326f 100644
--- a/lib/paperclip/attachment_extensions.rb
+++ b/lib/paperclip/attachment_extensions.rb
@@ -7,7 +7,7 @@ module Paperclip
     # usage, and we still want to generate thumbnails straight
     # away, it's the only style we need to exclude
     def process_style?(style_name, style_args)
-      if style_name == :original && instance.respond_to?(:delay_processing?) && instance.delay_processing?
+      if style_name == :original && instance.respond_to?(:delay_processing_for_attachment?) && instance.delay_processing_for_attachment?(name)
         false
       else
         style_args.empty? || style_args.include?(style_name)
diff --git a/lib/paperclip/image_extractor.rb b/lib/paperclip/image_extractor.rb
new file mode 100644
index 0000000000..114852e8b9
--- /dev/null
+++ b/lib/paperclip/image_extractor.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'mime/types/columnar'
+
+module Paperclip
+  class ImageExtractor < Paperclip::Processor
+    IMAGE_EXTRACTION_OPTIONS = {
+      convert_options: {
+        output: {
+          'loglevel' => 'fatal',
+          vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
+        }.freeze,
+      }.freeze,
+      format: 'png',
+      time: -1,
+      file_geometry_parser: FastGeometryParser,
+    }.freeze
+
+    def make
+      return @file unless options[:style] == :original
+
+      image = begin
+        begin
+          Paperclip::Transcoder.make(file, IMAGE_EXTRACTION_OPTIONS.dup, attachment)
+        rescue Paperclip::Error, ::Av::CommandError
+          nil
+        end
+      end
+
+      unless image.nil?
+        begin
+          attachment.instance.thumbnail = image if image.size.positive?
+        ensure
+          # Paperclip does not automatically delete the source file of
+          # a new attachment while working on copies of it, so we need
+          # to make sure it's cleaned up
+
+          begin
+            FileUtils.rm(image)
+          rescue Errno::ENOENT
+            nil
+          end
+        end
+      end
+
+      @file
+    end
+  end
+end
diff --git a/lib/paperclip/type_corrector.rb b/lib/paperclip/type_corrector.rb
index 0b0c10a56e..17e2fc5daa 100644
--- a/lib/paperclip/type_corrector.rb
+++ b/lib/paperclip/type_corrector.rb
@@ -5,13 +5,15 @@ require 'mime/types/columnar'
 module Paperclip
   class TypeCorrector < Paperclip::Processor
     def make
-      target_extension = options[:format]
-      extension        = File.extname(attachment.instance.file_file_name)
+      return @file unless options[:format]
+
+      target_extension = '.' + options[:format]
+      extension        = File.extname(attachment.instance_read(:file_name))
 
       return @file unless options[:style] == :original && target_extension && extension != target_extension
 
-      attachment.instance.file_content_type = options[:content_type] || attachment.instance.file_content_type
-      attachment.instance.file_file_name    = File.basename(attachment.instance.file_file_name, '.*') + '.' + target_extension
+      attachment.instance_write(:content_type, options[:content_type] || attachment.instance_read(:content_type))
+      attachment.instance_write(:file_name, File.basename(attachment.instance_read(:file_name), '.*') + target_extension)
 
       @file
     end
diff --git a/spec/models/concerns/remotable_spec.rb b/spec/models/concerns/remotable_spec.rb
index 99a60cbf65..6957b044f4 100644
--- a/spec/models/concerns/remotable_spec.rb
+++ b/spec/models/concerns/remotable_spec.rb
@@ -58,7 +58,11 @@ RSpec.describe Remotable do
       expect(foo).to respond_to(:reset_hoge!)
     end
 
-    describe '#hoge_remote_url' do
+    it 'defines a method #download_hoge!' do
+      expect(foo).to respond_to(:download_hoge!)
+    end
+
+    describe '#hoge_remote_url=' do
       before do
         request
       end
@@ -138,8 +142,8 @@ RSpec.describe Remotable do
           let(:code) { 500 }
 
           it 'calls not send' do
-            expect(foo).not_to receive(:send).with("#{hoge}=", any_args)
-            expect(foo).not_to receive(:send).with("#{hoge}_file_name=", any_args)
+            expect(foo).not_to receive(:public_send).with("#{hoge}=", any_args)
+            expect(foo).not_to receive(:public_send).with("#{hoge}_file_name=", any_args)
             foo.hoge_remote_url = url
           end
         end
@@ -159,26 +163,14 @@ RSpec.describe Remotable do
               allow(SecureRandom).to receive(:hex).and_return(basename)
               allow(StringIO).to receive(:new).with(anything).and_return(string_io)
 
-              expect(foo).to receive(:send).with("#{hoge}=", string_io)
-              expect(foo).to receive(:send).with("#{hoge}_file_name=", basename + extname)
-              foo.hoge_remote_url = url
-            end
-          end
+              expect(foo).to receive(:public_send).with("download_#{hoge}!")
 
-          context 'if has_attribute?' do
-            it 'calls foo[attribute_name] = url' do
-              allow(foo).to receive(:has_attribute?).with(attribute_name).and_return(true)
-              expect(foo).to receive('[]=').with(attribute_name, url)
               foo.hoge_remote_url = url
-            end
-          end
 
-          context 'unless has_attribute?' do
-            it 'calls not foo[attribute_name] = url' do
-              allow(foo).to receive(:has_attribute?)
-                .with(attribute_name).and_return(false)
-              expect(foo).not_to receive('[]=').with(attribute_name, url)
-              foo.hoge_remote_url = url
+              expect(foo).to receive(:public_send).with("#{hoge}=", string_io)
+              expect(foo).to receive(:public_send).with("#{hoge}_file_name=", basename + extname)
+
+              foo.download_hoge!
             end
           end
         end
@@ -205,26 +197,5 @@ RSpec.describe Remotable do
         end
       end
     end
-
-    describe '#reset_hoge!' do
-      context 'if url.blank?' do
-        it 'returns nil, without clearing foo[attribute_name] and calling #hoge_remote_url=' do
-          url = nil
-          expect(foo).not_to receive(:send).with(:hoge_remote_url=, url)
-          foo[attribute_name] = url
-          expect(foo.reset_hoge!).to be_nil
-          expect(foo[attribute_name]).to be_nil
-        end
-      end
-
-      context 'unless url.blank?' do
-        it 'clears foo[attribute_name] and calls #hoge_remote_url=' do
-          foo[attribute_name] = url
-          expect(foo).to receive(:send).with(:hoge_remote_url=, url)
-          foo.reset_hoge!
-          expect(foo[attribute_name]).to be ''
-        end
-      end
-    end
   end
 end

From d1c6dd2d277fee0cf65b480adc868c69df952622 Mon Sep 17 00:00:00 2001
From: trwnh <a@trwnh.com>
Date: Mon, 29 Jun 2020 06:57:44 -0500
Subject: [PATCH 06/29] Fix padding on account header (#14179)

---
 app/javascript/styles/mastodon/components.scss | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 7a22aeb04a..e22f712614 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -6497,7 +6497,7 @@ noscript {
   &__tabs {
     display: flex;
     align-items: flex-start;
-    padding: 7px 5px;
+    padding: 7px 10px;
     margin-top: -55px;
 
     &__buttons {
@@ -6519,7 +6519,7 @@ noscript {
     }
 
     &__name {
-      padding: 5px;
+      padding: 5px 10px;
 
       .account-role {
         vertical-align: top;

From 5e8f51b29fccfb7d19d53854f3472f7370593ebf Mon Sep 17 00:00:00 2001
From: Yamagishi Kazutoshi <ykzts@desire.sh>
Date: Mon, 29 Jun 2020 20:58:07 +0900
Subject: [PATCH 07/29] Replace to testing-library from enzyme (#14152)

---
 .../components/__tests__/button-test.js       |  10 +-
 .../ui/components/__tests__/column-test.js    |  15 +-
 app/javascript/mastodon/test_setup.js         |   6 +-
 package.json                                  |   4 +-
 yarn.lock                                     | 436 ++++++------------
 5 files changed, 159 insertions(+), 312 deletions(-)

diff --git a/app/javascript/mastodon/components/__tests__/button-test.js b/app/javascript/mastodon/components/__tests__/button-test.js
index 160cd3cbc7..f5a649f70e 100644
--- a/app/javascript/mastodon/components/__tests__/button-test.js
+++ b/app/javascript/mastodon/components/__tests__/button-test.js
@@ -1,4 +1,4 @@
-import { shallow } from 'enzyme';
+import { render, fireEvent, screen } from '@testing-library/react';
 import React from 'react';
 import renderer from 'react-test-renderer';
 import Button from '../button';
@@ -21,16 +21,16 @@ describe('<Button />', () => {
 
   it('handles click events using the given handler', () => {
     const handler = jest.fn();
-    const button  = shallow(<Button onClick={handler} />);
-    button.find('button').simulate('click');
+    render(<Button onClick={handler}>button</Button>);
+    fireEvent.click(screen.getByText('button'));
 
     expect(handler.mock.calls.length).toEqual(1);
   });
 
   it('does not handle click events if props.disabled given', () => {
     const handler = jest.fn();
-    const button  = shallow(<Button onClick={handler} disabled />);
-    button.find('button').simulate('click');
+    render(<Button onClick={handler} disabled>button</Button>);
+    fireEvent.click(screen.getByText('button'));
 
     expect(handler.mock.calls.length).toEqual(0);
   });
diff --git a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js
index d2791ce08d..a56859be07 100644
--- a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js
+++ b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js
@@ -1,25 +1,24 @@
+import { render, fireEvent, screen } from '@testing-library/react';
 import React from 'react';
-import { mount } from 'enzyme';
 import Column from '../column';
-import ColumnHeader from '../column_header';
 
 describe('<Column />', () => {
   describe('<ColumnHeader /> click handler', () => {
     it('runs the scroll animation if the column contains scrollable content', () => {
-      const wrapper = mount(
+      const scrollToMock = jest.fn();
+      const { container } = render(
         <Column heading='notifications'>
           <div className='scrollable' />
         </Column>,
       );
-      const scrollToMock = jest.fn();
-      wrapper.find(Column).find('.scrollable').getDOMNode().scrollTo = scrollToMock;
-      wrapper.find(ColumnHeader).find('button').simulate('click');
+      container.querySelector('.scrollable').scrollTo = scrollToMock;
+      fireEvent.click(screen.getByText('notifications'));
       expect(scrollToMock).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
     });
 
     it('does not try to scroll if there is no scrollable content', () => {
-      const wrapper = mount(<Column heading='notifications' />);
-      wrapper.find(ColumnHeader).find('button').simulate('click');
+      render(<Column heading='notifications' />);
+      fireEvent.click(screen.getByText('notifications'));
     });
   });
 });
diff --git a/app/javascript/mastodon/test_setup.js b/app/javascript/mastodon/test_setup.js
index 80148379b2..666127af39 100644
--- a/app/javascript/mastodon/test_setup.js
+++ b/app/javascript/mastodon/test_setup.js
@@ -1,5 +1 @@
-import { configure } from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
-
-const adapter = new Adapter();
-configure({ adapter });
+import '@testing-library/jest-dom/extend-expect';
diff --git a/package.json b/package.json
index 0f2e7f4f92..01cd3548f3 100644
--- a/package.json
+++ b/package.json
@@ -169,10 +169,10 @@
     "wicg-inert": "^3.0.3"
   },
   "devDependencies": {
+    "@testing-library/jest-dom": "^5.11.0",
+    "@testing-library/react": "^10.4.3",
     "babel-eslint": "^10.1.0",
     "babel-jest": "^25.2.4",
-    "enzyme": "^3.11.0",
-    "enzyme-adapter-react-16": "^1.15.2",
     "eslint": "^6.8.0",
     "eslint-plugin-import": "~2.21.2",
     "eslint-plugin-jsx-a11y": "~6.3.1",
diff --git a/yarn.lock b/yarn.lock
index df1cb0a734..10f401cfe1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -952,7 +952,7 @@
   dependencies:
     regenerator-runtime "^0.12.0"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
   version "7.10.3"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364"
   integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==
@@ -1370,6 +1370,40 @@
   dependencies:
     "@sinonjs/commons" "^1.7.0"
 
+"@testing-library/dom@^7.17.1":
+  version "7.18.1"
+  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.18.1.tgz#c49530410fb184522b3b59c4f9cd6397dc5b462d"
+  integrity sha512-tGq4KAFjaI7j375sMM1RRVleWA0viJWs/w69B+nyDkqYLNkhdTHdV6mGkspJlkn3PUfyBDi3rERDv4PA/LrpVA==
+  dependencies:
+    "@babel/runtime" "^7.10.3"
+    aria-query "^4.2.2"
+    dom-accessibility-api "^0.4.5"
+    pretty-format "^25.5.0"
+
+"@testing-library/jest-dom@^5.11.0":
+  version "5.11.0"
+  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.0.tgz#1439f08dc85ce7c6d3bbad0ee5d53b2206f55768"
+  integrity sha512-mhaCySy7dZlyfcxcYy+0jLllODHEiHkVdmwQ00wD0HrWiSx0fSVHz/0WmdlRkvhfSOuqsRsBUreXOtBvruWGQA==
+  dependencies:
+    "@babel/runtime" "^7.9.2"
+    "@types/testing-library__jest-dom" "^5.9.1"
+    aria-query "^4.2.2"
+    chalk "^3.0.0"
+    css "^2.2.4"
+    css.escape "^1.5.1"
+    jest-diff "^25.1.0"
+    jest-matcher-utils "^25.1.0"
+    lodash "^4.17.15"
+    redent "^3.0.0"
+
+"@testing-library/react@^10.4.3":
+  version "10.4.3"
+  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.4.3.tgz#c6f356688cffc51f6b35385583d664bb11a161f4"
+  integrity sha512-A/ydYXcwAcfY7vkPrfUkUTf9HQLL3/GtixTefcu3OyGQtAYQ7XBQj1S9FWbLEhfWa0BLwFwTBFS3Ao1O0tbMJg==
+  dependencies:
+    "@babel/runtime" "^7.10.3"
+    "@testing-library/dom" "^7.17.1"
+
 "@types/babel__core@^7.1.0", "@types/babel__core@^7.1.3":
   version "7.1.7"
   resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89"
@@ -1460,6 +1494,14 @@
     "@types/istanbul-lib-coverage" "*"
     "@types/istanbul-lib-report" "*"
 
+"@types/jest@*":
+  version "26.0.3"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.3.tgz#79534e0e94857171c0edc596db0ebe7cb7863251"
+  integrity sha512-v89ga1clpVL/Y1+YI0eIu1VMW+KU7Xl8PhylVtDKVWaSUHBHYPLXMQGBdrpHewaKoTvlXkksbYqPgz8b4cmRZg==
+  dependencies:
+    jest-diff "^25.2.1"
+    pretty-format "^25.2.1"
+
 "@types/json-schema@^7.0.4":
   version "7.0.4"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
@@ -1510,6 +1552,13 @@
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
   integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
 
+"@types/testing-library__jest-dom@^5.9.1":
+  version "5.9.1"
+  resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.1.tgz#aba5ee062b7880f69c212ef769389f30752806e5"
+  integrity sha512-yYn5EKHO3MPEMSOrcAb1dLWY+68CG29LiXKsWmmpVHqoP5+ZRiAVLyUHvPNrO2dABDdUGZvavMsaGpWNjM6N2g==
+  dependencies:
+    "@types/jest" "*"
+
 "@types/yargs-parser@*":
   version "15.0.0"
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -1743,22 +1792,6 @@ aggregate-error@^3.0.0:
     clean-stack "^2.0.0"
     indent-string "^4.0.0"
 
-airbnb-prop-types@^2.15.0:
-  version "2.15.0"
-  resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef"
-  integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA==
-  dependencies:
-    array.prototype.find "^2.1.0"
-    function.prototype.name "^1.1.1"
-    has "^1.0.3"
-    is-regex "^1.0.4"
-    object-is "^1.0.1"
-    object.assign "^4.1.0"
-    object.entries "^1.1.0"
-    prop-types "^15.7.2"
-    prop-types-exact "^1.2.0"
-    react-is "^16.9.0"
-
 ajv-errors@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
@@ -1918,11 +1951,6 @@ arr-union@^3.1.0:
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
   integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
 
-array-filter@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
-  integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
-
 array-flatten@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@@ -1964,14 +1992,6 @@ array-unique@^0.3.2:
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
   integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
 
-array.prototype.find@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c"
-  integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.4"
-
 array.prototype.flat@^1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b"
@@ -2773,18 +2793,6 @@ check-types@^8.0.3:
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
   integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
 
-cheerio@^1.0.0-rc.3:
-  version "1.0.0-rc.3"
-  resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
-  integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==
-  dependencies:
-    css-select "~1.2.0"
-    dom-serializer "~0.1.1"
-    entities "~1.1.1"
-    htmlparser2 "^3.9.1"
-    lodash "^4.15.0"
-    parse5 "^3.0.1"
-
 "chokidar@>=2.0.0 <4.0.0", chokidar@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8"
@@ -2999,7 +3007,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1:
+commander@^2.18.0, commander@^2.20.0, commander@^2.8.1:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -3355,16 +3363,6 @@ css-select@^2.0.0:
     domutils "^1.7.0"
     nth-check "^1.0.2"
 
-css-select@~1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
-  integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=
-  dependencies:
-    boolbase "~1.0.0"
-    css-what "2.1"
-    domutils "1.5.1"
-    nth-check "~1.0.1"
-
 css-system-font-keywords@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed"
@@ -3386,16 +3384,26 @@ css-tree@1.0.0-alpha.39:
     mdn-data "2.0.6"
     source-map "^0.6.1"
 
-css-what@2.1:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
-  integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
-
 css-what@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1"
   integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==
 
+css.escape@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
+  integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
+
+css@^2.2.4:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
+  integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
+  dependencies:
+    inherits "^2.0.3"
+    source-map "^0.6.1"
+    source-map-resolve "^0.5.2"
+    urix "^0.1.0"
+
 cssesc@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@@ -3705,6 +3713,11 @@ detect-passive-events@^1.0.2:
   resolved "https://registry.yarnpkg.com/detect-passive-events/-/detect-passive-events-1.0.4.tgz#6ed477e6e5bceb79079735dcd357789d37f9a91a"
   integrity sha1-btR35uW863kHlzXc01d4nTf5qRo=
 
+diff-sequences@^25.2.6:
+  version "25.2.6"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
+  integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
+
 diff-sequences@^26.0.0:
   version "26.0.0"
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.0.0.tgz#0760059a5c287637b842bd7085311db7060e88a6"
@@ -3726,11 +3739,6 @@ dir-glob@^3.0.1:
   dependencies:
     path-type "^4.0.0"
 
-discontinuous-range@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
-  integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=
-
 dns-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -3773,6 +3781,11 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+dom-accessibility-api@^0.4.5:
+  version "0.4.5"
+  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.5.tgz#d9c1cefa89f509d8cf132ab5d250004d755e76e3"
+  integrity sha512-HcPDilI95nKztbVikaN2vzwvmv0sE8Y2ZJFODy/m15n7mGXLeOKGiys9qWVbFbh+aq/KYj2lqMLybBOkYAEXqg==
+
 dom-helpers@^3.2.1, dom-helpers@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
@@ -3796,20 +3809,12 @@ dom-serializer@0:
     domelementtype "^2.0.1"
     entities "^2.0.0"
 
-dom-serializer@~0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
-  integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
-  dependencies:
-    domelementtype "^1.3.0"
-    entities "^1.1.1"
-
 domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
   integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
 
-domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
+domelementtype@1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
   integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
@@ -3826,22 +3831,7 @@ domexception@^2.0.1:
   dependencies:
     webidl-conversions "^5.0.0"
 
-domhandler@^2.3.0:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
-  integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
-  dependencies:
-    domelementtype "1"
-
-domutils@1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
-  integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=
-  dependencies:
-    dom-serializer "0"
-    domelementtype "1"
-
-domutils@^1.5.1, domutils@^1.7.0:
+domutils@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
   integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
@@ -3962,79 +3952,11 @@ enhanced-resolve@4.1.0, enhanced-resolve@^4.1.0:
     memory-fs "^0.4.0"
     tapable "^1.0.0"
 
-entities@^1.1.1, entities@~1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
-  integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
-
 entities@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
   integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
 
-enzyme-adapter-react-16@^1.15.2:
-  version "1.15.2"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501"
-  integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==
-  dependencies:
-    enzyme-adapter-utils "^1.13.0"
-    enzyme-shallow-equal "^1.0.1"
-    has "^1.0.3"
-    object.assign "^4.1.0"
-    object.values "^1.1.1"
-    prop-types "^15.7.2"
-    react-is "^16.12.0"
-    react-test-renderer "^16.0.0-0"
-    semver "^5.7.0"
-
-enzyme-adapter-utils@^1.13.0:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78"
-  integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ==
-  dependencies:
-    airbnb-prop-types "^2.15.0"
-    function.prototype.name "^1.1.2"
-    object.assign "^4.1.0"
-    object.fromentries "^2.0.2"
-    prop-types "^15.7.2"
-    semver "^5.7.1"
-
-enzyme-shallow-equal@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e"
-  integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==
-  dependencies:
-    has "^1.0.3"
-    object-is "^1.0.2"
-
-enzyme@^3.11.0:
-  version "3.11.0"
-  resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28"
-  integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==
-  dependencies:
-    array.prototype.flat "^1.2.3"
-    cheerio "^1.0.0-rc.3"
-    enzyme-shallow-equal "^1.0.1"
-    function.prototype.name "^1.1.2"
-    has "^1.0.3"
-    html-element-map "^1.2.0"
-    is-boolean-object "^1.0.1"
-    is-callable "^1.1.5"
-    is-number-object "^1.0.4"
-    is-regex "^1.0.5"
-    is-string "^1.0.5"
-    is-subset "^0.1.1"
-    lodash.escape "^4.0.1"
-    lodash.isequal "^4.5.0"
-    object-inspect "^1.7.0"
-    object-is "^1.0.2"
-    object.assign "^4.1.0"
-    object.entries "^1.1.1"
-    object.values "^1.1.1"
-    raf "^3.4.1"
-    rst-selector-parser "^2.2.3"
-    string.prototype.trim "^1.2.1"
-
 errno@^0.1.3, errno@~0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@@ -4056,7 +3978,7 @@ error-stack-parser@^2.0.6:
   dependencies:
     stackframe "^1.1.1"
 
-es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5:
+es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
   version "1.17.6"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
   integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
@@ -4993,25 +4915,11 @@ function-bind@^1.1.1:
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
-function.prototype.name@^1.1.1, function.prototype.name@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45"
-  integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.0-next.1"
-    functions-have-names "^1.2.0"
-
 functional-red-black-tree@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
   integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
 
-functions-have-names@^1.2.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91"
-  integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==
-
 gauge@~2.7.3:
   version "2.7.4"
   resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -5419,13 +5327,6 @@ html-comment-regex@^1.1.0:
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
   integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
 
-html-element-map@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22"
-  integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==
-  dependencies:
-    array-filter "^1.0.0"
-
 html-encoding-sniffer@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
@@ -5443,18 +5344,6 @@ html-escaper@^2.0.0:
   resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
   integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
 
-htmlparser2@^3.9.1:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
-  integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
-  dependencies:
-    domelementtype "^1.3.1"
-    domhandler "^2.3.0"
-    domutils "^1.5.1"
-    entities "^1.1.1"
-    inherits "^2.0.1"
-    readable-stream "^3.1.1"
-
 http-deceiver@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@@ -5877,12 +5766,7 @@ is-binary-path@~2.1.0:
   dependencies:
     binary-extensions "^2.0.0"
 
-is-boolean-object@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
-  integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==
-
-is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.0:
+is-callable@^1.1.4, is-callable@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
   integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
@@ -6034,11 +5918,6 @@ is-nan@^1.3.0:
   dependencies:
     define-properties "^1.1.3"
 
-is-number-object@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
-  integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
-
 is-number@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@@ -6102,7 +5981,7 @@ is-property@^1.0.0, is-property@^1.0.2:
   resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
   integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
 
-is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0:
+is-regex@^1.0.4, is-regex@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
   integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
@@ -6129,11 +6008,6 @@ is-string@^1.0.5:
   resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
   integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
 
-is-subset@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
-  integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=
-
 is-svg@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
@@ -6300,6 +6174,16 @@ jest-config@^26.0.1:
     micromatch "^4.0.2"
     pretty-format "^26.0.1"
 
+jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.5.0:
+  version "25.5.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
+  integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
+  dependencies:
+    chalk "^3.0.0"
+    diff-sequences "^25.2.6"
+    jest-get-type "^25.2.6"
+    pretty-format "^25.5.0"
+
 jest-diff@^26.0.1:
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.0.1.tgz#c44ab3cdd5977d466de69c46929e0e57f89aa1de"
@@ -6351,6 +6235,11 @@ jest-environment-node@^26.0.1:
     jest-mock "^26.0.1"
     jest-util "^26.0.1"
 
+jest-get-type@^25.2.6:
+  version "25.2.6"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
+  integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
+
 jest-get-type@^26.0.0:
   version "26.0.0"
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.0.0.tgz#381e986a718998dbfafcd5ec05934be538db4039"
@@ -6427,6 +6316,16 @@ jest-leak-detector@^26.0.1:
     jest-get-type "^26.0.0"
     pretty-format "^26.0.1"
 
+jest-matcher-utils@^25.1.0:
+  version "25.5.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867"
+  integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==
+  dependencies:
+    chalk "^3.0.0"
+    jest-diff "^25.5.0"
+    jest-get-type "^25.2.6"
+    pretty-format "^25.5.0"
+
 jest-matcher-utils@^26.0.1:
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.0.1.tgz#12e1fc386fe4f14678f4cc8dbd5ba75a58092911"
@@ -6994,16 +6893,6 @@ lodash.defaults@^4.0.1:
   resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
   integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
 
-lodash.escape@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
-  integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
-
-lodash.flattendeep@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
-  integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
-
 lodash.get@^4.0:
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@@ -7049,7 +6938,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.3.0, lodash@~4.17.12:
+lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.3.0, lodash@~4.17.12:
   version "4.17.15"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
   integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -7257,6 +7146,11 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0:
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
+min-indent@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
+  integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
+
 mini-css-extract-plugin@^0.9.0:
   version "0.9.0"
   resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
@@ -7366,11 +7260,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
-moo@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
-  integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
-
 mousetrap@^1.5.2:
   version "1.6.5"
   resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
@@ -7453,17 +7342,6 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
-nearley@^2.7.10:
-  version "2.19.4"
-  resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.4.tgz#7518cbdd7d0e8e08b5f82841b9edb0126239c8b1"
-  integrity sha512-oqj3m4oqwKsN77pETa9IPvxHHHLW68KrDc2KYoWMUOhDlrNUo7finubwffQMBRnwNCOXc4kRxCZO0Rvx4L6Zrw==
-  dependencies:
-    commander "^2.19.0"
-    moo "^0.5.0"
-    railroad-diagrams "^1.0.0"
-    randexp "0.4.6"
-    semver "^5.4.1"
-
 negotiator@0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -7616,7 +7494,7 @@ npmlog@^4.1.2:
     gauge "~2.7.3"
     set-blocking "~2.0.0"
 
-nth-check@^1.0.2, nth-check@~1.0.1:
+nth-check@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
   integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
@@ -7672,7 +7550,7 @@ object-inspect@^1.7.0:
   resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
   integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
 
-object-is@^1.0.1, object-is@^1.0.2:
+object-is@^1.0.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6"
   integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==
@@ -7702,7 +7580,7 @@ object.assign@^4.1.0:
     has-symbols "^1.0.0"
     object-keys "^1.0.11"
 
-object.entries@^1.1.0, object.entries@^1.1.1:
+object.entries@^1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add"
   integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==
@@ -8025,13 +7903,6 @@ parse5@5.1.1:
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
   integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
 
-parse5@^3.0.1:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
-  integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==
-  dependencies:
-    "@types/node" "*"
-
 parseurl@~1.3.2, parseurl@~1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -8654,6 +8525,16 @@ prepend-http@^1.0.0:
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
   integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
 
+pretty-format@^25.2.1, pretty-format@^25.5.0:
+  version "25.5.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
+  integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
+  dependencies:
+    "@jest/types" "^25.5.0"
+    ansi-regex "^5.0.0"
+    ansi-styles "^4.0.0"
+    react-is "^16.12.0"
+
 pretty-format@^26.0.1:
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.0.1.tgz#a4fe54fe428ad2fd3413ca6bbd1ec8c2e277e197"
@@ -8711,15 +8592,6 @@ prompts@^2.0.1:
     kleur "^3.0.3"
     sisteransi "^1.0.4"
 
-prop-types-exact@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869"
-  integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==
-  dependencies:
-    has "^1.0.3"
-    object.assign "^4.1.0"
-    reflect.ownkeys "^0.2.0"
-
 prop-types-extra@^1.0.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b"
@@ -8857,19 +8729,6 @@ raf@^3.1.0, raf@^3.4.1:
   dependencies:
     performance-now "^2.1.0"
 
-railroad-diagrams@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
-  integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=
-
-randexp@0.4.6:
-  version "0.4.6"
-  resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
-  integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==
-  dependencies:
-    discontinuous-range "1.0.0"
-    ret "~0.1.10"
-
 randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -9128,7 +8987,7 @@ react-swipeable-views@^0.13.9:
     react-swipeable-views-utils "^0.13.9"
     warning "^4.0.1"
 
-react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1:
+react-test-renderer@^16.13.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
   integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==
@@ -9232,7 +9091,7 @@ read-pkg@^5.2.0:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0:
+readable-stream@^3.0.6, readable-stream@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -9271,6 +9130,14 @@ realpath-native@^2.0.0:
   resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866"
   integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==
 
+redent@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
+  integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
+  dependencies:
+    indent-string "^4.0.0"
+    strip-indent "^3.0.0"
+
 redis-commands@^1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
@@ -9316,11 +9183,6 @@ redux@^4.0.5:
     loose-envify "^1.4.0"
     symbol-observable "^1.2.0"
 
-reflect.ownkeys@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
-  integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=
-
 regenerate-unicode-properties@^8.2.0:
   version "8.2.0"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@@ -9640,14 +9502,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     hash-base "^3.0.0"
     inherits "^2.0.1"
 
-rst-selector-parser@^2.2.3:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
-  integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=
-  dependencies:
-    lodash.flattendeep "^4.4.0"
-    nearley "^2.7.10"
-
 rsvp@^4.8.4:
   version "4.8.5"
   resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
@@ -9824,7 +9678,7 @@ selfsigned@^1.10.7:
   dependencies:
     node-forge "0.9.0"
 
-"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
+"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -10099,7 +9953,7 @@ source-list-map@^2.0.0:
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
   integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
 
-source-map-resolve@^0.5.0:
+source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
   version "0.5.3"
   resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
   integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==
@@ -10391,15 +10245,6 @@ string.prototype.matchall@^4.0.2:
     regexp.prototype.flags "^1.3.0"
     side-channel "^1.0.2"
 
-string.prototype.trim@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz#141233dff32c82bfad80684d7e5f0869ee0fb782"
-  integrity sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.0-next.1"
-    function-bind "^1.1.1"
-
 string.prototype.trimend@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
@@ -10485,6 +10330,13 @@ strip-final-newline@^2.0.0:
   resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
   integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
 
+strip-indent@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
+  integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
+  dependencies:
+    min-indent "^1.0.0"
+
 strip-json-comments@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"

From 6d3125f9c0127095913a9f1dfd7043929ba3bb9d Mon Sep 17 00:00:00 2001
From: Alex Dunn <dunn.alex@gmail.com>
Date: Mon, 29 Jun 2020 04:58:48 -0700
Subject: [PATCH 08/29] Add Helm chart (#14090)

* add Helm chart

known issues/future work:

- SSO is unsupported

- S3/Minio/GCS is unsupported

- Swift is unsupported

- WEB_DOMAIN is unsupported

- Tor is unsupported

* helm: clarify how LOCAL_DOMAIN is set

* helm: add chart description

* helm: make DB_POOL and Sidekiq concurrency configurable

* helm: only enforce pod affinity when using ReadWriteOnce

* helm: clarify compatibility

* helm: clean up application variables

* helm: add job to create initial admin
---
 .gitignore                                 |   5 +
 chart/.helmignore                          |  23 +++
 chart/Chart.yaml                           |  35 +++++
 chart/readme.md                            |  44 ++++++
 chart/templates/NOTES.txt                  |  21 +++
 chart/templates/_helpers.tpl               |  79 ++++++++++
 chart/templates/configmap-env.yaml         |  65 ++++++++
 chart/templates/deployment-sidekiq.yaml    |  97 ++++++++++++
 chart/templates/deployment-streaming.yaml  |  80 ++++++++++
 chart/templates/deployment-web.yaml        | 101 +++++++++++++
 chart/templates/hpa.yaml                   |  28 ++++
 chart/templates/ingress.yaml               |  41 ++++++
 chart/templates/job-assets-precompile.yaml |  69 +++++++++
 chart/templates/job-chewy-upgrade.yaml     |  71 +++++++++
 chart/templates/job-create-admin.yaml      |  76 ++++++++++
 chart/templates/job-db-migrate.yaml        |  69 +++++++++
 chart/templates/pvc-assets.yaml            |  13 ++
 chart/templates/pvc-system.yaml            |  13 ++
 chart/templates/secrets.yaml               |  28 ++++
 chart/templates/service-streaming.yaml     |  15 ++
 chart/templates/service-web.yaml           |  15 ++
 chart/templates/serviceaccount.yaml        |  12 ++
 chart/templates/tests/test-connection.yaml |  15 ++
 chart/values.yaml.template                 | 163 +++++++++++++++++++++
 24 files changed, 1178 insertions(+)
 create mode 100644 chart/.helmignore
 create mode 100644 chart/Chart.yaml
 create mode 100644 chart/readme.md
 create mode 100644 chart/templates/NOTES.txt
 create mode 100644 chart/templates/_helpers.tpl
 create mode 100644 chart/templates/configmap-env.yaml
 create mode 100644 chart/templates/deployment-sidekiq.yaml
 create mode 100644 chart/templates/deployment-streaming.yaml
 create mode 100644 chart/templates/deployment-web.yaml
 create mode 100644 chart/templates/hpa.yaml
 create mode 100644 chart/templates/ingress.yaml
 create mode 100644 chart/templates/job-assets-precompile.yaml
 create mode 100644 chart/templates/job-chewy-upgrade.yaml
 create mode 100644 chart/templates/job-create-admin.yaml
 create mode 100644 chart/templates/job-db-migrate.yaml
 create mode 100644 chart/templates/pvc-assets.yaml
 create mode 100644 chart/templates/pvc-system.yaml
 create mode 100644 chart/templates/secrets.yaml
 create mode 100644 chart/templates/service-streaming.yaml
 create mode 100644 chart/templates/service-web.yaml
 create mode 100644 chart/templates/serviceaccount.yaml
 create mode 100644 chart/templates/tests/test-connection.yaml
 create mode 100644 chart/values.yaml.template

diff --git a/.gitignore b/.gitignore
index ea61b2724c..9f6c4b4137 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,11 @@ postgres
 redis
 elasticsearch
 
+# ignore Helm lockfile, dependency charts, and local values file
+chart/Chart.lock
+chart/charts/*.tgz
+chart/values.yaml
+
 # Ignore Apple files
 .DS_Store
 
diff --git a/chart/.helmignore b/chart/.helmignore
new file mode 100644
index 0000000000..0e8a0eb36f
--- /dev/null
+++ b/chart/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/chart/Chart.yaml b/chart/Chart.yaml
new file mode 100644
index 0000000000..f028227e35
--- /dev/null
+++ b/chart/Chart.yaml
@@ -0,0 +1,35 @@
+apiVersion: v2
+name: mastodon
+description: Mastodon is a free, open-source social network server based on ActivityPub.
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+appVersion: 3.1.4
+
+dependencies:
+  - name: elasticsearch
+    version: "12.x.x"
+    repository: https://charts.bitnami.com/bitnami
+    condition: elasticsearch.enabled
+  - name: postgresql
+    version: "8.x.x"
+    repository: https://charts.bitnami.com/bitnami
+  - name: redis
+    version: "10.x.x"
+    repository: https://charts.bitnami.com/bitnami
diff --git a/chart/readme.md b/chart/readme.md
new file mode 100644
index 0000000000..804e980949
--- /dev/null
+++ b/chart/readme.md
@@ -0,0 +1,44 @@
+# Introduction
+
+This is a [Helm](https://helm.sh/) chart for installing Mastodon into a
+Kubernetes cluster.  The basic usage is:
+
+```
+cp values.yaml.template values.yaml
+edit values.yaml # configure required settings
+helm dep update
+helm upgrade --install my-mastodon ./
+```
+
+This chart has been tested on Helm 3.0.1 and above.
+
+# Configuration
+
+The variables that _must_ be configured are:
+
+- `ingress.hostname`; even if you aren’t using an Ingress, this value is used to
+  set `LOCAL_DOMAIN`.
+
+- password and keys in the `secrets`, `postgresql`, and `redis` groups; if
+  left blank, some of those values will be autogenerated, but will not persist
+  across upgrades.
+
+- SMTP settings for your mailer in the `smtp` group.
+
+# Missing features
+
+Currently this chart does _not_ support:
+
+- Hidden services
+- S3/Minio/GCS
+- Single Sign-On
+- Swift
+- configurations using `WEB_DOMAIN`
+
+# Upgrading
+
+Because database migrations are managed as a Job separate from the Rails and
+Sidekiq deployments, it’s possible they will occur in the wrong order.  After
+upgrading Mastodon versions, it may sometimes be necessary to manually delete
+the Rails and Sidekiq pods so that they are recreated against the latest
+migration.
diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt
new file mode 100644
index 0000000000..36cced67a5
--- /dev/null
+++ b/chart/templates/NOTES.txt
@@ -0,0 +1,21 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+  {{- range .paths }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
+  {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "mastodon.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "mastodon.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "mastodon.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mastodon.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
+{{- end }}
diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl
new file mode 100644
index 0000000000..5814a31203
--- /dev/null
+++ b/chart/templates/_helpers.tpl
@@ -0,0 +1,79 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "mastodon.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "mastodon.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "mastodon.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "mastodon.labels" -}}
+helm.sh/chart: {{ include "mastodon.chart" . }}
+{{ include "mastodon.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "mastodon.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "mastodon.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "mastodon.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "mastodon.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create a default fully qualified name for dependent services.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+*/}}
+{{- define "mastodon.elasticsearch.fullname" -}}
+{{- printf "%s-%s" .Release.Name "elasticsearch" | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{- define "mastodon.redis.fullname" -}}
+{{- printf "%s-%s" .Release.Name "redis" | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{- define "mastodon.postgresql.fullname" -}}
+{{- printf "%s-%s" .Release.Name "postgresql" | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
diff --git a/chart/templates/configmap-env.yaml b/chart/templates/configmap-env.yaml
new file mode 100644
index 0000000000..27351e97ef
--- /dev/null
+++ b/chart/templates/configmap-env.yaml
@@ -0,0 +1,65 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "mastodon.fullname" . }}-env
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+data:
+  DB_HOST: {{ template "mastodon.postgresql.fullname" . }}
+  DB_NAME: {{ .Values.postgresql.postgresqlDatabase }}
+  DB_POOL: {{ .Values.application.sidekiq.concurrency | quote }}
+  DB_PORT: "5432"
+  DB_USER: {{ .Values.postgresql.postgresqlUsername }}
+  DEFAULT_LOCALE: {{ .Values.locale }}
+  {{- if .Values.elasticsearch.enabled }}
+  ES_ENABLED: "true"
+  ES_HOST: {{ template "mastodon.elasticsearch.fullname" . }}-master
+  ES_PORT: "9200"
+  {{- end }}
+  LOCAL_DOMAIN: {{ .Values.ingress.hostname }}
+  # https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior
+  MALLOC_ARENA_MAX: "2"
+  NODE_ENV: "production"
+  RAILS_ENV: "production"
+  REDIS_HOST: {{ template "mastodon.redis.fullname" . }}-master
+  REDIS_PORT: "6379"
+  {{- if .Values.smtp.auth_method }}
+  SMTP_AUTH_METHOD: {{ .Values.smtp.auth_method }}
+  {{- end }}
+  {{- if .Values.smtp.ca_file }}
+  SMTP_CA_FILE: {{ .Values.smtp.ca_file }}
+  {{- end }}
+  {{- if .Values.smtp.delivery_method }}
+  SMTP_DELIVERY_METHOD: {{ .Values.smtp.delivery_method }}
+  {{- end }}
+  {{- if .Values.smtp.domain }}
+  SMTP_DOMAIN: {{ .Values.smtp.domain }}
+  {{- end }}
+  {{- if .Values.smtp.enable_starttls_auto }}
+  SMTP_ENABLE_STARTTLS_AUTO: {{ .Values.smtp.enable_starttls_auto | quote }}
+  {{- end }}
+  {{- if .Values.smtp.from_address }}
+  SMTP_FROM_ADDRESS: {{ .Values.smtp.from_address }}
+  {{- end }}
+  {{- if .Values.smtp.login }}
+  SMTP_LOGIN: {{ .Values.smtp.login }}
+  {{- end }}
+  {{- if .Values.smtp.openssl_verify_mode }}
+  SMTP_OPENSSL_VERIFY_MODE: {{ .Values.smtp.openssl_verify_mode }}
+  {{- end }}
+  {{- if .Values.smtp.password }}
+  SMTP_PASSWORD: {{ .Values.smtp.password }}
+  {{- end }}
+  {{- if .Values.smtp.port }}
+  SMTP_PORT: {{ .Values.smtp.port | quote }}
+  {{- end }}
+  {{- if .Values.smtp.reply_to }}
+  SMTP_REPLY_TO: {{ .Values.smtp.reply_to }}
+  {{- end }}
+  {{- if .Values.smtp.server }}
+  SMTP_SERVER: {{ .Values.smtp.server }}
+  {{- end }}
+  {{- if .Values.smtp.tls }}
+  SMTP_TLS: {{ .Values.smtp.tls | quote }}
+  {{- end }}
+  STREAMING_CLUSTER_NUM: {{ .Values.application.streaming.workers | quote }}
diff --git a/chart/templates/deployment-sidekiq.yaml b/chart/templates/deployment-sidekiq.yaml
new file mode 100644
index 0000000000..5457183a3c
--- /dev/null
+++ b/chart/templates/deployment-sidekiq.yaml
@@ -0,0 +1,97 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "mastodon.fullname" . }}-sidekiq
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+{{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+{{- end }}
+  selector:
+    matchLabels:
+      {{- include "mastodon.selectorLabels" . | nindent 6 }}
+      component: rails
+  template:
+    metadata:
+    {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+        # roll the pods to pick up any db migrations
+        rollme: {{ randAlphaNum 5 | quote }}
+    {{- end }}
+      labels:
+        {{- include "mastodon.selectorLabels" . | nindent 8 }}
+        component: rails
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "mastodon.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      # ensure we run on the same node as the other rails components; only
+      # required when using PVCs that are ReadWriteOnce
+      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+                - key: component
+                  operator: In
+                  values:
+                    - rails
+            topologyKey: kubernetes.io/hostname
+      {{- end }}
+      volumes:
+        - name: assets
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-assets
+        - name: system
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-system
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command:
+            - bundle
+            - exec
+            - sidekiq
+            - -c
+            - {{ .Values.application.sidekiq.concurrency | quote }}
+          envFrom:
+            - configMapRef:
+                name: {{ include "mastodon.fullname" . }}-env
+            - secretRef:
+                name: {{ template "mastodon.fullname" . }}
+          env:
+            - name: "DB_PASS"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-postgresql
+                  key: postgresql-password
+            - name: "REDIS_PASSWORD"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-redis
+                  key: redis-password
+          volumeMounts:
+            - name: assets
+              mountPath: /opt/mastodon/public/assets
+            - name: system
+              mountPath: /opt/mastodon/public/system
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/chart/templates/deployment-streaming.yaml b/chart/templates/deployment-streaming.yaml
new file mode 100644
index 0000000000..5d642d72c7
--- /dev/null
+++ b/chart/templates/deployment-streaming.yaml
@@ -0,0 +1,80 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "mastodon.fullname" . }}-streaming
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+{{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+{{- end }}
+  selector:
+    matchLabels:
+      {{- include "mastodon.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+    {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+    {{- end }}
+      labels:
+        {{- include "mastodon.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "mastodon.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command:
+            - node
+            - ./streaming
+          envFrom:
+            - configMapRef:
+                name: {{ include "mastodon.fullname" . }}-env
+          env:
+            - name: "DB_PASS"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-postgresql
+                  key: postgresql-password
+            - name: "REDIS_PASSWORD"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-redis
+                  key: redis-password
+            - name: "PORT"
+              value: {{ .Values.application.streaming.port | quote }}
+          ports:
+            - name: streaming
+              containerPort: {{ .Values.application.streaming.port }}
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              path: /api/v1/streaming/health
+              port: streaming
+          readinessProbe:
+            httpGet:
+              path: /api/v1/streaming/health
+              port: streaming
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/chart/templates/deployment-web.yaml b/chart/templates/deployment-web.yaml
new file mode 100644
index 0000000000..5010e567a8
--- /dev/null
+++ b/chart/templates/deployment-web.yaml
@@ -0,0 +1,101 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "mastodon.fullname" . }}-web
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+{{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+{{- end }}
+  selector:
+    matchLabels:
+      {{- include "mastodon.selectorLabels" . | nindent 6 }}
+      component: rails
+  template:
+    metadata:
+    {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+        # roll the pods to pick up any db migrations
+        rollme: {{ randAlphaNum 5 | quote }}
+    {{- end }}
+      labels:
+        {{- include "mastodon.selectorLabels" . | nindent 8 }}
+        component: rails
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "mastodon.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      volumes:
+        - name: assets
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-assets
+        - name: system
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-system
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command:
+            - bundle
+            - exec
+            - puma
+            - -C
+            - config/puma.rb
+          envFrom:
+            - configMapRef:
+                name: {{ include "mastodon.fullname" . }}-env
+            - secretRef:
+                name: {{ template "mastodon.fullname" . }}
+          env:
+            - name: "DB_PASS"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-postgresql
+                  key: postgresql-password
+            - name: "REDIS_PASSWORD"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-redis
+                  key: redis-password
+            - name: "PORT"
+              value: {{ .Values.application.web.port | quote }}
+          volumeMounts:
+            - name: assets
+              mountPath: /opt/mastodon/public/assets
+            - name: system
+              mountPath: /opt/mastodon/public/system
+          ports:
+            - name: http
+              containerPort: {{ .Values.application.web.port }}
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              path: /health
+              port: http
+          readinessProbe:
+            httpGet:
+              path: /health
+              port: http
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/chart/templates/hpa.yaml b/chart/templates/hpa.yaml
new file mode 100644
index 0000000000..3f9aa8a93b
--- /dev/null
+++ b/chart/templates/hpa.yaml
@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta1
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "mastodon.fullname" . }}
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "mastodon.fullname" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+  {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+  {{- end }}
+  {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: memory
+        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+  {{- end }}
+{{- end }}
diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml
new file mode 100644
index 0000000000..947bf5b70b
--- /dev/null
+++ b/chart/templates/ingress.yaml
@@ -0,0 +1,41 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "mastodon.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+  {{- with .Values.ingress.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  {{- if .Values.ingress.tls }}
+  tls:
+    {{- range .Values.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  rules:
+    - host: {{ .Values.ingress.hostname | quote }}
+      http:
+        paths:
+          - path: '/'
+            backend:
+              serviceName: {{ $fullName }}-web
+              servicePort: {{ $svcPort }}
+          - path: '/api/v1/streaming'
+            backend:
+              serviceName: {{ $fullName }}-streaming
+              servicePort: {{ .Values.application.streaming.port }}
+{{- end }}
diff --git a/chart/templates/job-assets-precompile.yaml b/chart/templates/job-assets-precompile.yaml
new file mode 100644
index 0000000000..5472e06d6c
--- /dev/null
+++ b/chart/templates/job-assets-precompile.yaml
@@ -0,0 +1,69 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: {{ include "mastodon.fullname" . }}-assets-precompile
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": post-install
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-weight": "-2"
+spec:
+  template:
+    metadata:
+      name: {{ include "mastodon.fullname" . }}-assets-precompile
+    spec:
+      restartPolicy: Never
+      # ensure we run on the same node as the other rails components; only
+      # required when using PVCs that are ReadWriteOnce
+      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+                - key: component
+                  operator: In
+                  values:
+                    - rails
+            topologyKey: kubernetes.io/hostname
+      {{- end }}
+      volumes:
+        - name: assets
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-assets
+        - name: system
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-system
+      containers:
+        - name: {{ include "mastodon.fullname" . }}-assets-precompile
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command:
+            - bash
+            - -c
+            - |
+                bundle exec rake assets:precompile && yarn cache clean
+          envFrom:
+            - configMapRef:
+                name: {{ include "mastodon.fullname" . }}-env
+            - secretRef:
+                name: {{ template "mastodon.fullname" . }}
+          env:
+            - name: "DB_PASS"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-postgresql
+                  key: postgresql-password
+            - name: "REDIS_PASSWORD"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-redis
+                  key: redis-password
+            - name: "PORT"
+              value: {{ .Values.application.web.port | quote }}
+          volumeMounts:
+            - name: assets
+              mountPath: /opt/mastodon/public/assets
+            - name: system
+              mountPath: /opt/mastodon/public/system
diff --git a/chart/templates/job-chewy-upgrade.yaml b/chart/templates/job-chewy-upgrade.yaml
new file mode 100644
index 0000000000..789fcff837
--- /dev/null
+++ b/chart/templates/job-chewy-upgrade.yaml
@@ -0,0 +1,71 @@
+{{- if .Values.elasticsearch.enabled }}
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: {{ include "mastodon.fullname" . }}-chewy-upgrade
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": post-install
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-weight": "-1"
+spec:
+  template:
+    metadata:
+      name: {{ include "mastodon.fullname" . }}-chewy-upgrade
+    spec:
+      restartPolicy: Never
+      # ensure we run on the same node as the other rails components; only
+      # required when using PVCs that are ReadWriteOnce
+      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+                - key: component
+                  operator: In
+                  values:
+                    - rails
+            topologyKey: kubernetes.io/hostname
+      {{- end }}
+      volumes:
+        - name: assets
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-assets
+        - name: system
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-system
+      containers:
+        - name: {{ include "mastodon.fullname" . }}-chewy-setup
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command:
+            - bundle
+            - exec
+            - rake
+            - chewy:upgrade
+          envFrom:
+            - configMapRef:
+                name: {{ include "mastodon.fullname" . }}-env
+            - secretRef:
+                name: {{ template "mastodon.fullname" . }}
+          env:
+            - name: "DB_PASS"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-postgresql
+                  key: postgresql-password
+            - name: "REDIS_PASSWORD"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-redis
+                  key: redis-password
+            - name: "PORT"
+              value: {{ .Values.application.web.port | quote }}
+          volumeMounts:
+            - name: assets
+              mountPath: /opt/mastodon/public/assets
+            - name: system
+              mountPath: /opt/mastodon/public/system
+{{- end }}
diff --git a/chart/templates/job-create-admin.yaml b/chart/templates/job-create-admin.yaml
new file mode 100644
index 0000000000..3c5bdd6eb2
--- /dev/null
+++ b/chart/templates/job-create-admin.yaml
@@ -0,0 +1,76 @@
+{{- if .Values.createAdmin.enabled }}
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: {{ include "mastodon.fullname" . }}-create-admin
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": post-install
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-weight": "-1"
+spec:
+  template:
+    metadata:
+      name: {{ include "mastodon.fullname" . }}-create-admin
+    spec:
+      restartPolicy: Never
+      # ensure we run on the same node as the other rails components; only
+      # required when using PVCs that are ReadWriteOnce
+      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+                - key: component
+                  operator: In
+                  values:
+                    - rails
+            topologyKey: kubernetes.io/hostname
+      {{- end }}
+      volumes:
+        - name: assets
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-assets
+        - name: system
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-system
+      containers:
+        - name: {{ include "mastodon.fullname" . }}-create-admin
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command:
+            - bin/tootctl
+            - accounts
+            - create
+            - {{ .Values.createAdmin.username }}
+            - --email
+            - {{ .Values.createAdmin.email }}
+            - --confirmed
+            - --role
+            - admin
+          envFrom:
+            - configMapRef:
+                name: {{ include "mastodon.fullname" . }}-env
+            - secretRef:
+                name: {{ template "mastodon.fullname" . }}
+          env:
+            - name: "DB_PASS"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-postgresql
+                  key: postgresql-password
+            - name: "REDIS_PASSWORD"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-redis
+                  key: redis-password
+            - name: "PORT"
+              value: {{ .Values.application.web.port | quote }}
+          volumeMounts:
+            - name: assets
+              mountPath: /opt/mastodon/public/assets
+            - name: system
+              mountPath: /opt/mastodon/public/system
+{{- end }}
diff --git a/chart/templates/job-db-migrate.yaml b/chart/templates/job-db-migrate.yaml
new file mode 100644
index 0000000000..e078323868
--- /dev/null
+++ b/chart/templates/job-db-migrate.yaml
@@ -0,0 +1,69 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: {{ include "mastodon.fullname" . }}-db-migrate
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": post-install,pre-upgrade
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-weight": "-2"
+spec:
+  template:
+    metadata:
+      name: {{ include "mastodon.fullname" . }}-db-migrate
+    spec:
+      restartPolicy: Never
+      # ensure we run on the same node as the other rails components; only
+      # required when using PVCs that are ReadWriteOnce
+      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+                - key: component
+                  operator: In
+                  values:
+                    - rails
+            topologyKey: kubernetes.io/hostname
+      {{- end }}
+      volumes:
+        - name: assets
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-assets
+        - name: system
+          persistentVolumeClaim:
+            claimName: {{ template "mastodon.fullname" . }}-system
+      containers:
+        - name: {{ include "mastodon.fullname" . }}-db-migrate
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command:
+            - bundle
+            - exec
+            - rake
+            - db:migrate
+          envFrom:
+            - configMapRef:
+                name: {{ include "mastodon.fullname" . }}-env
+            - secretRef:
+                name: {{ template "mastodon.fullname" . }}
+          env:
+            - name: "DB_PASS"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-postgresql
+                  key: postgresql-password
+            - name: "REDIS_PASSWORD"
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-redis
+                  key: redis-password
+            - name: "PORT"
+              value: {{ .Values.application.web.port | quote }}
+          volumeMounts:
+            - name: assets
+              mountPath: /opt/mastodon/public/assets
+            - name: system
+              mountPath: /opt/mastodon/public/system
diff --git a/chart/templates/pvc-assets.yaml b/chart/templates/pvc-assets.yaml
new file mode 100644
index 0000000000..5c5315100e
--- /dev/null
+++ b/chart/templates/pvc-assets.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: {{ template "mastodon.fullname" . }}-assets
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+  accessModes:
+    - {{ .Values.persistence.system.accessMode }}
+  resources:
+    {{- toYaml .Values.persistence.assets.resources | nindent 4}}
+  storageClassName: {{ .Values.persistence.assets.storageClassName }}
diff --git a/chart/templates/pvc-system.yaml b/chart/templates/pvc-system.yaml
new file mode 100644
index 0000000000..0285511519
--- /dev/null
+++ b/chart/templates/pvc-system.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: {{ template "mastodon.fullname" . }}-system
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+  accessModes:
+    - {{ .Values.persistence.system.accessMode }}
+  resources:
+    {{- toYaml .Values.persistence.system.resources | nindent 4}}
+  storageClassName: {{ .Values.persistence.system.storageClassName }}
diff --git a/chart/templates/secrets.yaml b/chart/templates/secrets.yaml
new file mode 100644
index 0000000000..74f4b15161
--- /dev/null
+++ b/chart/templates/secrets.yaml
@@ -0,0 +1,28 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: {{ template "mastodon.fullname" . }}
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+type: Opaque
+data:
+  {{- if not (empty .Values.secrets.secret_key_base) }}
+  SECRET_KEY_BASE: "{{ .Values.secrets.secret_key_base | b64enc }}"
+  {{- else }}
+  SECRET_KEY_BASE: {{ required "secret_key_base is required" .Values.secrets.secret_key_base }}
+  {{- end }}
+  {{- if not (empty .Values.secrets.otp_secret) }}
+  OTP_SECRET: "{{ .Values.secrets.otp_secret | b64enc }}"
+  {{- else }}
+  OTP_SECRET: {{ required "otp_secret is required" .Values.secrets.otp_secret }}
+  {{- end }}
+  {{- if not (empty .Values.secrets.vapid.private_key) }}
+  VAPID_PRIVATE_KEY: "{{ .Values.secrets.vapid.private_key | b64enc }}"
+  {{- else }}
+  VAPID_PRIVATE_KEY: {{ required "vapid.private_key is required" .Values.secrets.vapid.private_key }}
+  {{- end }}
+  {{- if not (empty .Values.secrets.vapid.public_key) }}
+  VAPID_PUBLIC_KEY: "{{ .Values.secrets.vapid.public_key | b64enc }}"
+  {{- else }}
+  VAPID_PUBLIC_KEY: {{ required "vapid.public_key is required" .Values.secrets.vapid.public_key }}
+  {{- end }}
diff --git a/chart/templates/service-streaming.yaml b/chart/templates/service-streaming.yaml
new file mode 100644
index 0000000000..ff5dc13eaa
--- /dev/null
+++ b/chart/templates/service-streaming.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "mastodon.fullname" . }}-streaming
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.application.streaming.port }}
+      targetPort: streaming
+      protocol: TCP
+      name: streaming
+  selector:
+    {{- include "mastodon.selectorLabels" . | nindent 4 }}
diff --git a/chart/templates/service-web.yaml b/chart/templates/service-web.yaml
new file mode 100644
index 0000000000..e0df35b250
--- /dev/null
+++ b/chart/templates/service-web.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "mastodon.fullname" . }}-web
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "mastodon.selectorLabels" . | nindent 4 }}
diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml
new file mode 100644
index 0000000000..b2f3d87c59
--- /dev/null
+++ b/chart/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "mastodon.serviceAccountName" . }}
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/chart/templates/tests/test-connection.yaml b/chart/templates/tests/test-connection.yaml
new file mode 100644
index 0000000000..09d9816911
--- /dev/null
+++ b/chart/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "mastodon.fullname" . }}-test-connection"
+  labels:
+    {{- include "mastodon.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test-success
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "mastodon.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never
diff --git a/chart/values.yaml.template b/chart/values.yaml.template
new file mode 100644
index 0000000000..2df6748a16
--- /dev/null
+++ b/chart/values.yaml.template
@@ -0,0 +1,163 @@
+replicaCount: 1
+
+image:
+  repository: tootsuite/mastodon
+  pullPolicy: Always
+  # https://hub.docker.com/r/tootsuite/mastodon/tags
+  tag: v3.1.4
+  # alternatively, use `latest` for the latest release or `edge` for the image
+  # built from the most recent commit
+  #
+  # tag: latest
+
+ingress:
+  enabled: false
+  annotations:
+    kubernetes.io/ingress.class: nginx
+    kubernetes.io/tls-acme: "true"
+    # cert-manager.io/cluster-issuer: "letsencrypt"
+  # this value is used for LOCAL_DOMAIN
+  hostname: mastodon.local
+  tls:
+    - secretName: mastodon-tls
+      hosts:
+        - mastodon.local
+
+# create an initial administrator user; the password is autogenerated and will
+# have to be reset
+createAdmin:
+  enabled: false
+  username: not_gargron
+  email: not@example.com
+
+# available locales: https://github.com/tootsuite/mastodon/blob/master/config/application.rb#L43
+locale: en
+
+application:
+  web:
+    port: 3000
+  streaming:
+    port: 4000
+    # this should be set manually since os.cpus() returns the number of CPUs on
+    # the node running the pod, which is unrelated to the resources allocated to
+    # the pod by k8s
+    workers: 1
+  sidekiq:
+    concurrency: 25
+
+# these must be set manually; autogenerated keys are rotated on each upgrade
+secrets:
+  secret_key_base: ""
+  otp_secret: ""
+  vapid:
+    private_key: ""
+    public_key: ""
+
+smtp:
+  auth_method: plain
+  ca_file:
+  delivery_method: smtp
+  domain:
+  enable_starttls_auto: true
+  from_address: notifications@example.com
+  login:
+  openssl_verify_mode: peer
+  password:
+  port: 587
+  reply_to:
+  server: smtp.mailgun.org
+  tls: false
+
+# https://github.com/bitnami/charts/tree/master/bitnami/elasticsearch#parameters
+elasticsearch:
+  # `false` will disable full-text search
+  #
+  # if you enable ES after the initial install, you will need to manually run
+  # RAILS_ENV=production bundle exec rake chewy:sync
+  # (https://docs.joinmastodon.org/admin/optional/elasticsearch/)
+  enabled: true
+  # may be removed once https://github.com/tootsuite/mastodon/pull/13828 is part
+  # of a tagged release
+  image:
+    tag: 6
+
+# https://github.com/bitnami/charts/tree/master/bitnami/postgresql#parameters
+postgresql:
+  postgresqlDatabase: mastodon_production
+  # you must set a password; the password generated by the postgresql chart will
+  # be rotated on each upgrade:
+  # https://github.com/bitnami/charts/tree/master/bitnami/postgresql#upgrade
+  postgresqlPassword: ""
+  postgresqlUsername: postgres
+
+# https://github.com/bitnami/charts/tree/master/bitnami/redis#parameters
+redis:
+  # you must set a password; the password generated by the redis chart will be
+  # rotated on each upgrade:
+  password: ""
+
+persistence:
+  assets:
+    # ReadWriteOnce is more widely supported than ReadWriteMany, but limits
+    # scalability, since it requires the Rails and Sidekiq pods to run on the
+    # same node.
+    accessMode: ReadWriteOnce
+    resources:
+      requests:
+        storage: 100Gi
+  system:
+    accessMode: ReadWriteOnce
+    resources:
+      requests:
+        storage: 10Gi
+
+service:
+  type: ClusterIP
+  port: 80
+
+# https://github.com/tootsuite/mastodon/blob/master/Dockerfile#L88
+#
+# if you manually change the UID/GID environment variables, ensure these values
+# match:
+podSecurityContext:
+  runAsUser: 991
+  runAsGroup: 991
+  fsGroup: 991
+
+securityContext: {}
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: true
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: ""
+
+podAnnotations: {}
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}

From a0f65e590908dcf0e6ff0a3ce60967a58da514b3 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 21:43:31 +0900
Subject: [PATCH 09/29] Bump aws-sdk-s3 from 1.69.0 to 1.72.0 (#14158)

Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.69.0 to 1.72.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Gemfile      |  2 +-
 Gemfile.lock | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/Gemfile b/Gemfile
index 1cec32e34d..568e25885f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,7 +20,7 @@ gem 'makara', '~> 0.4'
 gem 'pghero', '~> 2.5'
 gem 'dotenv-rails', '~> 2.7'
 
-gem 'aws-sdk-s3', '~> 1.69', require: false
+gem 'aws-sdk-s3', '~> 1.72', require: false
 gem 'fog-core', '<= 2.1.0'
 gem 'fog-openstack', '~> 0.3', require: false
 gem 'paperclip', '~> 6.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 15e8cdd3d1..569c117330 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -92,20 +92,20 @@ GEM
     av (0.9.0)
       cocaine (~> 0.5.3)
     aws-eventstream (1.1.0)
-    aws-partitions (1.332.0)
-    aws-sdk-core (3.100.0)
+    aws-partitions (1.336.0)
+    aws-sdk-core (3.102.1)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-partitions (~> 1, >= 1.239.0)
       aws-sigv4 (~> 1.1)
       jmespath (~> 1.0)
-    aws-sdk-kms (1.34.1)
+    aws-sdk-kms (1.35.0)
       aws-sdk-core (~> 3, >= 3.99.0)
       aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.69.0)
-      aws-sdk-core (~> 3, >= 3.99.0)
+    aws-sdk-s3 (1.72.0)
+      aws-sdk-core (~> 3, >= 3.102.1)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.1)
-    aws-sigv4 (1.2.0)
+    aws-sigv4 (1.2.1)
       aws-eventstream (~> 1, >= 1.0.2)
     bcrypt (3.1.13)
     better_errors (2.7.1)
@@ -674,7 +674,7 @@ DEPENDENCIES
   active_record_query_trace (~> 1.7)
   addressable (~> 2.7)
   annotate (~> 3.1)
-  aws-sdk-s3 (~> 1.69)
+  aws-sdk-s3 (~> 1.72)
   better_errors (~> 2.7)
   binding_of_caller (~> 0.7)
   blurhash (~> 0.1)

From aaef3b5f3ebe24241d230d57a73f13a812de0be0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 21:44:32 +0900
Subject: [PATCH 10/29] Bump rubocop from 0.85.1 to 0.86.0 (#14171)

Bumps [rubocop](https://github.com/rubocop-hq/rubocop) from 0.85.1 to 0.86.0.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.85.1...v0.86.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Gemfile      | 2 +-
 Gemfile.lock | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/Gemfile b/Gemfile
index 568e25885f..aa64679c14 100644
--- a/Gemfile
+++ b/Gemfile
@@ -139,7 +139,7 @@ group :development do
   gem 'letter_opener', '~> 1.7'
   gem 'letter_opener_web', '~> 1.4'
   gem 'memory_profiler'
-  gem 'rubocop', '~> 0.85', require: false
+  gem 'rubocop', '~> 0.86', require: false
   gem 'rubocop-rails', '~> 2.6', require: false
   gem 'brakeman', '~> 4.8', require: false
   gem 'bundler-audit', '~> 0.7', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 569c117330..533bbaf969 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -544,16 +544,16 @@ GEM
     rspec-support (3.9.3)
     rspec_junit_formatter (0.4.1)
       rspec-core (>= 2, < 4, != 2.12.0)
-    rubocop (0.85.1)
+    rubocop (0.86.0)
       parallel (~> 1.10)
       parser (>= 2.7.0.1)
       rainbow (>= 2.2.2, < 4.0)
       regexp_parser (>= 1.7)
       rexml
-      rubocop-ast (>= 0.0.3)
+      rubocop-ast (>= 0.0.3, < 1.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (>= 1.4.0, < 2.0)
-    rubocop-ast (0.0.3)
+    rubocop-ast (0.1.0)
       parser (>= 2.7.0.1)
     rubocop-rails (2.6.0)
       activesupport (>= 4.2.0)
@@ -772,7 +772,7 @@ DEPENDENCIES
   rspec-rails (~> 4.0)
   rspec-sidekiq (~> 3.1)
   rspec_junit_formatter (~> 0.4)
-  rubocop (~> 0.85)
+  rubocop (~> 0.86)
   rubocop-rails (~> 2.6)
   ruby-progressbar (~> 1.10)
   sanitize (~> 5.2)

From 9794ac131d6d89f26bd628aebd15a1646794c9d7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 21:47:42 +0900
Subject: [PATCH 11/29] Bump react-textarea-autosize from 8.0.1 to 8.1.1
 (#14177)

Bumps [react-textarea-autosize](https://github.com/Andarist/react-textarea-autosize) from 8.0.1 to 8.1.1.
- [Release notes](https://github.com/Andarist/react-textarea-autosize/releases)
- [Changelog](https://github.com/Andarist/react-textarea-autosize/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Andarist/react-textarea-autosize/compare/v8.0.1...v8.1.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json |  2 +-
 yarn.lock    | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/package.json b/package.json
index 01cd3548f3..f84c67a623 100644
--- a/package.json
+++ b/package.json
@@ -141,7 +141,7 @@
     "react-select": "^3.1.0",
     "react-sparklines": "^1.7.0",
     "react-swipeable-views": "^0.13.9",
-    "react-textarea-autosize": "^8.0.1",
+    "react-textarea-autosize": "^8.1.1",
     "react-toggle": "^4.1.1",
     "redis": "^3.0.2",
     "redux": "^4.0.5",
diff --git a/yarn.lock b/yarn.lock
index 10f401cfe1..3cfb09a872 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8997,12 +8997,12 @@ react-test-renderer@^16.13.1:
     react-is "^16.8.6"
     scheduler "^0.19.1"
 
-react-textarea-autosize@^8.0.1:
-  version "8.0.1"
-  resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.0.1.tgz#fce0dbf6a59b7b9d892c6af40b6be06a29f62c49"
-  integrity sha512-Qs7Lm17F0CIsWeDaUcHPpP22etVQHkayOcMgOXTfVasVToS6G+IL+5a7ECZtbDR2qTgTRIXjYcLmuZUuTX4tNA==
+react-textarea-autosize@^8.1.1:
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.1.1.tgz#d31dd1d04235af11161765782c70cb27c2c2832e"
+  integrity sha512-yJv7CbyXv8hb0xHpii9yQpMK0kwZ3A4TChRc5qGxQlHDR064oqStHbcuvexErRvJipTnDGNkcpGvE3hLnY0KAg==
   dependencies:
-    "@babel/runtime" "^7.8.4"
+    "@babel/runtime" "^7.10.2"
     use-composed-ref "^1.0.0"
     use-latest "^1.0.0"
 

From cdce0ac70cb877077cc16c8f4dc6996cc3476e05 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 21:49:48 +0900
Subject: [PATCH 12/29] Bump @babel/plugin-transform-runtime from 7.10.1 to
 7.10.3 (#14168)

Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.10.1 to 7.10.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.10.3/packages/babel-plugin-transform-runtime)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json |  2 +-
 yarn.lock    | 22 +++++++++++-----------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/package.json b/package.json
index f84c67a623..e76b4877e8 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
     "@babel/plugin-proposal-class-properties": "^7.8.3",
     "@babel/plugin-proposal-decorators": "^7.10.3",
     "@babel/plugin-transform-react-inline-elements": "^7.10.1",
-    "@babel/plugin-transform-runtime": "^7.10.1",
+    "@babel/plugin-transform-runtime": "^7.10.3",
     "@babel/preset-env": "^7.10.2",
     "@babel/preset-react": "^7.10.1",
     "@babel/runtime": "^7.8.4",
diff --git a/yarn.lock b/yarn.lock
index 3cfb09a872..c9f0f585a9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -192,12 +192,12 @@
   dependencies:
     "@babel/types" "^7.10.3"
 
-"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876"
-  integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==
+"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.10.3":
+  version "7.10.3"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz#766fa1d57608e53e5676f23ae498ec7a95e1b11a"
+  integrity sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.10.3"
 
 "@babel/helper-module-transforms@^7.10.1":
   version "7.10.1"
@@ -773,13 +773,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.10.1"
 
-"@babel/plugin-transform-runtime@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.1.tgz#fd1887f749637fb2ed86dc278e79eb41df37f4b1"
-  integrity sha512-4w2tcglDVEwXJ5qxsY++DgWQdNJcCCsPxfT34wCUwIf2E7dI7pMpH8JczkMBbgBTNzBX62SZlNJ9H+De6Zebaw==
+"@babel/plugin-transform-runtime@^7.10.3":
+  version "7.10.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.3.tgz#3b287b06acc534a7cb6e6c71d6b1d88b1922dd6c"
+  integrity sha512-b5OzMD1Hi8BBzgQdRHyVVaYrk9zG0wset1it2o3BgonkPadXfOv0aXRqd7864DeOIu3FGKP/h6lr15FE5mahVw==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-module-imports" "^7.10.3"
+    "@babel/helper-plugin-utils" "^7.10.3"
     resolve "^1.8.1"
     semver "^5.5.1"
 

From 44a54e2bd115e190b68a610e9c3be5eb22046273 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 21:50:12 +0900
Subject: [PATCH 13/29] Bump webpack-cli from 3.3.11 to 3.3.12 (#14164)

Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 3.3.11 to 3.3.12.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/v3.3.12/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/v3.3.11...v3.3.12)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json |   2 +-
 yarn.lock    | 206 +++++++++++++++++----------------------------------
 2 files changed, 67 insertions(+), 141 deletions(-)

diff --git a/package.json b/package.json
index e76b4877e8..e69644ce51 100644
--- a/package.json
+++ b/package.json
@@ -164,7 +164,7 @@
     "webpack": "^4.43.0",
     "webpack-assets-manifest": "^3.1.1",
     "webpack-bundle-analyzer": "^3.8.0",
-    "webpack-cli": "^3.3.11",
+    "webpack-cli": "^3.3.12",
     "webpack-merge": "^4.2.1",
     "wicg-inert": "^3.0.3"
   },
diff --git a/yarn.lock b/yarn.lock
index c9f0f585a9..4e60d7c39c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2742,15 +2742,6 @@ caseless@~0.12.0:
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
-chalk@2.4.2, chalk@^2.0, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
-  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
-  dependencies:
-    ansi-styles "^3.2.1"
-    escape-string-regexp "^1.0.5"
-    supports-color "^5.3.0"
-
 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"
@@ -2762,6 +2753,15 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
+chalk@^2.0, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+  dependencies:
+    ansi-styles "^3.2.1"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.3.0"
+
 chalk@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
@@ -3238,7 +3238,7 @@ cross-env@^7.0.2:
   dependencies:
     cross-spawn "^7.0.1"
 
-cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
+cross-spawn@^6.0.0, cross-spawn@^6.0.5:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
   integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -3249,7 +3249,7 @@ cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
     shebang-command "^1.2.0"
     which "^1.2.9"
 
-cross-spawn@^7.0.0:
+cross-spawn@^7.0.0, cross-spawn@^7.0.1:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -3258,15 +3258,6 @@ cross-spawn@^7.0.0:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
-cross-spawn@^7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14"
-  integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==
-  dependencies:
-    path-key "^3.1.0"
-    shebang-command "^2.0.0"
-    which "^2.0.1"
-
 crypto-browserify@^3.11.0:
   version "3.12.0"
   resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -3943,13 +3934,13 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
   dependencies:
     once "^1.4.0"
 
-enhanced-resolve@4.1.0, enhanced-resolve@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
-  integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
+enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d"
+  integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ==
   dependencies:
     graceful-fs "^4.1.2"
-    memory-fs "^0.4.0"
+    memory-fs "^0.5.0"
     tapable "^1.0.0"
 
 entities@^2.0.0:
@@ -4745,7 +4736,7 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
-findup-sync@3.0.0:
+findup-sync@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
   integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
@@ -5021,13 +5012,6 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-global-modules@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
-  integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
-  dependencies:
-    global-prefix "^3.0.0"
-
 global-modules@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
@@ -5037,6 +5021,13 @@ global-modules@^1.0.0:
     is-windows "^1.0.1"
     resolve-dir "^1.0.0"
 
+global-modules@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
+  integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
+  dependencies:
+    global-prefix "^3.0.0"
+
 global-prefix@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
@@ -5508,7 +5499,7 @@ import-from@^2.1.0:
   dependencies:
     resolve-from "^3.0.0"
 
-import-local@2.0.0, import-local@^2.0.0:
+import-local@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
   integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
@@ -5635,10 +5626,10 @@ internal-slot@^1.0.2:
     has "^1.0.3"
     side-channel "^1.0.2"
 
-interpret@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
-  integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
+interpret@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
+  integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
 
 intersection-observer@^0.10.0:
   version "0.10.0"
@@ -5693,11 +5684,6 @@ invariant@^2.1.1, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
   dependencies:
     loose-envify "^1.0.0"
 
-invert-kv@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
-  integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
-
 ip-regex@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -6776,13 +6762,6 @@ language-tags@^1.0.5:
   dependencies:
     language-subtag-registry "~0.3.2"
 
-lcid@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
-  integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==
-  dependencies:
-    invert-kv "^2.0.0"
-
 leven@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -6833,15 +6812,6 @@ loader-utils@0.2.x:
     json5 "^0.5.0"
     object-assign "^4.0.1"
 
-loader-utils@1.2.3:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
-  integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
-  dependencies:
-    big.js "^5.2.2"
-    emojis-list "^2.0.0"
-    json5 "^1.0.1"
-
 loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
@@ -6984,13 +6954,6 @@ makeerror@1.0.x:
   dependencies:
     tmpl "1.0.x"
 
-map-age-cleaner@^0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
-  integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
-  dependencies:
-    p-defer "^1.0.0"
-
 map-cache@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@@ -7037,21 +7000,12 @@ media-typer@0.3.0:
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
 
-mem@^4.0.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
-  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
-  dependencies:
-    map-age-cleaner "^0.1.1"
-    mimic-fn "^2.0.0"
-    p-is-promise "^2.0.0"
-
 memoize-one@^5.0.0:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
   integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
 
-memory-fs@^0.4.0, memory-fs@^0.4.1:
+memory-fs@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
   integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
@@ -7059,6 +7013,14 @@ memory-fs@^0.4.0, memory-fs@^0.4.1:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
+memory-fs@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c"
+  integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==
+  dependencies:
+    errno "^0.1.3"
+    readable-stream "^2.0.1"
+
 merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -7141,7 +7103,7 @@ mime@^2.4.4:
   resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
   integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
 
-mimic-fn@^2.0.0, mimic-fn@^2.1.0:
+mimic-fn@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
@@ -7717,25 +7679,11 @@ os-homedir@^1.0.0:
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
 
-os-locale@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
-  integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
-  dependencies:
-    execa "^1.0.0"
-    lcid "^2.0.0"
-    mem "^4.0.0"
-
 os-tmpdir@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
 
-p-defer@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
-  integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
-
 p-each-series@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
@@ -7746,11 +7694,6 @@ p-finally@^1.0.0:
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
-p-is-promise@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
-  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
-
 p-limit@^1.1.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@@ -10361,13 +10304,6 @@ substring-trie@^1.0.2:
   resolved "https://registry.yarnpkg.com/substring-trie/-/substring-trie-1.0.2.tgz#7b42592391628b4f2cb17365c6cce4257c7b7af5"
   integrity sha1-e0JZI5Fii08ssXNlxszkJXx7evU=
 
-supports-color@6.1.0, supports-color@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
-  integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
-  dependencies:
-    has-flag "^3.0.0"
-
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -10387,6 +10323,13 @@ supports-color@^5.3.0:
   dependencies:
     has-flag "^3.0.0"
 
+supports-color@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+  integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+  dependencies:
+    has-flag "^3.0.0"
+
 supports-color@^7.0.0, supports-color@^7.1.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
@@ -10990,10 +10933,10 @@ uuid@^8.1.0:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
   integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
 
-v8-compile-cache@2.0.3, v8-compile-cache@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
-  integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
+v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
+  integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
 
 v8-to-istanbul@^4.1.3:
   version "4.1.4"
@@ -11148,22 +11091,22 @@ webpack-bundle-analyzer@^3.8.0:
     opener "^1.5.1"
     ws "^6.0.0"
 
-webpack-cli@^3.3.11:
-  version "3.3.11"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631"
-  integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==
+webpack-cli@^3.3.12:
+  version "3.3.12"
+  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a"
+  integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==
   dependencies:
-    chalk "2.4.2"
-    cross-spawn "6.0.5"
-    enhanced-resolve "4.1.0"
-    findup-sync "3.0.0"
-    global-modules "2.0.0"
-    import-local "2.0.0"
-    interpret "1.2.0"
-    loader-utils "1.2.3"
-    supports-color "6.1.0"
-    v8-compile-cache "2.0.3"
-    yargs "13.2.4"
+    chalk "^2.4.2"
+    cross-spawn "^6.0.5"
+    enhanced-resolve "^4.1.1"
+    findup-sync "^3.0.0"
+    global-modules "^2.0.0"
+    import-local "^2.0.0"
+    interpret "^1.4.0"
+    loader-utils "^1.4.0"
+    supports-color "^6.1.0"
+    v8-compile-cache "^2.1.1"
+    yargs "^13.3.2"
 
 webpack-dev-middleware@^3.7.2:
   version "3.7.2"
@@ -11453,7 +11396,7 @@ yaml@^1.7.2:
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
   integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
 
-yargs-parser@^13.1.0, yargs-parser@^13.1.2:
+yargs-parser@^13.1.2:
   version "13.1.2"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
   integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
@@ -11469,23 +11412,6 @@ yargs-parser@^18.1.1:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs@13.2.4:
-  version "13.2.4"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
-  integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
-  dependencies:
-    cliui "^5.0.0"
-    find-up "^3.0.0"
-    get-caller-file "^2.0.1"
-    os-locale "^3.1.0"
-    require-directory "^2.1.1"
-    require-main-filename "^2.0.0"
-    set-blocking "^2.0.0"
-    string-width "^3.0.0"
-    which-module "^2.0.0"
-    y18n "^4.0.0"
-    yargs-parser "^13.1.0"
-
 yargs@^13.3.2:
   version "13.3.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"

From 1b016aeeb1459bc3d772ca1743b7aef1242dc6ed Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 21:55:22 +0900
Subject: [PATCH 14/29] Bump jest-environment-jsdom from 26.0.1 to 26.1.0
 (#14167)

Bumps [jest-environment-jsdom](https://github.com/facebook/jest/tree/HEAD/packages/jest-environment-jsdom) from 26.0.1 to 26.1.0.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v26.1.0/packages/jest-environment-jsdom)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 yarn.lock | 110 +++++++++++++++++++++++++++++++++---------------------
 1 file changed, 67 insertions(+), 43 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 4e60d7c39c..b82f5e3064 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1169,25 +1169,25 @@
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/environment@^26.0.1":
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.0.1.tgz#82f519bba71959be9b483675ee89de8c8f72a5c8"
-  integrity sha512-xBDxPe8/nx251u0VJ2dFAFz2H23Y98qdIaNwnMK6dFQr05jc+Ne/2np73lOAx+5mSBO/yuQldRrQOf6hP1h92g==
+"@jest/environment@^26.0.1", "@jest/environment@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.1.0.tgz#378853bcdd1c2443b4555ab908cfbabb851e96da"
+  integrity sha512-86+DNcGongbX7ai/KE/S3/NcUVZfrwvFzOOWX/W+OOTvTds7j07LtC+MgGydH5c8Ri3uIrvdmVgd1xFD5zt/xA==
   dependencies:
-    "@jest/fake-timers" "^26.0.1"
-    "@jest/types" "^26.0.1"
-    jest-mock "^26.0.1"
+    "@jest/fake-timers" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    jest-mock "^26.1.0"
 
-"@jest/fake-timers@^26.0.1":
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.0.1.tgz#f7aeff13b9f387e9d0cac9a8de3bba538d19d796"
-  integrity sha512-Oj/kCBnTKhm7CR+OJSjZty6N1bRDr9pgiYQr4wY221azLz5PHi08x/U+9+QpceAYOWheauLP8MhtSVFrqXQfhg==
+"@jest/fake-timers@^26.0.1", "@jest/fake-timers@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.1.0.tgz#9a76b7a94c351cdbc0ad53e5a748789f819a65fe"
+  integrity sha512-Y5F3kBVWxhau3TJ825iuWy++BAuQzK/xEa+wD9vDH3RytW9f2DbMVodfUQC54rZDX3POqdxCgcKdgcOL0rYUpA==
   dependencies:
-    "@jest/types" "^26.0.1"
+    "@jest/types" "^26.1.0"
     "@sinonjs/fake-timers" "^6.0.1"
-    jest-message-util "^26.0.1"
-    jest-mock "^26.0.1"
-    jest-util "^26.0.1"
+    jest-message-util "^26.1.0"
+    jest-mock "^26.1.0"
+    jest-util "^26.1.0"
 
 "@jest/globals@^26.0.1":
   version "26.0.1"
@@ -1313,10 +1313,10 @@
     "@types/yargs" "^15.0.0"
     chalk "^3.0.0"
 
-"@jest/types@^26.0.1":
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.0.1.tgz#b78333fbd113fa7aec8d39de24f88de8686dac67"
-  integrity sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==
+"@jest/types@^26.0.1", "@jest/types@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.1.0.tgz#f8afaaaeeb23b5cad49dd1f7779689941dcb6057"
+  integrity sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ==
   dependencies:
     "@types/istanbul-lib-coverage" "^2.0.0"
     "@types/istanbul-reports" "^1.1.1"
@@ -1474,7 +1474,12 @@
   dependencies:
     "@types/node" "*"
 
-"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
+  integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
+
+"@types/istanbul-lib-coverage@^2.0.1":
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz#79d7a78bad4219f4c03d6557a1c72d9ca6ba62d5"
   integrity sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w==
@@ -1779,11 +1784,16 @@ acorn@^6.4.1:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
   integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
 
-acorn@^7.1.0, acorn@^7.1.1:
+acorn@^7.1.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
   integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==
 
+acorn@^7.1.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd"
+  integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==
+
 aggregate-error@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0"
@@ -4078,9 +4088,9 @@ escape-string-regexp@^2.0.0:
   integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
 
 escodegen@^1.14.1:
-  version "1.14.2"
-  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.2.tgz#14ab71bf5026c2aa08173afba22c6f3173284a84"
-  integrity sha512-InuOIiKk8wwuOFg6x9BQXbzjrQhtyXh46K9bqVTPzSo2FnyMBaYGBMC6PhQy7yxxil9vIedFBweQBMK74/7o8A==
+  version "1.14.3"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
+  integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
   dependencies:
     esprima "^4.0.1"
     estraverse "^4.2.0"
@@ -6199,15 +6209,15 @@ jest-each@^26.0.1:
     pretty-format "^26.0.1"
 
 jest-environment-jsdom@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.0.1.tgz#217690852e5bdd7c846a4e3b50c8ffd441dfd249"
-  integrity sha512-u88NJa3aptz2Xix2pFhihRBAatwZHWwSiRLBDBQE1cdJvDjPvv7ZGA0NQBxWwDDn7D0g1uHqxM8aGgfA9Bx49g==
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.1.0.tgz#9dc7313ffe1b59761dad1fedb76e2503e5d37c5b"
+  integrity sha512-dWfiJ+spunVAwzXbdVqPH1LbuJW/kDL+FyqgA5YzquisHqTi0g9hquKif9xKm7c1bKBj6wbmJuDkeMCnxZEpUw==
   dependencies:
-    "@jest/environment" "^26.0.1"
-    "@jest/fake-timers" "^26.0.1"
-    "@jest/types" "^26.0.1"
-    jest-mock "^26.0.1"
-    jest-util "^26.0.1"
+    "@jest/environment" "^26.1.0"
+    "@jest/fake-timers" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    jest-mock "^26.1.0"
+    jest-util "^26.1.0"
     jsdom "^16.2.2"
 
 jest-environment-node@^26.0.1:
@@ -6336,12 +6346,26 @@ jest-message-util@^26.0.1:
     slash "^3.0.0"
     stack-utils "^2.0.2"
 
-jest-mock@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.0.1.tgz#7fd1517ed4955397cf1620a771dc2d61fad8fd40"
-  integrity sha512-MpYTBqycuPYSY6xKJognV7Ja46/TeRbAZept987Zp+tuJvMN0YBWyyhG9mXyYQaU3SBI0TUlSaO5L3p49agw7Q==
+jest-message-util@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.1.0.tgz#52573fbb8f5cea443c4d1747804d7a238a3e233c"
+  integrity sha512-dY0+UlldiAJwNDJ08SF0HdF32g9PkbF2NRK/+2iMPU40O6q+iSn1lgog/u0UH8ksWoPv0+gNq8cjhYO2MFtT0g==
   dependencies:
-    "@jest/types" "^26.0.1"
+    "@babel/code-frame" "^7.0.0"
+    "@jest/types" "^26.1.0"
+    "@types/stack-utils" "^1.0.1"
+    chalk "^4.0.0"
+    graceful-fs "^4.2.4"
+    micromatch "^4.0.2"
+    slash "^3.0.0"
+    stack-utils "^2.0.2"
+
+jest-mock@^26.0.1, jest-mock@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.1.0.tgz#80d8286da1f05a345fbad1bfd6fa49a899465d3d"
+  integrity sha512-1Rm8EIJ3ZFA8yCIie92UbxZWj9SuVmUGcyhLHyAhY6WI3NIct38nVcfOPWhJteqSn8V8e3xOMha9Ojfazfpovw==
+  dependencies:
+    "@jest/types" "^26.1.0"
 
 jest-pnp-resolver@^1.2.1:
   version "1.2.1"
@@ -6484,16 +6508,16 @@ jest-util@^25.5.0:
     is-ci "^2.0.0"
     make-dir "^3.0.0"
 
-jest-util@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.0.1.tgz#72c4c51177b695fdd795ca072a6f94e3d7cef00a"
-  integrity sha512-byQ3n7ad1BO/WyFkYvlWQHTsomB6GIewBh8tlGtusiylAlaxQ1UpS0XYH0ngOyhZuHVLN79Qvl6/pMiDMSSG1g==
+jest-util@^26.0.1, jest-util@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.1.0.tgz#80e85d4ba820decacf41a691c2042d5276e5d8d8"
+  integrity sha512-rNMOwFQevljfNGvbzNQAxdmXQ+NawW/J72dmddsK0E8vgxXCMtwQ/EH0BiWEIxh0hhMcTsxwAxINt7Lh46Uzbg==
   dependencies:
-    "@jest/types" "^26.0.1"
+    "@jest/types" "^26.1.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     is-ci "^2.0.0"
-    make-dir "^3.0.0"
+    micromatch "^4.0.2"
 
 jest-validate@^26.0.1:
   version "26.0.1"

From bc6ac0ae09787de2f8ac253e1319ff389da9ea34 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 22:43:34 +0900
Subject: [PATCH 15/29] Bump rails-controller-testing from 1.0.4 to 1.0.5
 (#14161)

Bumps [rails-controller-testing](https://github.com/rails/rails-controller-testing) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/rails/rails-controller-testing/releases)
- [Commits](https://github.com/rails/rails-controller-testing/compare/v1.0.4...v1.0.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Gemfile.lock | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 533bbaf969..c0028fa5a8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -463,10 +463,10 @@ GEM
       bundler (>= 1.3.0)
       railties (= 5.2.4.3)
       sprockets-rails (>= 2.0.0)
-    rails-controller-testing (1.0.4)
-      actionpack (>= 5.0.1.x)
-      actionview (>= 5.0.1.x)
-      activesupport (>= 5.0.1.x)
+    rails-controller-testing (1.0.5)
+      actionpack (>= 5.0.1.rc1)
+      actionview (>= 5.0.1.rc1)
+      activesupport (>= 5.0.1.rc1)
     rails-dom-testing (2.0.3)
       activesupport (>= 4.2.0)
       nokogiri (>= 1.6)

From e60ba7ca17ef7755383559250f1bb8ab6b334022 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 22:44:43 +0900
Subject: [PATCH 16/29] Bump pghero from 2.5.0 to 2.5.1 (#14163)

Bumps [pghero](https://github.com/ankane/pghero) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/ankane/pghero/releases)
- [Changelog](https://github.com/ankane/pghero/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ankane/pghero/compare/v2.5.0...v2.5.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index c0028fa5a8..962e2d5d8c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -414,7 +414,7 @@ GEM
       equatable (~> 0.6)
       tty-color (~> 0.5)
     pg (1.2.3)
-    pghero (2.5.0)
+    pghero (2.5.1)
       activerecord (>= 5)
     pkg-config (1.4.1)
     premailer (1.11.1)

From 5c43dbb855d401147a7b0c9e9b58e0bcf9b239be Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2020 22:45:50 +0900
Subject: [PATCH 17/29] Bump redis-store from 1.8.2 to 1.9.0 (#14160)

Bumps [redis-store](https://github.com/jodosha/redis-store) from 1.8.2 to 1.9.0.
- [Release notes](https://github.com/jodosha/redis-store/releases)
- [Changelog](https://github.com/redis-store/redis-store/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jodosha/redis-store/compare/v1.8.2...v1.9.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 962e2d5d8c..d99e0db7ff 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -507,7 +507,7 @@ GEM
       redis-actionpack (>= 5.0, < 6)
       redis-activesupport (>= 5.0, < 6)
       redis-store (>= 1.2, < 2)
-    redis-store (1.8.2)
+    redis-store (1.9.0)
       redis (>= 4, < 5)
     regexp_parser (1.7.1)
     request_store (1.5.0)

From fa183a51ab0d8c8bbacd2d5e7474cd2bc9c9000c Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 29 Jun 2020 16:10:49 +0200
Subject: [PATCH 18/29] Add GitHub Sponsors

---
 .github/FUNDING.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 91ee92a2e5..9526e17db7 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,3 @@
 patreon: mastodon
 open_collective: mastodon
+github: [Gargron]

From 1b198d64890de3eed5562c9b485ed8cafbff059f Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 29 Jun 2020 17:59:04 +0200
Subject: [PATCH 19/29] Fix trying to write non-existent image remote URL
 attribute on preview cards (#14181)

Regression from #14145
---
 app/models/concerns/remotable.rb       |   8 +-
 spec/models/concerns/remotable_spec.rb | 289 +++++++++++++------------
 2 files changed, 152 insertions(+), 145 deletions(-)

diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index 6fc1dcc268..53ebc08357 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -7,8 +7,8 @@ module Remotable
     def remotable_attachment(attachment_name, limit, suppress_errors: true, download_on_assign: true, attribute_name: nil)
       attribute_name ||= "#{attachment_name}_remote_url".to_sym
 
-      define_method("download_#{attachment_name}!") do
-        url = self[attribute_name]
+      define_method("download_#{attachment_name}!") do |url = nil|
+        url ||= self[attribute_name]
 
         return if url.blank?
 
@@ -51,9 +51,9 @@ module Remotable
       define_method("#{attribute_name}=") do |url|
         return if self[attribute_name] == url && public_send("#{attachment_name}_file_name").present?
 
-        self[attribute_name] = url
+        self[attribute_name] = url if has_attribute?(attribute_name)
 
-        public_send("download_#{attachment_name}!") if download_on_assign
+        public_send("download_#{attachment_name}!", url) if download_on_assign
       end
 
       alias_method("reset_#{attachment_name}!", "download_#{attachment_name}!")
diff --git a/spec/models/concerns/remotable_spec.rb b/spec/models/concerns/remotable_spec.rb
index 6957b044f4..2e6c8a9c6d 100644
--- a/spec/models/concerns/remotable_spec.rb
+++ b/spec/models/concerns/remotable_spec.rb
@@ -29,170 +29,177 @@ RSpec.describe Remotable do
     end
   end
 
-  context 'Remotable module is included' do
+  before do
+    class Foo
+      include Remotable
+
+      remotable_attachment :hoge, 1.kilobyte
+    end
+  end
+
+  let(:attribute_name) { "#{hoge}_remote_url".to_sym }
+  let(:code)           { 200 }
+  let(:file)           { 'filename="foo.txt"' }
+  let(:foo)            { Foo.new }
+  let(:headers)        { { 'content-disposition' => file } }
+  let(:hoge)           { :hoge }
+  let(:url)            { 'https://google.com' }
+
+  it 'defines a method #hoge_remote_url=' do
+    expect(foo).to respond_to(:hoge_remote_url=)
+  end
+
+  it 'defines a method #reset_hoge!' do
+    expect(foo).to respond_to(:reset_hoge!)
+  end
+
+  it 'defines a method #download_hoge!' do
+    expect(foo).to respond_to(:download_hoge!)
+  end
+
+  describe '#hoge_remote_url=' do
     before do
-      class Foo
-        include Remotable
-        remotable_attachment :hoge, 1.kilobyte
+      stub_request(:get, url).to_return(status: code, headers: headers)
+    end
+
+    it 'always returns its argument' do
+      [nil, '', [], {}].each do |arg|
+        expect(foo.hoge_remote_url = arg).to be arg
       end
     end
 
-    let(:attribute_name) { "#{hoge}_remote_url".to_sym }
-    let(:code)           { 200 }
-    let(:file)           { 'filename="foo.txt"' }
-    let(:foo)            { Foo.new }
-    let(:headers)        { { 'content-disposition' => file } }
-    let(:hoge)           { :hoge }
-    let(:url)            { 'https://google.com' }
-
-    let(:request) do
-      stub_request(:get, url)
-        .to_return(status: code, headers: headers)
-    end
-
-    it 'defines a method #hoge_remote_url=' do
-      expect(foo).to respond_to(:hoge_remote_url=)
-    end
-
-    it 'defines a method #reset_hoge!' do
-      expect(foo).to respond_to(:reset_hoge!)
-    end
-
-    it 'defines a method #download_hoge!' do
-      expect(foo).to respond_to(:download_hoge!)
-    end
-
-    describe '#hoge_remote_url=' do
+    context 'with an invalid URL' do
       before do
-        request
+        allow(Addressable::URI).to receive_message_chain(:parse, :normalize).with(url).with(no_args).and_raise(Addressable::URI::InvalidURIError)
       end
 
-      it 'always returns arg' do
-        [nil, '', [], {}].each do |arg|
-          expect(foo.hoge_remote_url = arg).to be arg
+      it 'makes no request' do
+        foo.hoge_remote_url = url
+        expect(a_request(:get, url)).to_not have_been_made
+      end
+    end
+
+    context 'with scheme that is neither http nor https' do
+      let(:url) { 'ftp://google.com' }
+
+      it 'makes no request' do
+        foo.hoge_remote_url = url
+        expect(a_request(:get, url)).to_not have_been_made
+      end
+    end
+
+    context 'with relative URL' do
+      let(:url) { 'https:///path' }
+
+      it 'makes no request' do
+        foo.hoge_remote_url = url
+        expect(a_request(:get, url)).to_not have_been_made
+      end
+    end
+
+    context 'when URL has not changed' do
+      it 'makes no request if file is already saved' do
+        allow(foo).to receive(:[]).with(attribute_name).and_return(url)
+        allow(foo).to receive(:hoge_file_name).and_return('foo.jpg')
+
+        foo.hoge_remote_url = url
+        expect(a_request(:get, url)).to_not have_been_made
+      end
+
+      it 'makes request if file is not already saved' do
+        allow(foo).to receive(:[]).with(attribute_name).and_return(url)
+        allow(foo).to receive(:hoge_file_name).and_return(nil)
+
+        foo.hoge_remote_url = url
+        expect(a_request(:get, url)).to have_been_made
+      end
+    end
+
+    context 'when instance has no attribute for URL' do
+      before do
+        allow(foo).to receive(:has_attribute?).with(attribute_name).and_return(false)
+      end
+
+      it 'does not try to write attribute' do
+        expect(foo).to_not receive('[]=').with(attribute_name, url)
+        foo.hoge_remote_url = url
+      end
+    end
+
+    context 'when instance has an attribute for URL' do
+      before do
+        allow(foo).to receive(:has_attribute?).with(attribute_name).and_return(true)
+      end
+
+      it 'does not try to write attribute' do
+        expect(foo).to receive('[]=').with(attribute_name, url)
+        foo.hoge_remote_url = url
+      end
+    end
+
+    context 'with a valid URL' do
+      it 'makes a request' do
+        foo.hoge_remote_url = url
+        expect(a_request(:get, url)).to have_been_made
+      end
+
+      context 'when the response is not successful' do
+        let(:code) { 500 }
+
+        it 'does not assign file' do
+          expect(foo).not_to receive(:public_send).with("#{hoge}=", any_args)
+          expect(foo).not_to receive(:public_send).with("#{hoge}_file_name=", any_args)
+
+          foo.hoge_remote_url = url
         end
       end
 
-      context 'Addressable::URI::InvalidURIError raised' do
-        it 'makes no request' do
-          allow(Addressable::URI).to receive_message_chain(:parse, :normalize)
-            .with(url).with(no_args).and_raise(Addressable::URI::InvalidURIError)
+      context 'when the response is successful' do
+        let(:code) { 200 }
 
-          foo.hoge_remote_url = url
-          expect(request).not_to have_been_requested
-        end
-      end
+        context 'and contains Content-Disposition header' do
+          let(:file)      { 'filename="foo.txt"' }
+          let(:headers)   { { 'content-disposition' => file } }
 
-      context 'scheme is neither http nor https' do
-        let(:url) { 'ftp://google.com' }
+          it 'assigns file' do
+            string_io = StringIO.new('')
+            extname   = '.txt'
+            basename  = '0123456789abcdef'
 
-        it 'makes no request' do
-          foo.hoge_remote_url = url
-          expect(request).not_to have_been_requested
-        end
-      end
+            allow(SecureRandom).to receive(:hex).and_return(basename)
+            allow(StringIO).to receive(:new).with(anything).and_return(string_io)
 
-      context 'parsed_url.host is empty' do
-        it 'makes no request' do
-          parsed_url = double(scheme: 'https', host: double(blank?: true))
-          allow(Addressable::URI).to receive_message_chain(:parse, :normalize)
-            .with(url).with(no_args).and_return(parsed_url)
+            expect(foo).to receive(:public_send).with("download_#{hoge}!", url)
 
-          foo.hoge_remote_url = url
-          expect(request).not_to have_been_requested
-        end
-      end
-
-      context 'parsed_url.host is nil' do
-        it 'makes no request' do
-          parsed_url = Addressable::URI.parse('https:https://example.com/path/file.png')
-          allow(Addressable::URI).to receive_message_chain(:parse, :normalize)
-            .with(url).with(no_args).and_return(parsed_url)
-
-          foo.hoge_remote_url = url
-          expect(request).not_to have_been_requested
-        end
-      end
-
-      context 'foo[attribute_name] == url' do
-        it 'makes no request if file is saved' do
-          allow(foo).to receive(:[]).with(attribute_name).and_return(url)
-          allow(foo).to receive(:hoge_file_name).and_return('foo.jpg')
-
-          foo.hoge_remote_url = url
-          expect(request).not_to have_been_requested
-        end
-
-        it 'makes request if file is not saved' do
-          allow(foo).to receive(:[]).with(attribute_name).and_return(url)
-          allow(foo).to receive(:hoge_file_name).and_return(nil)
-
-          foo.hoge_remote_url = url
-          expect(request).to have_been_requested
-        end
-      end
-
-      context "scheme is https, parsed_url.host isn't empty, and foo[attribute_name] != url" do
-        it 'makes a request' do
-          foo.hoge_remote_url = url
-          expect(request).to have_been_requested
-        end
-
-        context 'response.code != 200' do
-          let(:code) { 500 }
-
-          it 'calls not send' do
-            expect(foo).not_to receive(:public_send).with("#{hoge}=", any_args)
-            expect(foo).not_to receive(:public_send).with("#{hoge}_file_name=", any_args)
             foo.hoge_remote_url = url
+
+            expect(foo).to receive(:public_send).with("#{hoge}=", string_io)
+            expect(foo).to receive(:public_send).with("#{hoge}_file_name=", basename + extname)
+
+            foo.download_hoge!(url)
           end
         end
+      end
 
-        context 'response.code == 200' do
-          let(:code) { 200 }
-
-          context 'response contains headers["content-disposition"]' do
-            let(:file)      { 'filename="foo.txt"' }
-            let(:headers)   { { 'content-disposition' => file } }
-
-            it 'calls send' do
-              string_io = StringIO.new('')
-              extname   = '.txt'
-              basename  = '0123456789abcdef'
-
-              allow(SecureRandom).to receive(:hex).and_return(basename)
-              allow(StringIO).to receive(:new).with(anything).and_return(string_io)
-
-              expect(foo).to receive(:public_send).with("download_#{hoge}!")
-
-              foo.hoge_remote_url = url
-
-              expect(foo).to receive(:public_send).with("#{hoge}=", string_io)
-              expect(foo).to receive(:public_send).with("#{hoge}_file_name=", basename + extname)
-
-              foo.download_hoge!
-            end
-          end
+      context 'when an error is raised during the request' do
+        before do
+          stub_request(:get, url).to_raise(error_class)
         end
 
-        context 'an error raised during the request' do
-          let(:request) { stub_request(:get, url).to_raise(error_class) }
+        error_classes = [
+          HTTP::TimeoutError,
+          HTTP::ConnectionError,
+          OpenSSL::SSL::SSLError,
+          Paperclip::Errors::NotIdentifiedByImageMagickError,
+          Addressable::URI::InvalidURIError,
+        ]
 
-          error_classes = [
-            HTTP::TimeoutError,
-            HTTP::ConnectionError,
-            OpenSSL::SSL::SSLError,
-            Paperclip::Errors::NotIdentifiedByImageMagickError,
-            Addressable::URI::InvalidURIError,
-          ]
+        error_classes.each do |error_class|
+          let(:error_class) { error_class }
 
-          error_classes.each do |error_class|
-            let(:error_class) { error_class }
-
-            it 'calls Rails.logger.debug' do
-              expect(Rails.logger).to receive(:debug).with(/^Error fetching remote #{hoge}: /)
-              foo.hoge_remote_url = url
-            end
+          it 'calls Rails.logger.debug' do
+            expect(Rails.logger).to receive(:debug).with(/^Error fetching remote #{hoge}: /)
+            foo.hoge_remote_url = url
           end
         end
       end

From 59313b6f95c8d71d0f634c3e0387ddd37d09169d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 30 Jun 2020 01:24:46 +0900
Subject: [PATCH 20/29] Bump jest-config from 26.0.1 to 26.1.0 (#14176)

Bumps [jest-config](https://github.com/facebook/jest/tree/HEAD/packages/jest-config) from 26.0.1 to 26.1.0.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v26.1.0/packages/jest-config)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 yarn.lock | 450 +++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 329 insertions(+), 121 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index b82f5e3064..b7c999c081 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -178,14 +178,7 @@
   dependencies:
     "@babel/types" "^7.10.1"
 
-"@babel/helper-member-expression-to-functions@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15"
-  integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==
-  dependencies:
-    "@babel/types" "^7.10.1"
-
-"@babel/helper-member-expression-to-functions@^7.10.3":
+"@babel/helper-member-expression-to-functions@^7.10.1", "@babel/helper-member-expression-to-functions@^7.10.3":
   version "7.10.3"
   resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.3.tgz#bc3663ac81ac57c39148fef4c69bf48a77ba8dd6"
   integrity sha512-q7+37c4EPLSjNb2NmWOjNwj0+BOyYlssuQ58kHEWk1Z78K5i8vTUsteq78HMieRPQSl/NtpQyJfdjt3qZ5V2vw==
@@ -212,14 +205,7 @@
     "@babel/types" "^7.10.1"
     lodash "^4.17.13"
 
-"@babel/helper-optimise-call-expression@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543"
-  integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==
-  dependencies:
-    "@babel/types" "^7.10.1"
-
-"@babel/helper-optimise-call-expression@^7.10.3":
+"@babel/helper-optimise-call-expression@^7.10.1", "@babel/helper-optimise-call-expression@^7.10.3":
   version "7.10.3"
   resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.3.tgz#f53c4b6783093195b0f69330439908841660c530"
   integrity sha512-kT2R3VBH/cnSz+yChKpaKRJQJWxdGoc6SjioRId2wkeV3bK0wLLioFpJROrX0U4xr/NmxSSAWT/9Ih5snwIIzg==
@@ -453,6 +439,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
+"@babel/plugin-syntax-import-meta@^7.8.3":
+  version "7.10.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.1.tgz#3e59120ed8b3c2ccc5abb1cfc7aaa3ea01cd36b6"
+  integrity sha512-ypC4jwfIVF72og0dgvEcFRdOM2V9Qm1tu7RGmdZOlhsccyK0wisXmMObGuWEOd5jQ+K9wcIgSNftCpk2vkjUfQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.1"
+
 "@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
@@ -1136,6 +1129,17 @@
     jest-util "^26.0.1"
     slash "^3.0.0"
 
+"@jest/console@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.1.0.tgz#f67c89e4f4d04dbcf7b052aed5ab9c74f915b954"
+  integrity sha512-+0lpTHMd/8pJp+Nd4lyip+/Iyf2dZJvcCqrlkeZQoQid+JlThA4M9vxHtheyrQ99jJTMQam+es4BcvZ5W5cC3A==
+  dependencies:
+    "@jest/types" "^26.1.0"
+    chalk "^4.0.0"
+    jest-message-util "^26.1.0"
+    jest-util "^26.1.0"
+    slash "^3.0.0"
+
 "@jest/core@^26.0.1":
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.0.1.tgz#aa538d52497dfab56735efb00e506be83d841fae"
@@ -1198,6 +1202,15 @@
     "@jest/types" "^26.0.1"
     expect "^26.0.1"
 
+"@jest/globals@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.1.0.tgz#6cc5d7cbb79b76b120f2403d7d755693cf063ab1"
+  integrity sha512-MKiHPNaT+ZoG85oMaYUmGHEqu98y3WO2yeIDJrs2sJqHhYOy3Z6F7F/luzFomRQ8SQ1wEkmahFAz2291Iv8EAw==
+  dependencies:
+    "@jest/environment" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    expect "^26.1.0"
+
 "@jest/reporters@^26.0.1":
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.0.1.tgz#14ae00e7a93e498cec35b0c00ab21c375d9b078f"
@@ -1239,6 +1252,15 @@
     graceful-fs "^4.2.4"
     source-map "^0.6.0"
 
+"@jest/source-map@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.1.0.tgz#a6a020d00e7d9478f4b690167c5e8b77e63adb26"
+  integrity sha512-XYRPYx4eEVX15cMT9mstnO7hkHP3krNtKfxUYd8L7gbtia8JvZZ6bMzSwa6IQJENbudTwKMw5R1BePRD+bkEmA==
+  dependencies:
+    callsites "^3.0.0"
+    graceful-fs "^4.2.4"
+    source-map "^0.6.0"
+
 "@jest/test-result@^26.0.1":
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.0.1.tgz#1ffdc1ba4bc289919e54b9414b74c9c2f7b2b718"
@@ -1249,16 +1271,26 @@
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-sequencer@^26.0.1":
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.0.1.tgz#b0563424728f3fe9e75d1442b9ae4c11da73f090"
-  integrity sha512-ssga8XlwfP8YjbDcmVhwNlrmblddMfgUeAkWIXts1V22equp2GMIHxm7cyeD5Q/B0ZgKPK/tngt45sH99yLLGg==
+"@jest/test-result@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.1.0.tgz#a93fa15b21ad3c7ceb21c2b4c35be2e407d8e971"
+  integrity sha512-Xz44mhXph93EYMA8aYDz+75mFbarTV/d/x0yMdI3tfSRs/vh4CqSxgzVmCps1fPkHDCtn0tU8IH9iCKgGeGpfw==
   dependencies:
-    "@jest/test-result" "^26.0.1"
+    "@jest/console" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    collect-v8-coverage "^1.0.0"
+
+"@jest/test-sequencer@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.1.0.tgz#41a6fc8b850c3f33f48288ea9ea517c047e7f14e"
+  integrity sha512-Z/hcK+rTq56E6sBwMoQhSRDVjqrGtj1y14e2bIgcowARaIE1SgOanwx6gvY4Q9gTKMoZQXbXvptji+q5GYxa6Q==
+  dependencies:
+    "@jest/test-result" "^26.1.0"
     graceful-fs "^4.2.4"
-    jest-haste-map "^26.0.1"
-    jest-runner "^26.0.1"
-    jest-runtime "^26.0.1"
+    jest-haste-map "^26.1.0"
+    jest-runner "^26.1.0"
+    jest-runtime "^26.1.0"
 
 "@jest/transform@^25.2.4":
   version "25.5.1"
@@ -1303,6 +1335,27 @@
     source-map "^0.6.1"
     write-file-atomic "^3.0.0"
 
+"@jest/transform@^26.1.0":
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.1.0.tgz#697f48898c2a2787c9b4cb71d09d7e617464e509"
+  integrity sha512-ICPm6sUXmZJieq45ix28k0s+d/z2E8CHDsq+WwtWI6kW8m7I8kPqarSEcUN86entHQ570ZBRci5OWaKL0wlAWw==
+  dependencies:
+    "@babel/core" "^7.1.0"
+    "@jest/types" "^26.1.0"
+    babel-plugin-istanbul "^6.0.0"
+    chalk "^4.0.0"
+    convert-source-map "^1.4.0"
+    fast-json-stable-stringify "^2.0.0"
+    graceful-fs "^4.2.4"
+    jest-haste-map "^26.1.0"
+    jest-regex-util "^26.0.0"
+    jest-util "^26.1.0"
+    micromatch "^4.0.2"
+    pirates "^4.0.1"
+    slash "^3.0.0"
+    source-map "^0.6.1"
+    write-file-atomic "^3.0.0"
+
 "@jest/types@^25.2.3", "@jest/types@^25.5.0":
   version "25.5.0"
   resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
@@ -1404,10 +1457,10 @@
     "@babel/runtime" "^7.10.3"
     "@testing-library/dom" "^7.17.1"
 
-"@types/babel__core@^7.1.0", "@types/babel__core@^7.1.3":
-  version "7.1.7"
-  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89"
-  integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==
+"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
+  version "7.1.9"
+  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
+  integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==
   dependencies:
     "@babel/parser" "^7.1.0"
     "@babel/types" "^7.0.0"
@@ -1415,10 +1468,10 @@
     "@types/babel__template" "*"
     "@types/babel__traverse" "*"
 
-"@types/babel__core@^7.1.7":
-  version "7.1.8"
-  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.8.tgz#057f725aca3641f49fc11c7a87a9de5ec588a5d7"
-  integrity sha512-KXBiQG2OXvaPWFPDS1rD8yV9vO0OuWIqAEqLsbfX0oU2REN5KuoMnZ1gClWcBhO5I3n6oTVAmrMufOvRqdmFTQ==
+"@types/babel__core@^7.1.0", "@types/babel__core@^7.1.3":
+  version "7.1.7"
+  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89"
+  integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==
   dependencies:
     "@babel/parser" "^7.1.0"
     "@babel/types" "^7.0.0"
@@ -1523,9 +1576,9 @@
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 
 "@types/node@*":
-  version "14.0.11"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3"
-  integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg==
+  version "14.0.14"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
+  integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==
 
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
@@ -2151,16 +2204,16 @@ babel-jest@^25.2.4:
     chalk "^3.0.0"
     slash "^3.0.0"
 
-babel-jest@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.0.1.tgz#450139ce4b6c17174b136425bda91885c397bc46"
-  integrity sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw==
+babel-jest@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.1.0.tgz#b20751185fc7569a0f135730584044d1cb934328"
+  integrity sha512-Nkqgtfe7j6PxLO6TnCQQlkMm8wdTdnIF8xrdpooHCuD5hXRzVEPbPneTJKknH5Dsv3L8ip9unHDAp48YQ54Dkg==
   dependencies:
-    "@jest/transform" "^26.0.1"
-    "@jest/types" "^26.0.1"
+    "@jest/transform" "^26.1.0"
+    "@jest/types" "^26.1.0"
     "@types/babel__core" "^7.1.7"
     babel-plugin-istanbul "^6.0.0"
-    babel-preset-jest "^26.0.0"
+    babel-preset-jest "^26.1.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     slash "^3.0.0"
@@ -2219,13 +2272,14 @@ babel-plugin-jest-hoist@^25.5.0:
     "@babel/types" "^7.3.3"
     "@types/babel__traverse" "^7.0.6"
 
-babel-plugin-jest-hoist@^26.0.0:
-  version "26.0.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz#fd1d35f95cf8849fc65cb01b5e58aedd710b34a8"
-  integrity sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w==
+babel-plugin-jest-hoist@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.1.0.tgz#c6a774da08247a28285620a64dfadbd05dd5233a"
+  integrity sha512-qhqLVkkSlqmC83bdMhM8WW4Z9tB+JkjqAqlbbohS9sJLT5Ha2vfzuKqg5yenXrAjOPG2YC0WiXdH3a9PvB+YYw==
   dependencies:
     "@babel/template" "^7.3.3"
     "@babel/types" "^7.3.3"
+    "@types/babel__core" "^7.0.0"
     "@types/babel__traverse" "^7.0.6"
 
 babel-plugin-lodash@^3.3.4:
@@ -2281,13 +2335,14 @@ babel-plugin-transform-react-remove-prop-types@^0.4.24:
   integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==
 
 babel-preset-current-node-syntax@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz#fb4a4c51fe38ca60fede1dc74ab35eb843cb41d6"
-  integrity sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da"
+  integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==
   dependencies:
     "@babel/plugin-syntax-async-generators" "^7.8.4"
     "@babel/plugin-syntax-bigint" "^7.8.3"
     "@babel/plugin-syntax-class-properties" "^7.8.3"
+    "@babel/plugin-syntax-import-meta" "^7.8.3"
     "@babel/plugin-syntax-json-strings" "^7.8.3"
     "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3"
     "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
@@ -2304,12 +2359,12 @@ babel-preset-jest@^25.2.1:
     babel-plugin-jest-hoist "^25.5.0"
     babel-preset-current-node-syntax "^0.1.2"
 
-babel-preset-jest@^26.0.0:
-  version "26.0.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz#1eac82f513ad36c4db2e9263d7c485c825b1faa6"
-  integrity sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw==
+babel-preset-jest@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.1.0.tgz#612f714e5b457394acfd863793c564cbcdb7d1c1"
+  integrity sha512-na9qCqFksknlEj5iSdw1ehMVR06LCCTkZLGKeEtxDDdhg8xpUF09m29Kvh1pRbZ07h7AQ5ttLYUwpXL4tO6w7w==
   dependencies:
-    babel-plugin-jest-hoist "^26.0.0"
+    babel-plugin-jest-hoist "^26.1.0"
     babel-preset-current-node-syntax "^0.1.2"
 
 babel-runtime@^6.26.0:
@@ -4462,6 +4517,18 @@ expect@^26.0.1:
     jest-message-util "^26.0.1"
     jest-regex-util "^26.0.0"
 
+expect@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-26.1.0.tgz#8c62e31d0f8d5a8ebb186ee81473d15dd2fbf7c8"
+  integrity sha512-QbH4LZXDsno9AACrN9eM0zfnby9G+OsdNgZUohjg/P0mLy1O+/bzTAJGT6VSIjVCe8yKM6SzEl/ckEOFBT7Vnw==
+  dependencies:
+    "@jest/types" "^26.1.0"
+    ansi-styles "^4.0.0"
+    jest-get-type "^26.0.0"
+    jest-matcher-utils "^26.1.0"
+    jest-message-util "^26.1.0"
+    jest-regex-util "^26.0.0"
+
 express@^4.16.3, express@^4.17.1:
   version "4.17.1"
   resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@@ -6146,29 +6213,29 @@ jest-cli@^26.0.1:
     prompts "^2.0.1"
     yargs "^15.3.1"
 
-jest-config@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.0.1.tgz#096a3d4150afadf719d1fab00e9a6fb2d6d67507"
-  integrity sha512-9mWKx2L1LFgOXlDsC4YSeavnblN6A4CPfXFiobq+YYLaBMymA/SczN7xYTSmLaEYHZOcB98UdoN4m5uNt6tztg==
+jest-config@^26.0.1, jest-config@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.1.0.tgz#9074f7539acc185e0113ad6d22ed589c16a37a73"
+  integrity sha512-ONTGeoMbAwGCdq4WuKkMcdMoyfs5CLzHEkzFOlVvcDXufZSaIWh/OXMLa2fwKXiOaFcqEw8qFr4VOKJQfn4CVw==
   dependencies:
     "@babel/core" "^7.1.0"
-    "@jest/test-sequencer" "^26.0.1"
-    "@jest/types" "^26.0.1"
-    babel-jest "^26.0.1"
+    "@jest/test-sequencer" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    babel-jest "^26.1.0"
     chalk "^4.0.0"
     deepmerge "^4.2.2"
     glob "^7.1.1"
     graceful-fs "^4.2.4"
-    jest-environment-jsdom "^26.0.1"
-    jest-environment-node "^26.0.1"
+    jest-environment-jsdom "^26.1.0"
+    jest-environment-node "^26.1.0"
     jest-get-type "^26.0.0"
-    jest-jasmine2 "^26.0.1"
+    jest-jasmine2 "^26.1.0"
     jest-regex-util "^26.0.0"
-    jest-resolve "^26.0.1"
-    jest-util "^26.0.1"
-    jest-validate "^26.0.1"
+    jest-resolve "^26.1.0"
+    jest-util "^26.1.0"
+    jest-validate "^26.1.0"
     micromatch "^4.0.2"
-    pretty-format "^26.0.1"
+    pretty-format "^26.1.0"
 
 jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.5.0:
   version "25.5.0"
@@ -6190,6 +6257,16 @@ jest-diff@^26.0.1:
     jest-get-type "^26.0.0"
     pretty-format "^26.0.1"
 
+jest-diff@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.1.0.tgz#00a549bdc936c9691eb4dc25d1fbd78bf456abb2"
+  integrity sha512-GZpIcom339y0OXznsEKjtkfKxNdg7bVbEofK8Q6MnevTIiR1jNhDWKhRX6X0SDXJlwn3dy59nZ1z55fLkAqPWg==
+  dependencies:
+    chalk "^4.0.0"
+    diff-sequences "^26.0.0"
+    jest-get-type "^26.0.0"
+    pretty-format "^26.1.0"
+
 jest-docblock@^26.0.0:
   version "26.0.0"
   resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5"
@@ -6197,18 +6274,18 @@ jest-docblock@^26.0.0:
   dependencies:
     detect-newline "^3.0.0"
 
-jest-each@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.0.1.tgz#633083061619302fc90dd8f58350f9d77d67be04"
-  integrity sha512-OTgJlwXCAR8NIWaXFL5DBbeS4QIYPuNASkzSwMCJO+ywo9BEa6TqkaSWsfR7VdbMLdgYJqSfQcIyjJCNwl5n4Q==
+jest-each@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.1.0.tgz#e35449875009a22d74d1bda183b306db20f286f7"
+  integrity sha512-lYiSo4Igr81q6QRsVQq9LIkJW0hZcKxkIkHzNeTMPENYYDw/W/Raq28iJ0sLlNFYz2qxxeLnc5K2gQoFYlu2bA==
   dependencies:
-    "@jest/types" "^26.0.1"
+    "@jest/types" "^26.1.0"
     chalk "^4.0.0"
     jest-get-type "^26.0.0"
-    jest-util "^26.0.1"
-    pretty-format "^26.0.1"
+    jest-util "^26.1.0"
+    pretty-format "^26.1.0"
 
-jest-environment-jsdom@^26.0.1:
+jest-environment-jsdom@^26.1.0:
   version "26.1.0"
   resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.1.0.tgz#9dc7313ffe1b59761dad1fedb76e2503e5d37c5b"
   integrity sha512-dWfiJ+spunVAwzXbdVqPH1LbuJW/kDL+FyqgA5YzquisHqTi0g9hquKif9xKm7c1bKBj6wbmJuDkeMCnxZEpUw==
@@ -6220,16 +6297,16 @@ jest-environment-jsdom@^26.0.1:
     jest-util "^26.1.0"
     jsdom "^16.2.2"
 
-jest-environment-node@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.0.1.tgz#584a9ff623124ff6eeb49e0131b5f7612b310b13"
-  integrity sha512-4FRBWcSn5yVo0KtNav7+5NH5Z/tEgDLp7VRQVS5tCouWORxj+nI+1tOLutM07Zb2Qi7ja+HEDoOUkjBSWZg/IQ==
+jest-environment-node@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.1.0.tgz#8bb387b3eefb132eab7826f9a808e4e05618960b"
+  integrity sha512-DNm5x1aQH0iRAe9UYAkZenuzuJ69VKzDCAYISFHQ5i9e+2Tbeu2ONGY7YStubCLH8a1wdKBgqScYw85+ySxqxg==
   dependencies:
-    "@jest/environment" "^26.0.1"
-    "@jest/fake-timers" "^26.0.1"
-    "@jest/types" "^26.0.1"
-    jest-mock "^26.0.1"
-    jest-util "^26.0.1"
+    "@jest/environment" "^26.1.0"
+    "@jest/fake-timers" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    jest-mock "^26.1.0"
+    jest-util "^26.1.0"
 
 jest-get-type@^25.2.6:
   version "25.2.6"
@@ -6281,27 +6358,47 @@ jest-haste-map@^26.0.1:
   optionalDependencies:
     fsevents "^2.1.2"
 
-jest-jasmine2@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.0.1.tgz#947c40ee816636ba23112af3206d6fa7b23c1c1c"
-  integrity sha512-ILaRyiWxiXOJ+RWTKupzQWwnPaeXPIoLS5uW41h18varJzd9/7I0QJGqg69fhTT1ev9JpSSo9QtalriUN0oqOg==
+jest-haste-map@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.1.0.tgz#ef31209be73f09b0d9445e7d213e1b53d0d1476a"
+  integrity sha512-WeBS54xCIz9twzkEdm6+vJBXgRBQfdbbXD0dk8lJh7gLihopABlJmIQFdWSDDtuDe4PRiObsjZSUjbJ1uhWEpA==
+  dependencies:
+    "@jest/types" "^26.1.0"
+    "@types/graceful-fs" "^4.1.2"
+    anymatch "^3.0.3"
+    fb-watchman "^2.0.0"
+    graceful-fs "^4.2.4"
+    jest-serializer "^26.1.0"
+    jest-util "^26.1.0"
+    jest-worker "^26.1.0"
+    micromatch "^4.0.2"
+    sane "^4.0.3"
+    walker "^1.0.7"
+    which "^2.0.2"
+  optionalDependencies:
+    fsevents "^2.1.2"
+
+jest-jasmine2@^26.0.1, jest-jasmine2@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.1.0.tgz#4dfe349b2b2d3c6b3a27c024fd4cb57ac0ed4b6f"
+  integrity sha512-1IPtoDKOAG+MeBrKvvuxxGPJb35MTTRSDglNdWWCndCB3TIVzbLThRBkwH9P081vXLgiJHZY8Bz3yzFS803xqQ==
   dependencies:
     "@babel/traverse" "^7.1.0"
-    "@jest/environment" "^26.0.1"
-    "@jest/source-map" "^26.0.0"
-    "@jest/test-result" "^26.0.1"
-    "@jest/types" "^26.0.1"
+    "@jest/environment" "^26.1.0"
+    "@jest/source-map" "^26.1.0"
+    "@jest/test-result" "^26.1.0"
+    "@jest/types" "^26.1.0"
     chalk "^4.0.0"
     co "^4.6.0"
-    expect "^26.0.1"
+    expect "^26.1.0"
     is-generator-fn "^2.0.0"
-    jest-each "^26.0.1"
-    jest-matcher-utils "^26.0.1"
-    jest-message-util "^26.0.1"
-    jest-runtime "^26.0.1"
-    jest-snapshot "^26.0.1"
-    jest-util "^26.0.1"
-    pretty-format "^26.0.1"
+    jest-each "^26.1.0"
+    jest-matcher-utils "^26.1.0"
+    jest-message-util "^26.1.0"
+    jest-runtime "^26.1.0"
+    jest-snapshot "^26.1.0"
+    jest-util "^26.1.0"
+    pretty-format "^26.1.0"
     throat "^5.0.0"
 
 jest-leak-detector@^26.0.1:
@@ -6312,6 +6409,14 @@ jest-leak-detector@^26.0.1:
     jest-get-type "^26.0.0"
     pretty-format "^26.0.1"
 
+jest-leak-detector@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.1.0.tgz#039c3a07ebcd8adfa984b6ac015752c35792e0a6"
+  integrity sha512-dsMnKF+4BVOZwvQDlgn3MG+Ns4JuLv8jNvXH56bgqrrboyCbI1rQg6EI5rs+8IYagVcfVP2yZFKfWNZy0rK0Hw==
+  dependencies:
+    jest-get-type "^26.0.0"
+    pretty-format "^26.1.0"
+
 jest-matcher-utils@^25.1.0:
   version "25.5.0"
   resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867"
@@ -6332,6 +6437,16 @@ jest-matcher-utils@^26.0.1:
     jest-get-type "^26.0.0"
     pretty-format "^26.0.1"
 
+jest-matcher-utils@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.1.0.tgz#cf75a41bd413dda784f022de5a65a2a5c73a5c92"
+  integrity sha512-PW9JtItbYvES/xLn5mYxjMd+Rk+/kIt88EfH3N7w9KeOrHWaHrdYPnVHndGbsFGRJ2d5gKtwggCvkqbFDoouQA==
+  dependencies:
+    chalk "^4.0.0"
+    jest-diff "^26.1.0"
+    jest-get-type "^26.0.0"
+    pretty-format "^26.1.0"
+
 jest-message-util@^26.0.1:
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.0.1.tgz#07af1b42fc450b4cc8e90e4c9cef11b33ce9b0ac"
@@ -6368,9 +6483,9 @@ jest-mock@^26.0.1, jest-mock@^26.1.0:
     "@jest/types" "^26.1.0"
 
 jest-pnp-resolver@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a"
-  integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
+  integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==
 
 jest-regex-util@^25.2.6:
   version "25.2.6"
@@ -6391,16 +6506,16 @@ jest-resolve-dependencies@^26.0.1:
     jest-regex-util "^26.0.0"
     jest-snapshot "^26.0.1"
 
-jest-resolve@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.0.1.tgz#21d1ee06f9ea270a343a8893051aeed940cde736"
-  integrity sha512-6jWxk0IKZkPIVTvq6s72RH735P8f9eCJW3IM5CX/SJFeKq1p2cZx0U49wf/SdMlhaB/anann5J2nCJj6HrbezQ==
+jest-resolve@^26.0.1, jest-resolve@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.1.0.tgz#a530eaa302b1f6fa0479079d1561dd69abc00e68"
+  integrity sha512-KsY1JV9FeVgEmwIISbZZN83RNGJ1CC+XUCikf/ZWJBX/tO4a4NvA21YixokhdR9UnmPKKAC4LafVixJBrwlmfg==
   dependencies:
-    "@jest/types" "^26.0.1"
+    "@jest/types" "^26.1.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     jest-pnp-resolver "^1.2.1"
-    jest-util "^26.0.1"
+    jest-util "^26.1.0"
     read-pkg-up "^7.0.1"
     resolve "^1.17.0"
     slash "^3.0.0"
@@ -6430,6 +6545,31 @@ jest-runner@^26.0.1:
     source-map-support "^0.5.6"
     throat "^5.0.0"
 
+jest-runner@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.1.0.tgz#457f7fc522afe46ca6db1dccf19f87f500b3288d"
+  integrity sha512-elvP7y0fVDREnfqit0zAxiXkDRSw6dgCkzPCf1XvIMnSDZ8yogmSKJf192dpOgnUVykmQXwYYJnCx641uLTgcw==
+  dependencies:
+    "@jest/console" "^26.1.0"
+    "@jest/environment" "^26.1.0"
+    "@jest/test-result" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    chalk "^4.0.0"
+    exit "^0.1.2"
+    graceful-fs "^4.2.4"
+    jest-config "^26.1.0"
+    jest-docblock "^26.0.0"
+    jest-haste-map "^26.1.0"
+    jest-jasmine2 "^26.1.0"
+    jest-leak-detector "^26.1.0"
+    jest-message-util "^26.1.0"
+    jest-resolve "^26.1.0"
+    jest-runtime "^26.1.0"
+    jest-util "^26.1.0"
+    jest-worker "^26.1.0"
+    source-map-support "^0.5.6"
+    throat "^5.0.0"
+
 jest-runtime@^26.0.1:
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.0.1.tgz#a121a6321235987d294168e282d52b364d7d3f89"
@@ -6462,6 +6602,38 @@ jest-runtime@^26.0.1:
     strip-bom "^4.0.0"
     yargs "^15.3.1"
 
+jest-runtime@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.1.0.tgz#45a37af42115f123ed5c51f126c05502da2469cb"
+  integrity sha512-1qiYN+EZLmG1QV2wdEBRf+Ci8i3VSfIYLF02U18PiUDrMbhfpN/EAMMkJtT02jgJUoaEOpHAIXG6zS3QRMzRmA==
+  dependencies:
+    "@jest/console" "^26.1.0"
+    "@jest/environment" "^26.1.0"
+    "@jest/fake-timers" "^26.1.0"
+    "@jest/globals" "^26.1.0"
+    "@jest/source-map" "^26.1.0"
+    "@jest/test-result" "^26.1.0"
+    "@jest/transform" "^26.1.0"
+    "@jest/types" "^26.1.0"
+    "@types/yargs" "^15.0.0"
+    chalk "^4.0.0"
+    collect-v8-coverage "^1.0.0"
+    exit "^0.1.2"
+    glob "^7.1.3"
+    graceful-fs "^4.2.4"
+    jest-config "^26.1.0"
+    jest-haste-map "^26.1.0"
+    jest-message-util "^26.1.0"
+    jest-mock "^26.1.0"
+    jest-regex-util "^26.0.0"
+    jest-resolve "^26.1.0"
+    jest-snapshot "^26.1.0"
+    jest-util "^26.1.0"
+    jest-validate "^26.1.0"
+    slash "^3.0.0"
+    strip-bom "^4.0.0"
+    yargs "^15.3.1"
+
 jest-serializer@^25.5.0:
   version "25.5.0"
   resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.5.0.tgz#a993f484e769b4ed54e70e0efdb74007f503072b"
@@ -6476,6 +6648,13 @@ jest-serializer@^26.0.0:
   dependencies:
     graceful-fs "^4.2.4"
 
+jest-serializer@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.1.0.tgz#72a394531fc9b08e173dc7d297440ac610d95022"
+  integrity sha512-eqZOQG/0+MHmr25b2Z86g7+Kzd5dG9dhCiUoyUNJPgiqi38DqbDEOlHcNijyfZoj74soGBohKBZuJFS18YTJ5w==
+  dependencies:
+    graceful-fs "^4.2.4"
+
 jest-snapshot@^26.0.1:
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.0.1.tgz#1baa942bd83d47b837a84af7fcf5fd4a236da399"
@@ -6497,6 +6676,27 @@ jest-snapshot@^26.0.1:
     pretty-format "^26.0.1"
     semver "^7.3.2"
 
+jest-snapshot@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.1.0.tgz#c36ed1e0334bd7bd2fe5ad07e93a364ead7e1349"
+  integrity sha512-YhSbU7eMTVQO/iRbNs8j0mKRxGp4plo7sJ3GzOQ0IYjvsBiwg0T1o0zGQAYepza7lYHuPTrG5J2yDd0CE2YxSw==
+  dependencies:
+    "@babel/types" "^7.0.0"
+    "@jest/types" "^26.1.0"
+    "@types/prettier" "^2.0.0"
+    chalk "^4.0.0"
+    expect "^26.1.0"
+    graceful-fs "^4.2.4"
+    jest-diff "^26.1.0"
+    jest-get-type "^26.0.0"
+    jest-haste-map "^26.1.0"
+    jest-matcher-utils "^26.1.0"
+    jest-message-util "^26.1.0"
+    jest-resolve "^26.1.0"
+    natural-compare "^1.4.0"
+    pretty-format "^26.1.0"
+    semver "^7.3.2"
+
 jest-util@^25.5.0:
   version "25.5.0"
   resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0"
@@ -6519,17 +6719,17 @@ jest-util@^26.0.1, jest-util@^26.1.0:
     is-ci "^2.0.0"
     micromatch "^4.0.2"
 
-jest-validate@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.0.1.tgz#a62987e1da5b7f724130f904725e22f4e5b2e23c"
-  integrity sha512-u0xRc+rbmov/VqXnX3DlkxD74rHI/CfS5xaV2VpeaVySjbb1JioNVOyly5b56q2l9ZKe7bVG5qWmjfctkQb0bA==
+jest-validate@^26.0.1, jest-validate@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.1.0.tgz#942c85ad3d60f78250c488a7f85d8f11a29788e7"
+  integrity sha512-WPApOOnXsiwhZtmkDsxnpye+XLb/tUISP+H6cHjfUIXvlG+eKwP+isnivsxlHCPaO9Q5wvbhloIBkdF3qUn+Nw==
   dependencies:
-    "@jest/types" "^26.0.1"
+    "@jest/types" "^26.1.0"
     camelcase "^6.0.0"
     chalk "^4.0.0"
     jest-get-type "^26.0.0"
     leven "^3.1.0"
-    pretty-format "^26.0.1"
+    pretty-format "^26.1.0"
 
 jest-watcher@^26.0.1:
   version "26.0.1"
@@ -6559,6 +6759,14 @@ jest-worker@^26.0.0:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
+jest-worker@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.1.0.tgz#65d5641af74e08ccd561c240e7db61284f82f33d"
+  integrity sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ==
+  dependencies:
+    merge-stream "^2.0.0"
+    supports-color "^7.0.0"
+
 jest@^26.0.1:
   version "26.0.1"
   resolved "https://registry.yarnpkg.com/jest/-/jest-26.0.1.tgz#5c51a2e58dff7525b65f169721767173bf832694"
@@ -8502,12 +8710,12 @@ pretty-format@^25.2.1, pretty-format@^25.5.0:
     ansi-styles "^4.0.0"
     react-is "^16.12.0"
 
-pretty-format@^26.0.1:
-  version "26.0.1"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.0.1.tgz#a4fe54fe428ad2fd3413ca6bbd1ec8c2e277e197"
-  integrity sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==
+pretty-format@^26.0.1, pretty-format@^26.1.0:
+  version "26.1.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.1.0.tgz#272b9cd1f1a924ab5d443dc224899d7a65cb96ec"
+  integrity sha512-GmeO1PEYdM+non4BKCj+XsPJjFOJIPnsLewqhDVoqY1xo0yNmDas7tC2XwpMrRAHR3MaE2hPo37deX5OisJ2Wg==
   dependencies:
-    "@jest/types" "^26.0.1"
+    "@jest/types" "^26.1.0"
     ansi-regex "^5.0.0"
     ansi-styles "^4.0.0"
     react-is "^16.12.0"

From 444b8d6188c3055f8bb74be5a2a77262d212d059 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 30 Jun 2020 01:24:56 +0900
Subject: [PATCH 21/29] Bump uuid from 8.1.0 to 8.2.0 (#14162)

Bumps [uuid](https://github.com/uuidjs/uuid) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.1.0...v8.2.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 yarn.lock    | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/package.json b/package.json
index e69644ce51..527b0abb72 100644
--- a/package.json
+++ b/package.json
@@ -160,7 +160,7 @@
     "tesseract.js": "^2.1.1",
     "throng": "^4.0.0",
     "tiny-queue": "^0.2.1",
-    "uuid": "^8.1.0",
+    "uuid": "^8.2.0",
     "webpack": "^4.43.0",
     "webpack-assets-manifest": "^3.1.1",
     "webpack-bundle-analyzer": "^3.8.0",
diff --git a/yarn.lock b/yarn.lock
index b7c999c081..6699891826 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11160,10 +11160,10 @@ uuid@^7.0.3:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
   integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
 
-uuid@^8.1.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
-  integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
+uuid@^8.2.0:
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
+  integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
 
 v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
   version "2.1.1"

From 742b0fedbd7e165ea18504ee049e265537b33fe4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 30 Jun 2020 13:18:25 +0900
Subject: [PATCH 22/29] Bump faker from 2.12.0 to 2.13.0 (#14174)

Bumps [faker](https://github.com/faker-ruby/faker) from 2.12.0 to 2.13.0.
- [Release notes](https://github.com/faker-ruby/faker/releases)
- [Changelog](https://github.com/faker-ruby/faker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faker-ruby/faker/compare/v2.12.0...v2.13.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Gemfile      | 2 +-
 Gemfile.lock | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Gemfile b/Gemfile
index aa64679c14..d9415d874b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -120,7 +120,7 @@ end
 group :test do
   gem 'capybara', '~> 3.33'
   gem 'climate_control', '~> 0.2'
-  gem 'faker', '~> 2.12'
+  gem 'faker', '~> 2.13'
   gem 'microformats', '~> 4.2'
   gem 'rails-controller-testing', '~> 1.0'
   gem 'rspec-sidekiq', '~> 3.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index d99e0db7ff..16030be034 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -218,7 +218,7 @@ GEM
       tzinfo
     excon (0.75.0)
     fabrication (2.21.1)
-    faker (2.12.0)
+    faker (2.13.0)
       i18n (>= 1.6, < 2)
     faraday (1.0.1)
       multipart-post (>= 1.2, < 3)
@@ -703,7 +703,7 @@ DEPENDENCIES
   e2mmap (~> 0.1.0)
   ed25519 (~> 1.2)
   fabrication (~> 2.21)
-  faker (~> 2.12)
+  faker (~> 2.13)
   fast_blank (~> 1.0)
   fastimage
   fog-core (<= 2.1.0)

From ce9ae9aa50cab4de4db5c06f54f4f95d736d68c6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 30 Jun 2020 13:18:38 +0900
Subject: [PATCH 23/29] Bump diff-lcs from 1.3 to 1.4.3 (#14186)

Bumps [diff-lcs](https://github.com/halostatue/diff-lcs) from 1.3 to 1.4.3.
- [Release notes](https://github.com/halostatue/diff-lcs/releases)
- [Changelog](https://github.com/halostatue/diff-lcs/blob/master/History.md)
- [Commits](https://github.com/halostatue/diff-lcs/compare/v1.3...v1.4.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 16030be034..96572bde56 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -188,7 +188,7 @@ GEM
     devise_pam_authenticatable2 (9.2.0)
       devise (>= 4.0.0)
       rpam2 (~> 4.0)
-    diff-lcs (1.3)
+    diff-lcs (1.4.3)
     discard (1.2.0)
       activerecord (>= 4.2, < 7)
     docile (1.3.2)

From 65506bac3f3fe233b5b7b3241020bd74eb5c9259 Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Tue, 30 Jun 2020 19:19:50 +0200
Subject: [PATCH 24/29] Add user notes on accounts (#14148)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add UserNote model

* Add UI for user notes

* Put comment in relationships entity

* Add API to create user notes

* Copy user notes to new account when receiving a Move activity

* Address some of the review remarks

* Replace modal by inline edition

* Please CodeClimate

* Button design changes

* Change design again

* Cancel note edition when pressing Escape

* Fixes

* Tweak design again

* Move “Add note” item, and allow users to add notes to themselves

* Rename UserNote into AccountNote, rename “comment” Relationship attribute to “note”
---
 .../api/v1/accounts/notes_controller.rb       |  30 +++++
 .../mastodon/actions/account_notes.js         |  69 ++++++++++++
 .../account/components/account_note.js        | 103 ++++++++++++++++++
 .../features/account/components/header.js     |  11 ++
 .../containers/account_note_container.js      |  34 ++++++
 .../account_timeline/components/header.js     |   6 +
 .../containers/header_container.js            |   5 +
 .../mastodon/reducers/account_notes.js        |  44 ++++++++
 app/javascript/mastodon/reducers/index.js     |   2 +
 .../mastodon/reducers/relationships.js        |   4 +
 .../styles/mastodon/components.scss           |  62 ++++++++++-
 app/models/account_note.rb                    |  20 ++++
 app/models/concerns/account_interactions.rb   |   8 ++
 .../account_relationships_presenter.rb        |   6 +-
 .../rest/relationship_serializer.rb           |   6 +-
 app/workers/move_worker.rb                    |  17 +++
 config/locales/en.yml                         |   2 +
 config/routes.rb                              |   1 +
 .../20200628133322_create_account_notes.rb    |  13 +++
 db/schema.rb                                  |  14 ++-
 spec/fabricators/account_note_fabricator.rb   |   5 +
 spec/workers/move_worker_spec.rb              |  27 +++++
 22 files changed, 485 insertions(+), 4 deletions(-)
 create mode 100644 app/controllers/api/v1/accounts/notes_controller.rb
 create mode 100644 app/javascript/mastodon/actions/account_notes.js
 create mode 100644 app/javascript/mastodon/features/account/components/account_note.js
 create mode 100644 app/javascript/mastodon/features/account/containers/account_note_container.js
 create mode 100644 app/javascript/mastodon/reducers/account_notes.js
 create mode 100644 app/models/account_note.rb
 create mode 100644 db/migrate/20200628133322_create_account_notes.rb
 create mode 100644 spec/fabricators/account_note_fabricator.rb

diff --git a/app/controllers/api/v1/accounts/notes_controller.rb b/app/controllers/api/v1/accounts/notes_controller.rb
new file mode 100644
index 0000000000..032e807d11
--- /dev/null
+++ b/app/controllers/api/v1/accounts/notes_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class Api::V1::Accounts::NotesController < Api::BaseController
+  include Authorization
+
+  before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
+  before_action :require_user!
+  before_action :set_account
+
+  def create
+    if params[:comment].blank?
+      AccountNote.find_by(account: current_account, target_account: @account)&.destroy
+    else
+      @note = AccountNote.find_or_initialize_by(account: current_account, target_account: @account)
+      @note.comment = params[:comment]
+      @note.save! if @note.changed?
+    end
+    render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
+  end
+
+  private
+
+  def set_account
+    @account = Account.find(params[:account_id])
+  end
+
+  def relationships_presenter
+    AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
+  end
+end
diff --git a/app/javascript/mastodon/actions/account_notes.js b/app/javascript/mastodon/actions/account_notes.js
new file mode 100644
index 0000000000..059ed9e803
--- /dev/null
+++ b/app/javascript/mastodon/actions/account_notes.js
@@ -0,0 +1,69 @@
+import api from '../api';
+
+export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
+export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
+export const ACCOUNT_NOTE_SUBMIT_FAIL    = 'ACCOUNT_NOTE_SUBMIT_FAIL';
+
+export const ACCOUNT_NOTE_INIT_EDIT = 'ACCOUNT_NOTE_INIT_EDIT';
+export const ACCOUNT_NOTE_CANCEL    = 'ACCOUNT_NOTE_CANCEL';
+
+export const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT';
+
+export function submitAccountNote() {
+  return (dispatch, getState) => {
+    dispatch(submitAccountNoteRequest());
+
+    const id = getState().getIn(['account_notes', 'edit', 'account_id']);
+
+    api(getState).post(`/api/v1/accounts/${id}/note`, {
+      comment: getState().getIn(['account_notes', 'edit', 'comment']),
+    }).then(response => {
+      dispatch(submitAccountNoteSuccess(response.data));
+    }).catch(error => dispatch(submitAccountNoteFail(error)));
+  };
+};
+
+export function submitAccountNoteRequest() {
+  return {
+    type: ACCOUNT_NOTE_SUBMIT_REQUEST,
+  };
+};
+
+export function submitAccountNoteSuccess(relationship) {
+  return {
+    type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
+    relationship,
+  };
+};
+
+export function submitAccountNoteFail(error) {
+  return {
+    type: ACCOUNT_NOTE_SUBMIT_FAIL,
+    error,
+  };
+};
+
+export function initEditAccountNote(account) {
+  return (dispatch, getState) => {
+    const comment = getState().getIn(['relationships', account.get('id'), 'note']);
+
+    dispatch({
+      type: ACCOUNT_NOTE_INIT_EDIT,
+      account,
+      comment,
+    });
+  };
+};
+
+export function cancelAccountNote() {
+  return {
+    type: ACCOUNT_NOTE_CANCEL,
+  };
+};
+
+export function changeAccountNoteComment(comment) {
+  return {
+    type: ACCOUNT_NOTE_CHANGE_COMMENT,
+    comment,
+  };
+};
diff --git a/app/javascript/mastodon/features/account/components/account_note.js b/app/javascript/mastodon/features/account/components/account_note.js
new file mode 100644
index 0000000000..832a96a6ae
--- /dev/null
+++ b/app/javascript/mastodon/features/account/components/account_note.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import Icon from 'mastodon/components/icon';
+import Textarea from 'react-textarea-autosize';
+
+const messages = defineMessages({
+  placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' },
+});
+
+export default @injectIntl
+class Header extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.map.isRequired,
+    isEditing: PropTypes.bool,
+    isSubmitting: PropTypes.bool,
+    accountNote: PropTypes.string,
+    onEditAccountNote: PropTypes.func.isRequired,
+    onCancelAccountNote: PropTypes.func.isRequired,
+    onSaveAccountNote: PropTypes.func.isRequired,
+    onChangeAccountNote: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleChangeAccountNote = (e) => {
+    this.props.onChangeAccountNote(e.target.value);
+  };
+
+  componentWillUnmount () {
+    if (this.props.isEditing) {
+      this.props.onCancelAccountNote();
+    }
+  }
+
+  handleKeyDown = e => {
+    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+      this.props.onSaveAccountNote();
+    } else if (e.keyCode === 27) {
+      this.props.onCancelAccountNote();
+    }
+  }
+
+  render () {
+    const { account, accountNote, isEditing, isSubmitting, intl } = this.props;
+
+    if (!account || (!accountNote && !isEditing)) {
+      return null;
+    }
+
+    let action_buttons = null;
+    if (isEditing) {
+      action_buttons = (
+        <div className='account__header__account-note__buttons'>
+          <button className='text-btn' tabIndex='0' onClick={this.props.onCancelAccountNote} disabled={isSubmitting}>
+            <Icon id='times' size={15} /> <FormattedMessage id='account_note.cancel' defaultMessage='Cancel' />
+          </button>
+          <div className='flex-spacer' />
+          <button className='text-btn' tabIndex='0' onClick={this.props.onSaveAccountNote} disabled={isSubmitting}>
+            <Icon id='check' size={15} /> <FormattedMessage id='account_note.save' defaultMessage='Save' />
+          </button>
+        </div>
+      );
+    }
+
+    let note_container = null;
+    if (isEditing) {
+      note_container = (
+        <Textarea
+          className='account__header__account-note__content'
+          disabled={isSubmitting}
+          placeholder={intl.formatMessage(messages.placeholder)}
+          value={accountNote}
+          onChange={this.handleChangeAccountNote}
+          onKeyDown={this.handleKeyDown}
+          autoFocus
+        />
+      );
+    } else {
+      note_container = (<div className='account__header__account-note__content'>{accountNote}</div>);
+    }
+
+    return (
+      <div className='account__header__account-note'>
+        <div className='account__header__account-note__header'>
+          <strong><FormattedMessage id='account.account_note_header' defaultMessage='Your note for @{name}' values={{ name: account.get('username') }} /></strong>
+          {!isEditing && (
+            <div>
+              <button className='text-btn' tabIndex='0' onClick={this.props.onEditAccountNote} disabled={isSubmitting}>
+                <Icon id='pencil' size={15} /> <FormattedMessage id='account_note.edit' defaultMessage='Edit' />
+              </button>
+            </div>
+          )}
+        </div>
+        {note_container}
+        {action_buttons}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 8c85bbc393..eca0b7901a 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -11,6 +11,7 @@ import Avatar from 'mastodon/components/avatar';
 import { shortNumberFormat } from 'mastodon/utils/numbers';
 import { NavLink } from 'react-router-dom';
 import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
+import AccountNoteContainer from '../containers/account_note_container';
 
 const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@@ -45,6 +46,7 @@ const messages = defineMessages({
   unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
   add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
   admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
+  add_account_note: { id: 'account.add_account_note', defaultMessage: 'Add note for @{name}' },
 });
 
 const dateFormatOptions = {
@@ -64,6 +66,7 @@ class Header extends ImmutablePureComponent {
     identity_props: ImmutablePropTypes.list,
     onFollow: PropTypes.func.isRequired,
     onBlock: PropTypes.func.isRequired,
+    onEditAccountNote: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
     domain: PropTypes.string.isRequired,
   };
@@ -128,6 +131,8 @@ class Header extends ImmutablePureComponent {
       return null;
     }
 
+    const accountNote = account.getIn(['relationship', 'note']);
+
     let info        = [];
     let actionBtn   = '';
     let lockedIcon  = '';
@@ -178,6 +183,10 @@ class Header extends ImmutablePureComponent {
       menu.push(null);
     }
 
+    if (accountNote === null) {
+      menu.push({ text: intl.formatMessage(messages.add_account_note, { name: account.get('username') }), action: this.props.onEditAccountNote });
+    }
+
     if (account.get('id') === me) {
       menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
       menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
@@ -284,6 +293,8 @@ class Header extends ImmutablePureComponent {
             </h1>
           </div>
 
+          <AccountNoteContainer account={account} />
+
           <div className='account__header__extra'>
             <div className='account__header__bio'>
               { (fields.size > 0 || identity_proofs.size > 0) && (
diff --git a/app/javascript/mastodon/features/account/containers/account_note_container.js b/app/javascript/mastodon/features/account/containers/account_note_container.js
new file mode 100644
index 0000000000..92d470982b
--- /dev/null
+++ b/app/javascript/mastodon/features/account/containers/account_note_container.js
@@ -0,0 +1,34 @@
+import { connect } from 'react-redux';
+import { changeAccountNoteComment, submitAccountNote, initEditAccountNote, cancelAccountNote } from 'mastodon/actions/account_notes';
+import AccountNote from '../components/account_note';
+
+const mapStateToProps = (state, { account }) => {
+  const isEditing = state.getIn(['account_notes', 'edit', 'account_id']) === account.get('id');
+
+  return {
+    isSubmitting: state.getIn(['account_notes', 'edit', 'isSubmitting']),
+    accountNote: isEditing ? state.getIn(['account_notes', 'edit', 'comment']) : account.getIn(['relationship', 'note']),
+    isEditing,
+  };
+};
+
+const mapDispatchToProps = (dispatch, { account }) => ({
+
+  onEditAccountNote() {
+    dispatch(initEditAccountNote(account));
+  },
+
+  onSaveAccountNote() {
+    dispatch(submitAccountNote());
+  },
+
+  onCancelAccountNote() {
+    dispatch(cancelAccountNote());
+  },
+
+  onChangeAccountNote(comment) {
+    dispatch(changeAccountNoteComment(comment));
+  },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(AccountNote);
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index 844b8a236a..4e1b27466b 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -23,6 +23,7 @@ export default class Header extends ImmutablePureComponent {
     onUnblockDomain: PropTypes.func.isRequired,
     onEndorseToggle: PropTypes.func.isRequired,
     onAddToList: PropTypes.func.isRequired,
+    onEditAccountNote: PropTypes.func.isRequired,
     hideTabs: PropTypes.bool,
     domain: PropTypes.string.isRequired,
   };
@@ -83,6 +84,10 @@ export default class Header extends ImmutablePureComponent {
     this.props.onAddToList(this.props.account);
   }
 
+  handleEditAccountNote = () => {
+    this.props.onEditAccountNote(this.props.account);
+  }
+
   render () {
     const { account, hideTabs, identity_proofs } = this.props;
 
@@ -108,6 +113,7 @@ export default class Header extends ImmutablePureComponent {
           onUnblockDomain={this.handleUnblockDomain}
           onEndorseToggle={this.handleEndorseToggle}
           onAddToList={this.handleAddToList}
+          onEditAccountNote={this.handleEditAccountNote}
           domain={this.props.domain}
         />
 
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 8728b48068..e480fb2aa4 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -19,6 +19,7 @@ import { initBlockModal } from '../../../actions/blocks';
 import { initReport } from '../../../actions/reports';
 import { openModal } from '../../../actions/modal';
 import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
+import { initEditAccountNote } from 'mastodon/actions/account_notes';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { unfollowModal } from '../../../initial_state';
 import { List as ImmutableList } from 'immutable';
@@ -102,6 +103,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     }
   },
 
+  onEditAccountNote (account) {
+    dispatch(initEditAccountNote(account));
+  },
+
   onBlockDomain (domain) {
     dispatch(openModal('CONFIRM', {
       message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
diff --git a/app/javascript/mastodon/reducers/account_notes.js b/app/javascript/mastodon/reducers/account_notes.js
new file mode 100644
index 0000000000..b1cf2e0aa8
--- /dev/null
+++ b/app/javascript/mastodon/reducers/account_notes.js
@@ -0,0 +1,44 @@
+import { Map as ImmutableMap } from 'immutable';
+
+import {
+  ACCOUNT_NOTE_INIT_EDIT,
+  ACCOUNT_NOTE_CANCEL,
+  ACCOUNT_NOTE_CHANGE_COMMENT,
+  ACCOUNT_NOTE_SUBMIT_REQUEST,
+  ACCOUNT_NOTE_SUBMIT_FAIL,
+  ACCOUNT_NOTE_SUBMIT_SUCCESS,
+} from '../actions/account_notes';
+
+const initialState = ImmutableMap({
+  edit: ImmutableMap({
+    isSubmitting: false,
+    account_id: null,
+    comment: null,
+  }),
+});
+
+export default function account_notes(state = initialState, action) {
+  switch (action.type) {
+  case ACCOUNT_NOTE_INIT_EDIT:
+    return state.withMutations((state) => {
+      state.setIn(['edit', 'isSubmitting'], false);
+      state.setIn(['edit', 'account_id'], action.account.get('id'));
+      state.setIn(['edit', 'comment'], action.comment);
+    });
+  case ACCOUNT_NOTE_CHANGE_COMMENT:
+    return state.setIn(['edit', 'comment'], action.comment);
+  case ACCOUNT_NOTE_SUBMIT_REQUEST:
+    return state.setIn(['edit', 'isSubmitting'], true);
+  case ACCOUNT_NOTE_SUBMIT_FAIL:
+    return state.setIn(['edit', 'isSubmitting'], false);
+  case ACCOUNT_NOTE_SUBMIT_SUCCESS:
+  case ACCOUNT_NOTE_CANCEL:
+    return state.withMutations((state) => {
+      state.setIn(['edit', 'isSubmitting'], false);
+      state.setIn(['edit', 'account_id'], null);
+      state.setIn(['edit', 'comment'], null);
+    });
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index 3823bb05e0..690349b85f 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -36,6 +36,7 @@ import trends from './trends';
 import missed_updates from './missed_updates';
 import announcements from './announcements';
 import markers from './markers';
+import account_notes from './account_notes';
 
 const reducers = {
   announcements,
@@ -75,6 +76,7 @@ const reducers = {
   trends,
   missed_updates,
   markers,
+  account_notes,
 };
 
 export default combineReducers(reducers);
diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js
index 8322780de5..1d050cc634 100644
--- a/app/javascript/mastodon/reducers/relationships.js
+++ b/app/javascript/mastodon/reducers/relationships.js
@@ -17,6 +17,9 @@ import {
   DOMAIN_BLOCK_SUCCESS,
   DOMAIN_UNBLOCK_SUCCESS,
 } from '../actions/domain_blocks';
+import {
+  ACCOUNT_NOTE_SUBMIT_SUCCESS,
+} from '../actions/account_notes';
 import { Map as ImmutableMap, fromJS } from 'immutable';
 
 const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
@@ -57,6 +60,7 @@ export default function relationships(state = initialState, action) {
   case ACCOUNT_UNMUTE_SUCCESS:
   case ACCOUNT_PIN_SUCCESS:
   case ACCOUNT_UNPIN_SUCCESS:
+  case ACCOUNT_NOTE_SUBMIT_SUCCESS:
     return normalizeRelationship(state, action.relationship);
   case RELATIONSHIPS_FETCH_SUCCESS:
     return normalizeRelationships(state, action.relationships);
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index e22f712614..b3f6d5749f 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3841,7 +3841,6 @@ a.status-card.compact:hover {
   color: $primary-text-color;
   margin-bottom: 4px;
   display: block;
-  vertical-align: top;
   background-color: $base-overlay-background;
   text-transform: uppercase;
   font-size: 11px;
@@ -6605,6 +6604,67 @@ noscript {
       }
     }
   }
+
+  &__account-note {
+    margin: 5px;
+    padding: 10px;
+    background: $ui-highlight-color;
+    color: $primary-text-color;
+    display: flex;
+    flex-direction: column;
+    border-radius: 4px;
+    font-size: 14px;
+    font-weight: 400;
+
+    &__header {
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+    }
+
+    &__content {
+      white-space: pre-wrap;
+      margin-top: 5px;
+    }
+
+    &__buttons {
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-end;
+      margin-top: 5px;
+
+      .flex-spacer {
+        flex: 0 0 20px;
+        background: transparent;
+      }
+    }
+
+    strong {
+      font-size: 15px;
+      font-weight: 500;
+    }
+
+    button:hover span {
+      text-decoration: underline;
+    }
+
+    textarea {
+      display: block;
+      box-sizing: border-box;
+      width: 100%;
+      margin: 0;
+      margin-top: 5px;
+      color: $inverted-text-color;
+      background: $simple-background-color;
+      padding: 10px;
+      font-family: inherit;
+      font-size: 14px;
+      resize: none;
+      border: 0;
+      outline: 0;
+      border-radius: 4px;
+    }
+  }
 }
 
 .trends {
diff --git a/app/models/account_note.rb b/app/models/account_note.rb
new file mode 100644
index 0000000000..bf61df923f
--- /dev/null
+++ b/app/models/account_note.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: account_notes
+#
+#  id                :bigint(8)        not null, primary key
+#  account_id        :bigint(8)
+#  target_account_id :bigint(8)
+#  comment           :text             not null
+#  created_at        :datetime         not null
+#  updated_at        :datetime         not null
+#
+class AccountNote < ApplicationRecord
+  include RelationshipCacheable
+
+  belongs_to :account
+  belongs_to :target_account, class_name: 'Account'
+
+  validates :account_id, uniqueness: { scope: :target_account_id }
+end
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index 32fcb53975..be7211f2ca 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -44,6 +44,14 @@ module AccountInteractions
       follow_mapping(AccountPin.where(account_id: account_id, target_account_id: target_account_ids), :target_account_id)
     end
 
+    def account_note_map(target_account_ids, account_id)
+      AccountNote.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |note, mapping|
+        mapping[note.target_account_id] = {
+          comment: note.comment,
+        }
+      end
+    end
+
     def domain_blocking_map(target_account_ids, account_id)
       accounts_map    = Account.where(id: target_account_ids).select('id, domain').each_with_object({}) { |a, h| h[a.id] = a.domain }
       blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
diff --git a/app/presenters/account_relationships_presenter.rb b/app/presenters/account_relationships_presenter.rb
index 08614b67c2..d662380f69 100644
--- a/app/presenters/account_relationships_presenter.rb
+++ b/app/presenters/account_relationships_presenter.rb
@@ -3,7 +3,7 @@
 class AccountRelationshipsPresenter
   attr_reader :following, :followed_by, :blocking, :blocked_by,
               :muting, :requested, :domain_blocking,
-              :endorsed
+              :endorsed, :account_note
 
   def initialize(account_ids, current_account_id, **options)
     @account_ids        = account_ids.map { |a| a.is_a?(Account) ? a.id : a.to_i }
@@ -17,6 +17,7 @@ class AccountRelationshipsPresenter
     @requested       = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
     @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
     @endorsed        = cached[:endorsed].merge(Account.endorsed_map(@uncached_account_ids, @current_account_id))
+    @account_note    = cached[:account_note].merge(Account.account_note_map(@uncached_account_ids, @current_account_id))
 
     cache_uncached!
 
@@ -28,6 +29,7 @@ class AccountRelationshipsPresenter
     @requested.merge!(options[:requested_map] || {})
     @domain_blocking.merge!(options[:domain_blocking_map] || {})
     @endorsed.merge!(options[:endorsed_map] || {})
+    @account_note.merge!(options[:account_note_map] || {})
   end
 
   private
@@ -44,6 +46,7 @@ class AccountRelationshipsPresenter
       requested: {},
       domain_blocking: {},
       endorsed: {},
+      account_note: {},
     }
 
     @uncached_account_ids = []
@@ -72,6 +75,7 @@ class AccountRelationshipsPresenter
         requested:       { account_id => requested[account_id] },
         domain_blocking: { account_id => domain_blocking[account_id] },
         endorsed:        { account_id => endorsed[account_id] },
+        account_note:    { account_id => account_note[account_id] },
       }
 
       Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
diff --git a/app/serializers/rest/relationship_serializer.rb b/app/serializers/rest/relationship_serializer.rb
index 1a3fd915cb..e295fb8477 100644
--- a/app/serializers/rest/relationship_serializer.rb
+++ b/app/serializers/rest/relationship_serializer.rb
@@ -3,7 +3,7 @@
 class REST::RelationshipSerializer < ActiveModel::Serializer
   attributes :id, :following, :showing_reblogs, :followed_by, :blocking, :blocked_by,
              :muting, :muting_notifications, :requested, :domain_blocking,
-             :endorsed
+             :endorsed, :note
 
   def id
     object.id.to_s
@@ -50,4 +50,8 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
   def endorsed
     instance_options[:relationships].endorsed[object.id] || false
   end
+
+  def note
+    (instance_options[:relationships].account_note[object.id] || {})[:comment]
+  end
 end
diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb
index 5957302263..4d76461b07 100644
--- a/app/workers/move_worker.rb
+++ b/app/workers/move_worker.rb
@@ -12,6 +12,8 @@ class MoveWorker
     else
       queue_follow_unfollows!
     end
+
+    copy_account_notes!
   rescue ActiveRecord::RecordNotFound
     true
   end
@@ -34,4 +36,19 @@ class MoveWorker
       UnfollowFollowWorker.push_bulk(accounts.map(&:id)) { |follower_id| [follower_id, @source_account.id, @target_account.id, bypass_locked] }
     end
   end
+
+  def copy_account_notes!
+    AccountNote.where(target_account: @source_account).find_each do |note|
+      text = I18n.with_locale(note.account.user.locale || I18n.default_locale) do
+        I18n.t('move_handler.copy_account_note_text', acct: @source_account.acct)
+      end
+
+      new_note = AccountNote.find_by(account: note.account, target_account: @target_account)
+      if new_note.nil?
+        AccountNote.create!(account: note.account, target_account: @target_account, comment: [text, note.comment].join('\n'))
+      else
+        new_note.update!(comment: [text, note.comment, '\n', new_note.comment].join('\n'))
+      end
+    end
+  end
 end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 7fc58643fa..b3906a127a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -940,6 +940,8 @@ en:
       redirect: Your current account's profile will be updated with a redirect notice and be excluded from searches
   moderation:
     title: Moderation
+  move_handler:
+    copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
   notification_mailer:
     digest:
       action: View all notifications
diff --git a/config/routes.rb b/config/routes.rb
index 31333999e7..a16f89687f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -430,6 +430,7 @@ Rails.application.routes.draw do
 
         resource :pin, only: :create, controller: 'accounts/pins'
         post :unpin, to: 'accounts/pins#destroy'
+        resource :note, only: :create, controller: 'accounts/notes'
       end
 
       resources :lists, only: [:index, :create, :show, :update, :destroy] do
diff --git a/db/migrate/20200628133322_create_account_notes.rb b/db/migrate/20200628133322_create_account_notes.rb
new file mode 100644
index 0000000000..664727e60b
--- /dev/null
+++ b/db/migrate/20200628133322_create_account_notes.rb
@@ -0,0 +1,13 @@
+class CreateAccountNotes < ActiveRecord::Migration[5.2]
+  def change
+    create_table :account_notes do |t|
+      t.references :account, foreign_key: { on_delete: :cascade }, index: false
+      t.references :target_account, foreign_key: { to_table: :accounts, on_delete: :cascade }
+      t.text :comment, null: false
+      t.index [:account_id, :target_account_id], unique: true
+
+      t.timestamps
+    end
+  end
+end
+
diff --git a/db/schema.rb b/db/schema.rb
index 277eccba77..712ba1d2dc 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: 2020_06_27_125810) do
+ActiveRecord::Schema.define(version: 2020_06_28_133322) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -833,6 +833,16 @@ ActiveRecord::Schema.define(version: 2020_06_27_125810) do
     t.index ["user_id"], name: "index_user_invite_requests_on_user_id"
   end
 
+  create_table "account_notes", force: :cascade do |t|
+    t.bigint "account_id"
+    t.bigint "target_account_id"
+    t.text "comment", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["account_id", "target_account_id"], name: "index_account_notes_on_account_id_and_target_account_id", unique: true
+    t.index ["target_account_id"], name: "index_account_notes_on_target_account_id"
+  end
+
   create_table "users", force: :cascade do |t|
     t.string "email", default: "", null: false
     t.datetime "created_at", null: false
@@ -989,6 +999,8 @@ ActiveRecord::Schema.define(version: 2020_06_27_125810) do
   add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade
   add_foreign_key "tombstones", "accounts", on_delete: :cascade
   add_foreign_key "user_invite_requests", "users", on_delete: :cascade
+  add_foreign_key "account_notes", "accounts", column: "target_account_id", on_delete: :cascade
+  add_foreign_key "account_notes", "accounts", on_delete: :cascade
   add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade
   add_foreign_key "users", "invites", on_delete: :nullify
   add_foreign_key "users", "oauth_applications", column: "created_by_application_id", on_delete: :nullify
diff --git a/spec/fabricators/account_note_fabricator.rb b/spec/fabricators/account_note_fabricator.rb
new file mode 100644
index 0000000000..1b061745a3
--- /dev/null
+++ b/spec/fabricators/account_note_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:account_note) do
+  account
+  target_account { Fabricate(:account) }
+  comment        "User note text"
+end
diff --git a/spec/workers/move_worker_spec.rb b/spec/workers/move_worker_spec.rb
index b8f4d99007..77b921eaff 100644
--- a/spec/workers/move_worker_spec.rb
+++ b/spec/workers/move_worker_spec.rb
@@ -6,6 +6,8 @@ describe MoveWorker do
   let(:local_follower)   { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
   let(:source_account)   { Fabricate(:account, protocol: :activitypub, domain: 'example.com') }
   let(:target_account)   { Fabricate(:account, protocol: :activitypub, domain: 'example.com') }
+  let(:local_user)       { Fabricate(:user) }
+  let!(:account_note)    { Fabricate(:account_note, account: local_user.account, target_account: source_account) }
 
   subject { described_class.new }
 
@@ -13,6 +15,25 @@ describe MoveWorker do
     local_follower.follow!(source_account)
   end
 
+  shared_examples 'user note handling' do
+    it 'copies user note' do
+      allow(UnfollowFollowWorker).to receive(:push_bulk)
+      subject.perform(source_account.id, target_account.id)
+      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(source_account.acct)
+      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(account_note.comment)
+    end
+
+    it 'merges user notes when needed' do
+      new_account_note = AccountNote.create!(account: account_note.account, target_account: target_account, comment: 'new note prior to move')
+
+      allow(UnfollowFollowWorker).to receive(:push_bulk)
+      subject.perform(source_account.id, target_account.id)
+      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(source_account.acct)
+      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(account_note.comment)
+      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(new_account_note.comment)
+    end
+  end
+
   context 'both accounts are distant' do
     describe 'perform' do
       it 'calls UnfollowFollowWorker' do
@@ -20,6 +41,8 @@ describe MoveWorker do
         subject.perform(source_account.id, target_account.id)
         expect(UnfollowFollowWorker).to have_received(:push_bulk).with([local_follower.id])
       end
+
+      include_examples 'user note handling'
     end
   end
 
@@ -32,6 +55,8 @@ describe MoveWorker do
         subject.perform(source_account.id, target_account.id)
         expect(UnfollowFollowWorker).to have_received(:push_bulk).with([local_follower.id])
       end
+
+      include_examples 'user note handling'
     end
   end
 
@@ -45,6 +70,8 @@ describe MoveWorker do
         expect(local_follower.following?(target_account)).to be true
       end
 
+      include_examples 'user note handling'
+
       it 'does not fail when a local user is already following both accounts' do
         double_follower = Fabricate(:user, email: 'eve@example.com', account: Fabricate(:account, username: 'eve')).account
         double_follower.follow!(source_account)

From 934bacd05f88c1d17c1d9c61c8e8553ff9148c0e Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Fri, 26 Jun 2020 14:31:13 +0200
Subject: [PATCH 25/29] [Glitch] Change sensitive preview cards to not blur
 text

Port a1ad2ad9519fda525858ba5aef86815a6f6385f2 to glitch-soc

Signed-off-by: Thibaut Girka <thib@sitedethib.com>
---
 .../flavours/glitch/features/status/components/card.js    | 8 +++-----
 .../flavours/glitch/styles/components/status.scss         | 5 -----
 2 files changed, 3 insertions(+), 10 deletions(-)

diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js
index 13bc6c2b4c..d2de225c0e 100644
--- a/app/javascript/flavours/glitch/features/status/components/card.js
+++ b/app/javascript/flavours/glitch/features/status/components/card.js
@@ -7,7 +7,6 @@ import punycode from 'punycode';
 import classnames from 'classnames';
 import { decode as decodeIDNA } from 'flavours/glitch/util/idna';
 import Icon from 'flavours/glitch/components/icon';
-import classNames from 'classnames';
 import { useBlurhash } from 'flavours/glitch/util/initial_state';
 import { decode } from 'blurhash';
 
@@ -196,7 +195,7 @@ export default class Card extends React.PureComponent {
     const height      = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
 
     const description = (
-      <div className={classNames('status-card__content', { 'status-card__content--blurred': !revealed })}>
+      <div className='status-card__content'>
         {title}
         {!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
         <span className='status-card__host'>{provider}</span>
@@ -204,7 +203,7 @@ export default class Card extends React.PureComponent {
     );
 
     let embed     = '';
-    let canvas = <canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('status-card__image-preview', { 'status-card__image-preview--hidden' : revealed && this.state.previewLoaded })} />;
+    let canvas = <canvas width={32} height={32} ref={this.setCanvasRef} className={classnames('status-card__image-preview', { 'status-card__image-preview--hidden' : revealed && this.state.previewLoaded })} />;
     let thumbnail = <img src={card.get('image')} alt='' style={{ width: horizontal ? width : null, height: horizontal ? height : null, visibility: revealed ? null : 'hidden' }} onLoad={this.handleImageLoad} className='status-card__image-image' />;
     let spoilerButton = (
       <button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
@@ -212,7 +211,7 @@ export default class Card extends React.PureComponent {
       </button>
     );
     spoilerButton = (
-      <div className={classNames('spoiler-button', { 'spoiler-button--minified': revealed })}>
+      <div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}>
         {spoilerButton}
       </div>
     );
@@ -270,7 +269,6 @@ export default class Card extends React.PureComponent {
       <a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
         {embed}
         {description}
-        {!revealed && spoilerButton}
       </a>
     );
   }
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 4d308e6014..fe4f163532 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -875,11 +875,6 @@ a.status-card {
   flex: 1 1 auto;
   overflow: hidden;
   padding: 14px 14px 14px 8px;
-
-  &--blurred {
-    filter: blur(2px);
-    pointer-events: none;
-  }
 }
 
 .status-card__description {

From befe8c72ddd3b427ccf1489a6de6bcc23b6a55f5 Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Sun, 28 Jun 2020 20:55:32 +0200
Subject: [PATCH 26/29] [Glitch] Fix read marker state not being udpated
 internally

Port fa4876a1b93d4bb62038cca75bd5017fe49b59ae to glitch-soc

Signed-off-by: Thibaut Girka <thib@sitedethib.com>
---
 app/javascript/flavours/glitch/reducers/markers.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/javascript/flavours/glitch/reducers/markers.js b/app/javascript/flavours/glitch/reducers/markers.js
index 2e67be82e7..fb1572ff59 100644
--- a/app/javascript/flavours/glitch/reducers/markers.js
+++ b/app/javascript/flavours/glitch/reducers/markers.js
@@ -1,6 +1,6 @@
 import {
   MARKERS_SUBMIT_SUCCESS,
-} from '../actions/notifications';
+} from '../actions/markers';
 
 const initialState = ImmutableMap({
   home: '0',

From 50f5254568885afcb312514eebb4b0b997cb81f5 Mon Sep 17 00:00:00 2001
From: trwnh <a@trwnh.com>
Date: Mon, 29 Jun 2020 06:57:44 -0500
Subject: [PATCH 27/29] [Glitch] Fix padding on account header

Port d1c6dd2d277fee0cf65b480adc868c69df952622 to glitch-soc

Signed-off-by: Thibaut Girka <thib@sitedethib.com>
---
 .../flavours/glitch/styles/components/accounts.scss           | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index 610e48f920..e0239ff79d 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -605,7 +605,7 @@
   &__tabs {
     display: flex;
     align-items: flex-start;
-    padding: 7px 5px;
+    padding: 7px 10px;
     margin-top: -55px;
 
     &__buttons {
@@ -627,7 +627,7 @@
     }
 
     &__name {
-      padding: 5px;
+      padding: 5px 10px;
 
       .account-role {
         vertical-align: top;

From 511edccf617097046e16273a2364e8fbf5d913a0 Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Tue, 30 Jun 2020 19:19:50 +0200
Subject: [PATCH 28/29] [Glitch] Add user notes on accounts

Port 65506bac3f3fe233b5b7b3241020bd74eb5c9259 to glitch-soc

Signed-off-by: Thibaut Girka <thib@sitedethib.com>
---
 .../flavours/glitch/actions/account_notes.js  |  69 ++++++++++++
 .../account/components/account_note.js        | 103 ++++++++++++++++++
 .../features/account/components/header.js     |  11 ++
 .../containers/account_note_container.js      |  34 ++++++
 .../account_timeline/components/header.js     |   6 +
 .../containers/header_container.js            |   5 +
 .../flavours/glitch/reducers/account_notes.js |  44 ++++++++
 .../flavours/glitch/reducers/index.js         |   2 +
 .../flavours/glitch/reducers/relationships.js |   4 +
 .../glitch/styles/components/accounts.scss    |  62 ++++++++++-
 10 files changed, 339 insertions(+), 1 deletion(-)
 create mode 100644 app/javascript/flavours/glitch/actions/account_notes.js
 create mode 100644 app/javascript/flavours/glitch/features/account/components/account_note.js
 create mode 100644 app/javascript/flavours/glitch/features/account/containers/account_note_container.js
 create mode 100644 app/javascript/flavours/glitch/reducers/account_notes.js

diff --git a/app/javascript/flavours/glitch/actions/account_notes.js b/app/javascript/flavours/glitch/actions/account_notes.js
new file mode 100644
index 0000000000..c1cce31939
--- /dev/null
+++ b/app/javascript/flavours/glitch/actions/account_notes.js
@@ -0,0 +1,69 @@
+import api from 'flavours/glitch/util/api';
+
+export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
+export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
+export const ACCOUNT_NOTE_SUBMIT_FAIL    = 'ACCOUNT_NOTE_SUBMIT_FAIL';
+
+export const ACCOUNT_NOTE_INIT_EDIT = 'ACCOUNT_NOTE_INIT_EDIT';
+export const ACCOUNT_NOTE_CANCEL    = 'ACCOUNT_NOTE_CANCEL';
+
+export const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT';
+
+export function submitAccountNote() {
+  return (dispatch, getState) => {
+    dispatch(submitAccountNoteRequest());
+
+    const id = getState().getIn(['account_notes', 'edit', 'account_id']);
+
+    api(getState).post(`/api/v1/accounts/${id}/note`, {
+      comment: getState().getIn(['account_notes', 'edit', 'comment']),
+    }).then(response => {
+      dispatch(submitAccountNoteSuccess(response.data));
+    }).catch(error => dispatch(submitAccountNoteFail(error)));
+  };
+};
+
+export function submitAccountNoteRequest() {
+  return {
+    type: ACCOUNT_NOTE_SUBMIT_REQUEST,
+  };
+};
+
+export function submitAccountNoteSuccess(relationship) {
+  return {
+    type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
+    relationship,
+  };
+};
+
+export function submitAccountNoteFail(error) {
+  return {
+    type: ACCOUNT_NOTE_SUBMIT_FAIL,
+    error,
+  };
+};
+
+export function initEditAccountNote(account) {
+  return (dispatch, getState) => {
+    const comment = getState().getIn(['relationships', account.get('id'), 'note']);
+
+    dispatch({
+      type: ACCOUNT_NOTE_INIT_EDIT,
+      account,
+      comment,
+    });
+  };
+};
+
+export function cancelAccountNote() {
+  return {
+    type: ACCOUNT_NOTE_CANCEL,
+  };
+};
+
+export function changeAccountNoteComment(comment) {
+  return {
+    type: ACCOUNT_NOTE_CHANGE_COMMENT,
+    comment,
+  };
+};
diff --git a/app/javascript/flavours/glitch/features/account/components/account_note.js b/app/javascript/flavours/glitch/features/account/components/account_note.js
new file mode 100644
index 0000000000..e7fd4c5ff2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/account/components/account_note.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import Icon from 'flavours/glitch/components/icon';
+import Textarea from 'react-textarea-autosize';
+
+const messages = defineMessages({
+  placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' },
+});
+
+export default @injectIntl
+class Header extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.map.isRequired,
+    isEditing: PropTypes.bool,
+    isSubmitting: PropTypes.bool,
+    accountNote: PropTypes.string,
+    onEditAccountNote: PropTypes.func.isRequired,
+    onCancelAccountNote: PropTypes.func.isRequired,
+    onSaveAccountNote: PropTypes.func.isRequired,
+    onChangeAccountNote: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleChangeAccountNote = (e) => {
+    this.props.onChangeAccountNote(e.target.value);
+  };
+
+  componentWillUnmount () {
+    if (this.props.isEditing) {
+      this.props.onCancelAccountNote();
+    }
+  }
+
+  handleKeyDown = e => {
+    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+      this.props.onSaveAccountNote();
+    } else if (e.keyCode === 27) {
+      this.props.onCancelAccountNote();
+    }
+  }
+
+  render () {
+    const { account, accountNote, isEditing, isSubmitting, intl } = this.props;
+
+    if (!account || (!accountNote && !isEditing)) {
+      return null;
+    }
+
+    let action_buttons = null;
+    if (isEditing) {
+      action_buttons = (
+        <div className='account__header__account-note__buttons'>
+          <button className='text-btn' tabIndex='0' onClick={this.props.onCancelAccountNote} disabled={isSubmitting}>
+            <Icon id='times' size={15} /> <FormattedMessage id='account_note.cancel' defaultMessage='Cancel' />
+          </button>
+          <div className='flex-spacer' />
+          <button className='text-btn' tabIndex='0' onClick={this.props.onSaveAccountNote} disabled={isSubmitting}>
+            <Icon id='check' size={15} /> <FormattedMessage id='account_note.save' defaultMessage='Save' />
+          </button>
+        </div>
+      );
+    }
+
+    let note_container = null;
+    if (isEditing) {
+      note_container = (
+        <Textarea
+          className='account__header__account-note__content'
+          disabled={isSubmitting}
+          placeholder={intl.formatMessage(messages.placeholder)}
+          value={accountNote}
+          onChange={this.handleChangeAccountNote}
+          onKeyDown={this.handleKeyDown}
+          autoFocus
+        />
+      );
+    } else {
+      note_container = (<div className='account__header__account-note__content'>{accountNote}</div>);
+    }
+
+    return (
+      <div className='account__header__account-note'>
+        <div className='account__header__account-note__header'>
+          <strong><FormattedMessage id='account.account_note_header' defaultMessage='Your note for @{name}' values={{ name: account.get('username') }} /></strong>
+          {!isEditing && (
+            <div>
+              <button className='text-btn' tabIndex='0' onClick={this.props.onEditAccountNote} disabled={isSubmitting}>
+                <Icon id='pencil' size={15} /> <FormattedMessage id='account_note.edit' defaultMessage='Edit' />
+              </button>
+            </div>
+          )}
+        </div>
+        {note_container}
+        {action_buttons}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index c7b54649c9..a5abf38aec 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -12,6 +12,7 @@ import Button from 'flavours/glitch/components/button';
 import { shortNumberFormat } from 'flavours/glitch/util/numbers';
 import { NavLink } from 'react-router-dom';
 import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
+import AccountNoteContainer from '../containers/account_note_container';
 
 const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@@ -46,6 +47,7 @@ const messages = defineMessages({
   unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
   add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
   admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
+  add_account_note: { id: 'account.add_account_note', defaultMessage: 'Add note for @{name}' },
 });
 
 const dateFormatOptions = {
@@ -65,6 +67,7 @@ class Header extends ImmutablePureComponent {
     identity_props: ImmutablePropTypes.list,
     onFollow: PropTypes.func.isRequired,
     onBlock: PropTypes.func.isRequired,
+    onEditAccountNote: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
     domain: PropTypes.string.isRequired,
   };
@@ -121,6 +124,8 @@ class Header extends ImmutablePureComponent {
       return null;
     }
 
+    const accountNote = account.getIn(['relationship', 'note']);
+
     let info        = [];
     let actionBtn   = '';
     let lockedIcon  = '';
@@ -172,6 +177,10 @@ class Header extends ImmutablePureComponent {
       menu.push(null);
     }
 
+    if (accountNote === null) {
+      menu.push({ text: intl.formatMessage(messages.add_account_note, { name: account.get('username') }), action: this.props.onEditAccountNote });
+    }
+
     if (account.get('id') === me) {
       if (profileLink) menu.push({ text: intl.formatMessage(messages.edit_profile), href: profileLink });
       if (preferencesLink) menu.push({ text: intl.formatMessage(messages.preferences), href: preferencesLink });
@@ -278,6 +287,8 @@ class Header extends ImmutablePureComponent {
             </h1>
           </div>
 
+          <AccountNoteContainer account={account} />
+
           <div className='account__header__extra'>
             <div className='account__header__bio'>
               { (fields.size > 0 || identity_proofs.size > 0) && (
diff --git a/app/javascript/flavours/glitch/features/account/containers/account_note_container.js b/app/javascript/flavours/glitch/features/account/containers/account_note_container.js
new file mode 100644
index 0000000000..f1d007ecb0
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/account/containers/account_note_container.js
@@ -0,0 +1,34 @@
+import { connect } from 'react-redux';
+import { changeAccountNoteComment, submitAccountNote, initEditAccountNote, cancelAccountNote } from 'flavours/glitch/actions/account_notes';
+import AccountNote from '../components/account_note';
+
+const mapStateToProps = (state, { account }) => {
+  const isEditing = state.getIn(['account_notes', 'edit', 'account_id']) === account.get('id');
+
+  return {
+    isSubmitting: state.getIn(['account_notes', 'edit', 'isSubmitting']),
+    accountNote: isEditing ? state.getIn(['account_notes', 'edit', 'comment']) : account.getIn(['relationship', 'note']),
+    isEditing,
+  };
+};
+
+const mapDispatchToProps = (dispatch, { account }) => ({
+
+  onEditAccountNote() {
+    dispatch(initEditAccountNote(account));
+  },
+
+  onSaveAccountNote() {
+    dispatch(submitAccountNote());
+  },
+
+  onCancelAccountNote() {
+    dispatch(cancelAccountNote());
+  },
+
+  onChangeAccountNote(comment) {
+    dispatch(changeAccountNoteComment(comment));
+  },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(AccountNote);
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
index 0faa8a4245..1bab05c721 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
@@ -24,6 +24,7 @@ export default class Header extends ImmutablePureComponent {
     onUnblockDomain: PropTypes.func.isRequired,
     onEndorseToggle: PropTypes.func.isRequired,
     onAddToList: PropTypes.func.isRequired,
+    onEditAccountNote: PropTypes.func.isRequired,
     hideTabs: PropTypes.bool,
     domain: PropTypes.string.isRequired,
   };
@@ -84,6 +85,10 @@ export default class Header extends ImmutablePureComponent {
     this.props.onAddToList(this.props.account);
   }
 
+  handleEditAccountNote = () => {
+    this.props.onEditAccountNote(this.props.account);
+  }
+
   render () {
     const { account, hideTabs, identity_proofs } = this.props;
 
@@ -109,6 +114,7 @@ export default class Header extends ImmutablePureComponent {
           onUnblockDomain={this.handleUnblockDomain}
           onEndorseToggle={this.handleEndorseToggle}
           onAddToList={this.handleAddToList}
+          onEditAccountNote={this.handleEditAccountNote}
           domain={this.props.domain}
         />
 
diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
index fff5e097f7..2259102928 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
@@ -19,6 +19,7 @@ import { initBlockModal } from 'flavours/glitch/actions/blocks';
 import { initReport } from 'flavours/glitch/actions/reports';
 import { openModal } from 'flavours/glitch/actions/modal';
 import { blockDomain, unblockDomain } from 'flavours/glitch/actions/domain_blocks';
+import { initEditAccountNote } from 'flavours/glitch/actions/account_notes';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { unfollowModal } from 'flavours/glitch/util/initial_state';
 import { List as ImmutableList } from 'immutable';
@@ -106,6 +107,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     }
   },
 
+  onEditAccountNote (account) {
+    dispatch(initEditAccountNote(account));
+  },
+
   onBlockDomain (domain) {
     dispatch(openModal('CONFIRM', {
       message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />,
diff --git a/app/javascript/flavours/glitch/reducers/account_notes.js b/app/javascript/flavours/glitch/reducers/account_notes.js
new file mode 100644
index 0000000000..b1cf2e0aa8
--- /dev/null
+++ b/app/javascript/flavours/glitch/reducers/account_notes.js
@@ -0,0 +1,44 @@
+import { Map as ImmutableMap } from 'immutable';
+
+import {
+  ACCOUNT_NOTE_INIT_EDIT,
+  ACCOUNT_NOTE_CANCEL,
+  ACCOUNT_NOTE_CHANGE_COMMENT,
+  ACCOUNT_NOTE_SUBMIT_REQUEST,
+  ACCOUNT_NOTE_SUBMIT_FAIL,
+  ACCOUNT_NOTE_SUBMIT_SUCCESS,
+} from '../actions/account_notes';
+
+const initialState = ImmutableMap({
+  edit: ImmutableMap({
+    isSubmitting: false,
+    account_id: null,
+    comment: null,
+  }),
+});
+
+export default function account_notes(state = initialState, action) {
+  switch (action.type) {
+  case ACCOUNT_NOTE_INIT_EDIT:
+    return state.withMutations((state) => {
+      state.setIn(['edit', 'isSubmitting'], false);
+      state.setIn(['edit', 'account_id'], action.account.get('id'));
+      state.setIn(['edit', 'comment'], action.comment);
+    });
+  case ACCOUNT_NOTE_CHANGE_COMMENT:
+    return state.setIn(['edit', 'comment'], action.comment);
+  case ACCOUNT_NOTE_SUBMIT_REQUEST:
+    return state.setIn(['edit', 'isSubmitting'], true);
+  case ACCOUNT_NOTE_SUBMIT_FAIL:
+    return state.setIn(['edit', 'isSubmitting'], false);
+  case ACCOUNT_NOTE_SUBMIT_SUCCESS:
+  case ACCOUNT_NOTE_CANCEL:
+    return state.withMutations((state) => {
+      state.setIn(['edit', 'isSubmitting'], false);
+      state.setIn(['edit', 'account_id'], null);
+      state.setIn(['edit', 'comment'], null);
+    });
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js
index 852abe9dda..cadbd01a3b 100644
--- a/app/javascript/flavours/glitch/reducers/index.js
+++ b/app/javascript/flavours/glitch/reducers/index.js
@@ -37,6 +37,7 @@ import identity_proofs from './identity_proofs';
 import trends from './trends';
 import announcements from './announcements';
 import markers from './markers';
+import account_notes from './account_notes';
 
 const reducers = {
   announcements,
@@ -77,6 +78,7 @@ const reducers = {
   polls,
   trends,
   markers,
+  account_notes,
 };
 
 export default combineReducers(reducers);
diff --git a/app/javascript/flavours/glitch/reducers/relationships.js b/app/javascript/flavours/glitch/reducers/relationships.js
index 4652bbc14c..dcaeefcae8 100644
--- a/app/javascript/flavours/glitch/reducers/relationships.js
+++ b/app/javascript/flavours/glitch/reducers/relationships.js
@@ -13,6 +13,9 @@ import {
   DOMAIN_BLOCK_SUCCESS,
   DOMAIN_UNBLOCK_SUCCESS,
 } from 'flavours/glitch/actions/domain_blocks';
+import {
+  ACCOUNT_NOTE_SUBMIT_SUCCESS,
+} from 'flavours/glitch/actions/account_notes';
 import { Map as ImmutableMap, fromJS } from 'immutable';
 
 const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
@@ -45,6 +48,7 @@ export default function relationships(state = initialState, action) {
   case ACCOUNT_UNMUTE_SUCCESS:
   case ACCOUNT_PIN_SUCCESS:
   case ACCOUNT_UNPIN_SUCCESS:
+  case ACCOUNT_NOTE_SUBMIT_SUCCESS:
     return normalizeRelationship(state, action.relationship);
   case RELATIONSHIPS_FETCH_SUCCESS:
     return normalizeRelationships(state, action.relationships);
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index e0239ff79d..774254a4c1 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -379,7 +379,6 @@
   color: $primary-text-color;
   margin-bottom: 4px;
   display: block;
-  vertical-align: top;
   background-color: $base-overlay-background;
   text-transform: uppercase;
   font-size: 11px;
@@ -713,4 +712,65 @@
       }
     }
   }
+
+  &__account-note {
+    margin: 5px;
+    padding: 10px;
+    background: $ui-highlight-color;
+    color: $primary-text-color;
+    display: flex;
+    flex-direction: column;
+    border-radius: 4px;
+    font-size: 14px;
+    font-weight: 400;
+
+    &__header {
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+    }
+
+    &__content {
+      white-space: pre-wrap;
+      margin-top: 5px;
+    }
+
+    &__buttons {
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-end;
+      margin-top: 5px;
+
+      .flex-spacer {
+        flex: 0 0 20px;
+        background: transparent;
+      }
+    }
+
+    strong {
+      font-size: 15px;
+      font-weight: 500;
+    }
+
+    button:hover span {
+      text-decoration: underline;
+    }
+
+    textarea {
+      display: block;
+      box-sizing: border-box;
+      width: 100%;
+      margin: 0;
+      margin-top: 5px;
+      color: $inverted-text-color;
+      background: $simple-background-color;
+      padding: 10px;
+      font-family: inherit;
+      font-size: 14px;
+      resize: none;
+      border: 0;
+      outline: 0;
+      border-radius: 4px;
+    }
+  }
 }

From 3d7ce178dd7be9ef02d34bf6734365d0aefa3120 Mon Sep 17 00:00:00 2001
From: Thibaut Girka <thib@sitedethib.com>
Date: Tue, 30 Jun 2020 23:20:21 +0200
Subject: [PATCH 29/29] Use allow/deny-list instead of white/black-list in
 .env.production.sample
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

.env.production.sample has been nuked upstream, but we decided to keep it,
so change it to reflect latest changes in environment variable names.

(The link still refers to “whitelist_mode” because the documentation hasn't
been updated yet)
---
 .env.production.sample | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/.env.production.sample b/.env.production.sample
index 1292b752e2..b76a937ade 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -52,9 +52,9 @@ VAPID_PUBLIC_KEY=
 # Single user mode will disable registrations and redirect frontpage to the first profile
 # SINGLE_USER_MODE=true
 # Prevent registrations with following e-mail domains
-# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
+# EMAIL_DOMAIN_DENYLIST=example1.com|example2.de|etc
 # Only allow registrations with the following e-mail domains
-# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
+# EMAIL_DOMAIN_ALLOWLIST=example1.com|example2.de|etc
 
 # Optionally change default language
 # DEFAULT_LOCALE=de
@@ -286,7 +286,7 @@ STREAMING_CLUSTER_NUM=1
 # https://docs.joinmastodon.org/admin/config/#authorized_fetch
 # AUTHORIZED_FETCH=true
 
-# Whitelist mode (optional)
-# Only allow federation with whitelisted domains, see
+# Limited federation mode (optional)
+# Only allow federation with specific domains, see
 # https://docs.joinmastodon.org/admin/config/#whitelist_mode
-# WHITELIST_MODE=true
+# LIMITED_FEDERATION_MODE=true