about summary refs log tree commit diff
diff options
context:
space:
mode:
authorkibigo! <marrus-sh@users.noreply.github.com>2017-08-01 13:07:43 -0700
committerkibigo! <marrus-sh@users.noreply.github.com>2017-08-01 13:20:29 -0700
commit8150689b48716bb016d492d28cef08600a4b315e (patch)
treea05a2539e894c79ef17698dce0da5a6af0c25bf6
parentb61e3daf983d87c6d2de7e54d420c2e8f5a531e6 (diff)
parent7ef848256871454a790a9b7cc725053c67ba3da4 (diff)
Merge upstream (#111)
-rw-r--r--.dockerignore1
-rw-r--r--.env.production.sample2
-rw-r--r--.eslintrc.yml4
-rw-r--r--.gitignore1
-rw-r--r--.nanoignore1
-rw-r--r--.rubocop.yml9
-rw-r--r--.slugignore1
-rw-r--r--Aptfile10
-rw-r--r--README.md1
-rw-r--r--app.json2
-rw-r--r--app/controllers/settings/two_factor_authentications_controller.rb7
-rw-r--r--app/helpers/instance_helper.rb2
-rw-r--r--app/javascript/mastodon/components/autosuggest_textarea.js31
-rw-r--r--app/javascript/mastodon/components/column_back_button.js4
-rw-r--r--app/javascript/mastodon/components/column_header.js25
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js57
-rw-r--r--app/javascript/mastodon/components/icon_button.js7
-rw-r--r--app/javascript/mastodon/components/media_gallery.js4
-rw-r--r--app/javascript/mastodon/components/setting_text.js15
-rw-r--r--app/javascript/mastodon/components/status.js30
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js8
-rw-r--r--app/javascript/mastodon/components/status_content.js8
-rw-r--r--app/javascript/mastodon/components/status_list.js40
-rw-r--r--app/javascript/mastodon/containers/dropdown_menu_container.js16
-rw-r--r--app/javascript/mastodon/emoji.js4
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js14
-rw-r--r--app/javascript/mastodon/features/account/components/header.js7
-rw-r--r--app/javascript/mastodon/features/compose/components/character_counter.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js12
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js23
-rw-r--r--app/javascript/mastodon/features/compose/components/navigation_bar.js1
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js64
-rw-r--r--app/javascript/mastodon/features/compose/components/search.js21
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_button.js23
-rw-r--r--app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js7
-rw-r--r--app/javascript/mastodon/features/compose/containers/sensitive_button_container.js5
-rw-r--r--app/javascript/mastodon/features/compose/index.js18
-rw-r--r--app/javascript/mastodon/features/compose/util/counter.js7
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js58
-rw-r--r--app/javascript/mastodon/features/notifications/components/setting_toggle.js8
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js17
-rw-r--r--app/javascript/mastodon/features/ui/components/actions_modal.js71
-rw-r--r--app/javascript/mastodon/features/ui/components/column.js12
-rw-r--r--app/javascript/mastodon/features/ui/components/column_header.js5
-rw-r--r--app/javascript/mastodon/features/ui/components/column_link.js9
-rw-r--r--app/javascript/mastodon/features/ui/components/column_loading.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/columns_area.js9
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.js14
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.js30
-rw-r--r--app/javascript/mastodon/features/ui/components/tabs_bar.js70
-rw-r--r--app/javascript/mastodon/features/ui/index.js16
-rw-r--r--app/javascript/mastodon/features/ui/util/async-components.js4
-rw-r--r--app/javascript/mastodon/is_mobile.js9
-rw-r--r--app/javascript/mastodon/load_polyfills.js2
-rw-r--r--app/javascript/mastodon/locales/ar.json8
-rw-r--r--app/javascript/mastodon/locales/bg.json8
-rw-r--r--app/javascript/mastodon/locales/ca.json8
-rw-r--r--app/javascript/mastodon/locales/de.json8
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json36
-rw-r--r--app/javascript/mastodon/locales/en.json8
-rw-r--r--app/javascript/mastodon/locales/eo.json8
-rw-r--r--app/javascript/mastodon/locales/es.json8
-rw-r--r--app/javascript/mastodon/locales/fa.json36
-rw-r--r--app/javascript/mastodon/locales/fi.json8
-rw-r--r--app/javascript/mastodon/locales/fr.json10
-rw-r--r--app/javascript/mastodon/locales/he.json8
-rw-r--r--app/javascript/mastodon/locales/hr.json8
-rw-r--r--app/javascript/mastodon/locales/hu.json8
-rw-r--r--app/javascript/mastodon/locales/id.json8
-rw-r--r--app/javascript/mastodon/locales/io.json8
-rw-r--r--app/javascript/mastodon/locales/it.json8
-rw-r--r--app/javascript/mastodon/locales/ja.json14
-rw-r--r--app/javascript/mastodon/locales/ko.json8
-rw-r--r--app/javascript/mastodon/locales/nl.json14
-rw-r--r--app/javascript/mastodon/locales/no.json8
-rw-r--r--app/javascript/mastodon/locales/oc.json44
-rw-r--r--app/javascript/mastodon/locales/pl.json8
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json8
-rw-r--r--app/javascript/mastodon/locales/pt.json8
-rw-r--r--app/javascript/mastodon/locales/ru.json8
-rw-r--r--app/javascript/mastodon/locales/th.json8
-rw-r--r--app/javascript/mastodon/locales/tr.json8
-rw-r--r--app/javascript/mastodon/locales/uk.json8
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json8
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json8
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json8
-rw-r--r--app/javascript/mastodon/reducers/compose.js16
-rw-r--r--app/javascript/mastodon/service_worker/web_push_notifications.js79
-rw-r--r--app/javascript/styles/about.scss56
-rw-r--r--app/javascript/styles/components.scss104
-rw-r--r--app/lib/emoji.rb4
-rw-r--r--app/lib/exceptions.rb10
-rw-r--r--app/lib/language_detector.rb4
-rw-r--r--app/models/account.rb2
-rw-r--r--app/models/web/push_subscription.rb8
-rw-r--r--app/services/account_search_service.rb2
-rw-r--r--app/services/batched_remove_status_service.rb2
-rw-r--r--app/validators/status_length_validator.rb23
-rw-r--r--app/views/about/_contact.html.haml5
-rw-r--r--app/views/about/_registration.html.haml6
-rw-r--r--app/views/admin_mailer/new_report.text.erb2
-rw-r--r--app/views/auth/passwords/edit.html.haml4
-rw-r--r--app/views/auth/registrations/_sessions.html.haml4
-rw-r--r--app/views/auth/registrations/edit.html.haml6
-rw-r--r--app/views/auth/registrations/new.html.haml4
-rw-r--r--app/views/auth/sessions/new.html.haml2
-rw-r--r--app/views/auth/sessions/two_factor.html.haml2
-rw-r--r--app/views/notification_mailer/digest.text.erb2
-rw-r--r--app/views/notification_mailer/favourite.text.erb2
-rw-r--r--app/views/notification_mailer/follow.text.erb2
-rw-r--r--app/views/notification_mailer/follow_request.text.erb2
-rw-r--r--app/views/notification_mailer/mention.text.erb2
-rw-r--r--app/views/notification_mailer/reblog.text.erb2
-rw-r--r--app/views/settings/deletes/show.html.haml2
-rw-r--r--app/views/settings/two_factor_authentication/confirmations/new.html.haml2
-rw-r--r--app/views/settings/two_factor_authentications/show.html.haml2
-rw-r--r--app/views/user_mailer/confirmation_instructions.fa.html.erb2
-rw-r--r--app/views/user_mailer/confirmation_instructions.fa.text.erb2
-rw-r--r--app/workers/pubsubhubbub/delivery_worker.rb2
-rw-r--r--app/workers/pubsubhubbub/distribution_worker.rb4
-rw-r--r--app/workers/pubsubhubbub/subscribe_worker.rb10
-rw-r--r--app/workers/web_push_notification_worker.rb21
-rw-r--r--boxfile.yml9
-rw-r--r--config/locales/ca.yml2
-rw-r--r--config/locales/de.yml2
-rw-r--r--config/locales/en.yml5
-rw-r--r--config/locales/fa.yml234
-rw-r--r--config/locales/fr.yml2
-rw-r--r--config/locales/he.yml2
-rw-r--r--config/locales/id.yml2
-rw-r--r--config/locales/io.yml2
-rw-r--r--config/locales/ja.yml13
-rw-r--r--config/locales/ko.yml2
-rw-r--r--config/locales/nl.yml2
-rw-r--r--config/locales/no.yml2
-rw-r--r--config/locales/oc.yml83
-rw-r--r--config/locales/pl.yml4
-rw-r--r--config/locales/pt-BR.yml2
-rw-r--r--config/locales/pt.yml2
-rw-r--r--config/locales/ru.yml19
-rw-r--r--config/locales/simple_form.fa.yml7
-rw-r--r--config/locales/simple_form.nl.yml4
-rw-r--r--config/locales/simple_form.ru.yml5
-rw-r--r--config/locales/th.yml2
-rw-r--r--config/locales/tr.yml2
-rw-r--r--config/locales/uk.yml2
-rw-r--r--config/locales/zh-CN.yml2
-rw-r--r--config/locales/zh-TW.yml2
-rw-r--r--config/routes.rb2
-rw-r--r--config/settings.yml4
-rw-r--r--config/webpack/production.js8
-rw-r--r--config/webpack/shared.js2
-rw-r--r--db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb6
-rw-r--r--db/schema.rb3
-rw-r--r--lib/mastodon/version.rb2
-rw-r--r--package.json17
l---------public/sw.js1
-rw-r--r--public/web-push-icon_expand.pngbin0 -> 1380 bytes
-rw-r--r--public/web-push-icon_favourite.pngbin0 -> 1046 bytes
-rw-r--r--public/web-push-icon_reblog.pngbin0 -> 851 bytes
-rw-r--r--scalingo.json2
-rw-r--r--spec/controllers/api/v1/accounts/credentials_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/relationships_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/search_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/statuses_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/blocks_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/domain_blocks_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/favourites_controller_spec.rb78
-rw-r--r--spec/controllers/api/v1/follow_requests_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/follows_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/instances_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/media_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/mutes_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/notifications_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/reports_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/search_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/favourites_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/mutes_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/reblogs_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/timelines/home_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/timelines/public_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/timelines/tag_controller_spec.rb4
-rw-r--r--spec/fabricators/access_token_fabricator.rb2
-rw-r--r--spec/fabricators/accessible_access_token_fabricator.rb4
-rw-r--r--spec/helpers/emoji_helper_spec.rb5
-rw-r--r--spec/helpers/instance_helper_spec.rb2
-rw-r--r--spec/javascript/components/dropdown_menu.test.js49
-rw-r--r--spec/models/status_spec.rb2
-rw-r--r--spec/validators/status_length_validator_spec.rb44
-rw-r--r--storybook/config.js15
-rw-r--r--storybook/initial_state.js24
-rw-r--r--storybook/stories/autosuggest_textarea.story.js18
-rw-r--r--storybook/stories/button.story.js18
-rw-r--r--storybook/stories/character_counter.story.js21
-rw-r--r--storybook/stories/loading_indicator.story.js12
-rw-r--r--storybook/stories/onboarding_modal.story.js24
-rw-r--r--storybook/storybook.scss3
-rw-r--r--storybook/webpack.config.js21
-rw-r--r--yarn.lock976
205 files changed, 1927 insertions, 1507 deletions
diff --git a/.dockerignore b/.dockerignore
index 2ddfa9b95..5cd3b179a 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,7 +4,6 @@ public/system
 public/assets
 public/packs
 node_modules
-storybook
 neo4j
 vendor/bundle
 .DS_Store
diff --git a/.env.production.sample b/.env.production.sample
index eb1c5a48f..1d8a177aa 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -69,7 +69,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
 #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
diff --git a/.eslintrc.yml b/.eslintrc.yml
index a816bffef..fd2ba46dd 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -112,7 +112,7 @@ rules:
   jsx-a11y/iframe-has-title: warn
   jsx-a11y/img-has-alt: warn
   jsx-a11y/img-redundant-alt: warn
-  jsx-a11y/label-has-for: warn
+  jsx-a11y/label-has-for: off
   jsx-a11y/mouse-events-have-key-events: warn
   jsx-a11y/no-access-key: warn
   jsx-a11y/no-distracting-elements: warn
@@ -121,6 +121,6 @@ rules:
   jsx-a11y/onclick-has-focus: warn
   jsx-a11y/onclick-has-role: warn
   jsx-a11y/role-has-required-aria-props: warn
-  jsx-a11y/role-supports-aria-props: warn
+  jsx-a11y/role-supports-aria-props: off
   jsx-a11y/scope: warn
   jsx-a11y/tabindex-no-positive: warn
diff --git a/.gitignore b/.gitignore
index 868a84368..38ebc934f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,7 +21,6 @@ public/system
 public/assets
 public/packs
 public/packs-test
-public/sw.js
 .env
 .env.production
 node_modules/
diff --git a/.nanoignore b/.nanoignore
index f02c0a68a..80e939703 100644
--- a/.nanoignore
+++ b/.nanoignore
@@ -14,7 +14,6 @@ node_modules/
 public/assets/
 public/system/
 spec/
-storybook/
 tmp/
 .vagrant/
 vendor/bundle/
diff --git a/.rubocop.yml b/.rubocop.yml
index 1cbdadd49..ae3697174 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -27,6 +27,7 @@ Metrics/AbcSize:
   Max: 100
 
 Metrics/BlockLength:
+  Max: 35
   Exclude:
     - 'lib/tasks/**/*'
 
@@ -35,10 +36,10 @@ Metrics/BlockNesting:
 
 Metrics/ClassLength:
   CountComments: false
-  Max: 200
+  Max: 300
 
 Metrics/CyclomaticComplexity:
-  Max: 15
+  Max: 25
 
 Metrics/LineLength:
   AllowURI: true
@@ -53,11 +54,11 @@ Metrics/ModuleLength:
   Max: 200
 
 Metrics/ParameterLists:
-  Max: 4
+  Max: 5
   CountKeywordArgs: true
 
 Metrics/PerceivedComplexity:
-  Max: 10
+  Max: 20
 
 Rails:
   Enabled: true
diff --git a/.slugignore b/.slugignore
index b0141b0e2..5470f6e3f 100644
--- a/.slugignore
+++ b/.slugignore
@@ -2,4 +2,3 @@ node_modules/
 .cache/
 docs/
 spec/
-storybook/
diff --git a/Aptfile b/Aptfile
index f89f74bd4..48dff1a77 100644
--- a/Aptfile
+++ b/Aptfile
@@ -1,7 +1,9 @@
-protobuf-compiler
-libprotobuf-dev
 ffmpeg
-libxdamage1
-libxfixes3
 libicu-dev
+libidn11
 libidn11-dev
+libpq-dev
+libprotobuf-dev
+libxdamage1
+libxfixes3
+protobuf-compiler
diff --git a/README.md b/README.md
index 7d9b6e4ab..998d57005 100644
--- a/README.md
+++ b/README.md
@@ -8,4 +8,3 @@ So here's the deal: we all work on this code, and then it runs on dev.glitch.soc
 
 - You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
 - And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).
-
diff --git a/app.json b/app.json
index a935b8232..09adaac2c 100644
--- a/app.json
+++ b/app.json
@@ -2,7 +2,7 @@
   "name": "Mastodon",
   "description": "A GNU Social-compatible microblogging server",
   "repository": "https://github.com/tootsuite/mastodon",
-  "logo": "https://github.com/tootsuite/mastodon/raw/master/app/javascript/images/logo.svg",
+  "logo": "https://github.com/tootsuite.png",
   "env": {
     "HEROKU": {
       "description": "Leave this as true",
diff --git a/app/controllers/settings/two_factor_authentications_controller.rb b/app/controllers/settings/two_factor_authentications_controller.rb
index 983483881..863cc7351 100644
--- a/app/controllers/settings/two_factor_authentications_controller.rb
+++ b/app/controllers/settings/two_factor_authentications_controller.rb
@@ -18,7 +18,7 @@ module Settings
     end
 
     def destroy
-      if current_user.validate_and_consume_otp!(confirmation_params[:code])
+      if acceptable_code?
         current_user.otp_required_for_login = false
         current_user.save!
         redirect_to settings_two_factor_authentication_path
@@ -38,5 +38,10 @@ module Settings
     def verify_otp_required
       redirect_to settings_two_factor_authentication_path if current_user.otp_required_for_login?
     end
+
+    def acceptable_code?
+      current_user.validate_and_consume_otp!(confirmation_params[:code]) ||
+        current_user.invalidate_otp_backup_code!(confirmation_params[:code])
+    end
   end
 end
diff --git a/app/helpers/instance_helper.rb b/app/helpers/instance_helper.rb
index a1c3c3521..70027cca9 100644
--- a/app/helpers/instance_helper.rb
+++ b/app/helpers/instance_helper.rb
@@ -2,7 +2,7 @@
 
 module InstanceHelper
   def site_title
-    Setting.site_title.to_s
+    Setting.site_title.presence || site_hostname
   end
 
   def site_hostname
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index fa41e59e1..35b37600f 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -162,20 +162,23 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
 
     return (
       <div className='autosuggest-textarea'>
-        <Textarea
-          inputRef={this.setTextarea}
-          className='autosuggest-textarea__textarea'
-          disabled={disabled}
-          placeholder={placeholder}
-          autoFocus={autoFocus}
-          value={value}
-          onChange={this.onChange}
-          onKeyDown={this.onKeyDown}
-          onKeyUp={onKeyUp}
-          onBlur={this.onBlur}
-          onPaste={this.onPaste}
-          style={style}
-        />
+        <label>
+          <span style={{ display: 'none' }}>{placeholder}</span>
+          <Textarea
+            inputRef={this.setTextarea}
+            className='autosuggest-textarea__textarea'
+            disabled={disabled}
+            placeholder={placeholder}
+            autoFocus={autoFocus}
+            value={value}
+            onChange={this.onChange}
+            onKeyDown={this.onKeyDown}
+            onKeyUp={onKeyUp}
+            onBlur={this.onBlur}
+            onPaste={this.onPaste}
+            style={style}
+          />
+        </label>
 
         <div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
           {suggestions.map((suggestion, i) => (
diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js
index 589215ce8..50c3bf11f 100644
--- a/app/javascript/mastodon/components/column_back_button.js
+++ b/app/javascript/mastodon/components/column_back_button.js
@@ -19,10 +19,10 @@ export default class ColumnBackButton extends React.PureComponent {
 
   render () {
     return (
-      <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'>
+      <button onClick={this.handleClick} className='column-back-button'>
         <i className='fa fa-fw fa-chevron-left column-back-button__icon' />
         <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
-      </div>
+      </button>
     );
   }
 
diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js
index 9945fc209..e0042b055 100644
--- a/app/javascript/mastodon/components/column_header.js
+++ b/app/javascript/mastodon/components/column_header.js
@@ -8,6 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import NotificationPurgeButtonsContainer from '../../glitch/components/column/notif_cleaning_widget/container';
 
 const messages = defineMessages({
+  show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
+  hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
+  moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' },
+  moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
   enterNotifCleaning : { id: 'notification_purge.start', defaultMessage: 'Enter notification cleaning mode' },
 });
 
@@ -19,11 +23,13 @@ export default class ColumnHeader extends React.PureComponent {
   };
 
   static propTypes = {
+    intl: PropTypes.object.isRequired,
     title: PropTypes.node.isRequired,
     icon: PropTypes.string.isRequired,
     active: PropTypes.bool,
     localSettings : ImmutablePropTypes.map,
     multiColumn: PropTypes.bool,
+    focusable: PropTypes.bool,
     showBackButton: PropTypes.bool,
     notifCleaning: PropTypes.bool, // true only for the notification column
     notifCleaningActive: PropTypes.bool,
@@ -36,6 +42,10 @@ export default class ColumnHeader extends React.PureComponent {
     intl: PropTypes.object.isRequired,
   };
 
+  static defaultProps = {
+    focusable: true,
+  }
+
   state = {
     collapsed: true,
     animating: false,
@@ -82,10 +92,9 @@ export default class ColumnHeader extends React.PureComponent {
   }
 
   render () {
-    const { intl, icon, active, children, pinned, onPin, multiColumn, showBackButton, notifCleaning, notifCleaningActive } = this.props;
+    const { intl, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive } = this.props;
     const { collapsed, animating, animatingNCD } = this.state;
 
-
     let title = this.props.title;
 
     const wrapperClassName = classNames('column-header__wrapper', {
@@ -132,8 +141,8 @@ export default class ColumnHeader extends React.PureComponent {
 
       moveButtons = (
         <div key='move-buttons' className='column-header__setting-arrows'>
-          <button className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button>
-          <button className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button>
+          <button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button>
+          <button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button>
         </div>
       );
     } else if (multiColumn) {
@@ -159,12 +168,12 @@ export default class ColumnHeader extends React.PureComponent {
     }
 
     if (children || multiColumn) {
-      collapseButton = <button className={collapsibleButtonClassName} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
+      collapseButton = <button className={collapsibleButtonClassName} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
     }
 
     return (
       <div className={wrapperClassName}>
-        <div role='button heading' tabIndex='0' className={buttonClassName} onClick={this.handleTitleClick}>
+        <h1 tabIndex={focusable && '0'} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
           <i className={`fa fa-fw fa-${icon} column-header__icon`} />
           {title}
           <div className='column-header__buttons'>
@@ -181,7 +190,7 @@ export default class ColumnHeader extends React.PureComponent {
             ) : null}
             {collapseButton}
           </div>
-        </div>
+        </h1>
 
         { notifCleaning ? (
           <div className={notifCleaningDrawerClassName} onTransitionEnd={this.handleTransitionEndNCD}>
@@ -191,7 +200,7 @@ export default class ColumnHeader extends React.PureComponent {
           </div>
         ) : null}
 
-        <div className={collapsibleClassName} onTransitionEnd={this.handleTransitionEnd}>
+        <div className={collapsibleClassName} tabIndex={collapsed && -1} onTransitionEnd={this.handleTransitionEnd}>
           <div className='column-header__collapsible-inner'>
             {(!collapsed || animating) && collapsedContent}
           </div>
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 98323b069..28631f463 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -1,4 +1,5 @@
 import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
 import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
 import PropTypes from 'prop-types';
 
@@ -9,16 +10,23 @@ export default class DropdownMenu extends React.PureComponent {
   };
 
   static propTypes = {
+    isUserTouching: PropTypes.func,
+    isModalOpen: PropTypes.bool.isRequired,
+    onModalOpen: PropTypes.func,
+    onModalClose: PropTypes.func,
     icon: PropTypes.string.isRequired,
     items: PropTypes.array.isRequired,
     size: PropTypes.number.isRequired,
     direction: PropTypes.string,
+    status: ImmutablePropTypes.map,
     ariaLabel: PropTypes.string,
     disabled: PropTypes.bool,
   };
 
   static defaultProps = {
     ariaLabel: 'Menu',
+    isModalOpen: false,
+    isUserTouching: () => false,
   };
 
   state = {
@@ -34,6 +42,10 @@ export default class DropdownMenu extends React.PureComponent {
     const i = Number(e.currentTarget.getAttribute('data-index'));
     const { action, to } = this.props.items[i];
 
+    if (this.props.isModalOpen) {
+      this.props.onModalClose();
+    }
+
     // Don't call e.preventDefault() when the item uses 'href' property.
     // ex. "Edit profile" on the account action bar
 
@@ -48,10 +60,32 @@ export default class DropdownMenu extends React.PureComponent {
     this.dropdown.hide();
   }
 
-  handleShow = () => this.setState({ expanded: true })
+  handleShow = () => {
+    if (this.props.isUserTouching()) {
+      this.props.onModalOpen({
+        status: this.props.status,
+        actions: this.props.items,
+        onClick: this.handleClick,
+      });
+    } else {
+      this.setState({ expanded: true });
+    }
+  }
 
   handleHide = () => this.setState({ expanded: false })
 
+  handleToggle = (e) => {
+    if (e.key === 'Enter') {
+      if (this.props.isUserTouching()) {
+        this.handleShow();
+      } else {
+        this.setState({ expanded: !this.state.expanded });
+      }
+    } else if (e.key === 'Escape') {
+      this.setState({ expanded: false });
+    }
+  }
+
   renderItem = (item, i) => {
     if (item === null) {
       return <li key={`sep-${i}`} className='dropdown__sep' />;
@@ -61,7 +95,7 @@ export default class DropdownMenu extends React.PureComponent {
 
     return (
       <li className='dropdown__content-list-item' key={`${text}-${i}`}>
-        <a href={href} target='_blank' rel='noopener' onClick={this.handleClick} data-index={i} className='dropdown__content-list-link'>
+        <a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' autoFocus={i === 0} onClick={this.handleClick} data-index={i} className='dropdown__content-list-link'>
           {text}
         </a>
       </li>
@@ -71,6 +105,7 @@ export default class DropdownMenu extends React.PureComponent {
   render () {
     const { icon, items, size, direction, ariaLabel, disabled } = this.props;
     const { expanded }   = this.state;
+    const isUserTouching = this.props.isUserTouching();
     const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right';
     const iconStyle      = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` };
     const iconClassname  = `fa fa-fw fa-${icon} dropdown__icon`;
@@ -84,20 +119,26 @@ export default class DropdownMenu extends React.PureComponent {
     }
 
     const dropdownItems = expanded && (
-      <ul className='dropdown__content-list'>
+      <ul role='group' className='dropdown__content-list' onClick={this.handleHide}>
         {items.map(this.renderItem)}
       </ul>
     );
 
+    // No need to render the actual dropdown if we use the modal. If we
+    // don't render anything <Dropdow /> breaks, so we just put an empty div.
+    const dropdownContent = !isUserTouching ? (
+      <DropdownContent className={directionClass} >
+        {dropdownItems}
+      </DropdownContent>
+    ) : <div />;
+
     return (
-      <Dropdown ref={this.setRef} onShow={this.handleShow} onHide={this.handleHide}>
-        <DropdownTrigger className='icon-button' style={iconStyle} aria-label={ariaLabel}>
+      <Dropdown ref={this.setRef} active={isUserTouching ? false : expanded} onShow={this.handleShow} onHide={this.handleHide}>
+        <DropdownTrigger className='icon-button' style={iconStyle} role='button' aria-expanded={expanded} onKeyDown={this.handleToggle} tabIndex='0' aria-label={ariaLabel}>
           <i className={iconClassname} aria-hidden />
         </DropdownTrigger>
 
-        <DropdownContent className={directionClass}>
-          {dropdownItems}
-        </DropdownContent>
+        {dropdownContent}
       </Dropdown>
     );
   }
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
index 748283853..8c5b5e0b9 100644
--- a/app/javascript/mastodon/components/icon_button.js
+++ b/app/javascript/mastodon/components/icon_button.js
@@ -12,6 +12,8 @@ export default class IconButton extends React.PureComponent {
     onClick: PropTypes.func,
     size: PropTypes.number,
     active: PropTypes.bool,
+    pressed: PropTypes.bool,
+    expanded: PropTypes.bool,
     style: PropTypes.object,
     activeStyle: PropTypes.object,
     disabled: PropTypes.bool,
@@ -19,6 +21,7 @@ export default class IconButton extends React.PureComponent {
     animate: PropTypes.bool,
     flip: PropTypes.bool,
     overlay: PropTypes.bool,
+    tabIndex: PropTypes.string,
   };
 
   static defaultProps = {
@@ -27,6 +30,7 @@ export default class IconButton extends React.PureComponent {
     disabled: false,
     animate: false,
     overlay: false,
+    tabIndex: '0',
   };
 
   handleClick = (e) =>  {
@@ -74,10 +78,13 @@ export default class IconButton extends React.PureComponent {
         {({ rotate }) =>
           <button
             aria-label={this.props.title}
+            aria-pressed={this.props.pressed}
+            aria-expanded={this.props.expanded}
             title={this.props.title}
             className={classes.join(' ')}
             onClick={this.handleClick}
             style={style}
+            tabIndex={this.props.tabIndex}
           >
             <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
           </button>
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index d95e7c75d..fa6ea72d5 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -215,10 +215,10 @@ export default class MediaGallery extends React.PureComponent {
       }
 
       children = (
-        <div role='button' tabIndex='0' className='media-spoiler' onClick={this.handleOpen}>
+        <button className='media-spoiler' onClick={this.handleOpen}>
           <span className='media-spoiler__warning'>{warning}</span>
           <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
-        </div>
+        </button>
       );
     } else {
       const size = media.take(4).size;
diff --git a/app/javascript/mastodon/components/setting_text.js b/app/javascript/mastodon/components/setting_text.js
index dd975bc99..a6dde4c0f 100644
--- a/app/javascript/mastodon/components/setting_text.js
+++ b/app/javascript/mastodon/components/setting_text.js
@@ -19,12 +19,15 @@ export default class SettingText extends React.PureComponent {
     const { settings, settingKey, label } = this.props;
 
     return (
-      <input
-        className='setting-text'
-        value={settings.getIn(settingKey)}
-        onChange={this.handleChange}
-        placeholder={label}
-      />
+      <label>
+        <span style={{ display: 'none' }}>{label}</span>
+        <input
+          className='setting-text'
+          value={settings.getIn(settingKey)}
+          onChange={this.handleChange}
+          placeholder={label}
+        />
+      </label>
     );
   }
 
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 6605457f7..ac82e536f 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -44,6 +44,8 @@ export default class Status extends ImmutablePureComponent {
     autoPlayGif: PropTypes.bool,
     muted: PropTypes.bool,
     intersectionObserverWrapper: PropTypes.object,
+    index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+    listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
   };
 
   state = {
@@ -62,6 +64,7 @@ export default class Status extends ImmutablePureComponent {
     'boostModal',
     'autoPlayGif',
     'muted',
+    'listLength',
   ]
 
   updateOnStates = ['isExpanded']
@@ -70,8 +73,8 @@ export default class Status extends ImmutablePureComponent {
     if (!nextState.isIntersecting && nextState.isHidden) {
       // It's only if we're not intersecting (i.e. offscreen) and isHidden is true
       // that either "isIntersecting" or "isHidden" matter, and then they're
-      // the only things that matter.
-      return this.state.isIntersecting || !this.state.isHidden;
+      // the only things that matter (and updated ARIA attributes).
+      return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
     } else if (nextState.isIntersecting && !this.state.isIntersecting) {
       // If we're going from a non-intersecting state to an intersecting state,
       // (i.e. offscreen to onscreen), then we definitely need to re-render
@@ -110,17 +113,12 @@ export default class Status extends ImmutablePureComponent {
       this.height = getRectFromEntry(entry).height;
     }
 
-    // Edge 15 doesn't support isIntersecting, but we can infer it
-    // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12156111/
-    // https://github.com/WICG/IntersectionObserver/issues/211
-    const isIntersecting = (typeof entry.isIntersecting === 'boolean') ?
-      entry.isIntersecting : entry.intersectionRect.height > 0;
     this.setState((prevState) => {
-      if (prevState.isIntersecting && !isIntersecting) {
+      if (prevState.isIntersecting && !entry.isIntersecting) {
         scheduleIdleTask(this.hideIfNotIntersecting);
       }
       return {
-        isIntersecting: isIntersecting,
+        isIntersecting: entry.isIntersecting,
         isHidden: false,
       };
     });
@@ -177,7 +175,7 @@ export default class Status extends ImmutablePureComponent {
 
     // Exclude intersectionObserverWrapper from `other` variable
     // because intersection is managed in here.
-    const { status, account, intersectionObserverWrapper, ...other } = this.props;
+    const { status, account, intersectionObserverWrapper, index, listLength, wrapped, ...other } = this.props;
     const { isExpanded, isIntersecting, isHidden } = this.state;
 
     if (status === null) {
@@ -186,10 +184,10 @@ export default class Status extends ImmutablePureComponent {
 
     if (!isIntersecting && isHidden) {
       return (
-        <div ref={this.handleRef} data-id={status.get('id')} style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}>
+        <article ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0' style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}>
           {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
           {status.get('content')}
-        </div>
+        </article>
       );
     }
 
@@ -203,14 +201,14 @@ export default class Status extends ImmutablePureComponent {
       const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
 
       return (
-        <div className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} >
+        <article className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0'>
           <div className='status__prepend'>
             <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
             <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
           </div>
 
           <Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />
-        </div>
+        </article>
       );
     }
 
@@ -239,7 +237,7 @@ export default class Status extends ImmutablePureComponent {
     }
 
     return (
-      <div className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')} ref={this.handleRef}>
+      <article aria-posinset={index} aria-setsize={listLength} className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')} tabIndex={wrapped ? null : '0'}  ref={this.handleRef}>
         <div className='status__info'>
           <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
 
@@ -257,7 +255,7 @@ export default class Status extends ImmutablePureComponent {
         {media}
 
         <StatusActionBar {...this.props} />
-      </div>
+      </article>
     );
   }
 
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index 3e947b4c5..81c2a4e23 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -5,7 +5,7 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import IconButton from './icon_button';
-import DropdownMenu from './dropdown_menu';
+import DropdownMenuContainer from '../containers/dropdown_menu_container';
 import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
@@ -154,12 +154,12 @@ export default class StatusActionBar extends ImmutablePureComponent {
     return (
       <div className='status__action-bar'>
         <IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
-        <IconButton className='status__action-bar-button' disabled={anonymousAccess || reblogDisabled} active={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
-        <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
+        <IconButton className='status__action-bar-button' disabled={anonymousAccess || reblogDisabled} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
+        <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
         {shareButton}
 
         <div className='status__action-bar-dropdown'>
-          <DropdownMenu disabled={anonymousAccess} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
+          <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index ad925edef..5f02e3261 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -149,7 +149,7 @@ export default class StatusContent extends React.PureComponent {
       }
 
       return (
-        <div className={classNames} ref={this.setRef} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
+        <div className={classNames} ref={this.setRef} tabIndex='0' aria-label={status.get('search_index')} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
           <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
             <span dangerouslySetInnerHTML={spoilerContent} />
             {' '}
@@ -158,13 +158,15 @@ export default class StatusContent extends React.PureComponent {
 
           {mentionsPlaceholder}
 
-          <div className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
+          <div tabIndex={!hidden && 0} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
         </div>
       );
     } else if (this.props.onClick) {
       return (
         <div
           ref={this.setRef}
+          tabIndex='0'
+          aria-label={status.get('search_index')}
           className={classNames}
           style={directionStyle}
           onMouseDown={this.handleMouseDown}
@@ -175,6 +177,8 @@ export default class StatusContent extends React.PureComponent {
     } else {
       return (
         <div
+          tabIndex='0'
+          aria-label={status.get('search_index')}
           ref={this.setRef}
           className='status__content'
           style={directionStyle}
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index 3dd207dbc..639c8b4e7 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -6,7 +6,7 @@ import StatusContainer from '../../glitch/components/status/container';
 import LoadMore from './load_more';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
-import { debounce } from 'lodash';
+import { throttle } from 'lodash';
 
 export default class StatusList extends ImmutablePureComponent {
 
@@ -30,13 +30,13 @@ export default class StatusList extends ImmutablePureComponent {
 
   intersectionObserverWrapper = new IntersectionObserverWrapper();
 
-  handleScroll = debounce(() => {
+  handleScroll = throttle(() => {
     if (this.node) {
       const { scrollTop, scrollHeight, clientHeight } = this.node;
       const offset = scrollHeight - scrollTop - clientHeight;
       this._oldScrollPosition = scrollHeight - scrollTop;
 
-      if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
+      if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
         this.props.onScrollToBottom();
       } else if (scrollTop < 100 && this.props.onScrollToTop) {
         this.props.onScrollToTop();
@@ -44,7 +44,7 @@ export default class StatusList extends ImmutablePureComponent {
         this.props.onScroll();
       }
     }
-  }, 200, {
+  }, 150, {
     trailing: true,
   });
 
@@ -104,6 +104,32 @@ export default class StatusList extends ImmutablePureComponent {
     this.props.onScrollToBottom();
   }
 
+  handleKeyDown = (e) => {
+    if (['PageDown', 'PageUp', 'End', 'Home'].includes(e.key)) {
+      const article = (() => {
+        switch (e.key) {
+        case 'PageDown':
+          return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling;
+        case 'PageUp':
+          return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling;
+        case 'End':
+          return this.node.querySelector('[role="feed"] > article:last-of-type');
+        case 'Home':
+          return this.node.querySelector('[role="feed"] > article:first-of-type');
+        default:
+          return null;
+        }
+      })();
+
+
+      if (article) {
+        e.preventDefault();
+        article.focus();
+        article.scrollIntoView();
+      }
+    }
+  }
+
   render () {
     const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
 
@@ -113,11 +139,11 @@ export default class StatusList extends ImmutablePureComponent {
     if (isLoading || statusIds.size > 0 || !emptyMessage) {
       scrollableArea = (
         <div className='scrollable' ref={this.setRef}>
-          <div className='status-list'>
+          <div role='feed' className='status-list' onKeyDown={this.handleKeyDown}>
             {prepend}
 
-            {statusIds.map((statusId) => {
-              return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
+            {statusIds.map((statusId, index) => {
+              return <StatusContainer key={statusId} id={statusId} index={index} listLength={statusIds.size} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
             })}
 
             {loadMore}
diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
new file mode 100644
index 000000000..151f25390
--- /dev/null
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -0,0 +1,16 @@
+import { openModal, closeModal } from '../actions/modal';
+import { connect } from 'react-redux';
+import DropdownMenu from '../components/dropdown_menu';
+import { isUserTouching } from '../is_mobile';
+
+const mapStateToProps = state => ({
+  isModalOpen: state.get('modal').modalType === 'ACTIONS',
+});
+
+const mapDispatchToProps = dispatch => ({
+  isUserTouching,
+  onModalOpen: props => dispatch(openModal('ACTIONS', props)),
+  onModalClose: () => dispatch(closeModal()),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js
index 9b58cacf5..5695c86dd 100644
--- a/app/javascript/mastodon/emoji.js
+++ b/app/javascript/mastodon/emoji.js
@@ -3,6 +3,8 @@ import Trie from 'substring-trie';
 
 const trie = new Trie(Object.keys(unicodeMapping));
 
+const excluded = ['™', '©', '®'];
+
 function emojify(str) {
   // This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
   // and replacing valid unicode strings
@@ -19,7 +21,7 @@ function emojify(str) {
       insideTag = true;
     } else if (!insideTag && (match = trie.search(str.substring(i)))) {
       const unicodeStr = match;
-      if (unicodeStr in unicodeMapping) {
+      if (unicodeStr in unicodeMapping && excluded.indexOf(unicodeStr) === -1) {
         const [filename, shortCode] = unicodeMapping[unicodeStr];
         const alt      = unicodeStr;
         const replacement =  `<img draggable="false" class="emojione" alt="${alt}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
index b8df724c6..c12c0889e 100644
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import DropdownMenu from '../../../components/dropdown_menu';
+import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
 import Link from 'react-router-dom/Link';
 import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
 
@@ -15,6 +15,7 @@ const messages = defineMessages({
   mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
   report: { id: 'account.report', defaultMessage: 'Report @{name}' },
+  share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
   media: { id: 'account.media', defaultMessage: 'Media' },
   blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
@@ -36,6 +37,12 @@ export default class ActionBar extends React.PureComponent {
     intl: PropTypes.object.isRequired,
   };
 
+  handleShare = () => {
+    navigator.share({
+      url: this.props.account.get('url'),
+    });
+  }
+
   render () {
     const { account, me, intl } = this.props;
 
@@ -43,6 +50,9 @@ export default class ActionBar extends React.PureComponent {
     let extraInfo = '';
 
     menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+    if ('share' in navigator) {
+      menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
+    }
     menu.push(null);
     menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
     menu.push(null);
@@ -96,7 +106,7 @@ export default class ActionBar extends React.PureComponent {
 
         <div className='account__action-bar'>
           <div className='account__action-bar-dropdown'>
-            <DropdownMenu items={menu} icon='bars' size={24} direction='right' />
+            <DropdownMenuContainer items={menu} icon='bars' size={24} direction='right' />
           </div>
 
           <div className='account__action-bar-links'>
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 1133e8a4e..9d7bc82c0 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -55,9 +55,10 @@ class Avatar extends ImmutablePureComponent {
     return (
       <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
         {({ radius }) =>
-          <a // eslint-disable-line jsx-a11y/anchor-has-content
+          <a
             href={account.get('url')}
             className='account__header__avatar'
+            role='presentation'
             target='_blank'
             rel='noopener'
             style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
@@ -65,7 +66,9 @@ class Avatar extends ImmutablePureComponent {
             onMouseOut={this.handleMouseOut}
             onFocus={this.handleMouseOver}
             onBlur={this.handleMouseOut}
-          />
+          >
+            <span style={{ display: 'none' }}>{account.get('acct')}</span>
+          </a>
         }
       </Motion>
     );
diff --git a/app/javascript/mastodon/features/compose/components/character_counter.js b/app/javascript/mastodon/features/compose/components/character_counter.js
index 6c488b661..0ecfc9141 100644
--- a/app/javascript/mastodon/features/compose/components/character_counter.js
+++ b/app/javascript/mastodon/features/compose/components/character_counter.js
@@ -13,12 +13,12 @@ export default class CharacterCounter extends React.PureComponent {
     if (diff < 0) {
       return <span className='character-counter character-counter--over'>{diff}</span>;
     }
+
     return <span className='character-counter'>{diff}</span>;
   }
 
   render () {
     const diff = this.props.max - length(this.props.text);
-
     return this.checkRemainingText(diff);
   }
 
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 67906594f..0027783b4 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -19,6 +19,7 @@ import WarningContainer from '../containers/warning_container';
 import { isMobile } from '../../../is_mobile';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { length } from 'stringz';
+import { countableText } from '../util/counter';
 
 const messages = defineMessages({
   placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
@@ -150,9 +151,9 @@ export default class ComposeForm extends ImmutablePureComponent {
     const { intl, onPaste, showSearch } = this.props;
     const disabled = this.props.is_submitting;
     const maybeEye = this.props.advanced_options.get('do_not_federate') ? ' 👁️' : '';
-    const text = [this.props.spoiler_text, this.props.text, maybeEye].join('');
+    const text     = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
 
-    let publishText    = '';
+    let publishText = '';
 
     if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
       publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
@@ -164,7 +165,10 @@ export default class ComposeForm extends ImmutablePureComponent {
       <div className='compose-form'>
         <Collapsable isVisible={this.props.spoiler} fullHeight={50}>
           <div className='spoiler-input'>
-            <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input'  id='cw-spoiler-input' />
+            <label>
+              <span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
+              <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input'  id='cw-spoiler-input' />
+            </label>
           </div>
         </Collapsable>
 
@@ -206,7 +210,7 @@ export default class ComposeForm extends ImmutablePureComponent {
 
           <div className='compose-form__publish'>
             <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
-            <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !==0 && text.trim().length === 0)} block /></div>
+            <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
           </div>
         </div>
       </div>
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index acc584f20..9d05b7a34 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -65,6 +65,22 @@ export default class EmojiPickerDropdown extends React.PureComponent {
     this.setState({ active: false });
   }
 
+  onToggle = (e) => {
+    if (!this.state.loading && (!e.key || e.key === 'Enter')) {
+      if (this.state.active) {
+        this.onHideDropdown();
+      } else {
+        this.onShowDropdown();
+      }
+    }
+  }
+
+  onEmojiPickerKeyDown = (e) => {
+    if (e.key === 'Escape') {
+      this.onHideDropdown();
+    }
+  }
+
   render () {
     const { intl } = this.props;
 
@@ -104,10 +120,11 @@ export default class EmojiPickerDropdown extends React.PureComponent {
     };
 
     const { active, loading } = this.state;
+    const title = intl.formatMessage(messages.emoji);
 
     return (
-      <Dropdown ref={this.setRef} className='emoji-picker__dropdown' onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
-        <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)}>
+      <Dropdown ref={this.setRef} className='emoji-picker__dropdown' active={active && !loading} onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
+        <DropdownTrigger className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onKeyDown={this.onToggle} tabIndex={0} >
           <img
             className={`emojione ${active && loading ? 'pulse-loading' : ''}`}
             alt='🙂'
@@ -118,7 +135,7 @@ export default class EmojiPickerDropdown extends React.PureComponent {
         <DropdownContent className='dropdown__left'>
           {
             this.state.active && !this.state.loading &&
-            (<EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search />)
+            (<EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} onKeyDown={this.onEmojiPickerKeyDown} categories={categories} search />)
           }
         </DropdownContent>
       </Dropdown>
diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js
index b0bc0958e..5000ea2f1 100644
--- a/app/javascript/mastodon/features/compose/components/navigation_bar.js
+++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js
@@ -18,6 +18,7 @@ export default class NavigationBar extends ImmutablePureComponent {
     return (
       <div className='navigation-bar'>
         <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
+          <span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
           <Avatar src={this.props.account.get('avatar')} staticSrc={this.props.account.get('avatar_static')} size={40} />
         </Permalink>
 
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 9524f7501..da3c0a0ab 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -24,6 +24,10 @@ const iconStyle = {
 export default class PrivacyDropdown extends React.PureComponent {
 
   static propTypes = {
+    isUserTouching: PropTypes.func,
+    isModalOpen: PropTypes.bool.isRequired,
+    onModalOpen: PropTypes.func,
+    onModalClose: PropTypes.func,
     value: PropTypes.string.isRequired,
     onChange: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
@@ -34,22 +38,55 @@ export default class PrivacyDropdown extends React.PureComponent {
   };
 
   handleToggle = () => {
-    this.setState({ open: !this.state.open });
+    if (this.props.isUserTouching()) {
+      if (this.state.open) {
+        this.props.onModalClose();
+      } else {
+        this.props.onModalOpen({
+          actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })),
+          onClick: this.handleModalActionClick,
+        });
+      }
+    } else {
+      this.setState({ open: !this.state.open });
+    }
   }
 
-  handleClick = (e) => {
-    const value = e.currentTarget.getAttribute('data-index');
+  handleModalActionClick = (e) => {
     e.preventDefault();
-    this.setState({ open: false });
+    const { value } = this.options[e.currentTarget.getAttribute('data-index')];
+    this.props.onModalClose();
     this.props.onChange(value);
   }
 
+  handleClick = (e) => {
+    if (e.key === 'Escape') {
+      this.setState({ open: false });
+    } else if (!e.key || e.key === 'Enter') {
+      const value = e.currentTarget.getAttribute('data-index');
+      e.preventDefault();
+      this.setState({ open: false });
+      this.props.onChange(value);
+    }
+  }
+
   onGlobalClick = (e) => {
     if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
       this.setState({ open: false });
     }
   }
 
+  componentWillMount () {
+    const { intl: { formatMessage } } = this.props;
+
+    this.options = [
+      { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
+      { icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
+      { icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
+      { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
+    ];
+  }
+
   componentDidMount () {
     window.addEventListener('click', this.onGlobalClick);
     window.addEventListener('touchstart', this.onGlobalClick);
@@ -68,25 +105,18 @@ export default class PrivacyDropdown extends React.PureComponent {
     const { value, intl } = this.props;
     const { open } = this.state;
 
-    const options = [
-      { icon: 'globe', value: 'public', shortText: intl.formatMessage(messages.public_short), longText: intl.formatMessage(messages.public_long) },
-      { icon: 'unlock-alt', value: 'unlisted', shortText: intl.formatMessage(messages.unlisted_short), longText: intl.formatMessage(messages.unlisted_long) },
-      { icon: 'lock', value: 'private', shortText: intl.formatMessage(messages.private_short), longText: intl.formatMessage(messages.private_long) },
-      { icon: 'envelope', value: 'direct', shortText: intl.formatMessage(messages.direct_short), longText: intl.formatMessage(messages.direct_long) },
-    ];
-
-    const valueOption = options.find(item => item.value === value);
+    const valueOption = this.options.find(item => item.value === value);
 
     return (
       <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
-        <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div>
+        <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} expanded={open} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div>
         <div className='privacy-dropdown__dropdown'>
-          {open && options.map(item =>
-            <div role='button' tabIndex='0' key={item.value} data-index={item.value} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
+          {open && this.options.map(item =>
+            <div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
               <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
               <div className='privacy-dropdown__option__content'>
-                <strong>{item.shortText}</strong>
-                {item.longText}
+                <strong>{item.text}</strong>
+                {item.meta}
               </div>
             </div>
           )}
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index cdc7952c0..85ef767ab 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -52,15 +52,18 @@ export default class Search extends React.PureComponent {
 
     return (
       <div className='search'>
-        <input
-          className='search__input'
-          type='text'
-          placeholder={intl.formatMessage(messages.placeholder)}
-          value={value}
-          onChange={this.handleChange}
-          onKeyUp={this.handleKeyDown}
-          onFocus={this.handleFocus}
-        />
+        <label>
+          <span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
+          <input
+            className='search__input'
+            type='text'
+            placeholder={intl.formatMessage(messages.placeholder)}
+            value={value}
+            onChange={this.handleChange}
+            onKeyUp={this.handleKeyDown}
+            onFocus={this.handleFocus}
+          />
+        </label>
 
         <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
           <i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js
index badd6cfc5..70b28a2ba 100644
--- a/app/javascript/mastodon/features/compose/components/upload_button.js
+++ b/app/javascript/mastodon/features/compose/components/upload_button.js
@@ -57,16 +57,19 @@ export default class UploadButton extends ImmutablePureComponent {
     return (
       <div className='compose-form__upload-button'>
         <IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
-        <input
-          key={resetFileKey}
-          ref={this.setRef}
-          type='file'
-          multiple={false}
-          accept={acceptContentTypes.toArray().join(',')}
-          onChange={this.handleChange}
-          disabled={disabled}
-          style={{ display: 'none' }}
-        />
+        <label>
+          <span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span>
+          <input
+            key={resetFileKey}
+            ref={this.setRef}
+            type='file'
+            multiple={false}
+            accept={acceptContentTypes.toArray().join(',')}
+            onChange={this.handleChange}
+            disabled={disabled}
+            style={{ display: 'none' }}
+          />
+        </label>
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js
index 9c05e054e..0ddf531d3 100644
--- a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js
+++ b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js
@@ -1,8 +1,11 @@
 import { connect } from 'react-redux';
 import PrivacyDropdown from '../components/privacy_dropdown';
 import { changeComposeVisibility } from '../../../actions/compose';
+import { openModal, closeModal } from '../../../actions/modal';
+import { isUserTouching } from '../../../is_mobile';
 
 const mapStateToProps = state => ({
+  isModalOpen: state.get('modal').modalType === 'ACTIONS',
   value: state.getIn(['compose', 'privacy']),
 });
 
@@ -12,6 +15,10 @@ const mapDispatchToProps = dispatch => ({
     dispatch(changeComposeVisibility(value));
   },
 
+  isUserTouching,
+  onModalOpen: props => dispatch(openModal('ACTIONS', props)),
+  onModalClose: () => dispatch(closeModal()),
+
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
index 63c0e8ae4..8624849f3 100644
--- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
+++ b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
@@ -15,6 +15,7 @@ const messages = defineMessages({
 const mapStateToProps = state => ({
   visible: state.getIn(['compose', 'media_attachments']).size > 0,
   active: state.getIn(['compose', 'sensitive']),
+  disabled: state.getIn(['compose', 'spoiler']),
 });
 
 const mapDispatchToProps = dispatch => ({
@@ -30,12 +31,13 @@ class SensitiveButton extends React.PureComponent {
   static propTypes = {
     visible: PropTypes.bool,
     active: PropTypes.bool,
+    disabled: PropTypes.bool,
     onClick: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
   render () {
-    const { visible, active, onClick, intl } = this.props;
+    const { visible, active, disabled, onClick, intl } = this.props;
 
     return (
       <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}>
@@ -53,6 +55,7 @@ class SensitiveButton extends React.PureComponent {
                 onClick={onClick}
                 size={18}
                 active={active}
+                disabled={disabled}
                 style={{ lineHeight: null, height: null }}
                 inverted
               />
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 66b0746c5..f0bce1e40 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -76,23 +76,23 @@ export default class Compose extends React.PureComponent {
     if (multiColumn) {
       const { columns } = this.props;
       header = (
-        <div className='drawer__header'>
-          <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role='img' aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link>
+        <nav className='drawer__header'>
+          <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><i role='img' className='fa fa-fw fa-asterisk' /></Link>
           {!columns.some(column => column.get('id') === 'HOME') && (
-            <Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' aria-label={intl.formatMessage(messages.home_timeline)} /></Link>
+            <Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' /></Link>
           )}
           {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
-            <Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' aria-label={intl.formatMessage(messages.notifications)} /></Link>
+            <Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' /></Link>
           )}
           {!columns.some(column => column.get('id') === 'COMMUNITY') && (
-            <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link>
+            <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><i role='img' className='fa fa-fw fa-users' /></Link>
           )}
           {!columns.some(column => column.get('id') === 'PUBLIC') && (
-            <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
+            <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link>
           )}
-          <a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)}><i role='img' aria-label={intl.formatMessage(messages.settings)} className='fa fa-fw fa-cogs' /></a>
-          <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role='img' aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a>
-        </div>
+          <a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)} aria-label={intl.formatMessage(messages.settings)}><i role='img' className='fa fa-fw fa-cogs' /></a>
+          <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a>
+        </nav>
       );
     }
 
diff --git a/app/javascript/mastodon/features/compose/util/counter.js b/app/javascript/mastodon/features/compose/util/counter.js
new file mode 100644
index 000000000..f0fea1a0e
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/util/counter.js
@@ -0,0 +1,7 @@
+const urlPlaceholder = 'xxxxxxxxxxxxxxxxxxxxxxx';
+
+export function countableText(inputText) {
+  return inputText
+    .replace(/https?:\/\/\S+/g, urlPlaceholder)
+    .replace(/(?:^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+)/ig, '@$2');
+};
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index 31cac5bc7..88a29d4d3 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -36,40 +36,48 @@ export default class ColumnSettings extends React.PureComponent {
           <ClearColumnButton onClick={onClear} />
         </div>
 
-        <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
-
-        <div className='column-settings__row'>
-          <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
-          {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
+        <div role='group' aria-labelledby='notifications-follow'>
+          <span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
+
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
+          </div>
         </div>
 
-        <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
+        <div role='group' aria-labelledby='notifications-favourite'>
+          <span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
 
-        <div className='column-settings__row'>
-          <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
-          {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
+          </div>
         </div>
 
-        <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
+        <div role='group' aria-labelledby='notifications-mention'>
+          <span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
 
-        <div className='column-settings__row'>
-          <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
-          {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
+          </div>
         </div>
 
-        <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
+        <div role='group' aria-labelledby='notifications-reblog'>
+          <span id='notifications-reblog' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
 
-        <div className='column-settings__row'>
-          <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
-          {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
-          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
+          </div>
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
index be1ff91d6..a20e7ca51 100644
--- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js
+++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
@@ -18,13 +18,19 @@ export default class SettingToggle extends React.PureComponent {
     this.props.onChange(this.props.settingKey, target.checked);
   }
 
+  onKeyDown = e => {
+    if (e.key === ' ') {
+      this.props.onChange(this.props.settingKey, !e.target.checked);
+    }
+  }
+
   render () {
     const { prefix, settings, settingKey, label, meta } = this.props;
     const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
 
     return (
       <div className='setting-toggle'>
-        <Toggle id={id} checked={settings.getIn(settingKey)} onChange={this.onChange} />
+        <Toggle id={id} checked={settings.getIn(settingKey)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
         <label htmlFor={id} className='setting-toggle__label'>{label}</label>
         {meta && <span className='setting-meta__label'>{meta}</span>}
       </div>
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index 1eff04e97..a2885adda 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import IconButton from '../../../components/icon_button';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import DropdownMenu from '../../../components/dropdown_menu';
+import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
 import { defineMessages, injectIntl } from 'react-intl';
 
 const messages = defineMessages({
@@ -13,6 +13,7 @@ const messages = defineMessages({
   cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
   favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
   report: { id: 'status.report', defaultMessage: 'Report @{name}' },
+  share: { id: 'status.share', defaultMessage: 'Share' },
 });
 
 @injectIntl
@@ -58,6 +59,13 @@ export default class ActionBar extends React.PureComponent {
     this.props.onReport(this.props.status);
   }
 
+  handleShare = () => {
+    navigator.share({
+      text: this.props.status.get('search_index'),
+      url: this.props.status.get('url'),
+    });
+  }
+
   render () {
     const { status, me, intl } = this.props;
 
@@ -71,6 +79,10 @@ export default class ActionBar extends React.PureComponent {
       menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
     }
 
+    const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
+      <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
+    );
+
     let reblogIcon = 'retweet';
     //if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
     // else if (status.get('visibility') === 'private') reblogIcon = 'lock';
@@ -82,9 +94,10 @@ export default class ActionBar extends React.PureComponent {
         <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
         <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
         <div className='detailed-status__button'><IconButton animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
+        {shareButton}
 
         <div className='detailed-status__action-bar-dropdown'>
-          <DropdownMenu size={18} icon='ellipsis-h' items={menu} direction='left' ariaLabel='More' />
+          <DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' ariaLabel='More' />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.js b/app/javascript/mastodon/features/ui/components/actions_modal.js
new file mode 100644
index 000000000..cc0620d1c
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/actions_modal.js
@@ -0,0 +1,71 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import StatusContent from '../../../components/status_content';
+import Avatar from '../../../components/avatar';
+import RelativeTimestamp from '../../../components/relative_timestamp';
+import DisplayName from '../../../components/display_name';
+import IconButton from '../../../components/icon_button';
+
+export default class ActionsModal extends ImmutablePureComponent {
+
+  static propTypes = {
+    actions: PropTypes.array,
+    onClick: PropTypes.func,
+  };
+
+  renderAction = (action, i) => {
+    if (action === null) {
+      return <li key={`sep-${i}`} className='dropdown__sep' />;
+    }
+
+    const { icon = null, text, meta = null, active = false, href = '#' } = action;
+
+    return (
+      <li key={`${text}-${i}`}>
+        <a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={active && 'active'}>
+          {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' />}
+          <div>
+            <div>{text}</div>
+            <div>{meta}</div>
+          </div>
+        </a>
+      </li>
+    );
+  }
+
+  render () {
+    const status = this.props.status && (
+      <div className='status light'>
+        <div className='boost-modal__status-header'>
+          <div className='boost-modal__status-time'>
+            <a href={this.props.status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>
+              <RelativeTimestamp timestamp={this.props.status.get('created_at')} />
+            </a>
+          </div>
+
+          <a href={this.props.status.getIn(['account', 'url'])} className='status__display-name'>
+            <div className='status__avatar'>
+              <Avatar src={this.props.status.getIn(['account', 'avatar'])} staticSrc={this.props.status.getIn(['account', 'avatar_static'])} size={48} />
+            </div>
+
+            <DisplayName account={this.props.status.get('account')} />
+          </a>
+        </div>
+
+        <StatusContent status={this.props.status} />
+      </div>
+    );
+
+    return (
+      <div className='modal-root__modal actions-modal'>
+        {status}
+
+        <ul>
+          {this.props.actions.map(this.renderAction)}
+        </ul>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/column.js b/app/javascript/mastodon/features/ui/components/column.js
index ce1dca171..aea102aac 100644
--- a/app/javascript/mastodon/features/ui/components/column.js
+++ b/app/javascript/mastodon/features/ui/components/column.js
@@ -3,6 +3,7 @@ import ColumnHeader from './column_header';
 import PropTypes from 'prop-types';
 import { debounce } from 'lodash';
 import scrollTop from '../../../scroll';
+import { isMobile } from '../../../is_mobile';
 
 export default class Column extends React.PureComponent {
 
@@ -37,13 +38,12 @@ export default class Column extends React.PureComponent {
   render () {
     const { heading, icon, children, active, hideHeadingOnMobile } = this.props;
 
-    let columnHeaderId = null;
-    let header = '';
+    const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth)));
 
-    if (heading) {
-      columnHeaderId = heading.replace(/ /g, '-');
-      header = <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} hideOnMobile={hideHeadingOnMobile} columnHeaderId={columnHeaderId} />;
-    }
+    const columnHeaderId = showHeading && heading.replace(/ /g, '-');
+    const header = showHeading && (
+      <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} columnHeaderId={columnHeaderId} />
+    );
     return (
       <div
         ref={this.setRef}
diff --git a/app/javascript/mastodon/features/ui/components/column_header.js b/app/javascript/mastodon/features/ui/components/column_header.js
index dc601d6e1..af195ea9c 100644
--- a/app/javascript/mastodon/features/ui/components/column_header.js
+++ b/app/javascript/mastodon/features/ui/components/column_header.js
@@ -8,7 +8,6 @@ export default class ColumnHeader extends React.PureComponent {
     type: PropTypes.string,
     active: PropTypes.bool,
     onClick: PropTypes.func,
-    hideOnMobile: PropTypes.bool,
     columnHeaderId: PropTypes.string,
   };
 
@@ -17,7 +16,7 @@ export default class ColumnHeader extends React.PureComponent {
   }
 
   render () {
-    const { type, active, hideOnMobile, columnHeaderId } = this.props;
+    const { type, active, columnHeaderId } = this.props;
 
     let icon = '';
 
@@ -26,7 +25,7 @@ export default class ColumnHeader extends React.PureComponent {
     }
 
     return (
-      <div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''} ${hideOnMobile ? 'hidden-on-mobile' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
+      <div role='heading' tabIndex='0' className={`column-header ${active ? 'active' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
         {icon}
         {type}
       </div>
diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js
index cbc926581..06004e830 100644
--- a/app/javascript/mastodon/features/ui/components/column_link.js
+++ b/app/javascript/mastodon/features/ui/components/column_link.js
@@ -2,24 +2,24 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import Link from 'react-router-dom/Link';
 
-const ColumnLink = ({ icon, text, to, onClick, href, method, hideOnMobile }) => {
+const ColumnLink = ({ icon, text, to, onClick, href, method }) => {
   if (href) {
     return (
-      <a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
+      <a href={href} className='column-link' data-method={method}>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
       </a>
     );
   } else if (to) {
     return (
-      <Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
+      <Link to={to} className='column-link'>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
       </Link>
     );
   } else {
     return (
-      <a onClick={onClick} role='button' tabIndex='0' className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
+      <a onClick={onClick} role='button' tabIndex='0' data-method={method}>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
       </a>
@@ -34,7 +34,6 @@ ColumnLink.propTypes = {
   onClick: PropTypes.func,
   href: PropTypes.string,
   method: PropTypes.string,
-  hideOnMobile: PropTypes.bool,
 };
 
 export default ColumnLink;
diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.js
index 7ecfaf77a..1c4058926 100644
--- a/app/javascript/mastodon/features/ui/components/column_loading.js
+++ b/app/javascript/mastodon/features/ui/components/column_loading.js
@@ -6,7 +6,7 @@ import ColumnHeader from '../../../components/column_header';
 
 const ColumnLoading = ({ title = '', icon = ' ' }) => (
   <Column>
-    <ColumnHeader icon={icon} title={title} multiColumn={false} />
+    <ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} />
     <div className='scrollable' />
   </Column>
 );
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index 7de66ce3f..63bd1b021 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -56,6 +56,15 @@ export default class ColumnsArea extends ImmutablePureComponent {
 
   handleSwipe = (index) => {
     this.pendingIndex = index;
+
+    const nextLinkTranslationId = links[index].props['data-preview-title-id'];
+    const currentLinkSelector = '.tabs-bar__link.active';
+    const nextLinkSelector = `.tabs-bar__link[data-preview-title-id="${nextLinkTranslationId}"]`;
+
+    // HACK: Remove the active class from the current link and set it to the next one
+    // React-router does this for us, but too late, feeling laggy.
+    document.querySelector(currentLinkSelector).classList.remove('active');
+    document.querySelector(nextLinkSelector).classList.add('active');
   }
 
   handleAnimationEnd = () => {
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index dcc9becd3..828419d5a 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -10,6 +10,8 @@ import ImageLoader from './image_loader';
 
 const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
+  previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
+  next: { id: 'lightbox.next', defaultMessage: 'Next' },
 });
 
 @injectIntl
@@ -66,16 +68,10 @@ export default class MediaModal extends ImmutablePureComponent {
 
     const index = this.getIndex();
 
-    let leftNav, rightNav, content;
+    const leftNav  = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
+    const rightNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav  modal-container__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
 
-    leftNav = rightNav = content = '';
-
-    if (media.size > 1) {
-      leftNav  = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
-      rightNav = <div role='button' tabIndex='0' className='modal-container__nav  modal-container__nav--right' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
-    }
-
-    content = media.map((image) => {
+    const content = media.map((image) => {
       const width  = image.getIn(['meta', 'original', 'width']) || null;
       const height = image.getIn(['meta', 'original', 'height']) || null;
 
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index 3da3f6391..d316ff433 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -5,6 +5,7 @@ import spring from 'react-motion/lib/spring';
 import BundleContainer from '../containers/bundle_container';
 import BundleModalError from './bundle_modal_error';
 import ModalLoading from './modal_loading';
+import ActionsModal from '../components/actions_modal';
 import {
   MediaModal,
   OnboardingModal,
@@ -23,6 +24,7 @@ const MODAL_COMPONENTS = {
   'CONFIRM': ConfirmationModal,
   'REPORT': ReportModal,
   'SETTINGS': SettingsModal,
+  'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
 };
 
 export default class ModalRoot extends React.PureComponent {
@@ -44,10 +46,34 @@ export default class ModalRoot extends React.PureComponent {
     window.addEventListener('keyup', this.handleKeyUp, false);
   }
 
+  componentWillReceiveProps (nextProps) {
+    if (!!nextProps.type && !this.props.type) {
+      this.activeElement = document.activeElement;
+
+      this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true));
+    }
+  }
+
+  componentDidUpdate (prevProps) {
+    if (!this.props.type && !!prevProps.type) {
+      this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
+      this.activeElement.focus();
+      this.activeElement = null;
+    }
+  }
+
   componentWillUnmount () {
     window.removeEventListener('keyup', this.handleKeyUp);
   }
 
+  getSiblings = () => {
+    return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node);
+  }
+
+  setRef = ref => {
+    this.node = ref;
+  }
+
   willEnter () {
     return { opacity: 0, scale: 0.98 };
   }
@@ -86,11 +112,11 @@ export default class ModalRoot extends React.PureComponent {
         willLeave={this.willLeave}
       >
         {interpolatedStyles =>
-          <div className='modal-root'>
+          <div className='modal-root' ref={this.setRef}>
             {interpolatedStyles.map(({ key, data: { type, props }, style }) => (
               <div key={key} style={{ pointerEvents: visible ? 'auto' : 'none' }}>
                 <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} />
-                <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
+                <div role='dialog' className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
                   <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
                     {(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
                   </BundleContainer>
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js
index baec86d0d..af9e6bf45 100644
--- a/app/javascript/mastodon/features/ui/components/tabs_bar.js
+++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js
@@ -1,16 +1,19 @@
 import React from 'react';
+import PropTypes from 'prop-types';
 import NavLink from 'react-router-dom/NavLink';
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, injectIntl } from 'react-intl';
+import { debounce } from 'lodash';
+import { isUserTouching } from '../../../is_mobile';
 
 export const links = [
-  <NavLink className='tabs-bar__link primary' activeClassName='active' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
-  <NavLink className='tabs-bar__link primary' activeClassName='active' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
-  <NavLink className='tabs-bar__link primary' activeClassName='active' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
+  <NavLink className='tabs-bar__link primary' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
+  <NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
+  <NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
 
-  <NavLink className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
-  <NavLink className='tabs-bar__link secondary' activeClassName='active' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
+  <NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
+  <NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
 
-  <NavLink className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='tabs_bar.federated_timeline' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
+  <NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
 ];
 
 export function getIndex (path) {
@@ -21,13 +24,60 @@ export function getLink (index) {
   return links[index].props.to;
 }
 
+@injectIntl
 export default class TabsBar extends React.Component {
 
+  static contextTypes = {
+    router: PropTypes.object.isRequired,
+  }
+
+  static propTypes = {
+    intl: PropTypes.object.isRequired,
+  }
+
+  setRef = ref => {
+    this.node = ref;
+  }
+
+  handleClick = (e) => {
+    // Only apply optimization for touch devices, which we assume are slower
+    // We thus avoid the 250ms delay for non-touch devices and the lag for touch devices
+    if (isUserTouching()) {
+      e.preventDefault();
+      e.persist();
+
+      requestAnimationFrame(() => {
+        const tabs = Array(...this.node.querySelectorAll('.tabs-bar__link'));
+        const currentTab = tabs.find(tab => tab.classList.contains('active'));
+        const nextTab = tabs.find(tab => tab.contains(e.target));
+        const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)];
+
+
+        if (currentTab !== nextTab) {
+          if (currentTab) {
+            currentTab.classList.remove('active');
+          }
+
+          const listener = debounce(() => {
+            nextTab.removeEventListener('transitionend', listener);
+            this.context.router.history.push(to);
+          }, 50);
+
+          nextTab.addEventListener('transitionend', listener);
+          nextTab.classList.add('active');
+        }
+      });
+    }
+
+  }
+
   render () {
+    const { intl: { formatMessage } } = this.props;
+
     return (
-      <div className='tabs-bar'>
-        {React.Children.toArray(links)}
-      </div>
+      <nav className='tabs-bar' ref={this.setRef}>
+        {links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
+      </nav>
     );
   }
 
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index eb499c836..f7a6eb319 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -52,6 +52,10 @@ const mapStateToProps = state => ({
 @connect(mapStateToProps)
 export default class UI extends React.PureComponent {
 
+  static contextTypes = {
+    router: PropTypes.object.isRequired,
+  }
+
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
     children: PropTypes.node,
@@ -129,6 +133,14 @@ export default class UI extends React.PureComponent {
     this.setState({ draggingOver: false });
   }
 
+  handleServiceWorkerPostMessage = ({ data }) => {
+    if (data.type === 'navigate') {
+      this.context.router.history.push(data.path);
+    } else {
+      console.warn('Unknown message type:', data.type); // eslint-disable-line no-console
+    }
+  }
+
   componentWillMount () {
     window.addEventListener('resize', this.handleResize, { passive: true });
     document.addEventListener('dragenter', this.handleDragEnter, false);
@@ -137,6 +149,10 @@ export default class UI extends React.PureComponent {
     document.addEventListener('dragleave', this.handleDragLeave, false);
     document.addEventListener('dragend', this.handleDragEnd, false);
 
+    if ('serviceWorker' in  navigator) {
+      navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
+    }
+
     this.props.dispatch(refreshHomeTimeline());
     this.props.dispatch(refreshNotifications());
   }
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index b7c521ac3..9267519dd 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -110,9 +110,9 @@ export function SettingsModal () {
 //  IF MASTODON EVER CHANGES DETAILED STATUSES TO REQUIRE THEM, WE'LL NEED TO UPDATE THE URLS OR SOMETHING LOL.  //
 
 export function MediaGallery () {
-  return import(/* webpackChunkName: "status/MediaGallery" */'../../../components/media_gallery');
+  return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery');
 }
 
 export function VideoPlayer () {
-  return import(/* webpackChunkName: "status/VideoPlayer" */'../../../components/video_player');
+  return import(/* webpackChunkName: "status/video_player" */'../../../components/video_player');
 }
diff --git a/app/javascript/mastodon/is_mobile.js b/app/javascript/mastodon/is_mobile.js
index 014a9a8d5..129d66682 100644
--- a/app/javascript/mastodon/is_mobile.js
+++ b/app/javascript/mastodon/is_mobile.js
@@ -12,6 +12,15 @@ export function isMobile(width, columns) {
 };
 
 const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
+let userTouching = false;
+
+window.addEventListener('touchstart', () => {
+  userTouching = true;
+}, { once: true });
+
+export function isUserTouching() {
+  return userTouching;
+}
 
 export function isIOS() {
   return iOS;
diff --git a/app/javascript/mastodon/load_polyfills.js b/app/javascript/mastodon/load_polyfills.js
index df7889118..8927b7358 100644
--- a/app/javascript/mastodon/load_polyfills.js
+++ b/app/javascript/mastodon/load_polyfills.js
@@ -24,6 +24,8 @@ function loadPolyfills() {
   // This avoids shipping them all the polyfills.
   const needsExtraPolyfills = !(
     window.IntersectionObserver &&
+    window.IntersectionObserverEntry &&
+    'isIntersecting' in IntersectionObserverEntry.prototype &&
     window.requestIdleCallback &&
     'object-fit' in (new Image()).style
   );
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 7f27d78cd..f5cf77f92 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -13,6 +13,7 @@
   "account.posts": "المشاركات",
   "account.report": "أبلغ عن @{name}",
   "account.requested": "في انتظار الموافقة",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "إلغاء الحظر عن @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "إلغاء المتابعة",
@@ -34,7 +35,11 @@
   "column.notifications": "الإشعارات",
   "column.public": "الخيط العام الموحد",
   "column_back_button.label": "العودة",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "التصفح",
   "column_subheading.settings": "الإعدادات",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "عرض الردود",
   "home.settings": "إعدادات العمود",
   "lightbox.close": "إغلاق",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "تحميل ...",
   "media_gallery.toggle_visible": "عرض / إخفاء",
   "missing_indicator.label": "تعذر العثور عليه",
@@ -168,6 +175,7 @@
   "status.report": "إبلِغ عن @{name}",
   "status.sensitive_toggle": "اضغط للعرض",
   "status.sensitive_warning": "محتوى حساس",
+  "status.share": "Share",
   "status.show_less": "إعرض أقلّ",
   "status.show_more": "أظهر المزيد",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 68aaf56b0..e6788f9eb 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -13,6 +13,7 @@
   "account.posts": "Публикации",
   "account.report": "Report @{name}",
   "account.requested": "В очакване на одобрение",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Не блокирай",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Не следвай",
@@ -34,7 +35,11 @@
   "column.notifications": "Известия",
   "column.public": "Публичен канал",
   "column_back_button.label": "Назад",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Show replies",
   "home.settings": "Column settings",
   "lightbox.close": "Затвори",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Зареждане...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -168,6 +175,7 @@
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Покажи",
   "status.sensitive_warning": "Деликатно съдържание",
+  "status.share": "Share",
   "status.show_less": "Show less",
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 6fdcde4b4..95b3c60bf 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -13,6 +13,7 @@
   "account.posts": "Publicacions",
   "account.report": "Informe @{name}",
   "account.requested": "Esperant aprovació",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Desbloquejar @{name}",
   "account.unblock_domain": "Mostra {domain}",
   "account.unfollow": "Deixar de seguir",
@@ -34,7 +35,11 @@
   "column.notifications": "Notificacions",
   "column.public": "Línia de temps federada",
   "column_back_button.label": "Enrere",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navegació",
   "column_subheading.settings": "Configuració",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Mostrar respostes",
   "home.settings": "Ajustos de columna",
   "lightbox.close": "Tancar",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Carregant...",
   "media_gallery.toggle_visible": "Alternar visibilitat",
   "missing_indicator.label": "No trobat",
@@ -168,6 +175,7 @@
   "status.report": "Informar sobre @{name}",
   "status.sensitive_toggle": "Clic per veure",
   "status.sensitive_warning": "Contingut sensible",
+  "status.share": "Share",
   "status.show_less": "Mostra menys",
   "status.show_more": "Mostra més",
   "status.unmute_conversation": "Activar conversació",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index f911c7b75..67a99b765 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -13,6 +13,7 @@
   "account.posts": "Beiträge",
   "account.report": "@{name} melden",
   "account.requested": "Warte auf Erlaubnis",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "@{name} entblocken",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Entfolgen",
@@ -34,7 +35,11 @@
   "column.notifications": "Mitteilungen",
   "column.public": "Gesamtes bekanntes Netz",
   "column_back_button.label": "Zurück",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Antworten anzeigen",
   "home.settings": "Spalteneinstellungen",
   "lightbox.close": "Schließen",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Lade…",
   "media_gallery.toggle_visible": "Sichtbarkeit einstellen",
   "missing_indicator.label": "Nicht gefunden",
@@ -168,6 +175,7 @@
   "status.report": "@{name} melden",
   "status.sensitive_toggle": "Klicke, um sie zu sehen",
   "status.sensitive_warning": "Heikle Inhalte",
+  "status.share": "Share",
   "status.show_less": "Weniger anzeigen",
   "status.show_more": "Mehr anzeigen",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index a7b8f01d0..e5d541cd6 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -54,6 +54,22 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Show settings",
+        "id": "column_header.show_settings"
+      },
+      {
+        "defaultMessage": "Hide settings",
+        "id": "column_header.hide_settings"
+      },
+      {
+        "defaultMessage": "Move column to the left",
+        "id": "column_header.moveLeft_settings"
+      },
+      {
+        "defaultMessage": "Move column to the right",
+        "id": "column_header.moveRight_settings"
+      },
+      {
         "defaultMessage": "Unpin",
         "id": "column_header.unpin"
       },
@@ -139,6 +155,10 @@
         "id": "status.reply"
       },
       {
+        "defaultMessage": "Share",
+        "id": "status.share"
+      },
+      {
         "defaultMessage": "Reply to thread",
         "id": "status.replyAll"
       },
@@ -355,6 +375,10 @@
         "id": "account.report"
       },
       {
+        "defaultMessage": "Share @{name}'s profile",
+        "id": "account.share"
+      },
+      {
         "defaultMessage": "Media",
         "id": "account.media"
       },
@@ -1007,6 +1031,10 @@
       {
         "defaultMessage": "Report @{name}",
         "id": "status.report"
+      },
+      {
+        "defaultMessage": "Share",
+        "id": "status.share"
       }
     ],
     "path": "app/javascript/mastodon/features/status/components/action_bar.json"
@@ -1085,6 +1113,14 @@
       {
         "defaultMessage": "Close",
         "id": "lightbox.close"
+      },
+      {
+        "defaultMessage": "Previous",
+        "id": "lightbox.previous"
+      },
+      {
+        "defaultMessage": "Next",
+        "id": "lightbox.next"
       }
     ],
     "path": "app/javascript/mastodon/features/ui/components/media_modal.json"
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 3a201b9c1..2ea2062d3 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -13,6 +13,7 @@
   "account.posts": "Posts",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Unblock @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Unfollow",
@@ -34,7 +35,11 @@
   "column.notifications": "Notifications",
   "column.public": "Federated timeline",
   "column_back_button.label": "Back",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Show replies",
   "home.settings": "Column settings",
   "lightbox.close": "Close",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -168,6 +175,7 @@
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Click to view",
   "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
   "status.show_less": "Show less",
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 0bb5159c8..960d747ec 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -13,6 +13,7 @@
   "account.posts": "Mesaĝoj",
   "account.report": "Report @{name}",
   "account.requested": "Atendas aprobon",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Malbloki @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Malsekvi",
@@ -34,7 +35,11 @@
   "column.notifications": "Sciigoj",
   "column.public": "Fratara tempolinio",
   "column_back_button.label": "Reveni",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Show replies",
   "home.settings": "Column settings",
   "lightbox.close": "Fermi",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Ŝarĝanta...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -168,6 +175,7 @@
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Alklaki por vidi",
   "status.sensitive_warning": "Tikla enhavo",
+  "status.share": "Share",
   "status.show_less": "Show less",
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index a39b608c6..212d16639 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -13,6 +13,7 @@
   "account.posts": "Publicaciones",
   "account.report": "Report @{name}",
   "account.requested": "Esperando aprobación",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Desbloquear",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Dejar de seguir",
@@ -34,7 +35,11 @@
   "column.notifications": "Notificaciones",
   "column.public": "Historia federada",
   "column_back_button.label": "Atrás",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Show replies",
   "home.settings": "Column settings",
   "lightbox.close": "Cerrar",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Cargando...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -168,6 +175,7 @@
   "status.report": "Reportar",
   "status.sensitive_toggle": "Click para ver",
   "status.sensitive_warning": "Contenido sensible",
+  "status.share": "Share",
   "status.show_less": "Mostrar menos",
   "status.show_more": "Mostrar más",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 6842558d9..d2682ef12 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -13,18 +13,19 @@
   "account.posts": "نوشته‌ها",
   "account.report": "گزارش @{name}",
   "account.requested": "در انتظار پذیرش",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "رفع انسداد @{name}",
   "account.unblock_domain": "رفع پنهان‌سازی از {domain}",
   "account.unfollow": "پایان پیگیری",
   "account.unmute": "باصدا کردن @{name}",
-  "account.view_full_profile": "View full profile",
+  "account.view_full_profile": "نمایش نمایهٔ کامل",
   "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
-  "bundle_column_error.body": "Something went wrong while loading this component.",
-  "bundle_column_error.retry": "Try again",
-  "bundle_column_error.title": "Network error",
-  "bundle_modal_error.close": "Close",
-  "bundle_modal_error.message": "Something went wrong while loading this component.",
-  "bundle_modal_error.retry": "Try again",
+  "bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.",
+  "bundle_column_error.retry": "تلاش دوباره",
+  "bundle_column_error.title": "خطای شبکه",
+  "bundle_modal_error.close": "بستن",
+  "bundle_modal_error.message": "هنگام بازکردن این بخش خطایی رخ داد.",
+  "bundle_modal_error.retry": "تلاش دوباره",
   "column.blocks": "کاربران مسدودشده",
   "column.community": "نوشته‌های محلی",
   "column.favourites": "پسندیده‌ها",
@@ -34,8 +35,12 @@
   "column.notifications": "اعلان‌ها",
   "column.public": "نوشته‌های همه‌جا",
   "column_back_button.label": "بازگشت",
-  "column_header.pin": "Pin",
-  "column_header.unpin": "Unpin",
+  "column_header.hide_settings": "نهفتن تنظیمات",
+  "column_header.moveLeft_settings": "انتقال ستون به چپ",
+  "column_header.moveRight_settings": "انتقال ستون به راست",
+  "column_header.pin": "ثابت‌کردن",
+  "column_header.show_settings": "نمایش تنظیمات",
+  "column_header.unpin": "رهاکردن",
   "column_subheading.navigation": "گشت و گذار",
   "column_subheading.settings": "تنظیمات",
   "compose_form.lock_disclaimer": "حساب شما {locked} نیست. هر کسی می‌تواند پیگیر شما شود و نوشته‌های ویژهٔ پیگیران شما را ببیند.",
@@ -56,8 +61,8 @@
   "confirmations.domain_block.message": "آیا جدی جدی می‌خواهید کل دامین {domain} را مسدود کنید؟ بیشتر وقت‌ها مسدودکردن یا بی‌صداکردن چند حساب کاربری خاص کافی است و توصیه می‌شود.",
   "confirmations.mute.confirm": "بی‌صدا کن",
   "confirmations.mute.message": "آیا واقعاً می‌خواهید {name} را بی‌صدا کنید؟",
-  "confirmations.unfollow.confirm": "Unfollow",
-  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "confirmations.unfollow.confirm": "لغو پیگیری",
+  "confirmations.unfollow.message": "آیا واقعاً می‌خواهید به پیگیری از {name} پایان دهید؟",
   "emoji_button.activity": "فعالیت",
   "emoji_button.flags": "پرچم‌ها",
   "emoji_button.food": "غذا و نوشیدنی",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "نمایش پاسخ‌ها",
   "home.settings": "تنظیمات ستون",
   "lightbox.close": "بستن",
+  "lightbox.next": "بعدی",
+  "lightbox.previous": "قبلی",
   "loading_indicator.label": "بارگیری...",
   "media_gallery.toggle_visible": "تغییر پیدایی",
   "missing_indicator.label": "پیدا نشد",
@@ -112,8 +119,8 @@
   "notifications.column_settings.favourite": "پسندیده‌ها:",
   "notifications.column_settings.follow": "پیگیران تازه:",
   "notifications.column_settings.mention": "نام‌بردن‌ها:",
-  "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
+  "notifications.column_settings.push": "اعلان‌ها از سمت سرور",
+  "notifications.column_settings.push_meta": "این دستگاه",
   "notifications.column_settings.reblog": "بازبوق‌ها:",
   "notifications.column_settings.show": "نمایش در ستون",
   "notifications.column_settings.sound": "پخش صدا",
@@ -152,7 +159,7 @@
   "report.target": "گزارش‌دادن",
   "search.placeholder": "جستجو",
   "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
-  "standalone.public_title": "A look inside...",
+  "standalone.public_title": "نگاهی به کاربران این سرور...",
   "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید",
   "status.delete": "پاک‌کردن",
   "status.favourite": "پسندیدن",
@@ -168,6 +175,7 @@
   "status.report": "گزارش دادن @{name}",
   "status.sensitive_toggle": "برای دیدن کلیک کنید",
   "status.sensitive_warning": "محتوای حساس",
+  "status.share": "هم‌رسانی",
   "status.show_less": "نهفتن",
   "status.show_more": "نمایش",
   "status.unmute_conversation": "باصداکردن گفتگو",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index efc9b1053..cb9e9c2a6 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -13,6 +13,7 @@
   "account.posts": "Postit",
   "account.report": "Report @{name}",
   "account.requested": "Odottaa hyväksyntää",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Salli @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Lopeta seuraaminen",
@@ -34,7 +35,11 @@
   "column.notifications": "Ilmoitukset",
   "column.public": "Yleinen aikajana",
   "column_back_button.label": "Takaisin",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Show replies",
   "home.settings": "Column settings",
   "lightbox.close": "Sulje",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Ladataan...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -168,6 +175,7 @@
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Klikkaa nähdäksesi",
   "status.sensitive_warning": "Arkaluontoista sisältöä",
+  "status.share": "Share",
   "status.show_less": "Show less",
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 3cc1f152a..ad9060d25 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -13,11 +13,12 @@
   "account.posts": "Statuts",
   "account.report": "Signaler",
   "account.requested": "Invitation envoyée",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Débloquer",
   "account.unblock_domain": "Ne plus masquer {domain}",
   "account.unfollow": "Ne plus suivre",
   "account.unmute": "Ne plus masquer",
-"account.view_full_profile": "Afficher le profil complet",
+  "account.view_full_profile": "Afficher le profil complet",
   "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
   "bundle_column_error.body": "Une erreur s'est produite lors du chargement de ce composant.",
   "bundle_column_error.retry": "Réessayer",
@@ -34,7 +35,11 @@
   "column.notifications": "Notifications",
   "column.public": "Fil public global",
   "column_back_button.label": "Retour",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Épingler",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Retirer",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Paramètres",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Afficher les réponses",
   "home.settings": "Paramètres de la colonne",
   "lightbox.close": "Fermer",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Chargement…",
   "media_gallery.toggle_visible": "Modifier la visibilité",
   "missing_indicator.label": "Non trouvé",
@@ -168,6 +175,7 @@
   "status.report": "Signaler @{name}",
   "status.sensitive_toggle": "Cliquer pour afficher",
   "status.sensitive_warning": "Contenu sensible",
+  "status.share": "Share",
   "status.show_less": "Replier",
   "status.show_more": "Déplier",
   "status.unmute_conversation": "Ne plus masquer la conversation",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 36be0842b..34266d8e1 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -13,6 +13,7 @@
   "account.posts": "הודעות",
   "account.report": "לדווח על @{name}",
   "account.requested": "בהמתנה לאישור",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "הסרת חסימה מעל @{name}",
   "account.unblock_domain": "הסר חסימה מקהילת {domain}",
   "account.unfollow": "הפסקת מעקב",
@@ -34,7 +35,11 @@
   "column.notifications": "התראות",
   "column.public": "בפרהסיה",
   "column_back_button.label": "חזרה",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "ניווט",
   "column_subheading.settings": "אפשרויות",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "הצגת תגובות",
   "home.settings": "הגדרות טור",
   "lightbox.close": "סגירה",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "טוען...",
   "media_gallery.toggle_visible": "נראה\\בלתי נראה",
   "missing_indicator.label": "לא נמצא",
@@ -168,6 +175,7 @@
   "status.report": "דיווח על @{name}",
   "status.sensitive_toggle": "לחצו כדי לראות",
   "status.sensitive_warning": "תוכן רגיש",
+  "status.share": "Share",
   "status.show_less": "הראה פחות",
   "status.show_more": "הראה יותר",
   "status.unmute_conversation": "הסרת השתקת שיחה",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index 363c4c490..f69b096d4 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -13,6 +13,7 @@
   "account.posts": "Postovi",
   "account.report": "Prijavi @{name}",
   "account.requested": "Čeka pristanak",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Deblokiraj @{name}",
   "account.unblock_domain": "Otkrij {domain}",
   "account.unfollow": "Prestani slijediti",
@@ -34,7 +35,11 @@
   "column.notifications": "Notifikacije",
   "column.public": "Federalni timeline",
   "column_back_button.label": "Natrag",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigacija",
   "column_subheading.settings": "Postavke",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Pokaži odgovore",
   "home.settings": "Postavke Stupca",
   "lightbox.close": "Zatvori",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Učitavam...",
   "media_gallery.toggle_visible": "Preklopi vidljivost",
   "missing_indicator.label": "Nije nađen",
@@ -168,6 +175,7 @@
   "status.report": "Prijavi @{name}",
   "status.sensitive_toggle": "Klikni da bi vidio",
   "status.sensitive_warning": "Osjetljiv sadržaj",
+  "status.share": "Share",
   "status.show_less": "Pokaži manje",
   "status.show_more": "Pokaži više",
   "status.unmute_conversation": "Poništi utišavanje razgovora",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index d43570f0d..4d2a50963 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -13,6 +13,7 @@
   "account.posts": "Posts",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Blokkolás levétele",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Követés abbahagyása",
@@ -34,7 +35,11 @@
   "column.notifications": "Értesítések",
   "column.public": "Nyilvános",
   "column_back_button.label": "Vissza",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Show replies",
   "home.settings": "Column settings",
   "lightbox.close": "Bezárás",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Betöltés...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -168,6 +175,7 @@
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Katt a megtekintéshez",
   "status.sensitive_warning": "Érzékeny tartalom",
+  "status.share": "Share",
   "status.show_less": "Show less",
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 916f313bb..532739e3c 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -13,6 +13,7 @@
   "account.posts": "Postingan",
   "account.report": "Laporkan @{name}",
   "account.requested": "Menunggu persetujuan",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Hapus blokir @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Berhenti mengikuti",
@@ -34,7 +35,11 @@
   "column.notifications": "Notifikasi",
   "column.public": "Linimasa gabunggan",
   "column_back_button.label": "Kembali",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigasi",
   "column_subheading.settings": "Pengaturan",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Tampilkan balasan",
   "home.settings": "Pengaturan kolom",
   "lightbox.close": "Tutup",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Tunggu sebentar...",
   "media_gallery.toggle_visible": "Tampil/Sembunyikan",
   "missing_indicator.label": "Tidak ditemukan",
@@ -168,6 +175,7 @@
   "status.report": "Laporkan @{name}",
   "status.sensitive_toggle": "Klik untuk menampilkan",
   "status.sensitive_warning": "Konten sensitif",
+  "status.share": "Share",
   "status.show_less": "Tampilkan lebih sedikit",
   "status.show_more": "Tampilkan semua",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index a87cc9328..a5e363e40 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -13,6 +13,7 @@
   "account.posts": "Mesaji",
   "account.report": "Denuncar @{name}",
   "account.requested": "Vartante aprobo",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Desblokusar @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Ne plus sequar",
@@ -34,7 +35,11 @@
   "column.notifications": "Savigi",
   "column.public": "Federata tempolineo",
   "column_back_button.label": "Retro",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Montrar respondi",
   "home.settings": "Aranji di la kolumno",
   "lightbox.close": "Klozar",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Kargante...",
   "media_gallery.toggle_visible": "Chanjar videbleso",
   "missing_indicator.label": "Ne trovita",
@@ -168,6 +175,7 @@
   "status.report": "Denuncar @{name}",
   "status.sensitive_toggle": "Kliktar por vidar",
   "status.sensitive_warning": "Trubliva kontenajo",
+  "status.share": "Share",
   "status.show_less": "Montrar mine",
   "status.show_more": "Montrar plue",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 243ed7344..329eb82ca 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -13,6 +13,7 @@
   "account.posts": "Posts",
   "account.report": "Segnala @{name}",
   "account.requested": "In attesa di approvazione",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Sblocca @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Non seguire",
@@ -34,7 +35,11 @@
   "column.notifications": "Notifiche",
   "column.public": "Timeline federata",
   "column_back_button.label": "Indietro",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Mostra risposte",
   "home.settings": "Impostazioni colonna",
   "lightbox.close": "Chiudi",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Carico...",
   "media_gallery.toggle_visible": "Imposta visibilità",
   "missing_indicator.label": "Non trovato",
@@ -168,6 +175,7 @@
   "status.report": "Segnala @{name}",
   "status.sensitive_toggle": "Clicca per vedere",
   "status.sensitive_warning": "Materiale sensibile",
+  "status.share": "Share",
   "status.show_less": "Mostra meno",
   "status.show_more": "Mostra di più",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index ca36122f7..4c98086bb 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -1,7 +1,7 @@
 {
   "account.block": "ブロック",
   "account.block_domain": "{domain}全体を非表示",
-  "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.disclaimer_full": "以下の情報は不正確な可能性があります。",
   "account.edit_profile": "プロフィールを編集",
   "account.follow": "フォロー",
   "account.followers": "フォロワー",
@@ -13,11 +13,12 @@
   "account.posts": "投稿",
   "account.report": "通報",
   "account.requested": "承認待ち",
+  "account.share": "@{name} のプロフィールを共有する",
   "account.unblock": "ブロック解除",
   "account.unblock_domain": "{domain}を表示",
   "account.unfollow": "フォロー解除",
   "account.unmute": "ミュート解除",
-  "account.view_full_profile": "View full profile",
+  "account.view_full_profile": "全ての情報を見る",
   "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
   "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",
   "bundle_column_error.retry": "再試行",
@@ -34,7 +35,11 @@
   "column.notifications": "通知",
   "column.public": "連合タイムライン",
   "column_back_button.label": "戻る",
+  "column_header.hide_settings": "設定を隠す",
+  "column_header.moveLeft_settings": "カラムを左に移動する",
+  "column_header.moveRight_settings": "カラムを右に移動する",
   "column_header.pin": "ピン留めする",
+  "column_header.show_settings": "設定を表示",
   "column_header.unpin": "ピン留めを外す",
   "column_subheading.navigation": "ナビゲーション",
   "column_subheading.settings": "設定",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "返信表示",
   "home.settings": "カラム設定",
   "lightbox.close": "閉じる",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "読み込み中...",
   "media_gallery.toggle_visible": "表示切り替え",
   "missing_indicator.label": "見つかりません",
@@ -149,7 +156,7 @@
   "reply_indicator.cancel": "キャンセル",
   "report.placeholder": "コメント",
   "report.submit": "通報する",
-  "report.target": "問題のユーザー",
+  "report.target": "{target} を通報する",
   "search.placeholder": "検索",
   "search_results.total": "{count, number}件の結果",
   "standalone.public_title": "連合タイムライン",
@@ -168,6 +175,7 @@
   "status.report": "通報",
   "status.sensitive_toggle": "クリックして表示",
   "status.sensitive_warning": "閲覧注意",
+  "status.share": "共有",
   "status.show_less": "隠す",
   "status.show_more": "もっと見る",
   "status.unmute_conversation": "会話のミュートを解除",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 768efa37d..47d0d4087 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -13,6 +13,7 @@
   "account.posts": "포스트",
   "account.report": "신고",
   "account.requested": "승인 대기 중",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "차단 해제",
   "account.unblock_domain": "{domain} 숨김 해제",
   "account.unfollow": "팔로우 해제",
@@ -34,7 +35,11 @@
   "column.notifications": "알림",
   "column.public": "연합 타임라인",
   "column_back_button.label": "돌아가기",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "고정하기",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "고정 해제",
   "column_subheading.navigation": "내비게이션",
   "column_subheading.settings": "설정",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "답글 표시",
   "home.settings": "컬럼 설정",
   "lightbox.close": "닫기",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "불러오는 중...",
   "media_gallery.toggle_visible": "표시 전환",
   "missing_indicator.label": "찾을 수 없습니다",
@@ -168,6 +175,7 @@
   "status.report": "신고",
   "status.sensitive_toggle": "클릭해서 표시하기",
   "status.sensitive_warning": "민감한 미디어",
+  "status.share": "Share",
   "status.show_less": "숨기기",
   "status.show_more": "더 보기",
   "status.unmute_conversation": "이 대화의 뮤트 해제하기",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index fbfabc5d1..4d68c7992 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -13,11 +13,12 @@
   "account.posts": "Toots",
   "account.report": "Rapporteer @{name}",
   "account.requested": "Wacht op goedkeuring",
+  "account.share": "Profiel van @{name} delen",
   "account.unblock": "Deblokkeer @{name}",
   "account.unblock_domain": "{domain} niet meer negeren",
   "account.unfollow": "Ontvolgen",
   "account.unmute": "@{name} niet meer negeren",
-  "account.view_full_profile": "Volledig profiel tonen", 
+  "account.view_full_profile": "Volledig profiel tonen",
   "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
   "bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.",
   "bundle_column_error.retry": "Opnieuw proberen",
@@ -34,7 +35,11 @@
   "column.notifications": "Meldingen",
   "column.public": "Globale tijdlijn",
   "column_back_button.label": "terug",
+  "column_header.hide_settings": "Instellingen verbergen",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Vastmaken",
+  "column_header.show_settings": "Instellingen tonen",
   "column_header.unpin": "Losmaken",
   "column_subheading.navigation": "Navigatie",
   "column_subheading.settings": "Instellingen",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Reacties tonen",
   "home.settings": "Kolom-instellingen",
   "lightbox.close": "Sluiten",
+  "lightbox.next": "Volgende",
+  "lightbox.previous": "Vorige",
   "loading_indicator.label": "Laden…",
   "media_gallery.toggle_visible": "Media wel/niet tonen",
   "missing_indicator.label": "Niet gevonden",
@@ -147,12 +154,12 @@
   "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
   "privacy.unlisted.short": "Minder openbaar",
   "reply_indicator.cancel": "Annuleren",
-  "report.heading": "Rapporteren",
   "report.placeholder": "Extra opmerkingen",
   "report.submit": "Verzenden",
   "report.target": "Rapporteren van",
   "search.placeholder": "Zoeken",
   "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
+  "standalone.public_title": "Een kijkje binnenin...",
   "status.cannot_reblog": "Deze toot kan niet geboost worden",
   "status.delete": "Verwijderen",
   "status.favourite": "Favoriet",
@@ -166,8 +173,9 @@
   "status.reply": "Reageren",
   "status.replyAll": "Reageer op iedereen",
   "status.report": "Rapporteer @{name}",
-  "status.sensitive_toggle": "Klik om te zien",
+  "status.sensitive_toggle": "Klik om te bekijken",
   "status.sensitive_warning": "Gevoelige inhoud",
+  "status.share": "Delen",
   "status.show_less": "Minder tonen",
   "status.show_more": "Meer tonen",
   "status.unmute_conversation": "Conversatie niet meer negeren",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 8727f6147..9453e65ff 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -13,6 +13,7 @@
   "account.posts": "Innlegg",
   "account.report": "Rapportér @{name}",
   "account.requested": "Venter på godkjennelse",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Avblokker @{name}",
   "account.unblock_domain": "Vis {domain}",
   "account.unfollow": "Avfølg",
@@ -34,7 +35,11 @@
   "column.notifications": "Varsler",
   "column.public": "Felles tidslinje",
   "column_back_button.label": "Tilbake",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigasjon",
   "column_subheading.settings": "Innstillinger",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Vis svar",
   "home.settings": "Kolonneinnstillinger",
   "lightbox.close": "Lukk",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Laster...",
   "media_gallery.toggle_visible": "Veksle synlighet",
   "missing_indicator.label": "Ikke funnet",
@@ -168,6 +175,7 @@
   "status.report": "Rapporter @{name}",
   "status.sensitive_toggle": "Klikk for å vise",
   "status.sensitive_warning": "Følsomt innhold",
+  "status.share": "Share",
   "status.show_less": "Vis mindre",
   "status.show_more": "Vis mer",
   "status.unmute_conversation": "Ikke demp samtale",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index c39d57333..e2a5d7c59 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -1,7 +1,7 @@
 {
   "account.block": "Blocar @{name}",
   "account.block_domain": "Tot amagar del domeni {domain}",
-  "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incompletas.",
   "account.edit_profile": "Modificar lo perfil",
   "account.follow": "Sègre",
   "account.followers": "Seguidors",
@@ -13,18 +13,19 @@
   "account.posts": "Estatuts",
   "account.report": "Senhalar @{name}",
   "account.requested": "Invitacion mandada",
+  "account.share": "Partejar lo perfil a @{name}",
   "account.unblock": "Desblocar @{name}",
   "account.unblock_domain": "Desblocar {domain}",
   "account.unfollow": "Quitar de sègre",
   "account.unmute": "Quitar de rescondre @{name}",
-  "account.view_full_profile": "View full profile",
+  "account.view_full_profile": "Veire lo perfil complet",
   "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
   "bundle_column_error.body": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.",
-  "bundle_column_error.retry": "Tornar ensejar",
+  "bundle_column_error.retry": "Tornar ensajar",
   "bundle_column_error.title": "Error de ret",
   "bundle_modal_error.close": "Tampar",
-  "bundle_modal_error.message": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.",
-  "bundle_modal_error.retry": "Tornar ensejar",
+  "bundle_modal_error.message": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.",
+  "bundle_modal_error.retry": "Tornar ensajar",
   "column.blocks": "Personas blocadas",
   "column.community": "Flux public local",
   "column.favourites": "Favorits",
@@ -34,7 +35,11 @@
   "column.notifications": "Notificacions",
   "column.public": "Flux public global",
   "column_back_button.label": "Tornar",
+  "column_header.hide_settings": "Amagar los paramètres",
+  "column_header.moveLeft_settings": "Desplaçar la colomna a man drecha",
+  "column_header.moveRight_settings": "Desplaçar la colomna a man esquèrra",
   "column_header.pin": "Penjar",
+  "column_header.show_settings": "Mostrar los paramètres",
   "column_header.unpin": "Despenjar",
   "column_subheading.navigation": "Navigacion",
   "column_subheading.settings": "Paramètres",
@@ -46,35 +51,35 @@
   "compose_form.publish_loud": "{publish} !",
   "compose_form.sensitive": "Marcar lo mèdia coma sensible",
   "compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
-  "compose_form.spoiler_placeholder": "Avertiment",
+  "compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí",
   "confirmation_modal.cancel": "Anullar",
   "confirmations.block.confirm": "Blocar",
   "confirmations.block.message": "Sètz segur de voler blocar {name} ?",
   "confirmations.delete.confirm": "Suprimir",
   "confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
   "confirmations.domain_block.confirm": "Amagar tot lo domeni",
-  "confirmations.domain_block.message": "Sètz segur segur de voler blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
+  "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
   "confirmations.mute.confirm": "Metre en silenci",
   "confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
   "confirmations.unfollow.confirm": "Quitar de sègre",
   "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
-  "emoji_button.activity": "Activitat",
+  "emoji_button.activity": "Activitats",
   "emoji_button.flags": "Drapèus",
   "emoji_button.food": "Beure e manjar",
   "emoji_button.label": "Inserir un emoji",
   "emoji_button.nature": "Natura",
   "emoji_button.objects": "Objèctes",
   "emoji_button.people": "Gents",
-  "emoji_button.search": "Cercar...",
+  "emoji_button.search": "Cercar…",
   "emoji_button.symbols": "Simbòls",
   "emoji_button.travel": "Viatges & lòcs",
-  "empty_column.community": "Lo flux public local es void. Escribètz quicòm per lo garnir !",
+  "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !",
   "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
-  "empty_column.home": "Pel moment segètz pas degun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
+  "empty_column.home": "Pel moment seguètz pas degun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
   "empty_column.home.inactivity": "Vòstra pagina d’acuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.",
   "empty_column.home.public_timeline": "lo flux public",
   "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
-  "empty_column.public": "I a pas res aquí ! Escribètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
+  "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Regetar",
   "getting_started.appsshort": "Apps",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Mostrar las responsas",
   "home.settings": "Paramètres de la colomna",
   "lightbox.close": "Tampar",
+  "lightbox.next": "Seguent",
+  "lightbox.previous": "Precedent",
   "loading_indicator.label": "Cargament…",
   "media_gallery.toggle_visible": "Modificar la visibilitat",
   "missing_indicator.label": "Pas trobat",
@@ -103,11 +110,11 @@
   "navigation_bar.preferences": "Preferéncias",
   "navigation_bar.public_timeline": "Flux public global",
   "notification.favourite": "{name} a ajustat a sos favorits :",
-  "notification.follow": "{name} vos sèc.",
+  "notification.follow": "{name} vos sèc",
   "notification.mention": "{name} vos a mencionat :",
   "notification.reblog": "{name} a partejat vòstre estatut :",
-  "notifications.clear": "Levar",
-  "notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
+  "notifications.clear": "Escafar",
+  "notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?",
   "notifications.column_settings.alert": "Notificacions localas",
   "notifications.column_settings.favourite": "Favorits :",
   "notifications.column_settings.follow": "Nòus seguidors :",
@@ -119,15 +126,15 @@
   "notifications.column_settings.sound": "Emetre un son",
   "onboarding.done": "Fach",
   "onboarding.next": "Seguent",
-  "onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra intància, aquí {domain}. Lo flux federat mòstra los estatuts publics de tot lo mond sus {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.",
+  "onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra instància, aquí {domain}. Lo flux federat mòstra los estatuts publics de tot lo mond sus {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.",
   "onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.",
-  "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un enteragís amb vos",
+  "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos",
   "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum ma larg. Òm los apèla instàncias.",
   "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}",
   "onboarding.page_one.welcome": "Benvengut a Mastodon !",
   "onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
   "onboarding.page_six.almost_done": "Gaireben acabat…",
-  "onboarding.page_six.appetoot": "Bon Appetoot!",
+  "onboarding.page_six.appetoot": "Bon Appetut!",
   "onboarding.page_six.apps_available": "I a d’aplicacions per mobil per iOS, Android e mai.",
   "onboarding.page_six.github": "Mastodon es un logicial liure e open-source.  Podètz senhalar de bugs, demandar de foncionalitats e contribuir al còdi sus {github}.",
   "onboarding.page_six.guidelines": "guida de la comunitat",
@@ -168,6 +175,7 @@
   "status.report": "Senhalar @{name}",
   "status.sensitive_toggle": "Clicar per mostrar",
   "status.sensitive_warning": "Contengut sensible",
+  "status.share": "Partejar",
   "status.show_less": "Tornar plegar",
   "status.show_more": "Desplegar",
   "status.unmute_conversation": "Conversacions amb silenci levat",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index af069b6d7..c42721f64 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -13,6 +13,7 @@
   "account.posts": "Posty",
   "account.report": "Zgłoś @{name}",
   "account.requested": "Oczekująca prośba",
+  "account.share": "Udostępnij profil @{name}",
   "account.unblock": "Odblokuj @{name}",
   "account.unblock_domain": "Odblokuj domenę {domain}",
   "account.unfollow": "Przestań śledzić",
@@ -34,7 +35,11 @@
   "column.notifications": "Powiadomienia",
   "column.public": "Globalna oś czasu",
   "column_back_button.label": "Wróć",
+  "column_header.hide_settings": "Ukryj ustawienia",
+  "column_header.moveLeft_settings": "Przesuń kolumnę w lewo",
+  "column_header.moveRight_settings": "Przesuń kolumnę w prawo",
   "column_header.pin": "Przypnij",
+  "column_header.show_settings": "Pokaż ustawienia",
   "column_header.unpin": "Cofnij przypięcie",
   "column_subheading.navigation": "Nawigacja",
   "column_subheading.settings": "Ustawienia",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Pokazuj odpowiedzi",
   "home.settings": "Ustawienia kolumny",
   "lightbox.close": "Zamknij",
+  "lightbox.next": "Następne",
+  "lightbox.previous": "Poprzednie",
   "loading_indicator.label": "Ładowanie...",
   "media_gallery.toggle_visible": "Przełącz widoczność",
   "missing_indicator.label": "Nie znaleziono",
@@ -168,6 +175,7 @@
   "status.report": "Zgłoś @{name}",
   "status.sensitive_toggle": "Naciśnij aby wyświetlić",
   "status.sensitive_warning": "Wrażliwa zawartość",
+  "status.share": "Udostępnij",
   "status.show_less": "Pokaż mniej",
   "status.show_more": "Pokaż więcej",
   "status.unmute_conversation": "Cofnij wyciezenie konwersacji",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 86da7c4e6..55d2f05de 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -13,6 +13,7 @@
   "account.posts": "Posts",
   "account.report": "Denunciar @{name}",
   "account.requested": "A aguardar aprovação",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Não bloquear @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Deixar de seguir",
@@ -34,7 +35,11 @@
   "column.notifications": "Notificações",
   "column.public": "Global",
   "column_back_button.label": "Voltar",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Mostrar as respostas",
   "home.settings": "Parâmetros da listagem",
   "lightbox.close": "Fechar",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Carregando...",
   "media_gallery.toggle_visible": "Esconder/Mostrar",
   "missing_indicator.label": "Não encontrado",
@@ -168,6 +175,7 @@
   "status.report": "Denúnciar @{name}",
   "status.sensitive_toggle": "Clique para ver",
   "status.sensitive_warning": "Conteúdo sensível",
+  "status.share": "Share",
   "status.show_less": "Mostrar menos",
   "status.show_more": "Mostrar mais",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index 86da7c4e6..55d2f05de 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -13,6 +13,7 @@
   "account.posts": "Posts",
   "account.report": "Denunciar @{name}",
   "account.requested": "A aguardar aprovação",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Não bloquear @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Deixar de seguir",
@@ -34,7 +35,11 @@
   "column.notifications": "Notificações",
   "column.public": "Global",
   "column_back_button.label": "Voltar",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Mostrar as respostas",
   "home.settings": "Parâmetros da listagem",
   "lightbox.close": "Fechar",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Carregando...",
   "media_gallery.toggle_visible": "Esconder/Mostrar",
   "missing_indicator.label": "Não encontrado",
@@ -168,6 +175,7 @@
   "status.report": "Denúnciar @{name}",
   "status.sensitive_toggle": "Clique para ver",
   "status.sensitive_warning": "Conteúdo sensível",
+  "status.share": "Share",
   "status.show_less": "Mostrar menos",
   "status.show_more": "Mostrar mais",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 16af3fe7e..1abfb4370 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -13,6 +13,7 @@
   "account.posts": "Посты",
   "account.report": "Пожаловаться",
   "account.requested": "Ожидает подтверждения",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Разблокировать",
   "account.unblock_domain": "Разблокировать {domain}",
   "account.unfollow": "Отписаться",
@@ -34,7 +35,11 @@
   "column.notifications": "Уведомления",
   "column.public": "Глобальная лента",
   "column_back_button.label": "Назад",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Закрепить",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Открепить",
   "column_subheading.navigation": "Навигация",
   "column_subheading.settings": "Настройки",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Показывать ответы",
   "home.settings": "Настройки колонки",
   "lightbox.close": "Закрыть",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Загрузка...",
   "media_gallery.toggle_visible": "Показать/скрыть",
   "missing_indicator.label": "Не найдено",
@@ -168,6 +175,7 @@
   "status.report": "Пожаловаться",
   "status.sensitive_toggle": "Нажмите для просмотра",
   "status.sensitive_warning": "Чувствительный контент",
+  "status.share": "Share",
   "status.show_less": "Свернуть",
   "status.show_more": "Развернуть",
   "status.unmute_conversation": "Снять глушение с треда",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index be5c0815d..aa0929f82 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -13,6 +13,7 @@
   "account.posts": "Posts",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Unblock @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Unfollow",
@@ -34,7 +35,11 @@
   "column.notifications": "Notifications",
   "column.public": "Federated timeline",
   "column_back_button.label": "Back",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Settings",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Show replies",
   "home.settings": "Column settings",
   "lightbox.close": "Close",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -168,6 +175,7 @@
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Click to view",
   "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
   "status.show_less": "Show less",
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 9d4d5fa17..37ce8597e 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -13,6 +13,7 @@
   "account.posts": "Gönderiler",
   "account.report": "Rapor et @{name}",
   "account.requested": "Onay bekleniyor",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Engeli kaldır @{name}",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Takipten vazgeç",
@@ -34,7 +35,11 @@
   "column.notifications": "Bildirimler",
   "column.public": "Federe zaman tüneli",
   "column_back_button.label": "Geri",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Navigasyon",
   "column_subheading.settings": "Ayarlar",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Cevapları göster",
   "home.settings": "Kolon ayarları",
   "lightbox.close": "Kapat",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Yükleniyor...",
   "media_gallery.toggle_visible": "Görünürlüğü değiştir",
   "missing_indicator.label": "Bulunamadı",
@@ -168,6 +175,7 @@
   "status.report": "@{name}'i raporla",
   "status.sensitive_toggle": "Görmek için tıklayınız",
   "status.sensitive_warning": "Hassas içerik",
+  "status.share": "Share",
   "status.show_less": "Daha azı",
   "status.show_more": "Daha fazlası",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 60a551bb6..fea7bd94e 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -13,6 +13,7 @@
   "account.posts": "Пости",
   "account.report": "Поскаржитися",
   "account.requested": "Очікує підтвердження",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "Розблокувати",
   "account.unblock_domain": "Розблокувати {domain}",
   "account.unfollow": "Відписатися",
@@ -34,7 +35,11 @@
   "column.notifications": "Сповіщення",
   "column.public": "Глобальна стрічка",
   "column_back_button.label": "Назад",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "Навігація",
   "column_subheading.settings": "Налаштування",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "Показувати відповіді",
   "home.settings": "Налаштування колонок",
   "lightbox.close": "Закрити",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "Завантаження...",
   "media_gallery.toggle_visible": "Показати/приховати",
   "missing_indicator.label": "Не знайдено",
@@ -168,6 +175,7 @@
   "status.report": "Поскаржитися",
   "status.sensitive_toggle": "Натисніть, щоб подивитися",
   "status.sensitive_warning": "Непристойний зміст",
+  "status.share": "Share",
   "status.show_less": "Згорнути",
   "status.show_more": "Розгорнути",
   "status.unmute_conversation": "Зняти глушення з діалогу",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 97f1f5e27..d0c4b3d1b 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -13,6 +13,7 @@
   "account.posts": "嘟文",
   "account.report": "举报 @{name}",
   "account.requested": "等待审批",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "解除对 @{name} 的屏蔽",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "取消关注",
@@ -34,7 +35,11 @@
   "column.notifications": "通知",
   "column.public": "跨站公共时间轴",
   "column_back_button.label": "Back",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "导航",
   "column_subheading.settings": "设置",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "显示回应嘟文",
   "home.settings": "字段设置",
   "lightbox.close": "关闭",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "加载中……",
   "media_gallery.toggle_visible": "打开或关上",
   "missing_indicator.label": "找不到内容",
@@ -168,6 +175,7 @@
   "status.report": "举报 @{name}",
   "status.sensitive_toggle": "点击显示",
   "status.sensitive_warning": "敏感内容",
+  "status.share": "Share",
   "status.show_less": "减少显示",
   "status.show_more": "显示更多",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index c65c3d45c..7312aae82 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -13,6 +13,7 @@
   "account.posts": "文章",
   "account.report": "舉報 @{name}",
   "account.requested": "等候審批",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "解除對 @{name} 的封鎖",
   "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "取消關注",
@@ -34,7 +35,11 @@
   "column.notifications": "通知",
   "column.public": "跨站時間軸",
   "column_back_button.label": "返回",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "瀏覽",
   "column_subheading.settings": "設定",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "顯示回應文章",
   "home.settings": "欄位設定",
   "lightbox.close": "關閉",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "載入中...",
   "media_gallery.toggle_visible": "打開或關上",
   "missing_indicator.label": "找不到內容",
@@ -168,6 +175,7 @@
   "status.report": "舉報 @{name}",
   "status.sensitive_toggle": "點擊顯示",
   "status.sensitive_warning": "敏感內容",
+  "status.share": "Share",
   "status.show_less": "減少顯示",
   "status.show_more": "顯示更多",
   "status.unmute_conversation": "Unmute conversation",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 12e840b16..1c2e35272 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -13,6 +13,7 @@
   "account.posts": "貼文",
   "account.report": "檢舉 @{name}",
   "account.requested": "正在等待許可",
+  "account.share": "Share @{name}'s profile",
   "account.unblock": "取消封鎖 @{name}",
   "account.unblock_domain": "不再隱藏 {domain}",
   "account.unfollow": "取消關注",
@@ -34,7 +35,11 @@
   "column.notifications": "通知",
   "column.public": "聯盟時間軸",
   "column_back_button.label": "上一頁",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
   "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.navigation": "瀏覽",
   "column_subheading.settings": "設定",
@@ -89,6 +94,8 @@
   "home.column_settings.show_replies": "顯示回應",
   "home.settings": "欄位設定",
   "lightbox.close": "關閉",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
   "loading_indicator.label": "讀取中...",
   "media_gallery.toggle_visible": "切換可見性",
   "missing_indicator.label": "找不到",
@@ -168,6 +175,7 @@
   "status.report": "通報 @{name}",
   "status.sensitive_toggle": "點來看",
   "status.sensitive_warning": "敏感內容",
+  "status.share": "Share",
   "status.show_less": "看少點",
   "status.show_more": "看更多",
   "status.unmute_conversation": "不消音對話",
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 7c98854a2..07207c93b 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -96,7 +96,7 @@ function appendMedia(state, media) {
     map.set('focusDate', new Date());
     map.set('idempotencyKey', uuid());
 
-    if (prevSize === 0 && state.get('default_sensitive')) {
+    if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
       map.set('sensitive', true);
     }
   });
@@ -165,14 +165,22 @@ export default function compose(state = initialState, action) {
         state.get('advanced_options').set(action.option, !state.getIn(['advanced_options', action.option])))
       .set('idempotencyKey', uuid());
   case COMPOSE_SENSITIVITY_CHANGE:
-    return state
-      .set('sensitive', !state.get('sensitive'))
-      .set('idempotencyKey', uuid());
+    return state.withMutations(map => {
+      if (!state.get('spoiler')) {
+        map.set('sensitive', !state.get('sensitive'));
+      }
+
+      map.set('idempotencyKey', uuid());
+    });
   case COMPOSE_SPOILERNESS_CHANGE:
     return state.withMutations(map => {
       map.set('spoiler_text', '');
       map.set('spoiler', !state.get('spoiler'));
       map.set('idempotencyKey', uuid());
+
+      if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
+        map.set('sensitive', true);
+      }
     });
   case COMPOSE_SPOILER_TEXT_CHANGE:
     return state
diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js
index 4a8a57767..acb85f626 100644
--- a/app/javascript/mastodon/service_worker/web_push_notifications.js
+++ b/app/javascript/mastodon/service_worker/web_push_notifications.js
@@ -1,3 +1,45 @@
+const MAX_NOTIFICATIONS = 5;
+const GROUP_TAG = 'tag';
+
+// Avoid loading intl-messageformat and dealing with locales in the ServiceWorker
+const formatGroupTitle = (message, count) => message.replace('%{count}', count);
+
+const notify = options =>
+  self.registration.getNotifications().then(notifications => {
+    if (notifications.length === MAX_NOTIFICATIONS) {
+      // Reached the maximum number of notifications, proceed with grouping
+      const group = {
+        title: formatGroupTitle(options.data.message, notifications.length + 1),
+        body: notifications
+          .sort((n1, n2) => n1.timestamp < n2.timestamp)
+          .map(notification => notification.title).join('\n'),
+        badge: '/badge.png',
+        icon: '/android-chrome-192x192.png',
+        tag: GROUP_TAG,
+        data: {
+          url: (new URL('/web/notifications', self.location)).href,
+          count: notifications.length + 1,
+          message: options.data.message,
+        },
+      };
+
+      notifications.forEach(notification => notification.close());
+
+      return self.registration.showNotification(group.title, group);
+    } else if (notifications.length === 1 && notifications[0].tag === GROUP_TAG) {
+      // Already grouped, proceed with appending the notification to the group
+      const group = cloneNotification(notifications[0]);
+
+      group.title = formatGroupTitle(group.data.message, group.data.count + 1);
+      group.body = `${options.title}\n${group.body}`;
+      group.data = { ...group.data, count: group.data.count + 1 };
+
+      return self.registration.showNotification(group.title, group);
+    }
+
+    return self.registration.showNotification(options.title, options);
+  });
+
 const handlePush = (event) => {
   const options = event.data.json();
 
@@ -17,7 +59,7 @@ const handlePush = (event) => {
     options.actions = options.data.actions;
   }
 
-  event.waitUntil(self.registration.showNotification(options.title, options));
+  event.waitUntil(notify(options));
 };
 
 const cloneNotification = (notification) => {
@@ -50,22 +92,37 @@ const makeRequest = (notification, action) =>
     credentials: 'include',
   });
 
+const findBestClient = clients => {
+  const focusedClient = clients.find(client => client.focused);
+  const visibleClient = clients.find(client => client.visibilityState === 'visible');
+
+  return focusedClient || visibleClient || clients[0];
+};
+
 const openUrl = url =>
   self.clients.matchAll({ type: 'window' }).then(clientList => {
-    if (clientList.length !== 0 && 'navigate' in clientList[0]) { // Chrome 42-48 does not support navigate
-      const webClients = clientList
-        .filter(client => /\/web\//.test(client.url))
-        .sort(client => client !== 'visible');
+    if (clientList.length !== 0) {
+      const webClients = clientList.filter(client => /\/web\//.test(client.url));
 
-      const visibleClient = clientList.find(client => client.visibilityState === 'visible');
-      const focusedClient = clientList.find(client => client.focused);
+      if (webClients.length !== 0) {
+        const client = findBestClient(webClients);
 
-      const client = webClients[0] || visibleClient || focusedClient || clientList[0];
+        const { pathname } = new URL(url);
 
-      return client.navigate(url).then(client => client.focus());
-    } else {
-      return self.clients.openWindow(url);
+        if (pathname.startsWith('/web/')) {
+          return client.focus().then(client => client.postMessage({
+            type: 'navigate',
+            path: pathname.slice('/web/'.length - 1),
+          }));
+        }
+      } else if ('navigate' in clientList[0]) { // Chrome 42-48 does not support navigate
+        const client = findBestClient(clientList);
+
+        return client.navigate(url).then(client => client.focus());
+      }
     }
+
+    return self.clients.openWindow(url);
   });
 
 const removeActionFromNotification = (notification, action) => {
diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss
index deab66ff2..66da44086 100644
--- a/app/javascript/styles/about.scss
+++ b/app/javascript/styles/about.scss
@@ -121,7 +121,7 @@
 
 .information-board {
   background: darken($ui-base-color, 4%);
-  padding: 40px 0;
+  padding: 20px 0;
 
   .panel {
     position: absolute;
@@ -147,10 +147,15 @@
       white-space: nowrap;
       overflow: hidden;
 
+      a,
       span {
         font-weight: 400;
         color: lighten($ui-base-color, 34%);
       }
+
+      a {
+        text-decoration: none;
+      }
     }
   }
 
@@ -162,13 +167,14 @@
   .information-board-sections {
     display: flex;
     justify-content: space-between;
+    flex-wrap: wrap;
   }
 
   .section {
     flex: 1 0 0;
     font: 16px/28px 'mastodon-font-sans-serif', sans-serif;
     text-align: right;
-    padding: 0 15px;
+    padding: 10px 15px;
 
     span,
     strong {
@@ -190,14 +196,6 @@
       color: $primary-text-color;
     }
   }
-
-  @media screen and (max-width: 500px) {
-    flex-direction: column;
-
-    .section {
-      text-align: left;
-    }
-  }
 }
 
 .owner {
@@ -317,6 +315,17 @@
     }
   }
 
+  p,
+  li {
+    font: inherit;
+    font-weight: inherit;
+    margin-bottom: 0;
+  }
+
+  hr {
+    border-color: rgba($ui-base-lighter-color, .6);
+  }
+
   .header {
     line-height: 30px;
     overflow: hidden;
@@ -553,6 +562,7 @@
   }
 
   #mastodon-timeline {
+    display: flex;
     -webkit-overflow-scrolling: touch;
     -ms-overflow-style: -ms-autohiding-scrollbar;
     font-family: 'mastodon-font-sans-serif', sans-serif;
@@ -567,11 +577,20 @@
     overflow: hidden;
     box-shadow: 0 0 6px rgba($black, 0.1);
 
+    .column-header {
+      color: inherit;
+      font-family: inherit;
+      font-size: 16px;
+      line-height: inherit;
+      font-weight: inherit;
+      margin: 0;
+      padding: 15px;
+    }
+
     .column {
       padding: 0;
       border-radius: 4px;
       overflow: hidden;
-      height: 100%;
     }
 
     .scrollable {
@@ -652,21 +671,17 @@
     }
   }
 
-  @media screen and (max-width: 800px) {
+  @media screen and (max-width: 840px) {
     .container {
       padding: 0 20px;
     }
 
-    .information-board {
-      padding-bottom: 20px;
-    }
-
     .information-board .container {
       padding-right: 20px;
 
       .panel {
         position: static;
-        margin-top: 30px;
+        margin-top: 20px;
         width: 100%;
         border-radius: 4px;
 
@@ -694,6 +709,10 @@
   @media screen and (max-width: 675px) {
     .header-wrapper {
       padding-top: 0;
+
+      &.compact .hero .heading {
+        padding-bottom: 20px;
+      }
     }
 
     .header .container,
@@ -707,14 +726,13 @@
     }
 
     .header {
-      padding-top: 0;
 
       .hero {
         margin-top: 30px;
         padding: 0;
 
         .heading {
-          padding-bottom: 20px;
+          padding: 0 20px 20px;
         }
       }
 
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
index fe74bae84..fa604df5c 100644
--- a/app/javascript/styles/components.scss
+++ b/app/javascript/styles/components.scss
@@ -149,12 +149,16 @@
       color: $ui-base-lighter-color;
     }
 
+    &.disabled {
+      color: $ui-primary-color;
+    }
+
     &.active {
       color: $ui-highlight-color;
-    }
 
-    &.disabled {
-      color: $ui-primary-color;
+      &.disabled {
+        color: lighten($ui-highlight-color, 13%);
+      }
     }
   }
 
@@ -215,16 +219,18 @@
 }
 
 .dropdown--active::after {
-  content: "";
-  display: block;
-  position: absolute;
-  width: 0;
-  height: 0;
-  border-style: solid;
-  border-width: 0 4.5px 7.8px;
-  border-color: transparent transparent $ui-secondary-color;
-  bottom: 8px;
-  right: 104px;
+  @media screen and (min-width: 1025px) {
+    content: "";
+    display: block;
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-style: solid;
+    border-width: 0 4.5px 7.8px;
+    border-color: transparent transparent $ui-secondary-color;
+    bottom: 8px;
+    right: 104px;
+  }
 }
 
 .invisible {
@@ -1837,6 +1843,8 @@
   cursor: pointer;
   flex: 0 0 auto;
   font-size: 16px;
+  border: 0;
+  text-align: start;
   padding: 15px;
   z-index: 3;
 
@@ -1999,12 +2007,6 @@
   &:hover {
     background: lighten($ui-base-color, 11%);
   }
-
-  &.hidden-on-mobile {
-    @include single-column('screen and (max-width: 1024px)') {
-      display: none;
-    }
-  }
 }
 
 .column-link__icon {
@@ -2388,12 +2390,6 @@ button.icon-button.active i.fa-retweet {
     }
   }
 
-  &.hidden-on-mobile {
-    @include single-column('screen and (max-width: 1024px)') {
-      display: none;
-    }
-  }
-
   &:focus,
   &:active {
     outline: 0;
@@ -2672,6 +2668,8 @@ button.icon-button.active i.fa-retweet {
   cursor: pointer;
   display: flex;
   flex-direction: column;
+  border: 0;
+  width: 100%;
   height: 100%;
   justify-content: center;
   position: relative;
@@ -2754,6 +2752,7 @@ button.icon-button.active i.fa-retweet {
   align-items: center;
   background: rgba($base-overlay-background, 0.5);
   box-sizing: border-box;
+  border: 0;
   color: $primary-text-color;
   cursor: pointer;
   display: flex;
@@ -3848,7 +3847,8 @@ button.icon-button.active i.fa-retweet {
 
 .boost-modal,
 .confirmation-modal,
-.report-modal {
+.report-modal,
+.actions-modal {
   background: lighten($ui-secondary-color, 8%);
   color: $ui-base-color;
   border-radius: 8px;
@@ -3873,6 +3873,15 @@ button.icon-button.active i.fa-retweet {
   }
 }
 
+.actions-modal {
+  .status {
+    background: $white;
+    border-bottom-color: $ui-secondary-color;
+    padding-top: 10px;
+    padding-bottom: 10px;
+  }
+}
+
 .boost-modal__container {
   overflow-x: scroll;
   padding: 10px;
@@ -3914,7 +3923,7 @@ button.icon-button.active i.fa-retweet {
 }
 
 .confirmation-modal {
-  max-width: 280px;
+  max-width: 85vw;
 
   @media screen and (min-width: 480px) {
     max-width: 380px;
@@ -3939,6 +3948,47 @@ button.icon-button.active i.fa-retweet {
   }
 }
 
+.actions-modal {
+  .status {
+    overflow-y: auto;
+    max-height: 300px;
+  }
+
+  max-height: 80vh;
+  max-width: 80vw;
+
+  ul {
+    overflow-y: auto;
+    flex-shrink: 0;
+
+    li:empty {
+      margin: 0;
+    }
+
+    li:not(:empty) {
+      a {
+        color: $ui-base-color;
+        display: flex;
+        padding: 10px;
+        align-items: center;
+        text-decoration: none;
+
+        &.active {
+          &,
+          button {
+            background: $ui-highlight-color;
+            color: $primary-text-color;
+          }
+        }
+
+        button:first-child {
+          margin-right: 10px;
+        }
+      }
+    }
+  }
+}
+
 .confirmation-modal__action-bar {
   .confirmation-modal__cancel-button {
     background-color: transparent;
diff --git a/app/lib/emoji.rb b/app/lib/emoji.rb
index e444b6893..45b7f53de 100644
--- a/app/lib/emoji.rb
+++ b/app/lib/emoji.rb
@@ -6,7 +6,7 @@ class Emoji
   include Singleton
 
   def initialize
-    data = Oj.load(File.open(File.join(Rails.root, 'lib', 'assets', 'emoji.json')))
+    data = Oj.load(File.open(Rails.root.join('lib', 'assets', 'emoji.json')))
 
     @map = {}
 
@@ -32,7 +32,7 @@ class Emoji
 
   def codepoint_to_unicode(codepoint)
     if codepoint.include?('-')
-      codepoint.split('-').map(&:hex).pack('U')
+      codepoint.split('-').map(&:hex).pack('U*')
     else
       [codepoint.hex].pack('U')
     end
diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb
index 34d84a34f..b2489711d 100644
--- a/app/lib/exceptions.rb
+++ b/app/lib/exceptions.rb
@@ -8,11 +8,11 @@ module Mastodon
 
   class UnexpectedResponseError < Error
     def initialize(response = nil)
-      @response = response
-    end
-
-    def to_s
-      "#{@response.uri} returned code #{@response.code}"
+      if response.respond_to? :uri
+        super("#{response.uri} returned code #{response.code}")
+      else
+        super
+      end
     end
   end
 end
diff --git a/app/lib/language_detector.rb b/app/lib/language_detector.rb
index 6d6ae2fb3..cc7509fdc 100644
--- a/app/lib/language_detector.rb
+++ b/app/lib/language_detector.rb
@@ -33,9 +33,7 @@ class LanguageDetector
 
   def simplified_text
     text.dup.tap do |new_text|
-      URI.extract(new_text).each do |url|
-        new_text.gsub!(url, '')
-      end
+      new_text.gsub!(FetchLinkCardService::URL_PATTERN, '')
       new_text.gsub!(Account::MENTION_RE, '')
       new_text.gsub!(Tag::HASHTAG_RE, '')
       new_text.gsub!(/\s+/, ' ')
diff --git a/app/models/account.rb b/app/models/account.rb
index 46cc84746..e217733f5 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -44,7 +44,7 @@
 #
 
 class Account < ApplicationRecord
-  MENTION_RE = /(?:^|[^\/[:word:]])@([a-z0-9_]+(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
+  MENTION_RE = /(?:^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
 
   include AccountAvatar
   include AccountFinderConcern
diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb
index 86df9b591..e76f61278 100644
--- a/app/models/web/push_subscription.rb
+++ b/app/models/web/push_subscription.rb
@@ -53,6 +53,7 @@ class Web::PushSubscription < ApplicationRecord
           url: url,
           actions: actions,
           access_token: access_token,
+          message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker
         }
       ),
       endpoint: endpoint,
@@ -117,7 +118,7 @@ class Web::PushSubscription < ApplicationRecord
       when :mention then [
         {
           title: translate('push_notifications.mention.action_favourite'),
-          icon: full_asset_url('emoji/2764.png', skip_pipeline: true),
+          icon: full_asset_url('web-push-icon_favourite.png', skip_pipeline: true),
           todo: 'request',
           method: 'POST',
           action: "/api/v1/statuses/#{notification.target_status.id}/favourite",
@@ -130,11 +131,11 @@ class Web::PushSubscription < ApplicationRecord
     can_boost = notification.type.equal?(:mention) && !notification.target_status.nil? && !notification.target_status.hidden?
 
     if should_hide
-      actions.insert(0, title: translate('push_notifications.mention.action_expand'), icon: full_asset_url('emoji/1f441.png'), todo: 'expand', action: 'expand')
+      actions.insert(0, title: translate('push_notifications.mention.action_expand'), icon: full_asset_url('web-push-icon_expand.png', skip_pipeline: true), todo: 'expand', action: 'expand')
     end
 
     if can_boost
-      actions << { title: translate('push_notifications.mention.action_boost'), icon: full_asset_url('emoji/1f504.png'), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/reblog" }
+      actions << { title: translate('push_notifications.mention.action_boost'), icon: full_asset_url('web-push-icon_reblog.png', skip_pipeline: true), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/reblog" }
     end
 
     actions
@@ -160,6 +161,7 @@ class Web::PushSubscription < ApplicationRecord
           content: translate('push_notifications.subscribed.body'),
           actions: [],
           url: web_url('notifications'),
+          message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker
         }
       ),
       endpoint: endpoint,
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index c266494f0..b0c663d02 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -18,7 +18,7 @@ class AccountSearchService < BaseService
     return [] if query_blank_or_hashtag? || limit < 1
 
     if resolving_non_matching_remote_account?
-      [ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")]
+      [ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")].compact
     else
       search_results_and_exact_match.compact.uniq.slice(0, limit)
     end
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index b462154ae..ab810c628 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -90,7 +90,7 @@ class BatchedRemoveStatusService < BaseService
     key = FeedManager.instance.key(:home, follower_id)
 
     originals = statuses.reject(&:reblog?)
-    reblogs   = statuses.reject { |s| !s.reblog? }
+    reblogs   = statuses.select(&:reblog?)
 
     # Quickly remove all originals
     redis.pipelined do
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index cd791e2f3..2ce5d1ee9 100644
--- a/app/validators/status_length_validator.rb
+++ b/app/validators/status_length_validator.rb
@@ -5,6 +5,27 @@ class StatusLengthValidator < ActiveModel::Validator
 
   def validate(status)
     return unless status.local? && !status.reblog?
-    status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if [status.text, status.spoiler_text].join.mb_chars.grapheme_length > MAX_CHARS
+    status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?(status)
+  end
+
+  private
+
+  def too_long?(status)
+    countable_length(status) > MAX_CHARS
+  end
+
+  def countable_length(status)
+    total_text(status).mb_chars.grapheme_length
+  end
+
+  def total_text(status)
+    [status.spoiler_text, countable_text(status)].join
+  end
+
+  def countable_text(status)
+    status.text.dup.tap do |new_text|
+      new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23)
+      new_text.gsub!(Account::MENTION_RE, '@\2')
+    end
   end
 end
diff --git a/app/views/about/_contact.html.haml b/app/views/about/_contact.html.haml
index 822639962..cf21ad5a3 100644
--- a/app/views/about/_contact.html.haml
+++ b/app/views/about/_contact.html.haml
@@ -2,7 +2,10 @@
   .panel-header
     = succeed ':' do
       = t 'about.contact'
-    %span{ title: contact.site_contact_email.presence }= contact.site_contact_email.presence
+    - if contact.site_contact_email.present?
+      = mail_to contact.site_contact_email, nil, title: contact.site_contact_email
+    - else
+      %span= t 'about.contact_unavailable'
   .panel-body
     - if contact.contact_account
       .owner
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index eeeb0088f..f1c6e6b9d 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -14,15 +14,13 @@
     required: true,
     input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
   = f.input :password,
-    autocomplete: 'off',
     placeholder: t('simple_form.labels.defaults.password'),
     required: true,
-    input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
+    input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
   = f.input :password_confirmation,
-    autocomplete: 'off',
     placeholder: t('simple_form.labels.defaults.confirm_password'),
     required: true,
-    input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
+    input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('auth.register'), type: :submit, class: 'button button-alternative'
diff --git a/app/views/admin_mailer/new_report.text.erb b/app/views/admin_mailer/new_report.text.erb
index 6fa744bc3..671ae5ca7 100644
--- a/app/views/admin_mailer/new_report.text.erb
+++ b/app/views/admin_mailer/new_report.text.erb
@@ -1,4 +1,4 @@
-<%= display_name(@me) %>,
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
 <%= raw t('admin_mailer.new_report.body', target: @report.target_account.acct, reporter: @report.account.acct) %>
 
diff --git a/app/views/auth/passwords/edit.html.haml b/app/views/auth/passwords/edit.html.haml
index 5e2b4fbd6..5ef3de976 100644
--- a/app/views/auth/passwords/edit.html.haml
+++ b/app/views/auth/passwords/edit.html.haml
@@ -5,8 +5,8 @@
   = render 'shared/error_messages', object: resource
   = f.input :reset_password_token, as: :hidden
 
-  = f.input :password, autofocus: true, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password') }
-  = f.input :password_confirmation, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password') }
+  = f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
+  = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('auth.set_new_password'), type: :submit
diff --git a/app/views/auth/registrations/_sessions.html.haml b/app/views/auth/registrations/_sessions.html.haml
index 84207862a..7ac578bb1 100644
--- a/app/views/auth/registrations/_sessions.html.haml
+++ b/app/views/auth/registrations/_sessions.html.haml
@@ -19,10 +19,10 @@
         %td
           %samp= session.ip
         %td
-          - if request.session['auth_id'] == session.session_id
+          - if current_session.session_id == session.session_id
             = t 'sessions.current_session'
           - else
             %time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at)
         %td
-          - if request.session['auth_id'] != session.session_id
+          - if current_session.session_id != session.session_id
             = table_link_to 'times', t('sessions.revoke'), settings_session_path(session), method: :delete
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index fbc8d017b..f016a4883 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -5,9 +5,9 @@
   = render 'shared/error_messages', object: resource
 
   = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
-  = f.input :password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password') }
-  = f.input :password_confirmation, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password') }
-  = f.input :current_password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password') }
+  = f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
+  = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
+  = f.input :current_password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index af7ee2b28..d0529a20c 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -11,8 +11,8 @@
         = "@#{site_hostname}"
 
   = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
-  = f.input :password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
-  = f.input :password_confirmation, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
+  = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
+  = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('auth.register'), type: :submit
diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml
index f613100c1..e589377bf 100644
--- a/app/views/auth/sessions/new.html.haml
+++ b/app/views/auth/sessions/new.html.haml
@@ -3,7 +3,7 @@
 
 = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
   = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
-  = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
+  = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml
index 0321e1ec7..cb5e32f3e 100644
--- a/app/views/auth/sessions/two_factor.html.haml
+++ b/app/views/auth/sessions/two_factor.html.haml
@@ -3,7 +3,7 @@
 
 = simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
   = f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'),
-      input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt') }, required: true, autofocus: true, autocomplete: 'off',
+      input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, required: true, autofocus: true,
       hint: t('simple_form.hints.sessions.otp')
 
   .actions
diff --git a/app/views/notification_mailer/digest.text.erb b/app/views/notification_mailer/digest.text.erb
index b63352978..e0d1f9b8b 100644
--- a/app/views/notification_mailer/digest.text.erb
+++ b/app/views/notification_mailer/digest.text.erb
@@ -1,4 +1,4 @@
-<%= display_name(@me) %>,
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
 <%= raw t('notification_mailer.digest.body', since: l(@since), instance: root_url) %>
 <% @notifications.each do |notification| %>
diff --git a/app/views/notification_mailer/favourite.text.erb b/app/views/notification_mailer/favourite.text.erb
index 795045307..2581b4909 100644
--- a/app/views/notification_mailer/favourite.text.erb
+++ b/app/views/notification_mailer/favourite.text.erb
@@ -1,4 +1,4 @@
-<%= display_name(@me) %>,
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
 <%= raw t('notification_mailer.favourite.body', name: @account.acct) %>
 
diff --git a/app/views/notification_mailer/follow.text.erb b/app/views/notification_mailer/follow.text.erb
index af41a3080..cbe46f552 100644
--- a/app/views/notification_mailer/follow.text.erb
+++ b/app/views/notification_mailer/follow.text.erb
@@ -1,4 +1,4 @@
-<%= display_name(@me) %>,
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
 <%= raw t('notification_mailer.follow.body', name: @account.acct) %>
 
diff --git a/app/views/notification_mailer/follow_request.text.erb b/app/views/notification_mailer/follow_request.text.erb
index 49087a575..a018394b8 100644
--- a/app/views/notification_mailer/follow_request.text.erb
+++ b/app/views/notification_mailer/follow_request.text.erb
@@ -1,4 +1,4 @@
-<%= display_name(@me) %>,
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
 <%= raw t('notification_mailer.follow_request.body', name: @account.acct) %>
 
diff --git a/app/views/notification_mailer/mention.text.erb b/app/views/notification_mailer/mention.text.erb
index b38c5a4d0..03f53813b 100644
--- a/app/views/notification_mailer/mention.text.erb
+++ b/app/views/notification_mailer/mention.text.erb
@@ -1,4 +1,4 @@
-<%= display_name(@me) %>,
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
 <%= raw t('notification_mailer.mention.body', name: @status.account.acct) %>
 
diff --git a/app/views/notification_mailer/reblog.text.erb b/app/views/notification_mailer/reblog.text.erb
index fd85437a7..8fc841bf6 100644
--- a/app/views/notification_mailer/reblog.text.erb
+++ b/app/views/notification_mailer/reblog.text.erb
@@ -1,4 +1,4 @@
-<%= display_name(@me) %>,
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
 <%= raw t('notification_mailer.reblog.body', name: @account.acct) %>
 
diff --git a/app/views/settings/deletes/show.html.haml b/app/views/settings/deletes/show.html.haml
index d49a7bd0c..b246f83a1 100644
--- a/app/views/settings/deletes/show.html.haml
+++ b/app/views/settings/deletes/show.html.haml
@@ -10,7 +10,7 @@
 
   %p.hint= t('deletes.description_html')
 
-  = f.input :password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password') }, hint: t('deletes.confirm_password')
+  = f.input :password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, hint: t('deletes.confirm_password')
 
   .actions
     = f.button :button, t('deletes.proceed'), type: :submit, class: 'negative'
diff --git a/app/views/settings/two_factor_authentication/confirmations/new.html.haml b/app/views/settings/two_factor_authentication/confirmations/new.html.haml
index b7eb0c23d..fd4a3e768 100644
--- a/app/views/settings/two_factor_authentication/confirmations/new.html.haml
+++ b/app/views/settings/two_factor_authentication/confirmations/new.html.haml
@@ -11,7 +11,7 @@
       %p.hint= t('two_factor_authentication.manual_instructions')
       %samp.qr-alternative__code= current_user.otp_secret.scan(/.{4}/).join(' ')
 
-  = f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt')
+  = f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('two_factor_authentication.enable'), type: :submit
diff --git a/app/views/settings/two_factor_authentications/show.html.haml b/app/views/settings/two_factor_authentications/show.html.haml
index 8ba42a101..67a64a046 100644
--- a/app/views/settings/two_factor_authentications/show.html.haml
+++ b/app/views/settings/two_factor_authentications/show.html.haml
@@ -10,7 +10,7 @@
   %hr/
 
   = simple_form_for @confirmation, url: settings_two_factor_authentication_path, method: :delete do |f|
-    = f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt')
+    = f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }
 
     .actions
       = f.button :button, t('two_factor_authentication.disable'), type: :submit
diff --git a/app/views/user_mailer/confirmation_instructions.fa.html.erb b/app/views/user_mailer/confirmation_instructions.fa.html.erb
index cccdaa2c5..3e77e043b 100644
--- a/app/views/user_mailer/confirmation_instructions.fa.html.erb
+++ b/app/views/user_mailer/confirmation_instructions.fa.html.erb
@@ -9,4 +9,4 @@
 
 <p dir="rtl">با احترام,<p>
 
-<p dir="rtl">گردانندگان سرور <%= @instance %></p>
+<p dir="rtl">گردانندگان سرور <%= @instance %></p>
\ No newline at end of file
diff --git a/app/views/user_mailer/confirmation_instructions.fa.text.erb b/app/views/user_mailer/confirmation_instructions.fa.text.erb
index 904bd5bfe..76727b3be 100644
--- a/app/views/user_mailer/confirmation_instructions.fa.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.fa.text.erb
@@ -9,4 +9,4 @@
 
 با احترام،
 
-گردانندگان سرور <%= @instance %>
+گردانندگان سرور <%= @instance %>
\ No newline at end of file
diff --git a/app/workers/pubsubhubbub/delivery_worker.rb b/app/workers/pubsubhubbub/delivery_worker.rb
index 035a59048..88645cf33 100644
--- a/app/workers/pubsubhubbub/delivery_worker.rb
+++ b/app/workers/pubsubhubbub/delivery_worker.rb
@@ -16,6 +16,8 @@ class Pubsubhubbub::DeliveryWorker
     @subscription = Subscription.find(subscription_id)
     @payload = payload
     process_delivery unless blocked_domain?
+  rescue => e
+    raise e.class, "Delivery failed for #{subscription&.callback_url}: #{e.message}"
   end
 
   private
diff --git a/app/workers/pubsubhubbub/distribution_worker.rb b/app/workers/pubsubhubbub/distribution_worker.rb
index ce467d18b..ea246128d 100644
--- a/app/workers/pubsubhubbub/distribution_worker.rb
+++ b/app/workers/pubsubhubbub/distribution_worker.rb
@@ -14,7 +14,7 @@ class Pubsubhubbub::DistributionWorker
     @subscriptions = active_subscriptions.to_a
 
     distribute_public!(stream_entries.reject(&:hidden?))
-    distribute_hidden!(stream_entries.reject { |s| !s.hidden? })
+    distribute_hidden!(stream_entries.select(&:hidden?))
   end
 
   private
@@ -35,7 +35,7 @@ class Pubsubhubbub::DistributionWorker
     @payload = OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, stream_entries))
     @domains = @account.followers.domains
 
-    Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions.reject { |s| !allowed_to_receive?(s.callback_url, s.domain) }) do |subscription|
+    Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions.select { |s| allowed_to_receive?(s.callback_url, s.domain) }) do |subscription|
       [subscription.id, @payload]
     end
   end
diff --git a/app/workers/pubsubhubbub/subscribe_worker.rb b/app/workers/pubsubhubbub/subscribe_worker.rb
index 6865e7136..7560c2671 100644
--- a/app/workers/pubsubhubbub/subscribe_worker.rb
+++ b/app/workers/pubsubhubbub/subscribe_worker.rb
@@ -3,7 +3,7 @@
 class Pubsubhubbub::SubscribeWorker
   include Sidekiq::Worker
 
-  sidekiq_options queue: 'push', retry: 10, unique: :until_executed
+  sidekiq_options queue: 'push', retry: 10, unique: :until_executed, dead: false
 
   sidekiq_retry_in do |count|
     case count
@@ -18,9 +18,17 @@ class Pubsubhubbub::SubscribeWorker
     end
   end
 
+  sidekiq_retries_exhausted do |msg, _e|
+    account = Account.find(msg['args'].first)
+    logger.error "PuSH subscription attempts for #{account.acct} exhausted. Unsubscribing"
+    ::UnsubscribeService.new.call(account)
+  end
+
   def perform(account_id)
     account = Account.find(account_id)
     logger.debug "PuSH re-subscribing to #{account.acct}"
     ::SubscribeService.new.call(account)
+  rescue => e
+    raise e.class, "Subscribe failed for #{account&.acct}: #{e.message}"
   end
 end
diff --git a/app/workers/web_push_notification_worker.rb b/app/workers/web_push_notification_worker.rb
index da4043ddb..eacea04c3 100644
--- a/app/workers/web_push_notification_worker.rb
+++ b/app/workers/web_push_notification_worker.rb
@@ -7,16 +7,19 @@ class WebPushNotificationWorker
 
   def perform(session_activation_id, notification_id)
     session_activation = SessionActivation.find(session_activation_id)
-    notification = Notification.find(notification_id)
+    notification       = Notification.find(notification_id)
 
-    begin
-      session_activation.web_push_subscription.push(notification)
-    rescue Webpush::InvalidSubscription, Webpush::ExpiredSubscription => e
-      # Subscription expiration is not currently implemented in any browser
-      session_activation.web_push_subscription.destroy!
-      session_activation.update!(web_push_subscription: nil)
+    return if session_activation.web_push_subscription.nil? || notification.activity.nil?
 
-      raise e
-    end
+    session_activation.web_push_subscription.push(notification)
+  rescue Webpush::InvalidSubscription, Webpush::ExpiredSubscription
+    # Subscription expiration is not currently implemented in any browser
+
+    session_activation.web_push_subscription.destroy!
+    session_activation.update!(web_push_subscription: nil)
+
+    true
+  rescue ActiveRecord::RecordNotFound
+    true
   end
 end
diff --git a/boxfile.yml b/boxfile.yml
index 330223110..59a66d87b 100644
--- a/boxfile.yml
+++ b/boxfile.yml
@@ -1,7 +1,7 @@
 run.config:
   engine: ruby
   engine.config:
-    runtime: ruby-2.4.1
+    runtime: ruby-2.4
 
   extra_packages:
     # basic servers:
@@ -20,6 +20,9 @@ run.config:
     # for node-gyp, used in the asset compilation process:
     - python-2
 
+    # i18n:
+    - libidn
+
   cache_dirs:
     - node_modules
 
@@ -35,10 +38,6 @@ run.config:
 
   extra_steps:
     - envsubst < .env.nanobox > .env
-    - gem install bundler
-    - bundle config build.nokogiri --with-iconv-dir=/data/ --with-zlib-dir=/data/
-    - bundle config build.nokogumbo --with-iconv-dir=/data/ --with-zlib-dir=/data/
-    - bundle install --clean
     - yarn
 
   fs_watch: true
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index ce4831ac2..725b120ec 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -161,7 +161,7 @@ ca:
       confirmed: Confirmat
       expires_in: Expira en
       last_delivery: Últim lliurament
-      title: PubSubHubbub
+      title: WebSub
       topic: Tòpic
     title: Administració
   application_mailer:
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 04d3fd0b8..87c5fa67a 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -144,7 +144,7 @@ de:
       confirmed: Bestätigt
       expires_in: Verfällt in
       last_delivery: Letzte Zustellung
-      title: PubSubHubbub
+      title: WebSub
       topic: Thema
     title: Administration
   application_mailer:
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8fa1ac0e3..90b4fe82b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -207,7 +207,7 @@ en:
       confirmed: Confirmed
       expires_in: Expires in
       last_delivery: Last delivery
-      title: PubSubHubbub
+      title: WebSub
       topic: Topic
     title: Administration
   admin_mailer:
@@ -215,6 +215,7 @@ en:
       body: "%{reporter} has reported %{target}"
       subject: New report for %{instance} (#%{id})
   application_mailer:
+    salutation: '%{name},'
     settings: 'Change e-mail preferences: %{link}'
     signature: Mastodon notifications from %{instance}
     view: 'View:'
@@ -348,6 +349,8 @@ en:
       title: "%{name} favourited your status"
     follow:
       title: "%{name} is now following you"
+    group:
+      title: "%{count} notifications"
     mention:
       action_boost: Boost
       action_expand: Show more
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index c42016eb3..eb66a9c41 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -1,19 +1,38 @@
 ---
 fa:
   about:
-    about_mastodon_html: ماستدون (Mastodon) یک شبکهٔ اجتماعی <em>آزاد و کدباز</em> است. یک جایگزین <em>غیرمتمرکز</em> برای شبکه‌های تجاری، که نمی‌گذارد ارتباط‌های شما را یک شرکت در انحصار خود بگیرد. یک سرور مورد اعتماد را انتخاب کنید &mdash; هر سروری که باشد، همچنان می‌توانید با سرورهای دیگر ارتباط داشته باشید. هر کسی می‌تواند سرور ماستدون خود را راه بیندازد و در <em>شبکهٔ اجتماعی</em> سهیم شود.
-    about_this: دربارهٔ این سرور
-    closed_registrations: امکان ثبت نام روی این سرور هم‌اینک فعال نیست.
+    about_mastodon_html: ماستدون (Mastodon) یک شبکهٔ اجتماعی است که بر اساس پروتکل‌های آزاد وب و نرم‌افزارهای آزاد و کدباز ساخته شده است. این شبکه مانند ایمیل غیرمتمرکز است.
+    about_this: درباره.
+    closed_registrations: ثبت‌نام روی این سرور هم‌اینک فعال نیست. اما شما می‌توانید سرور دیگری بیابید و با حسابی که آن‌جا می‌سازید دقیقاً به همین شبکه دسترسی داشته باشید.
     contact: تماس
+    contact_missing: تعیین نشده
+    contact_unavailable: موجود نیست
     description_headline: "%{domain} چیست؟"
     domain_count_after: سرور دیگر
     domain_count_before: متصل به
-    other_instances: سرورهای دیگر
+    extended_description_html: |
+      <h3>جای خوبی برای قانون‌ها</h3>
+      <p>توضیحات تکمیلی نوشته نشده است.</p>
+    features:
+      humane_approach_body: با آموختن از کاستی‌های شبکه‌های دیگر، ماستدون می‌خواهد به کمک انتخاب‌های اخلاقی‌تر در طراحی خودش با آسیب‌های شبکه‌های اجتماعی مبارزه کند.
+      humane_approach_title: رویکردی انسانی‌تر
+      not_a_product_body: ماستدون یک شبکهٔ تجاری نیست. بدون تبلیغات، بدون داده‌کاوی، بدون حصارکشی. هیچ قدرت مرکزی‌ای وجود ندارد.
+      not_a_product_title: شما یک انسان هستید، نه یک محصول
+      real_conversation_body: با ۵۰۰ نویسه برای هر نوشته و با پشتیبانی از هشدارهای موردی برای نوشته‌ها و تصاویر، می‌توانید خود را همان گونه که می‌خواهید ابراز کنید.
+      real_conversation_title: برای گفتگوهای واقعی
+      within_reach_body: اپ‌های متنوع برای iOS، اندروید، و سیستم‌های دیگر به خاطر وحود یک اکوسیستم API دوستانه برای برنامه‌نویسان. از همه جا با دوستان خود ارتباط داشته باشید.
+      within_reach_title: همیشه در دسترس
+    find_another_instance: یافتن سرورهای دیگر
+    generic_description: "%{domain} یک سرور روی شبکه است"
+    hosted_on: ماستدون میزبانی‌شده روی %{domain}
+    learn_more: بیشتر بدانید
+    other_instances: فهرست سرورها
     source_code: کدهای منبع
     status_count_after: چیز نوشته‌اند
     status_count_before: که جمعاً
     user_count_after: کاربر
     user_count_before: دارای
+    what_is_mastodon: ماستدون چیست؟
   accounts:
     follow: پی بگیرید
     followers: پیگیران
@@ -23,12 +42,14 @@ fa:
     people_who_follow: کسانی که %{name} را پی می‌گیرند
     posts: نوشته
     remote_follow: پیگیری غیرمستقیم
+    reserved_username: این نام کاربری در دسترس نیست
     unfollow: پایان پیگیری
   admin:
     accounts:
       are_you_sure: آیا مطمئن هستید؟
       confirm: تأیید
       confirmed: تأیید شد
+      disable_two_factor_authentication: غیرفعال‌سازی ورود دومرحله‌ای
       display_name: نمایش به نام
       domain: دامین
       edit: ویرایش
@@ -36,6 +57,7 @@ fa:
       feed_url: نشانی فید
       followers: پیگیران
       follows: پی می‌گیرد
+      ip: IP
       location:
         all: همه
         local: محلی
@@ -58,17 +80,23 @@ fa:
       profile_url: نشانی نمایه
       public: عمومی
       push_subscription_expires: عضویت از راه PuSH منقضی شد
+      redownload: به‌روزرسانی تصویر نمایه
+      reset: بازنشانی
       reset_password: بازنشانی رمز
+      resubscribe: اشتراک دوباره
       salmon_url: نشانی Salmon
+      search: جستجو
       show:
         created_reports: گزارش‌ها از طرف این حساب
         report: گزارش
         targeted_reports: گزارش‌ها دربارهٔ این حساب
       silence: بی‌صدا
       statuses: نوشته‌ها
+      subscribe: اشتراک
       title: حساب‌ها
       undo_silenced: واگردانی بی‌صداکردن
       undo_suspension: واگردانی تعلیق
+      unsubscribe: لغو اشتراک
       username: نام کاربری
       web: وب
     domain_blocks:
@@ -81,12 +109,14 @@ fa:
         hint: مسدودسازی دامین جلوی فهرست‌شدن حساب‌ها در پایگاه داده را نمی‌گیرد، بلکه به طور خودکار روش‌های مدیریتی را روی فعالیت‌های فعلی و گذشتهٔ آن حساب‌ها اعمال می‌کند.
         severity:
           desc_html: "<strong>بی‌صداکردن</strong> یک حساب نوشته‌های آن را برای همه (به جز پیگیرانش) ناپدید می‌کند. <strong>معلق‌کردن</strong> حساب همهٔ نوشته‌ها، تصویرها، و اطلاعات حساب را پاک می‌کند."
+          noop: هیچ
           silence: بی‌صداکردن
           suspend: معلق‌کردن
         title: مسدودسازی دامین دیگر
       reject_media: نپذیرفتن پرونده‌های تصویری
       reject_media_hint: تصویرهای ذخیره‌شده در این‌جا را پاک می‌کند و جلوی دریافت تصویرها را در آینده می‌گیرد. بی‌تأثیر برای معلق‌شده‌ها
       severities:
+        noop: هیچ
         silence: بی‌صداکردن
         suspend: معلق‌کردن
       severity: شدت
@@ -106,12 +136,17 @@ fa:
       domain_name: دامین
       title: سرورهای شناخته‌شده
     reports:
+      action_taken_by: انجام‌دهنده
+      are_you_sure: آیا مطمئن هستید؟
       comment:
         label: توضیح
         none: خالی
       delete: پاک‌کردن
       id: شناسه
       mark_as_resolved: علامت‌گذاری به عنوان حل‌شده
+      nsfw:
+        'false': نمایش پیوست‌های تصویری
+        'true': نهفتن پیوست‌های تصویری
       report: 'گزارش #%{id}'
       report_contents: محتوا
       reported_account: حساب گزارش‌شده
@@ -126,38 +161,71 @@ fa:
       view: نمایش
     settings:
       contact_information:
-        email: یک نشانی ایمیل عمومی وارد کنید
-        username: یک نام کاربری وارد کنید
+        email: ایمیل کاری
+        username: نام کاربری
       registrations:
         closed_message:
           desc_html: وقتی امکان ثبت نام روی سرور فعال نباشد در صفحهٔ اصلی نمایش می‌یابد<br>می‌توانید HTML بنویسید
           title: پیغام برای فعال‌نبودن ثبت نام
+        deletion:
+          desc_html: هر کسی بتواند حساب خود را پاک کند
+          title: فعال‌سازی پاک‌کردن حساب
         open:
+          desc_html: همه بتوانند حساب باز کنند
           title: امکان ثبت نام
       site_description:
         desc_html: روی صفحهٔ اصلی نمایش می‌یابد و همچنین به عنوان تگ‌های HTML.<br>می‌توانید HTML بنویسید, به‌ویژه <code>&lt;a&gt;</code> و <code>&lt;em&gt;</code>.
         title: دربارهٔ سایت
       site_description_extended:
-        desc_html: در صفحهٔ اطلاعات تکمیلی نشان داده می‌شود<br>می‌توانید HTML بنویسید
-        title: اطلاعات بیشتر دربارهٔ سایت
-      site_title: نام سایت
+        desc_html: جای خوبی برای نوشتن سیاست‌های کاربری، قانون‌ها، راهنماها، و هر چیزی که ویژهٔ این سرور است. تگ‌های HTML هم مجاز است
+        title: اطلاعات تکمیلی سفارشی
+      site_terms:
+        desc_html: می‌توانید سیاست رازداری، شرایط استفاده، یا سایر مسائل قانونی را به دلخواه خود بنویسید. تگ‌های HTML هم مجاز است
+        title: شرایط استفادهٔ سفارشی
+      site_title: نام سرور
+      timeline_preview:
+        desc_html: نوشته‌های عمومی این سرور را در صفحهٔ آغازین نشان دهید
+        title: پیش‌نمایش نوشته‌ها
       title: تنظیمات سایت
+    statuses:
+      back_to_account: بازگشت به صفحهٔ حساب
+      batch:
+        delete: پاک‌کردن
+        nsfw_off: NSFW خاموش
+        nsfw_on: NSFW روشن
+      execute: اجرا
+      failed_to_execute: اجرا نشد
+      media:
+        hide: نهفتن رسانه
+        show: نمایش رسانه
+        title: رسانه
+      no_media: بدون رسانه
+      title: نوشته‌های حساب
+      with_media: دارای رسانه
     subscriptions:
       callback_url: نشانی Callback
       confirmed: تأییدشده
       expires_in: مهلت انقضا
       last_delivery: آخرین ارسال
-      title: PubSubHubbub
+      title: WebSub
       topic: موضوع
     title: مدیریت
+  admin_mailer:
+    new_report:
+      body: "کاربر %{reporter} کاربر %{target} را گزارش داد"
+      subject: گزارش تازه‌ای برای %{instance} (#%{id})
   application_mailer:
+    salutation: '%{name},'
     settings: 'تغییر تنظیمات ایمیل: %{link}'
     signature: اعلان‌های ماستدون از %{instance}
     view: 'نمایش:'
   applications:
     invalid_url: نشانی واردشده معتبر نیست
   auth:
-    change_password: اطلاعات حساب
+    agreement_html: پیش از عضو شدن باید <a href="%{rules_path}">شرایط استفاده</a> و <a href="%{terms_path}">سیاست رازداری</a> ما را بپذیرید.
+    change_password: امنیت
+    delete_account: پاک‌کردن حساب
+    delete_account_html: اگر می‌خواهید حساب خود را پاک کنید، از <a href="%{path}">این‌جا</a> پیش بروید. از شما درخواست تأیید خواهد شد.
     didnt_get_confirmation: راهنمایی برای تأیید را دریافت نکردید؟
     forgot_password: رمزتان را گم کرده‌اید؟
     login: ورود
@@ -169,6 +237,12 @@ fa:
   authorize_follow:
     error: متأسفانه حین یافتن آن حساب خطایی رخ داد
     follow: پی بگیرید
+    follow_request: 'شما درخواست پیگیری فرستاده‌اید به:'
+    following: 'انجام شد! شما هم‌اینک پیگیر این کاربر هستید:'
+    post_follow:
+      close: یا این پنجره را ببندید.
+      return: به نمایهٔ این کاربر بازگردید
+      web: رفتن به وب
     prompt_html: 'شما (<strong>%{self}</strong>) می‌خواهید این حساب را پی بگیرید:'
     title: پیگیری %{acct}
   datetime:
@@ -185,6 +259,14 @@ fa:
       x_minutes: "%{count} دقیقه"
       x_months: "%{count} ماه"
       x_seconds: "%{count} ثانیه"
+  deletes:
+    bad_password_msg: هکر گرامی، رمزی که وارد کردید اشتباه است ؛)
+    confirm_password: رمز فعلی خود را وارد کنید تا معلوم شود که خود شمایید
+    description_html: این کار همهٔ محتوای حساب شما را <strong>برای همیشه و به‌طور بازگشت‌ناپذیری</strong> پاک کرده و حساب را غیرفعال می‌کند. نام کاربری شما برای جلوگیری از جعل هویت احتمالی در آینده از دسترس خارج خواهد شد.
+    proceed: پاک‌کردن حساب
+    success_msg: حساب شما با موفقیت پاک شد
+    warning_html: تنها پاک‌شدن محتوای حساب در این سرور خاص تضمین می‌شود. محتوایی که به گستردگی هم‌رسانی شده باشد ممکن است ردش همچنان باقی بماند. سرورهای آفلاین یا سرورهایی که دیگر مشترک شما نیستند پایگاه‌های دادهٔ خود را به‌روز نخواهند کرد.
+    warning_title: دسترس‌پذیری محتوای هم‌رسان‌شده
   errors:
     '403': شما اجازهٔ دیدن این صفحه را ندارید.
     '404': صفحه‌ای که به دنبالش بودید وجود ندارد.
@@ -193,6 +275,7 @@ fa:
       content: تأیید امنیتی انجام نشد. آیا مرورگر شما کوکی‌ها را مسدود می‌کند؟
       title: تأیید امنیتی شکست خورد
     '429': درخواست‌های بیش از حد
+    noscript: برای استفاده از نسخهٔ تحت وب ماستدون، لطفاً جاوااسکریپت را فعال کنید. یا به جایش می‌توانید یک اپ ماستدون را به‌کار ببرید.
   exports:
     blocks: حساب‌های مسدودشده
     csv: CSV
@@ -261,14 +344,71 @@ fa:
     next: بعدی
     prev: قبلی
     truncate: "&hellip;"
+  push_notifications:
+    favourite:
+      title: "%{name} نوشتهٔ شما را پسندید"
+    follow:
+      title: "%{name} هم‌اینک پیگیر شماست"
+    group:
+      title: "%{count} اعلان"
+    mention:
+      action_boost: بازبوق
+      action_expand: نمایش بیشتر
+      action_favourite: پسندیدن
+      title: "%{name} از شما نام برد"
+    reblog:
+      title: "%{name} نوشتهٔ شما را بازبوقید"
+    subscribed:
+      body: از این به بعد سرور می‌تواندبه شما اعلان‌های تازه بفرستد .
+      title: عضویت ثبت شد!
   remote_follow:
     acct: نشانی حساب username@domain خود را این‌جا بنویسید
     missing_resource: نشانی اینترنتی برای رسیدن به حساب شما پیدا نشد
     proceed: درخواست پیگیری
     prompt: 'شما قرار است این حساب را پیگیری کنید:'
+  sessions:
+    activity: آخرین کنش
+    browser: مرورگر
+    browsers:
+      alipay: Alipay
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      firefox: Firefox
+      generic: مرورگر ناشناخته
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Opera
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UCBrowser
+      weibo: Weibo
+    current_session: نشست فعلی
+    description: "%{browser} روی %{platform}"
+    explanation: مرورگرهای زیر هم‌اینک به حساب شما وارد شده‌اند.
+    ip: IP
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
+      other: سیستم ناشناخته
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: لغو کردن
+    revoke_success: نشست با موفقیت لغو شد
+    title: نشست‌ها
   settings:
     authorized_apps: برنامه‌های مجاز
     back: بازگشت به ماستدون
+    delete: پاک‌کردن حساب
     edit_profile: ویرایش نمایه
     export: برون‌سپاری داده‌ها
     followers: پیگیران مورد تأیید
@@ -291,6 +431,76 @@ fa:
     click_to_show: برای نمایش کلیک کنید
     reblogged: بازبوقید
     sensitive_content: محتوای حساس
+  terms:
+    body_html: |
+      <h2>Privacy Policy</h2>
+
+      <h3 id="collect">ما چه اطلاعاتی را گردآوری می‌کنیم؟</h3>
+
+      <p>این سایت برخی از اطلاعات مربوط به شما را ثبت می‌کند. این موارد شامل اطلاعات ثبت‌نامی شماست، و نیز شامل نوشته‌هایی است که این‌جا می‌خوانید، می‌نویسید، یا واکنش‌هایی که  به نوشته‌های دیگران نشان می‌دهید.</p>
+
+      <p>وقتی که در این سایت ثبت‌نام می‌کنید، ممکن است از شما بخواهیم که نام و نشانی ایمیل خود را وارد کنید. البته بدون ثبت‌نام نیز می‌توان از این سایت بازدید کرد. برای تأیید ایمیل شما، ما یک نشانی اینترنتی یکتا را به آن می‌فرستیم. اگر آن نشانی را کسی باز کند، ما می‌فهمیم که آن شما بوده‌اید و بنابراین نشانی ایمیل متعلق به شماست.</p>
+
+      <p>وقتی که عضو باشید و چیزی بنویسید، ما نشانی اینترنتی‌ای (IP) را که نوشته از آن آمده است ثبت می‌کنیم. سیاههٔ کاری (log) سرور شامل نشانی IP همهٔ درخواست‌ها به سرور است که ما شاید آن را هم ثبت کنیم.</p>
+
+      <h3 id="use">ما با اطلاعات شما چه کار می‌کنیم؟</h3>
+
+      <p>اطلاعاتی را که ما از شما ثبت می‌کنیم، ممکن است در موارد زیر به کار بروند:</p>
+
+      <ul>
+        <li>برای شخصی‌سازی تجربهٔ کاربری شما &mdash; ما به کمک اطلاعات شما بهتر می‌توانیم نیازهای شما را برآورده کنیم.</li>
+        <li>برای بهتر کردن سایت &mdash; ما پیوسته می‌کوشیم تا خدمات این سایت را به کمک اطلاعات و بازخوردی که از شما می‌گیریم بهتر کنیم.</li>
+        <li>برای بهتر کردن خدمات به کاربران &mdash; ما به کمک اطلاعات شما به طور مؤثرتری می‌توانیم به درخواست‌های پشتیبانی شما پاسخ دهیم.</li>
+        <li>برای فرستادن ایمیل‌های دوره‌ای &mdash; ما گاهی به نشانی ایمیلی که وارد کرده‌اید نامه می‌فرستیم تا درخواست‌های شما پاسخ دهیم یا شما را در جریان پاسخ دیگران به شما قرار دهیم.</li>
+      </ul>
+
+      <h3 id="protect">ما چگونه از اطلاعات شما محافظت می‌کنیم؟</h3>
+
+      <p>ما روش‌های امنیتی گوناگونی را پیاده کرده‌ایم تا امنیت اطلاعات شخصی شما هنگام ثبت، فرستاده‌شدن، و بازیابی آن‌ها حفظ شود.</p>
+
+      <h3 id="data-retention">سیاست ما برای نگهداری اطلاعات شما چیست؟</h3>
+
+      <p>ما با حسن نیت تلاش می‌کنیم تا:</p>
+
+      <ul>
+        <li>سیاههٔ کاری سرور که شامل نشانی IP همهٔ درخواست‌ها به این سرور است را بیشتر از ۹۰ روز ذخیره نکنیم.</li>
+        <li>نشانی IP مربوط به کاربران ثبت‌نام‌شده را بیشتر از ۵ سال نگه نداریم.</li>
+      </ul>
+
+      <h3 id="cookies">آیا ما کوکی‌ها را به‌کار می‌بریم؟</h3>
+
+      <p>بله. کوکی‌ها پرونده‌های کوچکی هستند که یک سایت یا خدمات‌دهنده‌اش (اگر شما اجازه بدهید) از راه مرورگر در کامپیوتر شما ذخیره می‌کنند. به کمک این کوکی‌ها سایت می‌تواند مرورگر شما را بشناسد و اگر شما ثبت‌نام کرده باشید، حساب شما را به مرورگرتان مرتبط کند.</p>
+
+      <p>ما به کمک کوکی‌ها ترجیحات شما را برای بازدیدهای آینده می‌فهمیم و ذخیره می‌کنیم و داده‌های جامعی دربارهٔ بازدیدها از سایت و برهمکنش‌ها با آن را تهیه می‌کنیم. به این ترتیب می‌توانیم در آینده تجربهٔ کاربری سایت و ابزارهای مربوط به آن را بهتر کنیم. برای داشتن درک بهتری از بازدیدکنندگان این سایت، ما گاهی از خدمات‌دهنده‌های دیگر نیز کمک می‌گیریم. این خدمات‌دهنده‌ها اجازه ندارند تا از اطلاعاتی که به جای ما جمع می‌کنند برای کاری به جز بهترکردن کار ما استفاده کنند.</p>
+
+      <h3 id="disclose">آیا ما اطلاعاتی به نهادهای دیگر فاش می‌کنیم؟</h3>
+
+      <p>ما اطلاعاتی را که بتواند شما را شناسایی کند به نهادهای دیگر نمی‌فروشیم، معامله نمی‌کنیم، یا به هر روش دیگری منتقل نمی‌کنیم. این شامل نهادهای مورد اعتمادی نمی‌شود که به ما در گرداندن این سایت یا انجام کارهایمان کمک می‌کنند، یا به شما خدمات می‌رسانند، تا جایی که آن‌ها این داده‌ها را محرمانه نگه دارند. ما همچنین ممکن است اطلاعات شما را به حکم قانون یا برای اِعمال سیاست‌های سایت، یا به خاطر حفظ حقوق، دارایی‌ها، یا امنیت خودمان یا دیگران منتشر کنیم. ما ممکن است اطلاعات بازدیدکنندگان سایت را که با آن نمی‌توان شما را شناسایی کرد برای بازاریابی، تبلیغات، یا هدف‌های دیگر به نهادهای دیگر ارائه دهیم.</p>
+
+      <h3 id="third-party">پیوند (لینک) به صفحه‌های دیگران</h3>
+
+      <p>ما گاهی ممکن است به صلاحدید خودمان محصولات یا خدمات دیگران را در این سایت بگنجانیم یا پیشنهاد دهیم. سایت‌های مرتبط با این محصولات و خدمات دارای سیاست‌های رازداری جداگانه و مستقل خودشان هستند. بنابراین ما مسئولیتی دربارهٔ محتوا و کنش‌های این سایت‌ها به عهده نمی‌گیریم. با این وجود، ما تلاش می‌کنیم که این سایت به درستی کار کند و از بازخورد شما برای چنین محصولات و خدماتی استقبال می‌کنیم.</p>
+
+      <h3 id="coppa">پیروی از قانون پشتیبانی از حریم خصوصی آنلاین کودکان</h3>
+
+      <p>سایت ما، محصولات و خدماتش همه برای کسانی است که دست‌کم ۱۳ سال سن داشته باشند. اگر این سرور در خاک ایالات متحدهٔ امریکا قرار دارد و سن شما کمتر از ۱۳ سال است، به خاطر رعایت قانون COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) لطفاً این سایت را به کار نبرید.</p>
+
+      <h3 id="online">تنها سیاست رازداری آنلاین</h3>
+
+      <p>این سیاست رازداری آنلاین تنها مربوط به اطلاعاتی است که از راه سایت ما گردآوری می‌شود و شامل اطلاعاتی که به طور آفلاین گردآوری شده نیست.</p>
+
+      <h3 id="consent">موافقت شما</h3>
+
+      <p>با استفاده از این سایت، شما موافقت خود را با سیاست رازداری ما اعلام می‌کنید.</p>
+
+      <h3 id="changes">تغییرات در سیاست رازداری ما</h3>
+
+      <p>اگر ما سیاست رازداری خود را تغییر دهیم، این تغییرات را در این صفحه خواهیم نوشت.</p>
+
+      <p>این نوشته تحت اجازه‌نامهٔ CC-BY-SA قرار دارد. تاریخ آخرین به‌روزرسانی آن ۱۰ خرداد ۱۳۹۲ است.</p>
+
+      <p>این نوشته اقتباسی است از <a href="https://github.com/discourse/discourse">سیاست رازداری Discourse</a>.</p>
+    title: "شرایط استفاده و سیاست رازداری %{instance}"
   time:
     formats:
       default: "%d %b %Y, %H:%M"
@@ -299,11 +509,13 @@ fa:
     description_html: اگر <strong>ورود دومرحله‌ای</strong> را فعال کنید، برای ورود به سیستم به تلفن خود نیاز خواهید داشت تا برایتان یک کد موقتی بسازد
     disable: غیرفعال‌کردن
     enable: فعال‌کردن
+    enabled: ورود دومرحله‌ای فعال است
     enabled_success: ورود دومرحله‌ای با موفقیت فعال شد
     generate_recovery_codes: ساخت کدهای بازیابی
     instructions_html: "<strong>این کد QR را با برنامهٔ Google Authenticator یا برنامه‌های TOTP مشابه اسکن کنید</strong>. از این به بعد، آن برنامه کدهایی موقتی خواهد ساخت که برای ورود باید آن‌ها را وارد کنید."
     lost_recovery_codes: با کدهای بازیابی می‌توانید اگر تلفن خود را گم کردید به حساب خود دسترسی داشته باشید. اگر کدهای بازیابی خود را گم کردید، آن‌ها را این‌جا دوباره بسازید. کدهای بازیابی قبلی شما نامعتبر خواهند شد.
     manual_instructions: 'اگر نمی‌توانید کدها را اسکن کنید و باید آن‌ها را دستی وارد کنید، متن کد امنیتی این‌جاست:'
+    recovery_codes: پشتیبان‌گیری از کدهای بازیابی
     recovery_codes_regenerated: کدهای بازیابی با موفقیت ساخته شدند
     recovery_instructions_html: اگر تلفن خود را گم کردید، می‌توانید با یکی از کدهای بازیابی زیر کنترل حساب خود را به دست بگیرید. این کدها را در جای امنی نگه دارید، مثلاً آن‌ها را چاپ کنید و کنار سایر مدارک مهم خود قرار دهید
     setup: راه اندازی
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 13514bfc3..7fde60a2b 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -200,7 +200,7 @@ fr:
       confirmed: Confirmé
       expires_in: Expire dans
       last_delivery: Dernière livraison
-      title: PubSubHubbub
+      title: WebSub
       topic: Sujet
     title: Administration
   admin_mailer:
diff --git a/config/locales/he.yml b/config/locales/he.yml
index dc6caf87a..7772e6a76 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -155,7 +155,7 @@ he:
       confirmed: מאושר
       expires_in: פג תוקף ב-
       last_delivery: משלוח אחרון
-      title: PubSubHubbub
+      title: WebSub
       topic: נושא
     title: ניהול
   application_mailer:
diff --git a/config/locales/id.yml b/config/locales/id.yml
index e0e82d378..0d5937cfb 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -146,7 +146,7 @@ id:
       confirmed: Dikonfirmasi
       expires_in: Kadaluarsa dalam
       last_delivery: Terakhir dikirim
-      title: PubSubHubbub
+      title: WebSub
       topic: Topik
     title: Administrasi
   application_mailer:
diff --git a/config/locales/io.yml b/config/locales/io.yml
index 4f7323a6f..c9abd5711 100644
--- a/config/locales/io.yml
+++ b/config/locales/io.yml
@@ -144,7 +144,7 @@ io:
       confirmed: Confirmed
       expires_in: Expires in
       last_delivery: Last delivery
-      title: PubSubHubbub
+      title: WebSub
       topic: Topic
     title: Administration
   application_mailer:
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 6763ed301..fa8f4566c 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -5,9 +5,14 @@ ja:
     about_this: 詳細情報
     closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。
     contact: 連絡先
+    contact_missing: 未設定
+    contact_unavailable: N/A
     description_headline: "%{domain} とは?"
     domain_count_after: 個のインスタンス
     domain_count_before: 接続中
+    extended_description_html: |
+      <h3>ルールを書くのに適した場所</h3>
+      <p>詳細説明が設定されていません。</p>
     features:
       humane_approach_body: 他の SNS の失敗から学び、Mastodon はソーシャルメディアが誤った使い方をされることの無いように倫理的な設計を目指しています。
       humane_approach_title: より思いやりのある設計
@@ -104,12 +109,14 @@ ja:
         hint: ドメインブロックはデータベース中のアカウント項目の作成を妨げませんが、遡って自動的に指定されたモデレーションをそれらのアカウントに適用します。
         severity:
           desc_html: "<strong>サイレンス</strong>はアカウントのトゥートをフォローしていない人から隠します。<strong>停止</strong>はそのアカウントのコンテンツ、メディア、プロフィールデータをすべて削除します。"
+          noop: なし
           silence: サイレンス
           suspend: 停止
         title: 新規ドメインブロック
       reject_media: メディアファイルを拒否
       reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。
       severities:
+        noop: なし
         silence: サイレンス
         suspend: 停止
       severity: 深刻度
@@ -200,7 +207,7 @@ ja:
       confirmed: 確認済み
       expires_in: 期限
       last_delivery: 最終配送
-      title: PubSubHubbub
+      title: WebSub
       topic: トピック
     title: 管理
   admin_mailer:
@@ -341,6 +348,8 @@ ja:
       title: あなたのトゥートが %{name} さんにお気に入り登録されました
     follow:
       title: "%{name} さんにフォローされました"
+    group:
+      title: "%{count} 件の通知"
     mention:
       action_boost: ブースト
       action_expand: もっと見る
@@ -392,6 +401,8 @@ ja:
       windows: Windows
       windows_mobile: Windows Mobile
       windows_phone: Windows Phone
+    revoke: 削除
+    revoke_success: セッションを削除しました
     title: セッション
   settings:
     authorized_apps: 認証済みアプリ
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index a081de38d..aae0e62e7 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -161,7 +161,7 @@ ko:
       confirmed: 확인됨
       expires_in: 기한
       last_delivery: 최종 발송
-      title: PubSubHubbub
+      title: WebSub
       topic: 토픽
     title: 관리
   admin_mailer:
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 58282259d..e65658d8b 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -206,7 +206,7 @@ nl:
       confirmed: Bevestigd
       expires_in: Verloopt over
       last_delivery: Laatste bezorging
-      title: PubSubHubbub
+      title: WebSub
       topic: Account
     title: Beheer
   admin_mailer:
diff --git a/config/locales/no.yml b/config/locales/no.yml
index 122ad5675..b2e5773de 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -148,7 +148,7 @@
       confirmed: Bekreftet
       expires_in: Utløper om
       last_delivery: Siste levering
-      title: PubSubHubbub
+      title: WebSub
       topic: Emne
     title: Administrasjon
   application_mailer:
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
index 6d9996dbc..d9a589287 100644
--- a/config/locales/oc.yml
+++ b/config/locales/oc.yml
@@ -1,19 +1,38 @@
 ---
 oc:
   about:
-    about_mastodon_html: Mastodon es un malhum social <em>liure e open-source</em>. Una alternativa <em>descentralizada</em> a las plat-formas comercialas, aquò evita qu’una sola companhiá monopolize vòstra comunicacion. Causissètz un servidor que vos fisatz, quina que siasque vòstra causida, podètz interagir amb tot lo mond. Qual que siasque pòt aver son instància Mastodon e participar al <em>malhum social</em> sens cap de problèmas.
+    about_mastodon_html: Mastodon es un malhum social bastit amb de protocòls liures e gratuits. Es descentralizat coma los corrièls.
     about_this: A prepaus d’aquesta instància
     closed_registrations: Las inscripcions son clavadas pel moment sus aquesta instància.
     contact: Contacte
+    contact_missing: Pas parametrat
+    contact_unavailable: Pas disponible
     description_headline: Qué es %{domain} ?
     domain_count_after: autras instàncias
     domain_count_before: Connectat a
-    other_instances: Autras instàncias
+    extended_description_html: |
+      <h3>Una bona plaça per las règlas</h3>
+      <p>La descripcion longa es pas estada causida pel moment.</p>
+    features:
+      humane_approach_body: Amb l’experiéncia dels fracasses d’autres malhums, Mastodon ten per objectiu de lutar contra los abuses dels malhums socials en far de causidas eticas.
+      humane_approach_title: Un biais mai uman
+      not_a_product_body: Mastodon es pas un malhum comercial. Pas cap de reclama, d’utilizacion de vòstras donadas o d’òrt daurat clavat. I a pas cap d’autoritat centrala.
+      not_a_product_title: Sètz una persona, non pas un produit
+      real_conversation_body: Amb 500 caractèrs a vòstra disposicion e un nivèl de confidencialitat per cada publicacion, podètz vos exprimir coma volètz.
+      real_conversation_title: Fach per de conversacions vertadièras
+      within_reach_body: Multiplas aplicacion per iOS, Android, e autras plataformas mercés a un entorn API de bon utilizar, vos permet de gardar lo contacte pertot.
+      within_reach_title: Totjorn al costat
+    find_another_instance: Trobar mai instàncias
+    generic_description: "%{domain} es un dels servidors del malhum"
+    hosted_on: Mastodon albergat sus %{domain}
+    learn_more: Ne saber mai
+    other_instances: Lista d’instàncias
     source_code: Còdi font
     status_count_after: estatuts
     status_count_before: qu’an escrich
     user_count_after: personas
-    user_count_before: Ostal de
+    user_count_before: Ostal de 
+    what_is_mastodon: Qu’es Mastodon ?
   accounts:
     follow: Sègre
     followers: Seguidors
@@ -23,6 +42,7 @@ oc:
     people_who_follow: Lo mond que sègon %{name}
     posts: Estatuts
     remote_follow: Sègre a distància
+    reserved_username: Aqueste nom d’utilizaire es reservat
     unfollow: Quitar de sègre
   admin:
     accounts:
@@ -60,8 +80,10 @@ oc:
       profile_url: URL del perfil
       public: Public
       push_subscription_expires: Fin de l’abonament PuSH
+      redownload: Actualizar los avatars
       reset: Reïnicializar
       reset_password: Reïnicializar lo senhal
+      resubscribe: Se tornar abonar
       salmon_url: URL Salmon
       search: Cercar
       show:
@@ -70,13 +92,14 @@ oc:
         targeted_reports: Rapòrts faches tocant aqueste compte
       silence: Silenci
       statuses: Estatuts
+      subscribe: S’abonar
       title: Comptes
       undo_silenced: Levar lo silenci
       undo_suspension: Levar la suspension
       username: Nom d’utilizaire
       web: Web
     domain_blocks:
-      add_new: Ajustar un nòu
+      add_new: N’ajustar un nòu
       created_msg: Domeni blocat es a èsser tractat
       destroyed_msg: Lo blocatge del domeni es estat levat
       domain: Domeni
@@ -85,12 +108,14 @@ oc:
         hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes.
         severity:
           desc_html: "<strong>Silenci</strong> farà venir invisibles los estatuts del compte al mond que son pas de seguidors. <strong>Suspendre</strong> levarà tot lo contengut del compte, los mèdias e las donadas de perfil."
+          noop: Cap
           silence: Silenci
           suspend: Suspendre
         title: Nòu blocatge domeni
       reject_media: Regetar los fichièrs mèdias
       reject_media_hint: Lèva los fichièrs gardats localament e regèta las demandas de telecargament dins lo futur. Servís pas a res per las suspensions
       severities:
+        noop: Cap
         silence: Silenci
         suspend: Suspendre
       severity: Severitat
@@ -110,6 +135,7 @@ oc:
       domain_name: Domeni
       title: Instàncias conegudas
     reports:
+      action_taken_by: Accion menada per
       are_you_sure: Es segur ?
       comment:
         label: Comentari
@@ -147,7 +173,7 @@ oc:
           desc_html: Autorizar lo monde a se marcar
           title: Inscripcions
       site_description:
-        desc_html: Afichada jos la forma de paragrafe sus la pagina d’acuèlh e utilizada coma balisa meta.<br> Podètz utilizar de balisas HTML, coma <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>.
+        desc_html: Afichada jos la forma de paragraf sus la pagina d’acuèlh e utilizada coma balisa meta.<br> Podètz utilizar de balisas HTML, coma <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>.
         title: Descripcion del site
       site_description_extended:
         desc_html: Afichada sus la pagina d’informacion complementària del site<br>Podètz utilizar de balisas HTML
@@ -180,9 +206,13 @@ oc:
       confirmed: Confirmat
       expires_in: S’acaba dins
       last_delivery: Darrièra distribucion
-      title: PubSubHubbub
+      title: WebSub
       topic: Subjècte
     title: Administracion
+  admin_mailer:
+    new_report:
+      body: "%{reporter} a senhalat %{target}"
+      subject: Novèl senhalament per %{instance} (#%{id})
   application_mailer:
     settings: 'Cambiar las preferéncias de corrièl : %{link}'
     signature: Notificacion de Mastodon sus %{instance}
@@ -190,7 +220,8 @@ oc:
   applications:
     invalid_url: L’URL donada es invalida
   auth:
-    change_password: Cambiar lo senhal
+    agreement_html: En vos marcar acceptatz <a href="%{rules_path}">nòstres tèrmes de servici</a> e <a href="%{terms_path}">politica de confidencialitat</a>.
+    change_password: Seguretat
     delete_account: Suprimir lo compte
     delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz.
     didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ?
@@ -204,6 +235,12 @@ oc:
   authorize_follow:
     error: O planhèm, i a agut una error al moment de cercar lo compte
     follow: Sègre
+    follow_request: 'Avètz demandat de sègre :'
+    following: 'Felicitacion ! Seguètz ara :'
+    post_follow:
+      close: O podètz tampar aquesta fenèstra.
+      return: Tornar al perfil
+      web: Tornar a l’interfàcia Web
     prompt_html: 'Avètz (<strong>%{self}</strong>) demandat de sègre :'
     title: Sègre %{acct}
   date:
@@ -288,12 +325,14 @@ oc:
     warning_html: La supression del contengut d’aquesta instància es sola assegurada. Lo contengut fòrça partejat daissarà probablament de traças. Los servidors fòra-linha e los que vos sègon pas mai auràn pas la mesa a jorn de lor basa de donada.
     warning_title: Disponibilitat del contengut difusat
   errors:
+    '403': Avètz pas l’autorizacion de veire aquesta pagina.
     '404': La pagina que recercatz existís pas.
     '410': La pagina que cercatz existís pas mai.
     '422':
       content: Verificacion de seguretat fracassada. Blocatz los cookies ?
       title: Verificacion de seguretat fracassada
     '429': Lo servidor mòla (subrecargada)
+    noscript: Per utilizar l’aplicacion web de Mastodon, mercés d’activar JavaScript. O podètz utilizar una aplicacion per vòstra plataforma coma alernativa.
   exports:
     blocks: Personas que blocatz
     csv: CSV
@@ -327,7 +366,7 @@ oc:
       following: Lista de mond que seguètz
       muting: Lista de mond que volètz pas legir
     upload: Importar
-  landing_strip_html: "<strong>%{name}</strong> es un utilizaire de %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse."
+  landing_strip_html: "<strong>%{name}</strong> utiliza %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse."
   landing_strip_signup_html: S’es pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>.
   media_attachments:
     validations:
@@ -362,6 +401,23 @@ oc:
     next: Seguent
     prev: Precedent
     truncate: "&hellip;"
+  push_notifications:
+    favourite:
+      title: "%{name} a mes vòstre estatut en favorit"
+    follow:
+      title: "%{name} vos sèc ara"
+    group:
+      title: "%{count} notificacions"
+    mention:
+      action_boost: Partejar
+      action_expand: Ne veire mai
+      action_favourite: Ajustar als favorits
+      title: "%{name} vos a mencionat"
+    reblog:
+      title: "%{name} a partejat vòstre estatut"
+    subscribed:
+      body: Podètz ara recebre las notificacions push.
+      title: Abonament enregistrat !
   remote_follow:
     acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire
     missing_resource: URL de redireccion pas trobada
@@ -438,7 +494,7 @@ oc:
 
       <h3 id="collect">Quinas informacions collectem ?</h3>
 
-      <p>Collectem informacions sus vos quand vos marcatz sus nòstre site e juntem las donadas quand participatz a nòstre forum ne legissent, escrivent e notant lo contengut partejat aquí.</p>
+      <p>Collectem informacions sus vos quand vos marcatz sus nòstre site e juntem las donadas quand participatz a nòstre forum en legissent, escrivent e notant lo contengut partejat aquí.</p>
 
       <p>Pendent l’inscripcion podèm vos demandar vòstre nom e adreça de corrièl. Podètz çaquelà visitar nòstre site sens vos marcar. Verificarem vòstra adreça amb un messatge donant un ligam unic. Se clicatz sul ligam sauprem qu’avètz lo contraròtle de l’adreça.</p>
 
@@ -472,13 +528,13 @@ oc:
 
       <p>Òc-ben. Los cookies son de pichons fichièrs qu’un site o sos forneires de servicis plaçan dins lo disc dur de vòstre ordenador via lo navigator Web (Se los acceptatz). Aqueles cookies permeton al site de reconéisser vòstre navigator e se tenètz un compte enregistrat de l’associar a vòstre compte.</p>
 
-      <p>Empleguem de cookies per comprendre e enregistrar vòstras preferéncias per vòstras visitas venentas, per recampar de donadas sul trafic del site e las interaccions per fin que posquem ofrir una melhora experiéncia del site e de las aisinas pel futur. Pòt arribar que contractèssem amb de forneires de servicis tèrces per nos ajudar a comprendre melhor nòstres visitors.  Aqueles forneires an pas lo drech que d’utilizar las donadas collectadas per nos ajudar a menar e melhorar nòstre afar.</p>
+      <p>Empleguem de cookies per comprendre e enregistrar vòstras preferéncias per vòstras visitas venentas, per recampar de donadas sul trafic del site e las interaccions per dire que posquem ofrir una melhora experiéncia del site e de las aisinas pel futur. Pòt arribar que contractèssem amb de forneires de servicis tèrces per nos ajudar a comprendre melhor nòstres visitors.  Aqueles forneires an pas lo drech que d’utilizar las donadas collectadas per nos ajudar a menar e melhorar nòstre afar.</p>
 
-      <h3 id="disclose">Divulguem d’informacions a de partits exteriors ?</h3>
+      <h3 id="disclose">Divulguem d’informacions a de tèrces ?</h3>
 
-      <p>Vendèm pas, comercem o qualque transferiment que siasque a de partits exteriors vòstras informacions personalas identificablas. Aquò inclutz pas los tèrces partits de confisança que nos assiston a menar nòstre site, menar nòstre afar o vos servir, baste que son d’acòrd per gardar aquelas informacions confidencialas. Pòt tanben arribar que liberèssem vòstras informacions quand cresèm qu’es apropriat d’o far per se sometre a la lei, per refortir nòstras politicas, o per protegir los dreches, proprietats o seguritat de qualqu’un o de nosautres. Pasmens es possible que mandèssem d’informacions non-personalas e identificablas de nòstres visitors a d’autres partits per d’utilizacion en marketing, publicitat o un emplec mai.</p>
+      <p>Vendèm pas, comercem o qualque transferiment que siasque a de tèrces vòstras informacions personalas identificablas. Aquò inclutz pas los tèrces partits de confisança que nos assiston a menar nòstre site, menar nòstre afar o vos servir, baste que son d’acòrd per gardar aquelas informacions confidencialas. Pòt tanben arribar que liberèssem vòstras informacions quand cresèm qu’es apropriat d’o far per se sometre a la lei, per refortir nòstras politicas, o per protegir los dreches, proprietats o seguritat de qualqu’un o de nosautres. Pasmens es possible que mandèssem d’informacions non-personalas e identificablas de nòstres visitors a d’autres partits per d’utilizacion en marketing, publicitat o un emplec mai.</p>
 
-      <h3 id="third-party">Ligams de tèrces partits</h3>
+      <h3 id="third-party">Ligams de tèrces</h3>
 
       <p>Pòt arribar, a nòstra discrecion, qu’incluguèssem o ofriguèssem  de produches o servicis de tèrces partits sus nòstre site. Aqueles sites tèrces an de politicas de confidencialitats separadas e independentas. En consequéncia avèm pas cap de responsabilitat pel contengut e las activitats d’aqueles sites ligats. Pasmens cerquem de protegir l’integritat de nòstre site e aculhèm los comentaris tocant aqueles sites.</p>
 
@@ -515,6 +571,7 @@ oc:
     instructions_html: "<strong>Escanatz aqueste còdi QR amb Google Authenticator o una aplicacion similària sus vòstre mobil</strong>. A partir d’ara, aquesta aplicacion generarà un geton que vos caldrà picar per vos connectar."
     lost_recovery_codes: Los còdi de recuperacion vos permeton d’accedir a vòstre compte se perdètz vòstre mobil. S’avètz perdut vòstres còdis de recuperacion los podètz tornar generar aquí. Los ancians còdis seràn pas mai valides.
     manual_instructions: 'Se podètz pas numerizar lo còdi QR e que vos cal picar lo còdi a la man, vaquí lo còdi en clar :'
+    recovery_codes: Salvar los còdis de recuperacion
     recovery_codes_regenerated: Los còdis de recuperacion son ben estats tornats generar
     recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants.
     setup: Paramètres
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 02c97e955..a30092d50 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -205,7 +205,7 @@ pl:
       confirmed: Potwierdzono
       expires_in: Wygasa
       last_delivery: Ostatnio doręczono
-      title: PubSubHubbub
+      title: WebSub
       topic: Temat
     title: Administracja
   admin_mailer:
@@ -350,6 +350,8 @@ pl:
       title: "%{name} dodał Twój status do ulubionych"
     follow:
       title: "%{name} zaczął Cię śledzić"
+    group:
+      title: "%{count} powiadomień"
     mention:
       action_boost: Podbij
       action_expand: Pokaż więcej
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 9bd1b0d28..68b1c549c 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -147,7 +147,7 @@ pt-BR:
       confirmed: Confirmado
       expires_in: Expira em
       last_delivery: Última entrega
-      title: PubSubHubbub
+      title: WebSub
       topic: Tópico
     title: Administração
   application_mailer:
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index 6bf592d1c..f6dd32200 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -142,7 +142,7 @@ pt:
       confirmed: Confirmado
       expires_in: Expira em
       last_delivery: Última entrega
-      title: PubSubHubbub
+      title: WebSub
       topic: Tópico
     title: Administração
   application_mailer:
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 141017f40..348f670b5 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -138,7 +138,7 @@ ru:
       confirmed: Подтверждено
       expires_in: Истекает через
       last_delivery: Последняя доставка
-      title: PubSubHubbub
+      title: WebSub
       topic: Тема
     title: Администрирование
   application_mailer:
@@ -267,6 +267,21 @@ ru:
     next: След
     prev: Пред
     truncate: "&hellip;"
+  push_notifications:
+    favourite:
+      title: "Ваш статус понравился %{name}"
+    follow:
+      title: "%{name} теперь подписан(а) на Вас"
+    mention:
+      action_boost: Продвинуть
+      action_expand: Развернуть
+      action_favourite: Нравится
+      title: "Вас упомянул(а) %{name}"
+    reblog:
+      title: "%{name} продвинул(а) Ваш статус"
+    subscribed:
+      body: Теперь Вы можете получать push-уведомления.
+      title: Подписка зарегистрирована!
   remote_follow:
     acct: Введите username@domain, откуда Вы хотите подписаться
     missing_resource: Поиск требуемого перенаправления URL для Вашего аккаунта завершился неудачей
@@ -335,6 +350,8 @@ ru:
     click_to_show: Показать
     reblogged: продвинул(а)
     sensitive_content: Чувствительный контент
+  terms:
+    title: "Условия обслуживания и политика конфиденциальности %{instance}"
   time:
     formats:
       default: "%b %d, %Y, %H:%M"
diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml
index 3de1fe971..71a9d6e1f 100644
--- a/config/locales/simple_form.fa.yml
+++ b/config/locales/simple_form.fa.yml
@@ -12,6 +12,7 @@ fa:
         note:
           one: '<span class="note-counter">1</span> حرف باقی مانده'
           other: '<span class="note-counter">%{count}</span> حرف باقی مانده'
+        setting_noindex: روی نمایهٔ عمومی و صفحهٔ نوشته‌های شما تأثیر می‌گذارد
       imports:
         data: پروندهٔ CSV که از سرور ماستدون دیگری برون‌سپاری شده
       sessions:
@@ -27,6 +28,7 @@ fa:
         data: داده‌ها
         display_name: نمایش به نام
         email: نشانی ایمیل
+        filtered_languages: زبان‌های فیلترشده
         header: تصویر زمینه
         locale: زبان
         locked: خصوصی‌کردن حساب
@@ -37,6 +39,11 @@ fa:
         setting_auto_play_gif: پخش خودکار تصویرهای متحرک
         setting_boost_modal: نمایش پیغام تأیید پیش از بازبوقیدن
         setting_default_privacy: حریم خصوصی نوشته‌ها
+        setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن
+        setting_delete_modal: پیش از پاک کردن یک بوق پیغام تأیید نشان بده
+        setting_noindex: درخواست از موتورهای جستجو برای لغو فهرست‌سازی
+        setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم
+        setting_unfollow_modal: نمایش پیغام تأیید پیش از لغو پیگیری دیگران
         severity: شدت
         type: نوع درون‌ریزی
         username: نام کاربری (تنها حروف انگلیسی)
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index d52d53109..351d1800c 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -37,8 +37,8 @@ nl:
         type: Importtype
         username: gebruikersnaam
       interactions:
-        must_be_follower: Blokkeermeldingen van mensen die jou niet volgen
-        must_be_following: Blokkeermeldingen van mensen die jij niet volgt
+        must_be_follower: Blokkeer meldingen van mensen die jou niet volgen
+        must_be_following: Blokkeer meldingen van mensen die jij niet volgt
       notification_emails:
         digest: Verstuur periodiek e-mails met een samenvatting
         favourite: Verstuur een e-mail wanneer iemand jouw toot als favoriet markeert
diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml
index c4e6ad8a8..3bdb7870f 100644
--- a/config/locales/simple_form.ru.yml
+++ b/config/locales/simple_form.ru.yml
@@ -16,6 +16,7 @@ ru:
           many: Осталось <span class="name-counter">%{count}</span> символов
           one: Остался <span class="name-counter">1</span> символ
           other: Осталось <span class="name-counter">%{count}</span> символов
+        setting_noindex: Относится к Вашему публичному профилю и страницам статусов
       imports:
         data: Файл CSV, экспортированный с другого узла Mastodon
       sessions:
@@ -42,7 +43,11 @@ ru:
         setting_auto_play_gif: Автоматически проигрывать анимированные GIF
         setting_boost_modal: Показывать диалог подтверждения перед продвижением
         setting_default_privacy: Видимость постов
+        setting_default_sensitive: Всегда отмечать медиаконтент как чувствительный
         setting_delete_modal: Показывать диалог подтверждения перед удалением
+        setting_noindex: Отказаться от индексации в поисковых машинах
+        setting_system_font_ui: Использовать шрифт системы по умолчанию
+        setting_unfollow_modal: Показывать диалог подтверждения перед тем, как отписаться от аккаунта
         severity: Строгость
         type: Тип импорта
         username: Имя пользователя
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 89782209e..801f4886f 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -148,7 +148,7 @@ th:
       confirmed: ยืนยัน
       expires_in: หมดอายุภายใน
       last_delivery: จัดส่งครั้งล่าสุด
-      title: PubSubHubbub
+      title: WebSub
       topic: ชื่อเรื่อง
     title: แอดมิน
   application_mailer:
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index a1f2d2078..ac378090c 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -147,7 +147,7 @@ tr:
       confirmed: Onaylandı
       expires_in: Bitiş Tarihi
       last_delivery: Son gönderim
-      title: PubSubHubbub
+      title: WebSub
       topic: Konu
     title: Yönetim
   application_mailer:
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 3237ea1db..22fff6961 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -138,7 +138,7 @@ uk:
       confirmed: Підтверджено
       expires_in: Спливає через
       last_delivery: Остання доставка
-      title: PubSubHubbub
+      title: WebSub
       topic: Тема
     title: Адміністрування
   application_mailer:
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 0b0263294..5018b48b8 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -154,7 +154,7 @@ zh-CN:
       confirmed: 确定
       expires_in: 期限
       last_delivery: 数据最后送抵时间
-      title: PubSubHubbub 订阅
+      title: WebSub 订阅
       topic: 所订阅资源
     title: 管理
   application_mailer:
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index e25edc890..0ea3457c7 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -118,7 +118,7 @@ zh-TW:
       confirmed: 已確認
       expires_in: 期限
       last_delivery: 最後遞送
-      title: PubSubHubbub
+      title: WebSub
       topic: 主題
     title: 管理介面
   application_mailer:
diff --git a/config/routes.rb b/config/routes.rb
index 71729fee5..c60a8b131 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -3,6 +3,8 @@
 require 'sidekiq/web'
 require 'sidekiq-scheduler/web'
 
+Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]
+
 Rails.application.routes.draw do
   mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development?
 
diff --git a/config/settings.yml b/config/settings.yml
index 38871c772..acaab3166 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -17,9 +17,11 @@ defaults: &defaults
   closed_registrations_message: ''
   open_deletion: true
   timeline_preview: false
+  default_sensitive: false
+  unfollow_modal: false
   boost_modal: false
-  auto_play_gif: false
   delete_modal: true
+  auto_play_gif: false
   system_font_ui: false
   noindex: false
   notification_emails:
diff --git a/config/webpack/production.js b/config/webpack/production.js
index 4592db89e..cd1dd91dc 100644
--- a/config/webpack/production.js
+++ b/config/webpack/production.js
@@ -10,7 +10,11 @@ const { publicPath } = require('./configuration.js');
 const path = require('path');
 
 module.exports = merge(sharedConfig, {
-  output: { filename: '[name]-[chunkhash].js' },
+  output: {
+    filename: '[name]-[chunkhash].js',
+    chunkFilename: '[name]-[chunkhash].js',
+  },
+
   devtool: 'source-map', // separate sourcemap file, suitable for production
   stats: 'normal',
 
@@ -48,7 +52,7 @@ module.exports = merge(sharedConfig, {
       ServiceWorker: {
         entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'),
         cacheName: 'mastodon',
-        output: '../sw.js',
+        output: '../assets/sw.js',
         publicPath: '/sw.js',
         minify: true,
       },
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
index 425918d66..be1b49421 100644
--- a/config/webpack/shared.js
+++ b/config/webpack/shared.js
@@ -33,7 +33,7 @@ module.exports = {
 
   output: {
     filename: '[name].js',
-    chunkFilename: '[name]-[chunkhash].js',
+    chunkFilename: '[name].js',
     path: output.path,
     publicPath: output.publicPath,
   },
diff --git a/db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb b/db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb
new file mode 100644
index 000000000..99903584c
--- /dev/null
+++ b/db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb
@@ -0,0 +1,6 @@
+class AddIndexFavouritesOnAccountIdAndId < ActiveRecord::Migration[5.1]
+  def change
+    # Used to query favourites of an account ordered by id.
+    add_index :favourites, [:account_id, :id]
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cf7b0722f..2501e451d 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: 20170718211102) do
+ActiveRecord::Schema.define(version: 20170720000000) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -103,6 +103,7 @@ ActiveRecord::Schema.define(version: 20170718211102) do
     t.integer "status_id", null: false
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
+    t.index ["account_id", "id"], name: "index_favourites_on_account_id_and_id"
     t.index ["account_id", "status_id"], name: "index_favourites_on_account_id_and_status_id", unique: true
     t.index ["status_id"], name: "index_favourites_on_status_id"
   end
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 07969aff4..aeb5492dc 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -21,7 +21,7 @@ module Mastodon
     end
 
     def flags
-      'rc1'
+      ''
     end
 
     def to_a
diff --git a/package.json b/package.json
index 132d7017d..5fdd491ee 100644
--- a/package.json
+++ b/package.json
@@ -7,10 +7,9 @@
     "build:production": "cross-env RAILS_ENV=production ./bin/webpack",
     "manage:translations": "node ./config/webpack/translationRunner.js",
     "start": "node ./streaming/index.js",
-    "storybook": "cross-env NODE_ENV=test start-storybook -s ./public -p 9001 -c storybook",
     "test": "npm run test:lint && npm run test:mocha",
-    "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ storybook/ streaming/",
-    "test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.js",
+    "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ streaming/",
+    "test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/**/*.test.js",
     "postinstall": "npm rebuild node-sass"
   },
   "repository": {
@@ -57,7 +56,7 @@
     "glob": "^7.1.1",
     "http-link-header": "^0.8.0",
     "immutable": "^3.8.1",
-    "intersection-observer": "^0.3.2",
+    "intersection-observer": "^0.4.0",
     "intl": "^1.2.5",
     "intl-relativeformat": "^2.0.0",
     "is-nan": "^1.2.1",
@@ -113,15 +112,13 @@
     "tiny-queue": "^0.2.1",
     "uuid": "^3.1.0",
     "uws": "^8.14.0",
-    "webpack": "^3.0.0",
-    "webpack-bundle-analyzer": "^2.8.2",
-    "webpack-manifest-plugin": "^1.1.2",
+    "webpack": "^3.4.1",
+    "webpack-bundle-analyzer": "^2.8.3",
+    "webpack-manifest-plugin": "^1.2.1",
     "webpack-merge": "^4.1.0",
     "websocket.js": "^0.1.12"
   },
   "devDependencies": {
-    "@storybook/addon-actions": "^3.1.8",
-    "@storybook/react": "^3.1.8",
     "babel-eslint": "^7.2.3",
     "chai": "^4.1.0",
     "chai-enzyme": "^0.8.0",
@@ -134,7 +131,7 @@
     "react-intl-translations-manager": "^5.0.0",
     "react-test-renderer": "^15.6.1",
     "sinon": "^2.3.7",
-    "webpack-dev-server": "^2.5.1",
+    "webpack-dev-server": "^2.6.1",
     "yargs": "^8.0.2"
   },
   "optionalDependencies": {
diff --git a/public/sw.js b/public/sw.js
new file mode 120000
index 000000000..1471a9e64
--- /dev/null
+++ b/public/sw.js
@@ -0,0 +1 @@
+assets/sw.js
\ No newline at end of file
diff --git a/public/web-push-icon_expand.png b/public/web-push-icon_expand.png
new file mode 100644
index 000000000..972c28886
--- /dev/null
+++ b/public/web-push-icon_expand.png
Binary files differdiff --git a/public/web-push-icon_favourite.png b/public/web-push-icon_favourite.png
new file mode 100644
index 000000000..ef36b8898
--- /dev/null
+++ b/public/web-push-icon_favourite.png
Binary files differdiff --git a/public/web-push-icon_reblog.png b/public/web-push-icon_reblog.png
new file mode 100644
index 000000000..0f555ed09
--- /dev/null
+++ b/public/web-push-icon_reblog.png
Binary files differdiff --git a/scalingo.json b/scalingo.json
index 8df2caba1..426698b9c 100644
--- a/scalingo.json
+++ b/scalingo.json
@@ -2,7 +2,7 @@
   "name": "Mastodon",
   "description": "A GNU Social-compatible microblogging server",
   "repository": "https://github.com/tootsuite/mastodon",
-  "logo": "https://github.com/tootsuite/mastodon/raw/master/app/javascript/images/logo.svg",
+  "logo": "https://github.com/tootsuite.png",
   "env": {
     "LOCAL_DOMAIN": {
       "description": "The domain that your Mastodon instance will run on (this can be appname.scalingo.io or a custom domain)",
diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
index 7bcf9fe0e..3f655c7b2 100644
--- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
@@ -4,7 +4,7 @@ describe Api::V1::Accounts::CredentialsController do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
index 171852c75..33982cb8f 100644
--- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
@@ -4,7 +4,7 @@ describe Api::V1::Accounts::FollowerAccountsController do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
   before do
     Fabricate(:follow, target_account: user.account)
diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
index a4cad9163..e22f54a31 100644
--- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
@@ -4,7 +4,7 @@ describe Api::V1::Accounts::FollowingAccountsController do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
   before do
     Fabricate(:follow, account: user.account)
diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
index e281afcb9..3a9607317 100644
--- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
@@ -4,7 +4,7 @@ describe Api::V1::Accounts::RelationshipsController do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/accounts/search_controller_spec.rb b/spec/controllers/api/v1/accounts/search_controller_spec.rb
index 40c82437d..42cc3f64d 100644
--- a/spec/controllers/api/v1/accounts/search_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/search_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::Accounts::SearchController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
index 55cb5bcc2..8b4fd6a5b 100644
--- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
@@ -4,7 +4,7 @@ describe Api::V1::Accounts::StatusesController do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb
index 216a9cb3b..c13509e7b 100644
--- a/spec/controllers/api/v1/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow read') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/blocks_controller_spec.rb b/spec/controllers/api/v1/blocks_controller_spec.rb
index 4fd968b27..f25a7e878 100644
--- a/spec/controllers/api/v1/blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/blocks_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::BlocksController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
 
   before do
     Fabricate(:block, account: user.account)
diff --git a/spec/controllers/api/v1/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/domain_blocks_controller_spec.rb
index ff5c5f330..3713931dc 100644
--- a/spec/controllers/api/v1/domain_blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/domain_blocks_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
 
   before do
     user.account.block_domain!('example.com')
diff --git a/spec/controllers/api/v1/favourites_controller_spec.rb b/spec/controllers/api/v1/favourites_controller_spec.rb
index 062e91adc..3de045377 100644
--- a/spec/controllers/api/v1/favourites_controller_spec.rb
+++ b/spec/controllers/api/v1/favourites_controller_spec.rb
@@ -3,19 +3,77 @@ require 'rails_helper'
 RSpec.describe Api::V1::FavouritesController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
-
-  before do
-    Fabricate(:favourite, account: user.account)
-    allow(controller).to receive(:doorkeeper_token) { token }
-  end
+  let(:user)  { Fabricate(:user) }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
   describe 'GET #index' do
-    it 'returns http success' do
-      get :index, params: { limit: 1 }
+    context 'without token' do
+      it 'returns http unauthorized' do
+        get :index
+        expect(response).to have_http_status :unauthorized
+      end
+    end
+
+    context 'with token' do
+      context 'without read scope' do
+        before do
+          allow(controller).to receive(:doorkeeper_token) do
+            Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: '')
+          end
+        end
+
+        it 'returns http forbidden' do
+          get :index
+          expect(response).to have_http_status :forbidden
+        end
+      end
+
+      context 'without valid resource owner' do
+        before do
+          token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read')
+          user.destroy!
+
+          allow(controller).to receive(:doorkeeper_token) { token }
+        end
+
+        it 'returns http unprocessable entity' do
+          get :index
+          expect(response).to have_http_status :unprocessable_entity
+        end
+      end
+
+      context 'with read scope and valid resource owner' do
+        before do
+          allow(controller).to receive(:doorkeeper_token) do
+            Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read')
+          end
+        end
+
+        it 'shows favourites owned by the user' do
+          favourite_by_user = Fabricate(:favourite, account: user.account)
+          favourite_by_others = Fabricate(:favourite)
+
+          get :index
+
+          expect(assigns(:statuses)).to match_array [favourite_by_user.status]
+        end
+
+        it 'adds pagination headers if necessary' do
+          favourite = Fabricate(:favourite, account: user.account)
+
+          get :index, params: { limit: 1 }
+
+          expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq "http://test.host/api/v1/favourites?limit=1&max_id=#{favourite.id}"
+          expect(response.headers['Link'].find_link(['rel', 'prev']).href).to eq "http://test.host/api/v1/favourites?limit=1&since_id=#{favourite.id}"
+        end
+
+        it 'does not add pagination headers if not necessary' do
+          get :index
 
-      expect(response).to have_http_status(:success)
+          expect(response.headers['Link'].find_link(['rel', 'next'])).to eq nil
+          expect(response.headers['Link'].find_link(['rel', 'prev'])).to eq nil
+        end
+      end
     end
   end
 end
diff --git a/spec/controllers/api/v1/follow_requests_controller_spec.rb b/spec/controllers/api/v1/follow_requests_controller_spec.rb
index d455a0255..51df006a2 100644
--- a/spec/controllers/api/v1/follow_requests_controller_spec.rb
+++ b/spec/controllers/api/v1/follow_requests_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do
   render_views
 
   let(:user)     { Fabricate(:user, account: Fabricate(:account, username: 'alice', locked: true)) }
-  let(:token)    { double acceptable?: true, resource_owner_id: user.id }
+  let(:token)    { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
   let(:follower) { Fabricate(:account, username: 'bob') }
 
   before do
diff --git a/spec/controllers/api/v1/follows_controller_spec.rb b/spec/controllers/api/v1/follows_controller_spec.rb
index cc4958ab5..b5e1d16dd 100644
--- a/spec/controllers/api/v1/follows_controller_spec.rb
+++ b/spec/controllers/api/v1/follows_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::FollowsController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/instances_controller_spec.rb b/spec/controllers/api/v1/instances_controller_spec.rb
index 544f3d28f..eba233b05 100644
--- a/spec/controllers/api/v1/instances_controller_spec.rb
+++ b/spec/controllers/api/v1/instances_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Api::V1::InstancesController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/media_controller_spec.rb b/spec/controllers/api/v1/media_controller_spec.rb
index 00dcac95d..6bad3f05d 100644
--- a/spec/controllers/api/v1/media_controller_spec.rb
+++ b/spec/controllers/api/v1/media_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb
index 85aad4384..3e6fa887b 100644
--- a/spec/controllers/api/v1/mutes_controller_spec.rb
+++ b/spec/controllers/api/v1/mutes_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::MutesController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
 
   before do
     Fabricate(:mute, account: user.account)
diff --git a/spec/controllers/api/v1/notifications_controller_spec.rb b/spec/controllers/api/v1/notifications_controller_spec.rb
index e06230913..f493d0d38 100644
--- a/spec/controllers/api/v1/notifications_controller_spec.rb
+++ b/spec/controllers/api/v1/notifications_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
   let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
 
   before do
diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb
index 471ea4e0b..1eb5a4353 100644
--- a/spec/controllers/api/v1/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/reports_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Api::V1::ReportsController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read write') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/search_controller_spec.rb b/spec/controllers/api/v1/search_controller_spec.rb
index 4d22ddc98..ff0c254b1 100644
--- a/spec/controllers/api/v1/search_controller_spec.rb
+++ b/spec/controllers/api/v1/search_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Api::V1::SearchController, type: :controller do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
index 1acb990a0..556731d57 100644
--- a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app) }
 
   context 'with an oauth token' do
     before do
diff --git a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
index eb77072d2..2a029230d 100644
--- a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
@@ -7,7 +7,7 @@ describe Api::V1::Statuses::FavouritesController do
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write', application: app) }
 
   context 'with an oauth token' do
     before do
diff --git a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb
index 1f8c29e3d..54c594e92 100644
--- a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb
@@ -7,7 +7,7 @@ describe Api::V1::Statuses::MutesController do
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write', application: app) }
 
   context 'with an oauth token' do
     before do
diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
index c5624023f..ba022a96e 100644
--- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app) }
 
   context 'with an oauth token' do
     before do
diff --git a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb
index 36c323736..d6d36c1b2 100644
--- a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb
@@ -7,7 +7,7 @@ describe Api::V1::Statuses::ReblogsController do
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write', application: app) }
 
   context 'with an oauth token' do
     before do
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
index 3d65180ab..a36265395 100644
--- a/spec/controllers/api/v1/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
-  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'write') }
 
   context 'with an oauth token' do
     before do
diff --git a/spec/controllers/api/v1/timelines/home_controller_spec.rb b/spec/controllers/api/v1/timelines/home_controller_spec.rb
index faa6c60ce..4d4523520 100644
--- a/spec/controllers/api/v1/timelines/home_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/home_controller_spec.rb
@@ -12,7 +12,7 @@ describe Api::V1::Timelines::HomeController do
   end
 
   context 'with a user context' do
-    let(:token) { double acceptable?: true, resource_owner_id: user.id }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
     describe 'GET #show' do
       before do
@@ -30,7 +30,7 @@ describe Api::V1::Timelines::HomeController do
   end
 
   context 'without a user context' do
-    let(:token) { double acceptable?: true, resource_owner_id: nil }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') }
 
     describe 'GET #show' do
       it 'returns http unprocessable entity' do
diff --git a/spec/controllers/api/v1/timelines/public_controller_spec.rb b/spec/controllers/api/v1/timelines/public_controller_spec.rb
index 353ab9bc2..3acf2e267 100644
--- a/spec/controllers/api/v1/timelines/public_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/public_controller_spec.rb
@@ -12,7 +12,7 @@ describe Api::V1::Timelines::PublicController do
   end
 
   context 'with a user context' do
-    let(:token) { double acceptable?: true, resource_owner_id: user.id }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
 
     describe 'GET #show' do
       before do
@@ -42,7 +42,7 @@ describe Api::V1::Timelines::PublicController do
   end
 
   context 'without a user context' do
-    let(:token) { double acceptable?: true, resource_owner_id: nil }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil) }
 
     describe 'GET #show' do
       it 'returns http success' do
diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
index f743f0cde..74de1e81f 100644
--- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
@@ -12,7 +12,7 @@ describe Api::V1::Timelines::TagController do
   end
 
   context 'with a user context' do
-    let(:token) { double acceptable?: true, resource_owner_id: user.id }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
 
     describe 'GET #show' do
       before do
@@ -28,7 +28,7 @@ describe Api::V1::Timelines::TagController do
   end
 
   context 'without a user context' do
-    let(:token) { double acceptable?: true, resource_owner_id: nil }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil) }
 
     describe 'GET #show' do
       it 'returns http success' do
diff --git a/spec/fabricators/access_token_fabricator.rb b/spec/fabricators/access_token_fabricator.rb
new file mode 100644
index 000000000..1856a8eb3
--- /dev/null
+++ b/spec/fabricators/access_token_fabricator.rb
@@ -0,0 +1,2 @@
+Fabricator :access_token, from: 'Doorkeeper::AccessToken' do
+end
diff --git a/spec/fabricators/accessible_access_token_fabricator.rb b/spec/fabricators/accessible_access_token_fabricator.rb
new file mode 100644
index 000000000..4b7e99b20
--- /dev/null
+++ b/spec/fabricators/accessible_access_token_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator :accessible_access_token, from: :access_token do
+  expires_in { nil }
+  revoked_at { nil }
+end
diff --git a/spec/helpers/emoji_helper_spec.rb b/spec/helpers/emoji_helper_spec.rb
index 1eedfb719..6edf7672f 100644
--- a/spec/helpers/emoji_helper_spec.rb
+++ b/spec/helpers/emoji_helper_spec.rb
@@ -7,6 +7,11 @@ RSpec.describe EmojiHelper, type: :helper do
       expect(emojify(text)).to eq '📖 Book'
     end
 
+    it 'converts composite emoji shortcodes to unicode' do
+      text = ':couple_ww:'
+      expect(emojify(text)).to eq '👩❤👩'
+    end
+
     it 'does not convert shortcodes that are part of a string into unicode' do
       text = ':see_no_evil::hear_no_evil::speak_no_evil:'
       expect(emojify(text)).to eq text
diff --git a/spec/helpers/instance_helper_spec.rb b/spec/helpers/instance_helper_spec.rb
index c3d28544f..bc5950d91 100644
--- a/spec/helpers/instance_helper_spec.rb
+++ b/spec/helpers/instance_helper_spec.rb
@@ -19,7 +19,7 @@ describe InstanceHelper do
     it 'returns empty string when Setting.site_title is nil' do
       Setting.site_title = nil
 
-      expect(helper.site_title).to eq ''
+      expect(helper.site_title).to eq 'cb6e6126.ngrok.io'
     end
   end
 
diff --git a/spec/javascript/components/dropdown_menu.test.js b/spec/javascript/components/dropdown_menu.test.js
index 54cdcabf0..a5af730ef 100644
--- a/spec/javascript/components/dropdown_menu.test.js
+++ b/spec/javascript/components/dropdown_menu.test.js
@@ -5,16 +5,24 @@ import React from 'react';
 import DropdownMenu from '../../../app/javascript/mastodon/components/dropdown_menu';
 import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
 
+const isTrue = () => true;
+
 describe('<DropdownMenu />', () => {
   const icon = 'my-icon';
   const size = 123;
-  const action = sinon.spy();
-
-  const items = [
-    { text: 'first item',  action: action, href: '/some/url' },
-    { text: 'second item', action: 'noop' },
-  ];
-  const wrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} />);
+  let items;
+  let wrapper;
+  let action;
+
+  beforeEach(() => {
+    action = sinon.spy();
+
+    items = [
+      { text: 'first item',  action: action, href: '/some/url' },
+      { text: 'second item', action: 'noop' },
+    ];
+    wrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} />);
+  });
 
   it('contains one <Dropdown />', () => {
     expect(wrapper).to.have.exactly(1).descendants(Dropdown);
@@ -28,6 +36,16 @@ describe('<DropdownMenu />', () => {
     expect(wrapper.find(Dropdown)).to.have.exactly(1).descendants(DropdownContent);
   });
 
+  it('does not contain a <DropdownContent /> if isUserTouching', () => {
+    const touchingWrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} isUserTouching={isTrue} />);
+    expect(touchingWrapper.find(Dropdown)).to.have.exactly(0).descendants(DropdownContent);
+  });
+
+  it('does not contain a <DropdownContent /> if isUserTouching', () => {
+    const touchingWrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} isUserTouching={isTrue} />);
+    expect(touchingWrapper.find(Dropdown)).to.have.exactly(0).descendants(DropdownContent);
+  });
+
   it('uses props.size for <DropdownTrigger /> style values', () => {
     ['font-size', 'width', 'line-height'].map((property) => {
       expect(wrapper.find(DropdownTrigger)).to.have.style(property, `${size}px`);
@@ -53,6 +71,23 @@ describe('<DropdownMenu />', () => {
     expect(wrapper.state('expanded')).to.be.equal(true);
   });
 
+  it('calls onModalOpen when clicking the trigger if isUserTouching', () => {
+    const onModalOpen = sinon.spy();
+    const touchingWrapper = mount(<DropdownMenu icon={icon} items={items} status={3.14} size={size} onModalOpen={onModalOpen} isUserTouching={isTrue} />);
+    touchingWrapper.find(DropdownTrigger).first().simulate('click');
+    expect(onModalOpen.calledOnce).to.be.equal(true);
+    expect(onModalOpen.args[0][0]).to.be.deep.equal({ status: 3.14, actions: items, onClick: touchingWrapper.node.handleClick });
+  });
+
+  it('calls onModalClose when clicking an action if isUserTouching and isModalOpen', () => {
+    const onModalOpen = sinon.spy();
+    const onModalClose = sinon.spy();
+    const touchingWrapper = mount(<DropdownMenu icon={icon} items={items} status={3.14} size={size} isModalOpen onModalOpen={onModalOpen} onModalClose={onModalClose} isUserTouching={isTrue} />);
+    touchingWrapper.find(DropdownTrigger).first().simulate('click');
+    touchingWrapper.node.handleClick({ currentTarget: { getAttribute: () => '0' }, preventDefault: () => null });
+    expect(onModalClose.calledOnce).to.be.equal(true);
+  });
+
   // Error: ReactWrapper::state() can only be called on the root
   /*it('sets expanded to false when clicking outside', () => {
     const wrapper = mount((
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 0b90205ee..626fc3f98 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Status, type: :model do
   let(:alice) { Fabricate(:account, username: 'alice') }
   let(:bob)   { Fabricate(:account, username: 'bob') }
-  let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!')}
+  let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!') }
 
   subject { Fabricate(:status, account: alice) }
 
diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb
new file mode 100644
index 000000000..e2d1a15ec
--- /dev/null
+++ b/spec/validators/status_length_validator_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe StatusLengthValidator do
+  describe '#validate' do
+    it 'does not add errors onto remote statuses'
+    it 'does not add errors onto local reblogs'
+
+    it 'adds an error when content warning is over 500 characters' do
+      status = double(spoiler_text: 'a' * 520, text: '', errors: double(add: nil), local?: true, reblog?: false)
+      subject.validate(status)
+      expect(status.errors).to have_received(:add)
+    end
+
+    it 'adds an error when text is over 500 characters' do
+      status = double(spoiler_text: '', text: 'a' * 520, errors: double(add: nil), local?: true, reblog?: false)
+      subject.validate(status)
+      expect(status.errors).to have_received(:add)
+    end
+
+    it 'adds an error when text and content warning are over 500 characters total' do
+      status = double(spoiler_text: 'a' * 250, text: 'b' * 251, errors: double(add: nil), local?: true, reblog?: false)
+      subject.validate(status)
+      expect(status.errors).to have_received(:add)
+    end
+
+    it 'counts URLs as 23 characters flat' do
+      text   = ('a' * 476) + " http://#{'b' * 30}.com/example"
+      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+
+      subject.validate(status)
+      expect(status.errors).to_not have_received(:add)
+    end
+
+    it 'counts only the front part of remote usernames' do
+      text   = ('a' * 475) + " @alice@#{'b' * 30}.com"
+      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+
+      subject.validate(status)
+      expect(status.errors).to_not have_received(:add)
+    end
+  end
+end
diff --git a/storybook/config.js b/storybook/config.js
deleted file mode 100644
index 87479560f..000000000
--- a/storybook/config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { configure } from '@storybook/react';
-import { addLocaleData } from 'react-intl';
-import en from 'react-intl/locale-data/en';
-import '../app/javascript/styles/application.scss';
-import './storybook.scss';
-
-addLocaleData(en);
-
-let req = require.context('./stories/', true, /.story.js$/);
-
-function loadStories () {
-  req.keys().forEach((filename) => req(filename));
-}
-
-configure(loadStories, module);
diff --git a/storybook/initial_state.js b/storybook/initial_state.js
deleted file mode 100644
index 3872586f6..000000000
--- a/storybook/initial_state.js
+++ /dev/null
@@ -1,24 +0,0 @@
-export default {
-  meta: {
-    admin: 1,
-    domain: 'example.com',
-    me: 2,
-  },
-  accounts: {
-    1: {
-      acct: 'admin',
-      avatar: '/avatars/original/missing.png',
-      id: 1,
-      url: 'https://example.com/@admin',
-    },
-    2: {
-      acct: 'user',
-      avatar: '/avatars/original/missing.png',
-      id: 1,
-      url: 'https://example.com/@user',
-    },
-  },
-  media_attachments: {
-    accept_content_types: [],
-  },
-};
diff --git a/storybook/stories/autosuggest_textarea.story.js b/storybook/stories/autosuggest_textarea.story.js
deleted file mode 100644
index 65dfe965c..000000000
--- a/storybook/stories/autosuggest_textarea.story.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { List } from 'immutable';
-import { storiesOf } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
-import AutosuggestTextarea from 'mastodon/components/autosuggest_textarea';
-
-const props = {
-  onChange: action('changed'),
-  onPaste: action('pasted'),
-  onSuggestionSelected: action('suggestionsSelected'),
-  onSuggestionsClearRequested: action('suggestionsClearRequested'),
-  onSuggestionsFetchRequested: action('suggestionsFetchRequested'),
-  suggestions: List([]),
-};
-
-storiesOf('AutosuggestTextarea', module)
-  .add('default state', () => <AutosuggestTextarea value='' {...props} />)
-  .add('with text', () => <AutosuggestTextarea value='Hello' {...props} />);
diff --git a/storybook/stories/button.story.js b/storybook/stories/button.story.js
deleted file mode 100644
index 1971451e8..000000000
--- a/storybook/stories/button.story.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { storiesOf } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
-import Button from 'mastodon/components/button';
-
-storiesOf('Button', module)
-  .add('default state', () => (
-    <Button text='submit' onClick={action('clicked')} />
-  ))
-  .add('secondary', () => (
-    <Button secondary text='submit' onClick={action('clicked')} />
-  ))
-  .add('disabled', () => (
-    <Button disabled text='submit' onClick={action('clicked')} />
-  ))
-  .add('block', () => (
-    <Button block text='submit' onClick={action('clicked')} />
-  ));
diff --git a/storybook/stories/character_counter.story.js b/storybook/stories/character_counter.story.js
deleted file mode 100644
index 39d9afb56..000000000
--- a/storybook/stories/character_counter.story.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { storiesOf } from '@storybook/react';
-import CharacterCounter from 'mastodon/features/compose/components/character_counter';
-
-storiesOf('CharacterCounter', module)
-  .add('no text', () => {
-    const text = '';
-    return <CharacterCounter text={text} max={500} />;
-  })
-  .add('a few strings text', () => {
-    const text = '0123456789';
-    return <CharacterCounter text={text} max={500} />;
-  })
-  .add('the same text', () => {
-    const text = '01234567890123456789';
-    return <CharacterCounter text={text} max={20} />;
-  })
-  .add('over text', () => {
-    const text = '01234567890123456789012345678901234567890123456789';
-    return <CharacterCounter text={text} max={10} />;
-  });
diff --git a/storybook/stories/loading_indicator.story.js b/storybook/stories/loading_indicator.story.js
deleted file mode 100644
index 6ee822758..000000000
--- a/storybook/stories/loading_indicator.story.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { IntlProvider } from 'react-intl';
-import { storiesOf } from '@storybook/react';
-import en from 'mastodon/locales/en.json';
-import LoadingIndicator from 'mastodon/components/loading_indicator';
-
-storiesOf('LoadingIndicator', module)
-  .add('default state', () => (
-    <IntlProvider locale='en' messages={en}>
-      <LoadingIndicator />
-    </IntlProvider>
-  ));
diff --git a/storybook/stories/onboarding_modal.story.js b/storybook/stories/onboarding_modal.story.js
deleted file mode 100644
index 91727bdb2..000000000
--- a/storybook/stories/onboarding_modal.story.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import { Provider } from 'react-redux';
-import { IntlProvider } from 'react-intl';
-import { storiesOf } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
-import en from 'mastodon/locales/en.json';
-import configureStore from 'mastodon/store/configureStore';
-import { hydrateStore } from 'mastodon/actions/store';
-import OnboadingModal from 'mastodon/features/ui/components/onboarding_modal';
-import initialState from '../initial_state';
-
-const store = configureStore();
-store.dispatch(hydrateStore(initialState));
-
-storiesOf('OnboadingModal', module)
-  .add('default state', () => (
-    <IntlProvider locale='en' messages={en}>
-      <Provider store={store}>
-        <div style={{ position: 'absolute' }}>
-          <OnboadingModal onClose={action('close')} />
-        </div>
-      </Provider>
-    </IntlProvider>
-  ));
diff --git a/storybook/storybook.scss b/storybook/storybook.scss
deleted file mode 100644
index 3bda9e64c..000000000
--- a/storybook/storybook.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-#root {
-  padding: 4rem;
-}
diff --git a/storybook/webpack.config.js b/storybook/webpack.config.js
deleted file mode 100644
index e16910b29..000000000
--- a/storybook/webpack.config.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const path = require('path');
-
-module.exports = {
-  module: {
-    rules: [
-      {
-        test: /\.(jpg|jpeg|png|gif|svg|eot|ttf|woff|woff2)$/i,
-        loader: 'url-loader',
-      },
-      {
-        test: /.scss$/,
-        loaders: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
-      },
-    ],
-  },
-  resolve: {
-    alias: {
-      mastodon: path.resolve(__dirname, '..', 'app', 'javascript', 'mastodon'),
-    },
-  },
-};
diff --git a/yarn.lock b/yarn.lock
index 0f895f3b6..cfb0f5175 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,125 +2,6 @@
 # yarn lockfile v1
 
 
-"@storybook/addon-actions@^3.1.8":
-  version "3.1.8"
-  resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.1.8.tgz#2b6d7aa97530b19965c1010b822f40b130ebbc4d"
-  dependencies:
-    "@storybook/addons" "^3.1.6"
-    deep-equal "^1.0.1"
-    json-stringify-safe "^5.0.1"
-    prop-types "^15.5.8"
-    react-inspector "^2.1.1"
-    uuid "^3.1.0"
-
-"@storybook/addon-links@^3.1.6":
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-3.1.6.tgz#62c8a839e54ff0adb04c6023dae467b336ced5d9"
-  dependencies:
-    "@storybook/addons" "^3.1.6"
-
-"@storybook/addons@^3.1.6":
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.1.6.tgz#29ef2348550f5a74d5e83dd75d04714cac751c39"
-
-"@storybook/channel-postmessage@^3.1.6":
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-3.1.6.tgz#867768a2ca2efbd796432300fe5e9b834d9c2ca5"
-  dependencies:
-    "@storybook/channels" "^3.1.6"
-    global "^4.3.2"
-    json-stringify-safe "^5.0.1"
-
-"@storybook/channels@^3.1.6":
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-3.1.6.tgz#81d61591bf7613dd2bcd81d26da40aeaa2899034"
-
-"@storybook/react-fuzzy@^0.4.0":
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/@storybook/react-fuzzy/-/react-fuzzy-0.4.0.tgz#2961e8a1f6c1afcce97e9e9a14d1dfe9d9061087"
-  dependencies:
-    babel-runtime "^6.23.0"
-    classnames "^2.2.5"
-    fuse.js "^3.0.1"
-    prop-types "^15.5.9"
-
-"@storybook/react@^3.1.8":
-  version "3.1.8"
-  resolved "https://registry.yarnpkg.com/@storybook/react/-/react-3.1.8.tgz#4d140c5ae7e9b5eaf627f2071d7324aa38c7af49"
-  dependencies:
-    "@storybook/addon-actions" "^3.1.8"
-    "@storybook/addon-links" "^3.1.6"
-    "@storybook/addons" "^3.1.6"
-    "@storybook/channel-postmessage" "^3.1.6"
-    "@storybook/ui" "^3.1.6"
-    airbnb-js-shims "^1.1.1"
-    autoprefixer "^7.1.1"
-    babel-core "^6.24.1"
-    babel-loader "^7.0.0"
-    babel-plugin-react-docgen "^1.5.0"
-    babel-preset-es2015 "^6.24.1"
-    babel-preset-es2016 "^6.24.1"
-    babel-preset-react "^6.24.1"
-    babel-preset-react-app "^3.0.0"
-    babel-preset-stage-0 "^6.24.1"
-    babel-runtime "^6.23.0"
-    case-sensitive-paths-webpack-plugin "^2.0.0"
-    chalk "^1.1.3"
-    commander "^2.9.0"
-    common-tags "^1.4.0"
-    configstore "^3.1.0"
-    css-loader "^0.28.1"
-    express "^4.15.3"
-    file-loader "^0.11.1"
-    find-cache-dir "^1.0.0"
-    glamor "^2.20.25"
-    glamorous "^3.22.1"
-    global "^4.3.2"
-    json-loader "^0.5.4"
-    json-stringify-safe "^5.0.1"
-    json5 "^0.5.1"
-    lodash.pick "^4.4.0"
-    postcss-flexbugs-fixes "^3.0.0"
-    postcss-loader "^2.0.5"
-    prop-types "^15.5.10"
-    qs "^6.4.0"
-    react-modal "^1.7.7"
-    redux "^3.6.0"
-    request "^2.81.0"
-    serve-favicon "^2.4.3"
-    shelljs "^0.7.7"
-    style-loader "^0.17.0"
-    url-loader "^0.5.8"
-    util-deprecate "^1.0.2"
-    uuid "^3.0.1"
-    webpack "^2.5.1 || ^3.0.0"
-    webpack-dev-middleware "^1.10.2"
-    webpack-hot-middleware "^2.18.0"
-
-"@storybook/ui@^3.1.6":
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-3.1.6.tgz#5d47c6003a2d78c06ede43861089747d986d918e"
-  dependencies:
-    "@storybook/react-fuzzy" "^0.4.0"
-    babel-runtime "^6.23.0"
-    deep-equal "^1.0.1"
-    events "^1.1.1"
-    fuzzysearch "^1.0.3"
-    global "^4.3.2"
-    json-stringify-safe "^5.0.1"
-    keycode "^2.1.8"
-    lodash.pick "^4.4.0"
-    lodash.sortby "^4.7.0"
-    mantra-core "^1.7.0"
-    podda "^1.2.2"
-    prop-types "^15.5.8"
-    qs "^6.4.0"
-    react-inspector "^2.0.0"
-    react-komposer "^2.0.0"
-    react-modal "^1.7.6"
-    react-split-pane "^0.1.63"
-    redux "^3.6.0"
-
 "@types/node@^6.0.46":
   version "6.0.80"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.80.tgz#914a75799605b4609bd9a2918c865ba3c4141367"
@@ -166,7 +47,7 @@ acorn@^4.0.3, acorn@^4.0.4:
   version "4.0.13"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
 
-acorn@^5.0.0, acorn@^5.0.1, acorn@^5.0.3:
+acorn@^5.0.0, acorn@^5.0.1, acorn@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
 
@@ -182,19 +63,6 @@ adjust-sourcemap-loader@^1.1.0:
     object-path "^0.9.2"
     regex-parser "^2.2.1"
 
-airbnb-js-shims@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-1.1.1.tgz#27224f0030f244e6570442ed1020772c1434aec2"
-  dependencies:
-    array-includes "^3.0.2"
-    es5-shim "^4.5.9"
-    es6-shim "^0.35.1"
-    object.entries "^1.0.3"
-    object.getownpropertydescriptors "^2.0.3"
-    object.values "^1.0.3"
-    string.prototype.padend "^3.0.0"
-    string.prototype.padstart "^3.0.0"
-
 ajv-keywords@^1.0.0:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
@@ -325,7 +193,7 @@ array-flatten@^2.1.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
 
-array-includes@^3.0.2, array-includes@^3.0.3:
+array-includes@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
   dependencies:
@@ -395,10 +263,6 @@ ast-types-flow@0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
 
-ast-types@0.9.6:
-  version "0.9.6"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
-
 async-each@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -415,7 +279,7 @@ async@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
-async@^2.1.2, async@^2.1.4, async@^2.1.5:
+async@^2.1.2, async@^2.1.5:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
   dependencies:
@@ -440,7 +304,7 @@ autoprefixer@^6.3.1:
     postcss "^5.2.16"
     postcss-value-parser "^3.2.3"
 
-autoprefixer@^7.1.1, autoprefixer@^7.1.2:
+autoprefixer@^7.1.2:
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.2.tgz#fbeaf07d48fd878e0682bf7cbeeade728adb2b18"
   dependencies:
@@ -520,14 +384,6 @@ babel-generator@^6.25.0:
     source-map "^0.5.0"
     trim-right "^1.0.1"
 
-babel-helper-bindify-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
@@ -570,15 +426,6 @@ babel-helper-explode-assignable-expression@^6.24.1:
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-helper-explode-class@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb"
-  dependencies:
-    babel-helper-bindify-decorators "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-helper-function-name@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
@@ -646,7 +493,7 @@ babel-helpers@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-loader@^7.0.0, babel-loader@^7.1.1:
+babel-loader@^7.1.1:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.1.tgz#b87134c8b12e3e4c2a94e0546085bc680a2b8488"
   dependencies:
@@ -666,14 +513,6 @@ babel-plugin-check-es2015-constants@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-dynamic-import-node@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-1.0.2.tgz#adb5bc8f48a89311540395ae9f0cc3ed4b10bb2e"
-  dependencies:
-    babel-plugin-syntax-dynamic-import "^6.18.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-plugin-lodash@^3.2.11:
   version "3.2.11"
   resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.2.11.tgz#21c8fdec9fe1835efaa737873e3902bdd66d5701"
@@ -689,14 +528,6 @@ babel-plugin-preval@^1.3.2:
     babylon "^6.17.4"
     require-from-string "^1.2.1"
 
-babel-plugin-react-docgen@^1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-react-docgen/-/babel-plugin-react-docgen-1.5.0.tgz#0339717ad51f4a5ce4349330b8266ea5a56f53b4"
-  dependencies:
-    babel-types "^6.24.1"
-    lodash "4.x.x"
-    react-docgen "^2.15.0"
-
 babel-plugin-react-intl@^2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-react-intl/-/babel-plugin-react-intl-2.3.1.tgz#3d43912e824da005e08e8e8239d5ba784374bb00"
@@ -715,27 +546,15 @@ babel-plugin-syntax-async-functions@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
 
-babel-plugin-syntax-async-generators@^6.5.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
-
-babel-plugin-syntax-class-constructor-call@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416"
-
 babel-plugin-syntax-class-properties@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
 
-babel-plugin-syntax-decorators@^6.1.18, babel-plugin-syntax-decorators@^6.13.0:
+babel-plugin-syntax-decorators@^6.1.18:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
 
-babel-plugin-syntax-do-expressions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d"
-
-babel-plugin-syntax-dynamic-import@6.18.0, babel-plugin-syntax-dynamic-import@^6.18.0:
+babel-plugin-syntax-dynamic-import@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
 
@@ -743,18 +562,10 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
 
-babel-plugin-syntax-export-extensions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
-
 babel-plugin-syntax-flow@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
 
-babel-plugin-syntax-function-bind@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46"
-
 babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
@@ -767,15 +578,7 @@ babel-plugin-syntax-trailing-function-commas@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
 
-babel-plugin-transform-async-generator-functions@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-generators "^6.5.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1:
+babel-plugin-transform-async-to-generator@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
   dependencies:
@@ -783,15 +586,7 @@ babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-
     babel-plugin-syntax-async-functions "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-class-constructor-call@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9"
-  dependencies:
-    babel-plugin-syntax-class-constructor-call "^6.18.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-class-properties@6.24.1, babel-plugin-transform-class-properties@^6.24.1:
+babel-plugin-transform-class-properties@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
   dependencies:
@@ -808,23 +603,6 @@ babel-plugin-transform-decorators-legacy@^1.3.4:
     babel-runtime "^6.2.0"
     babel-template "^6.3.0"
 
-babel-plugin-transform-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d"
-  dependencies:
-    babel-helper-explode-class "^6.24.1"
-    babel-plugin-syntax-decorators "^6.13.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-do-expressions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz#28ccaf92812d949c2cd1281f690c8fdc468ae9bb"
-  dependencies:
-    babel-plugin-syntax-do-expressions "^6.8.0"
-    babel-runtime "^6.22.0"
-
 babel-plugin-transform-es2015-arrow-functions@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
@@ -837,7 +615,7 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1:
+babel-plugin-transform-es2015-block-scoping@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576"
   dependencies:
@@ -847,7 +625,7 @@ babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es20
     babel-types "^6.24.1"
     lodash "^4.2.0"
 
-babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1:
+babel-plugin-transform-es2015-classes@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
   dependencies:
@@ -861,33 +639,33 @@ babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-cla
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1:
+babel-plugin-transform-es2015-computed-properties@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
   dependencies:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0:
+babel-plugin-transform-es2015-destructuring@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
+babel-plugin-transform-es2015-duplicate-keys@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
   dependencies:
     babel-runtime "^6.22.0"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0:
+babel-plugin-transform-es2015-for-of@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1:
+babel-plugin-transform-es2015-function-name@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
   dependencies:
@@ -918,7 +696,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-e
     babel-template "^6.24.1"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
+babel-plugin-transform-es2015-modules-systemjs@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
   dependencies:
@@ -926,7 +704,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-e
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1:
+babel-plugin-transform-es2015-modules-umd@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
   dependencies:
@@ -934,14 +712,14 @@ babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1:
+babel-plugin-transform-es2015-object-super@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
   dependencies:
     babel-helper-replace-supers "^6.24.1"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1:
+babel-plugin-transform-es2015-parameters@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
   dependencies:
@@ -952,7 +730,7 @@ babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
+babel-plugin-transform-es2015-shorthand-properties@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
   dependencies:
@@ -965,7 +743,7 @@ babel-plugin-transform-es2015-spread@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1:
+babel-plugin-transform-es2015-sticky-regex@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
   dependencies:
@@ -979,13 +757,13 @@ babel-plugin-transform-es2015-template-literals@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
+babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1:
+babel-plugin-transform-es2015-unicode-regex@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
   dependencies:
@@ -993,7 +771,7 @@ babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es20
     babel-runtime "^6.22.0"
     regexpu-core "^2.0.0"
 
-babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1:
+babel-plugin-transform-exponentiation-operator@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
   dependencies:
@@ -1001,13 +779,6 @@ babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-e
     babel-plugin-syntax-exponentiation-operator "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-export-extensions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653"
-  dependencies:
-    babel-plugin-syntax-export-extensions "^6.8.0"
-    babel-runtime "^6.22.0"
-
 babel-plugin-transform-flow-strip-types@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
@@ -1015,26 +786,13 @@ babel-plugin-transform-flow-strip-types@^6.22.0:
     babel-plugin-syntax-flow "^6.18.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-function-bind@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97"
-  dependencies:
-    babel-plugin-syntax-function-bind "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-object-rest-spread@6.23.0, babel-plugin-transform-object-rest-spread@^6.22.0, babel-plugin-transform-object-rest-spread@^6.23.0:
+babel-plugin-transform-object-rest-spread@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
   dependencies:
     babel-plugin-syntax-object-rest-spread "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-react-constant-elements@6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-constant-elements/-/babel-plugin-transform-react-constant-elements-6.23.0.tgz#2f119bf4d2cdd45eb9baaae574053c604f6147dd"
-  dependencies:
-    babel-runtime "^6.22.0"
-
 babel-plugin-transform-react-display-name@^6.23.0:
   version "6.25.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1"
@@ -1047,21 +805,21 @@ babel-plugin-transform-react-inline-elements@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-react-jsx-self@6.22.0, babel-plugin-transform-react-jsx-self@^6.22.0:
+babel-plugin-transform-react-jsx-self@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e"
   dependencies:
     babel-plugin-syntax-jsx "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-react-jsx-source@6.22.0, babel-plugin-transform-react-jsx-source@^6.22.0:
+babel-plugin-transform-react-jsx-source@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6"
   dependencies:
     babel-plugin-syntax-jsx "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-react-jsx@6.24.1, babel-plugin-transform-react-jsx@^6.24.1:
+babel-plugin-transform-react-jsx@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3"
   dependencies:
@@ -1075,13 +833,13 @@ babel-plugin-transform-react-remove-prop-types@^0.4.6:
   dependencies:
     babel-traverse "^6.24.1"
 
-babel-plugin-transform-regenerator@6.24.1, babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1:
+babel-plugin-transform-regenerator@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz#b8da305ad43c3c99b4848e4fe4037b770d23c418"
   dependencies:
     regenerator-transform "0.9.11"
 
-babel-plugin-transform-runtime@6.23.0, babel-plugin-transform-runtime@^6.23.0:
+babel-plugin-transform-runtime@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee"
   dependencies:
@@ -1094,41 +852,6 @@ babel-plugin-transform-strict-mode@^6.24.1:
     babel-runtime "^6.22.0"
     babel-types "^6.24.1"
 
-babel-preset-env@1.5.2:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.5.2.tgz#cd4ae90a6e94b709f97374b33e5f8b983556adef"
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-syntax-trailing-function-commas "^6.22.0"
-    babel-plugin-transform-async-to-generator "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.23.0"
-    babel-plugin-transform-es2015-classes "^6.23.0"
-    babel-plugin-transform-es2015-computed-properties "^6.22.0"
-    babel-plugin-transform-es2015-destructuring "^6.23.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
-    babel-plugin-transform-es2015-for-of "^6.23.0"
-    babel-plugin-transform-es2015-function-name "^6.22.0"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.22.0"
-    babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
-    babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
-    babel-plugin-transform-es2015-modules-umd "^6.23.0"
-    babel-plugin-transform-es2015-object-super "^6.22.0"
-    babel-plugin-transform-es2015-parameters "^6.23.0"
-    babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.22.0"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.22.0"
-    babel-plugin-transform-exponentiation-operator "^6.22.0"
-    babel-plugin-transform-regenerator "^6.22.0"
-    browserslist "^2.1.2"
-    invariant "^2.2.2"
-    semver "^5.3.0"
-
 babel-preset-env@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4"
@@ -1164,65 +887,13 @@ babel-preset-env@^1.6.0:
     invariant "^2.2.2"
     semver "^5.3.0"
 
-babel-preset-es2015@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.24.1"
-    babel-plugin-transform-es2015-classes "^6.24.1"
-    babel-plugin-transform-es2015-computed-properties "^6.24.1"
-    babel-plugin-transform-es2015-destructuring "^6.22.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
-    babel-plugin-transform-es2015-for-of "^6.22.0"
-    babel-plugin-transform-es2015-function-name "^6.24.1"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-umd "^6.24.1"
-    babel-plugin-transform-es2015-object-super "^6.24.1"
-    babel-plugin-transform-es2015-parameters "^6.24.1"
-    babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.24.1"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.24.1"
-    babel-plugin-transform-regenerator "^6.24.1"
-
-babel-preset-es2016@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz#f900bf93e2ebc0d276df9b8ab59724ebfd959f8b"
-  dependencies:
-    babel-plugin-transform-exponentiation-operator "^6.24.1"
-
 babel-preset-flow@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d"
   dependencies:
     babel-plugin-transform-flow-strip-types "^6.22.0"
 
-babel-preset-react-app@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-3.0.1.tgz#8b744cbe47fd57c868e6f913552ceae26ae31860"
-  dependencies:
-    babel-plugin-dynamic-import-node "1.0.2"
-    babel-plugin-syntax-dynamic-import "6.18.0"
-    babel-plugin-transform-class-properties "6.24.1"
-    babel-plugin-transform-object-rest-spread "6.23.0"
-    babel-plugin-transform-react-constant-elements "6.23.0"
-    babel-plugin-transform-react-jsx "6.24.1"
-    babel-plugin-transform-react-jsx-self "6.22.0"
-    babel-plugin-transform-react-jsx-source "6.22.0"
-    babel-plugin-transform-regenerator "6.24.1"
-    babel-plugin-transform-runtime "6.23.0"
-    babel-preset-env "1.5.2"
-    babel-preset-react "6.24.1"
-
-babel-preset-react@6.24.1, babel-preset-react@^6.24.1:
+babel-preset-react@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380"
   dependencies:
@@ -1233,41 +904,6 @@ babel-preset-react@6.24.1, babel-preset-react@^6.24.1:
     babel-plugin-transform-react-jsx-source "^6.22.0"
     babel-preset-flow "^6.23.0"
 
-babel-preset-stage-0@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz#5642d15042f91384d7e5af8bc88b1db95b039e6a"
-  dependencies:
-    babel-plugin-transform-do-expressions "^6.22.0"
-    babel-plugin-transform-function-bind "^6.22.0"
-    babel-preset-stage-1 "^6.24.1"
-
-babel-preset-stage-1@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0"
-  dependencies:
-    babel-plugin-transform-class-constructor-call "^6.24.1"
-    babel-plugin-transform-export-extensions "^6.22.0"
-    babel-preset-stage-2 "^6.24.1"
-
-babel-preset-stage-2@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1"
-  dependencies:
-    babel-plugin-syntax-dynamic-import "^6.18.0"
-    babel-plugin-transform-class-properties "^6.24.1"
-    babel-plugin-transform-decorators "^6.24.1"
-    babel-preset-stage-3 "^6.24.1"
-
-babel-preset-stage-3@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395"
-  dependencies:
-    babel-plugin-syntax-trailing-function-commas "^6.22.0"
-    babel-plugin-transform-async-generator-functions "^6.24.1"
-    babel-plugin-transform-async-to-generator "^6.24.1"
-    babel-plugin-transform-exponentiation-operator "^6.24.1"
-    babel-plugin-transform-object-rest-spread "^6.22.0"
-
 babel-register@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f"
@@ -1280,7 +916,7 @@ babel-register@^6.24.1:
     mkdirp "^0.5.1"
     source-map-support "^0.4.2"
 
-babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.5.0, babel-runtime@^6.9.2:
+babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
   dependencies:
@@ -1324,10 +960,6 @@ babylon@^6.17.0, babylon@^6.17.2, babylon@^6.17.4:
   version "6.17.4"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a"
 
-babylon@~5.8.3:
-  version "5.8.38"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-5.8.38.tgz#ec9b120b11bf6ccd4173a18bf217e60b79859ffd"
-
 backoff@^2.4.1:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f"
@@ -1399,10 +1031,6 @@ boom@2.x.x:
   dependencies:
     hoek "2.x.x"
 
-bowser@^1.6.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.0.tgz#169de4018711f994242bff9a8009e77a1f35e003"
-
 brace-expansion@^1.1.7:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
@@ -1418,10 +1046,6 @@ braces@^1.8.2:
     preserve "^0.2.0"
     repeat-element "^1.1.2"
 
-brcast@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/brcast/-/brcast-2.0.1.tgz#4311508f0634a6f5a2465b6cf2db27f06902aaca"
-
 brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -1581,10 +1205,6 @@ caniuse-lite@^1.0.30000684, caniuse-lite@^1.0.30000697:
   version "1.0.30000700"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000700.tgz#6084871ec75c6fa62327de97622514f95d9db26a"
 
-case-sensitive-paths-webpack-plugin@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.1.tgz#3d29ced8c1f124bf6f53846fb3f5894731fdc909"
-
 caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -1657,7 +1277,7 @@ cheerio@^0.22.0:
     lodash.reject "^4.4.0"
     lodash.some "^4.4.0"
 
-chokidar@^1.4.3, chokidar@^1.6.0, chokidar@^1.7.0:
+chokidar@^1.6.0, chokidar@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
   dependencies:
@@ -1802,12 +1422,6 @@ commander@^2.8.1, commander@^2.9.0:
   version "2.11.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
 
-common-tags@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0"
-  dependencies:
-    babel-runtime "^6.18.0"
-
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -1855,17 +1469,6 @@ concat-stream@^1.4.7, concat-stream@^1.5.2:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
-configstore@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.0.tgz#45df907073e26dfa1cf4b2d52f5b60545eaa11d1"
-  dependencies:
-    dot-prop "^4.1.0"
-    graceful-fs "^4.1.2"
-    make-dir "^1.0.0"
-    unique-string "^1.0.0"
-    write-file-atomic "^2.0.0"
-    xdg-basedir "^3.0.0"
-
 connect-history-api-fallback@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169"
@@ -1963,7 +1566,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
-create-react-class@^15.5.2, create-react-class@^15.5.3, create-react-class@^15.6.0:
+create-react-class@^15.5.3, create-react-class@^15.6.0:
   version "15.6.0"
   resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.0.tgz#ab448497c26566e1e29413e883207d57cfe7bed4"
   dependencies:
@@ -2021,10 +1624,6 @@ crypto-browserify@^3.11.0:
     public-encrypt "^4.0.0"
     randombytes "^2.0.0"
 
-crypto-random-string@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
-
 css-color-function@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.0.tgz#72c767baf978f01b8a8a94f42f17ba5d22a776fc"
@@ -2058,19 +1657,13 @@ css-global-keywords@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69"
 
-css-in-js-utils@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-1.0.3.tgz#9ac7e02f763cf85d94017666565ed68a5b5f3215"
-  dependencies:
-    hyphenate-style-name "^1.0.2"
-
 css-list-helpers@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/css-list-helpers/-/css-list-helpers-1.0.1.tgz#fff57192202db83240c41686f919e449a7024f7d"
   dependencies:
     tcomb "^2.5.0"
 
-css-loader@^0.28.1, css-loader@^0.28.4:
+css-loader@^0.28.4:
   version "0.28.4"
   resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.4.tgz#6cf3579192ce355e8b38d5f42dd7a1f2ec898d0f"
   dependencies:
@@ -2219,7 +1812,7 @@ debug@2.6.7:
   dependencies:
     ms "2.0.0"
 
-debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.8:
+debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.6, debug@^2.6.8:
   version "2.6.8"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
   dependencies:
@@ -2390,10 +1983,6 @@ dom-serializer@0, dom-serializer@~0.1.0:
     domelementtype "~1.1.1"
     entities "~1.1.1"
 
-dom-walk@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
-
 domain-browser@^1.1.1:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
@@ -2426,12 +2015,6 @@ domutils@^1.5.1:
     dom-serializer "0"
     domelementtype "1"
 
-dot-prop@^4.1.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.1.tgz#a8493f0b7b5eeec82525b5c7587fa7de7ca859c1"
-  dependencies:
-    is-obj "^1.0.0"
-
 dotenv@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
@@ -2466,10 +2049,6 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.14:
   version "1.3.15"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.15.tgz#08397934891cbcfaebbd18b82a95b5a481138369"
 
-element-class@^0.2.0:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/element-class/-/element-class-0.2.2.tgz#9d3bbd0767f9013ef8e1c8ebe722c1402a60050e"
-
 elliptic@^6.0.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
@@ -2516,14 +2095,14 @@ encoding@^0.1.11:
   dependencies:
     iconv-lite "~0.4.13"
 
-enhanced-resolve@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz#950964ecc7f0332a42321b673b38dc8ff15535b3"
+enhanced-resolve@^3.4.0:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
   dependencies:
     graceful-fs "^4.1.2"
     memory-fs "^0.4.0"
     object-assign "^4.0.1"
-    tapable "^0.2.5"
+    tapable "^0.2.7"
 
 entities@^1.1.1, entities@~1.1.1:
   version "1.1.1"
@@ -2556,7 +2135,7 @@ error-ex@^1.2.0:
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0:
+es-abstract@^1.6.1, es-abstract@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
   dependencies:
@@ -2580,10 +2159,6 @@ es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
     es6-iterator "2"
     es6-symbol "~3.1"
 
-es5-shim@^4.5.9:
-  version "4.5.9"
-  resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.9.tgz#2a1e2b9e583ff5fed0c20a3ee2cbf3f75230a5c0"
-
 es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
@@ -2613,10 +2188,6 @@ es6-set@~0.1.5:
     es6-symbol "3.1.1"
     event-emitter "~0.3.5"
 
-es6-shim@^0.35.1:
-  version "0.35.3"
-  resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26"
-
 es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
@@ -2737,10 +2308,6 @@ esprima@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
 
-esprima@~3.1.0:
-  version "3.1.3"
-  resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
-
 esquery@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
@@ -2781,7 +2348,7 @@ eventemitter3@1.x.x:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
 
-events@^1.0.0, events@^1.1.1:
+events@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
 
@@ -2809,10 +2376,6 @@ execa@^0.5.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
-exenv@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.0.tgz#3835f127abf075bfe082d0aed4484057c78e3c89"
-
 exit-hook@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@@ -2829,7 +2392,7 @@ expand-range@^1.8.1:
   dependencies:
     fill-range "^2.1.0"
 
-express@^4.13.3, express@^4.15.2, express@^4.15.3:
+express@^4.13.3, express@^4.15.2:
   version "4.15.3"
   resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662"
   dependencies:
@@ -2893,10 +2456,6 @@ fast-levenshtein@~2.0.4:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
 
-fast-memoize@^2.2.7:
-  version "2.2.7"
-  resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.2.7.tgz#f145c5c22039cedf0a1d4ff6ca592ad0268470ca"
-
 fastparse@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
@@ -2913,7 +2472,7 @@ faye-websocket@~0.11.0:
   dependencies:
     websocket-driver ">=0.5.1"
 
-fbjs@^0.8.4, fbjs@^0.8.8, fbjs@^0.8.9:
+fbjs@^0.8.4, fbjs@^0.8.9:
   version "0.8.12"
   resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
   dependencies:
@@ -2939,7 +2498,7 @@ file-entry-cache@^2.0.0:
     flat-cache "^1.2.1"
     object-assign "^4.0.1"
 
-file-loader@^0.11.1, file-loader@^0.11.2:
+file-loader@^0.11.2:
   version "0.11.2"
   resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.11.2.tgz#4ff1df28af38719a6098093b88c82c71d1794a34"
   dependencies:
@@ -3123,14 +2682,6 @@ function.prototype.name@^1.0.0:
     function-bind "^1.1.0"
     is-callable "^1.1.3"
 
-fuse.js@^3.0.1:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.0.5.tgz#b58d85878802321de94461654947b93af1086727"
-
-fuzzysearch@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/fuzzysearch/-/fuzzysearch-1.0.3.tgz#dffc80f6d6b04223f2226aa79dd194231096d008"
-
 gauge@~2.7.3:
   version "2.7.4"
   resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -3189,25 +2740,6 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
-glamor@^2.20.25:
-  version "2.20.25"
-  resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.25.tgz#71b84b82b67a9327771ac59de53ee915d148a4a3"
-  dependencies:
-    babel-runtime "^6.18.0"
-    fbjs "^0.8.8"
-    object-assign "^4.1.0"
-    prop-types "^15.5.8"
-
-glamorous@^3.22.1:
-  version "3.23.5"
-  resolved "https://registry.yarnpkg.com/glamorous/-/glamorous-3.23.5.tgz#49f613a29f64cdee80948679c66dbcd4084e5fd5"
-  dependencies:
-    brcast "^2.0.0"
-    fast-memoize "^2.2.7"
-    html-tag-names "^1.1.1"
-    react-html-attributes "^1.3.0"
-    svg-tag-names "^1.1.0"
-
 glob-base@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -3243,13 +2775,6 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-global@^4.3.2:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
-  dependencies:
-    min-document "^2.19.0"
-    process "~0.5.1"
-
 globals@^9.0.0, globals@^9.14.0:
   version "9.18.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@@ -3289,7 +2814,7 @@ gonzales-pe@^4.0.3:
   dependencies:
     minimist "1.1.x"
 
-graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
@@ -3390,7 +2915,7 @@ hoek@2.x.x:
   version "2.16.3"
   resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
 
-hoist-non-react-statics@1.x.x, hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
+hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
 
@@ -3418,10 +2943,6 @@ html-comment-regex@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
 
-html-element-attributes@^1.0.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/html-element-attributes/-/html-element-attributes-1.3.0.tgz#f06ebdfce22de979db82020265cac541fb17d4fc"
-
 html-encoding-sniffer@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da"
@@ -3432,10 +2953,6 @@ html-entities@^1.2.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
 
-html-tag-names@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/html-tag-names/-/html-tag-names-1.1.2.tgz#f65168964c5a9c82675efda882875dcb2a875c22"
-
 html@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/html/-/html-1.0.0.tgz#a544fa9ea5492bfb3a2cca8210a10be7b5af1f61"
@@ -3498,10 +3015,6 @@ https-browserify@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
 
-hyphenate-style-name@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b"
-
 iconv-lite@0.4.13:
   version "0.4.13"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
@@ -3573,13 +3086,6 @@ ini@~1.3.0:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
 
-inline-style-prefixer@^3.0.6:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.6.tgz#b27fe309b4168a31eaf38c8e8c60ab9e7c11731f"
-  dependencies:
-    bowser "^1.6.0"
-    css-in-js-utils "^1.0.3"
-
 inquirer@^0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
@@ -3608,9 +3114,9 @@ interpret@^1.0.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
 
-intersection-observer@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.3.2.tgz#9ed30021c08b29e9e8565c8d512ed84515727433"
+intersection-observer@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.4.0.tgz#e7c3580be96fc1698170250b500da986c59824fb"
 
 intl-format-cache@^2.0.5:
   version "2.0.5"
@@ -3652,7 +3158,7 @@ intl@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde"
 
-invariant@2.x.x, invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
   dependencies:
@@ -3706,10 +3212,6 @@ is-directory@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
 
-is-dom@^1.0.9:
-  version "1.0.9"
-  resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.0.9.tgz#483832d52972073de12b9fe3f60320870da8370d"
-
 is-dotfile@^1.0.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
@@ -3787,10 +3289,6 @@ is-number@^3.0.0:
   dependencies:
     kind-of "^3.0.2"
 
-is-obj@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
-
 is-path-cwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@@ -3990,7 +3488,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
   dependencies:
     jsonify "~0.0.0"
 
-json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
+json-stringify-safe@~5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
 
@@ -4029,7 +3527,7 @@ jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.4:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
 
-keycode@^2.1.7, keycode@^2.1.8:
+keycode@^2.1.7:
   version "2.1.9"
   resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
 
@@ -4231,7 +3729,7 @@ lodash.isarray@^3.0.0:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
 
-lodash.keys@^3.0.0, lodash.keys@^3.1.2:
+lodash.keys@^3.0.0:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
   dependencies:
@@ -4255,7 +3753,7 @@ lodash.mergewith@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
 
-lodash.pick@^4.2.1, lodash.pick@^4.4.0:
+lodash.pick@^4.2.1:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
 
@@ -4287,10 +3785,14 @@ lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
-lodash@4.x.x, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.4:
+"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.4:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
 
+loglevel@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd"
+
 lolex@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
@@ -4329,14 +3831,6 @@ make-dir@^1.0.0:
   dependencies:
     pify "^2.3.0"
 
-mantra-core@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/mantra-core/-/mantra-core-1.7.0.tgz#a8c83e8cee83ef6a7383131519fe8031ad546386"
-  dependencies:
-    babel-runtime "6.x.x"
-    react-komposer "^1.9.0"
-    react-simple-di "^1.2.0"
-
 map-obj@^1.0.0, map-obj@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
@@ -4448,7 +3942,7 @@ mime@1.3.4:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
 
-mime@1.3.x, mime@^1.3.4:
+mime@^1.3.4:
   version "1.3.6"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
 
@@ -4456,12 +3950,6 @@ mimic-fn@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
 
-min-document@^2.19.0:
-  version "2.19.0"
-  resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
-  dependencies:
-    dom-walk "^0.1.0"
-
 minimalistic-assert@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
@@ -4501,10 +3989,6 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi
   dependencies:
     minimist "0.0.8"
 
-mobx@^2.3.4:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/mobx/-/mobx-2.7.0.tgz#cf3d82d18c0ca7f458d8f2a240817b3dc7e54a01"
-
 mocha@^3.4.1:
   version "3.4.2"
   resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.4.2.tgz#d0ef4d332126dbf18d0d640c9b382dd48be97594"
@@ -4560,12 +4044,6 @@ negotiator@0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
 
-node-dir@^0.1.10:
-  version "0.1.17"
-  resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
-  dependencies:
-    minimatch "^3.0.2"
-
 node-fetch@^1.0.1:
   version "1.7.1"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5"
@@ -4779,7 +4257,7 @@ object.assign@^4.0.4:
     function-bind "^1.1.0"
     object-keys "^1.0.10"
 
-object.entries@^1.0.3, object.entries@^1.0.4:
+object.entries@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f"
   dependencies:
@@ -4788,13 +4266,6 @@ object.entries@^1.0.3, object.entries@^1.0.4:
     function-bind "^1.1.0"
     has "^1.0.1"
 
-object.getownpropertydescriptors@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.5.1"
-
 object.omit@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
@@ -4802,7 +4273,7 @@ object.omit@^2.0.0:
     for-own "^0.1.4"
     is-extendable "^0.1.1"
 
-object.values@^1.0.3, object.values@^1.0.4:
+object.values@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a"
   dependencies:
@@ -5132,13 +4603,6 @@ pn@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/pn/-/pn-1.0.0.tgz#1cf5a30b0d806cd18f88fc41a6b5d4ad615b3ba9"
 
-podda@^1.2.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/podda/-/podda-1.2.2.tgz#15b0edbd334ade145813343f5ecf9c10a71cf500"
-  dependencies:
-    babel-runtime "^6.11.6"
-    immutable "^3.8.1"
-
 portfinder@^1.0.9:
   version "1.0.13"
   resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
@@ -5255,12 +4719,6 @@ postcss-filter-plugins@^2.0.0:
     postcss "^5.0.4"
     uniqid "^4.0.0"
 
-postcss-flexbugs-fixes@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-3.0.0.tgz#7b31cb6c27d0417a35a67914c295f83c403c7ed4"
-  dependencies:
-    postcss "^6.0.1"
-
 postcss-import@^10.0.0:
   version "10.0.0"
   resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-10.0.0.tgz#4c85c97b099136cc5ea0240dc1dfdbfde4e2ebbe"
@@ -5301,7 +4759,7 @@ postcss-load-plugins@^2.3.0:
     cosmiconfig "^2.1.1"
     object-assign "^4.1.0"
 
-postcss-loader@^2.0.5, postcss-loader@^2.0.6:
+postcss-loader@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.6.tgz#8c7e0055a3df1889abc6bad52dd45b2f41bbc6fc"
   dependencies:
@@ -5651,7 +5109,7 @@ preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
 
-private@^0.1.6, private@~0.1.5:
+private@^0.1.6:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
 
@@ -5663,10 +5121,6 @@ process@^0.11.0:
   version "0.11.10"
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
 
-process@~0.5.1:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
-
 progress@^1.1.8:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
@@ -5683,7 +5137,7 @@ promise@^7.1.1:
   dependencies:
     asap "~2.0.3"
 
-prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9:
+prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8:
   version "15.5.10"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
   dependencies:
@@ -5735,10 +5189,6 @@ qs@6.4.0, qs@~6.4.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
 
-qs@^6.4.0:
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49"
-
 query-string@^4.1.0:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@@ -5750,7 +5200,7 @@ querystring-es3@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
 
-querystring@0.2.0, querystring@^0.2.0:
+querystring@0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
 
@@ -5816,22 +5266,6 @@ react-addons-shallow-compare@>=0.14.0, react-addons-shallow-compare@^15.6.0:
     fbjs "^0.8.4"
     object-assign "^4.1.0"
 
-react-docgen@^2.15.0:
-  version "2.16.0"
-  resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-2.16.0.tgz#03c9eba935de8031d791ab62657b7b6606ec5da6"
-  dependencies:
-    async "^2.1.4"
-    babel-runtime "^6.9.2"
-    babylon "~5.8.3"
-    commander "^2.9.0"
-    doctrine "^2.0.0"
-    node-dir "^0.1.10"
-    recast "^0.11.5"
-
-react-dom-factories@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.0.tgz#f43c05e5051b304f33251618d5bc859b29e46b6d"
-
 react-dom@^15.6.1:
   version "15.6.1"
   resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470"
@@ -5861,12 +5295,6 @@ react-event-listener@^0.4.5:
     prop-types "^15.5.4"
     warning "^3.0.0"
 
-react-html-attributes@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/react-html-attributes/-/react-html-attributes-1.3.0.tgz#c97896e9cac47ad9c4e6618b835029a826f5d28c"
-  dependencies:
-    html-element-attributes "^1.0.0"
-
 react-immutable-proptypes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
@@ -5875,13 +5303,6 @@ react-immutable-pure-component@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-1.0.0.tgz#761d27b1497c5af64d2d2454e17b26ce7c9cda88"
 
-react-inspector@^2.0.0, react-inspector@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-2.1.1.tgz#2e68030d7ef0811a012f167258dd84232fd5ead1"
-  dependencies:
-    babel-runtime "^6.23.0"
-    is-dom "^1.0.9"
-
 react-intl-translations-manager@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/react-intl-translations-manager/-/react-intl-translations-manager-5.0.0.tgz#3c78d3e3e44c5804d7a15c60e89c3aefd9d06615"
@@ -5900,37 +5321,6 @@ react-intl@^2.3.0:
     intl-relativeformat "^1.3.0"
     invariant "^2.1.1"
 
-react-komposer@^1.9.0:
-  version "1.13.1"
-  resolved "https://registry.yarnpkg.com/react-komposer/-/react-komposer-1.13.1.tgz#4b8ac4bcc71323bd7413dcab95c831197f50eed0"
-  dependencies:
-    babel-runtime "6.x.x"
-    hoist-non-react-statics "1.x.x"
-    invariant "2.x.x"
-    mobx "^2.3.4"
-    shallowequal "0.2.x"
-
-react-komposer@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/react-komposer/-/react-komposer-2.0.0.tgz#b964738014a9b4aee494a83c0b5b833d66072a90"
-  dependencies:
-    babel-runtime "^6.11.6"
-    hoist-non-react-statics "^1.2.0"
-    lodash.pick "^4.4.0"
-    react-stubber "^1.0.0"
-    shallowequal "^0.2.2"
-
-react-modal@^1.7.6, react-modal@^1.7.7:
-  version "1.9.7"
-  resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d"
-  dependencies:
-    create-react-class "^15.5.2"
-    element-class "^0.2.0"
-    exenv "1.2.0"
-    lodash.assign "^4.2.0"
-    prop-types "^15.5.7"
-    react-dom-factories "^1.0.0"
-
 react-motion@^0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.0.tgz#1708fc2aee552900d21c1e6bed28346863e017b6"
@@ -5991,13 +5381,6 @@ react-router@^4.1.1:
     prop-types "^15.5.4"
     warning "^3.0.0"
 
-react-simple-di@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/react-simple-di/-/react-simple-di-1.2.0.tgz#dde0e5bf689f391ef2ab02c9043b213fe239c6d0"
-  dependencies:
-    babel-runtime "6.x.x"
-    hoist-non-react-statics "1.x.x"
-
 react-simple-dropdown@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/react-simple-dropdown/-/react-simple-dropdown-3.0.0.tgz#5a2cac441748a090a3b7009b4807ea206002b7c3"
@@ -6005,26 +5388,6 @@ react-simple-dropdown@^3.0.0:
     classnames "^2.1.2"
     prop-types "^15.5.8"
 
-react-split-pane@^0.1.63:
-  version "0.1.64"
-  resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.64.tgz#725029b9fcb51059aa82f2b8622832473849f559"
-  dependencies:
-    inline-style-prefixer "^3.0.6"
-    prop-types "^15.5.10"
-    react-style-proptype "^3.0.0"
-
-react-stubber@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/react-stubber/-/react-stubber-1.0.0.tgz#41ee2cac72d4d4fd70a63896da98e13739b84628"
-  dependencies:
-    babel-runtime "^6.5.0"
-
-react-style-proptype@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/react-style-proptype/-/react-style-proptype-3.0.0.tgz#89e0b646f266c656abb0f0dd8202dbd5036c31e6"
-  dependencies:
-    prop-types "^15.5.4"
-
 react-swipeable-views-core@^0.11.1:
   version "0.11.1"
   resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.11.1.tgz#61d046799f90725bbf91a0eb3abcab805c774cac"
@@ -6158,15 +5521,6 @@ readline2@^1.0.1:
     is-fullwidth-code-point "^1.0.0"
     mute-stream "0.0.5"
 
-recast@^0.11.5:
-  version "0.11.23"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
-  dependencies:
-    ast-types "0.9.6"
-    esprima "~3.1.0"
-    private "~0.1.5"
-    source-map "~0.5.0"
-
 rechoir@^0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@@ -6218,7 +5572,7 @@ redux-thunk@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
 
-redux@^3.6.0, redux@^3.7.1:
+redux@^3.7.1:
   version "3.7.1"
   resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.1.tgz#bfc535c757d3849562ead0af18ac52122cd7268e"
   dependencies:
@@ -6453,14 +5807,14 @@ rx-lite@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
 
-safe-buffer@5.0.1, safe-buffer@~5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
-
 safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
 
+safe-buffer@~5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+
 samsam@1.x, samsam@^1.1.3:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67"
@@ -6548,16 +5902,6 @@ send@0.15.3:
     range-parser "~1.2.0"
     statuses "~1.3.1"
 
-serve-favicon@^2.4.3:
-  version "2.4.3"
-  resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.4.3.tgz#5986b17b0502642b641c21f818b1acce32025d23"
-  dependencies:
-    etag "~1.8.0"
-    fresh "0.5.0"
-    ms "2.0.0"
-    parseurl "~1.3.1"
-    safe-buffer "5.0.1"
-
 serve-index@^1.7.2:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.0.tgz#d2b280fc560d616ee81b48bf0fa82abed2485ce7"
@@ -6610,12 +5954,6 @@ shallow-clone@^0.1.2:
     lazy-cache "^0.2.3"
     mixin-object "^2.0.1"
 
-shallowequal@0.2.x, shallowequal@^0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e"
-  dependencies:
-    lodash.keys "^3.1.2"
-
 shebang-command@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -6626,7 +5964,7 @@ shebang-regex@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
 
-shelljs@^0.7.5, shelljs@^0.7.7:
+shelljs@^0.7.5:
   version "0.7.8"
   resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
   dependencies:
@@ -6659,26 +5997,22 @@ slice-ansi@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
 
-slide@^1.1.5:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
-
 sntp@1.x.x:
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
   dependencies:
     hoek "2.x.x"
 
-sockjs-client@1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5"
+sockjs-client@1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12"
   dependencies:
-    debug "^2.2.0"
+    debug "^2.6.6"
     eventsource "0.1.6"
     faye-websocket "~0.11.0"
     inherits "^2.0.1"
     json3 "^3.3.2"
-    url-parse "^1.1.1"
+    url-parse "^1.1.8"
 
 sockjs@0.3.18:
   version "0.3.18"
@@ -6738,7 +6072,7 @@ source-map@^0.4.2:
   dependencies:
     amdefine ">=0.0.4"
 
-source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
+source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
   version "0.5.6"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
 
@@ -6863,22 +6197,6 @@ string-width@^2.0.0:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
-string.prototype.padend@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0"
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.4.3"
-    function-bind "^1.0.2"
-
-string.prototype.padstart@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/string.prototype.padstart/-/string.prototype.padstart-3.0.0.tgz#5bcfad39f4649bb2d031292e19bcf0b510d4b242"
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.4.3"
-    function-bind "^1.0.2"
-
 string_decoder@^0.10.25:
   version "0.10.31"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@@ -6940,12 +6258,6 @@ strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 
-style-loader@^0.17.0:
-  version "0.17.0"
-  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.17.0.tgz#e8254bccdb7af74bd58274e36107b4d5ab4df310"
-  dependencies:
-    loader-utils "^1.0.2"
-
 style-loader@^0.18.2:
   version "0.18.2"
   resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb"
@@ -6973,7 +6285,7 @@ supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
 
-supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.2.3:
+supports-color@^3.1.1, supports-color@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
   dependencies:
@@ -6985,9 +6297,11 @@ supports-color@^4.0.0, supports-color@^4.1.0:
   dependencies:
     has-flag "^2.0.0"
 
-svg-tag-names@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/svg-tag-names/-/svg-tag-names-1.1.1.tgz#9641b29ef71025ee094c7043f7cdde7d99fbd50a"
+supports-color@^4.2.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836"
+  dependencies:
+    has-flag "^2.0.0"
 
 svgo@^0.7.0:
   version "0.7.2"
@@ -7020,9 +6334,9 @@ table@^3.7.8:
     slice-ansi "0.0.4"
     string-width "^2.0.0"
 
-tapable@^0.2.5, tapable@~0.2.5:
-  version "0.2.6"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d"
+tapable@^0.2.7:
+  version "0.2.7"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c"
 
 tar-pack@^3.4.0:
   version "3.4.0"
@@ -7209,12 +6523,6 @@ uniqs@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
 
-unique-string@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
-  dependencies:
-    crypto-random-string "^1.0.0"
-
 unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -7227,13 +6535,6 @@ urix@^0.1.0, urix@~0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
 
-url-loader@^0.5.8:
-  version "0.5.9"
-  resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295"
-  dependencies:
-    loader-utils "^1.0.2"
-    mime "1.3.x"
-
 url-parse@1.0.x:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
@@ -7241,7 +6542,7 @@ url-parse@1.0.x:
     querystringify "0.0.x"
     requires-port "1.0.x"
 
-url-parse@^1.1.1:
+url-parse@^1.1.8:
   version "1.1.9"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19"
   dependencies:
@@ -7261,7 +6562,7 @@ user-home@^2.0.0:
   dependencies:
     os-homedir "^1.0.0"
 
-util-deprecate@^1.0.2, util-deprecate@~1.0.1:
+util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
 
@@ -7324,14 +6625,6 @@ warning@^3.0.0:
   dependencies:
     loose-envify "^1.0.0"
 
-watchpack@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87"
-  dependencies:
-    async "^2.1.2"
-    chokidar "^1.4.3"
-    graceful-fs "^4.1.2"
-
 watchpack@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"
@@ -7350,11 +6643,11 @@ webidl-conversions@^4.0.0, webidl-conversions@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0"
 
-webpack-bundle-analyzer@^2.8.2:
-  version "2.8.2"
-  resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.8.2.tgz#8b6240c29a9d63bc72f09d920fb050adbcce9fe8"
+webpack-bundle-analyzer@^2.8.3:
+  version "2.8.3"
+  resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.8.3.tgz#8e7b3deb3832698c24b09c84dfe5b43902a83991"
   dependencies:
-    acorn "^5.0.3"
+    acorn "^5.1.1"
     chalk "^1.1.3"
     commander "^2.9.0"
     ejs "^2.5.6"
@@ -7366,7 +6659,7 @@ webpack-bundle-analyzer@^2.8.2:
     opener "^1.4.3"
     ws "^2.3.1"
 
-webpack-dev-middleware@^1.10.2, webpack-dev-middleware@^1.11.0:
+webpack-dev-middleware@^1.11.0:
   version "1.11.0"
   resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9"
   dependencies:
@@ -7375,9 +6668,9 @@ webpack-dev-middleware@^1.10.2, webpack-dev-middleware@^1.11.0:
     path-is-absolute "^1.0.0"
     range-parser "^1.0.3"
 
-webpack-dev-server@^2.5.1:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.5.1.tgz#a02e726a87bb603db5d71abb7d6d2649bf10c769"
+webpack-dev-server@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.6.1.tgz#0b292a9da96daf80a65988f69f87b4166e5defe7"
   dependencies:
     ansi-html "0.0.7"
     bonjour "^3.5.0"
@@ -7389,30 +6682,22 @@ webpack-dev-server@^2.5.1:
     html-entities "^1.2.0"
     http-proxy-middleware "~0.17.4"
     internal-ip "^1.2.0"
+    loglevel "^1.4.1"
     opn "4.0.2"
     portfinder "^1.0.9"
     selfsigned "^1.9.1"
     serve-index "^1.7.2"
     sockjs "0.3.18"
-    sockjs-client "1.1.2"
+    sockjs-client "1.1.4"
     spdy "^3.4.1"
     strip-ansi "^3.0.0"
     supports-color "^3.1.1"
     webpack-dev-middleware "^1.11.0"
     yargs "^6.0.0"
 
-webpack-hot-middleware@^2.18.0:
-  version "2.18.2"
-  resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.18.2.tgz#84dee643f037c3d59c9de142548430371aa8d3b2"
-  dependencies:
-    ansi-html "0.0.7"
-    html-entities "^1.2.0"
-    querystring "^0.2.0"
-    strip-ansi "^3.0.0"
-
-webpack-manifest-plugin@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.1.2.tgz#e9d9967f4f739ee25380ca57de7f9417c5bea029"
+webpack-manifest-plugin@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.2.1.tgz#e02f0846834ce98dca516946ee3ee679745e7db1"
   dependencies:
     fs-extra "^0.30.0"
     lodash ">=3.5 <5"
@@ -7437,16 +6722,16 @@ webpack-sources@^1.0.1:
     source-list-map "^2.0.0"
     source-map "~0.5.3"
 
-"webpack@^2.5.1 || ^3.0.0":
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.2.0.tgz#8b0cae0e1a9fd76bfbf0eab61a8c2ada848c312f"
+webpack@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.1.tgz#4c3f4f3fb318155a4db0cb6a36ff05c5697418f4"
   dependencies:
     acorn "^5.0.0"
     acorn-dynamic-import "^2.0.0"
     ajv "^5.1.5"
     ajv-keywords "^2.0.0"
     async "^2.1.2"
-    enhanced-resolve "^3.3.0"
+    enhanced-resolve "^3.4.0"
     escope "^3.6.0"
     interpret "^1.0.0"
     json-loader "^0.5.4"
@@ -7457,39 +6742,12 @@ webpack-sources@^1.0.1:
     mkdirp "~0.5.0"
     node-libs-browser "^2.0.0"
     source-map "^0.5.3"
-    supports-color "^3.1.0"
-    tapable "~0.2.5"
-    uglifyjs-webpack-plugin "^0.4.6"
-    watchpack "^1.3.1"
-    webpack-sources "^1.0.1"
-    yargs "^6.0.0"
-
-webpack@^3.0.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.3.0.tgz#ce2f9e076566aba91f74887133a883fd7da187bc"
-  dependencies:
-    acorn "^5.0.0"
-    acorn-dynamic-import "^2.0.0"
-    ajv "^5.1.5"
-    ajv-keywords "^2.0.0"
-    async "^2.1.2"
-    enhanced-resolve "^3.3.0"
-    escope "^3.6.0"
-    interpret "^1.0.0"
-    json-loader "^0.5.4"
-    json5 "^0.5.1"
-    loader-runner "^2.3.0"
-    loader-utils "^1.1.0"
-    memory-fs "~0.4.1"
-    mkdirp "~0.5.0"
-    node-libs-browser "^2.0.0"
-    source-map "^0.5.3"
-    supports-color "^3.1.0"
-    tapable "~0.2.5"
+    supports-color "^4.2.1"
+    tapable "^0.2.7"
     uglifyjs-webpack-plugin "^0.4.6"
     watchpack "^1.4.0"
     webpack-sources "^1.0.1"
-    yargs "^6.0.0"
+    yargs "^8.0.2"
 
 websocket-driver@>=0.5.1:
   version "0.6.5"
@@ -7572,14 +6830,6 @@ wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
-write-file-atomic@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.1.0.tgz#1769f4b551eedce419f0505deae2e26763542d37"
-  dependencies:
-    graceful-fs "^4.1.11"
-    imurmurhash "^0.1.4"
-    slide "^1.1.5"
-
 write@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
@@ -7593,10 +6843,6 @@ ws@^2.3.1:
     safe-buffer "~5.0.1"
     ultron "~1.1.0"
 
-xdg-basedir@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
-
 xml-name-validator@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"